├── .gitignore ├── LICENSE ├── README.md ├── firmware └── src │ ├── Button.cpp │ ├── Button.h │ ├── CMakeLists.txt │ ├── CommonLogic.cpp │ ├── CommonLogic.h │ ├── ConfigOpts.h │ ├── Debug.h │ ├── Display.cpp │ ├── Display.h │ ├── DutyCycle.h │ ├── Flash.cpp │ ├── Flash.h │ ├── Pico.cpp │ ├── Pico.h │ ├── Potentiometer.h │ ├── PotentiometerLogic.cpp │ ├── PotentiometerLogic.h │ ├── Presets.cpp │ ├── Presets.h │ ├── Pwm.pio │ ├── RotaryEncoder.h │ ├── RotaryLogic.cpp │ ├── RotaryLogic.h │ ├── TwoButtonLogic.cpp │ ├── TwoButtonLogic.h │ ├── Uart.cpp │ ├── Uart.h │ ├── Utils.h │ ├── config.h.in │ ├── main.cpp │ └── pico_sdk_import.cmake ├── img ├── ThrottleBlaster.svg ├── ThrottleBlaster_PCB_back.jpg ├── ThrottleBlaster_PCB_front.jpg ├── ThrottleBlaster_breadboard.jpg ├── ThrottleBlaster_pcb.jpg ├── button2_states.fig ├── button2_states.png ├── button_states.fig ├── button_states.png ├── diagram.png ├── rotary_states.fig ├── rotary_states.png ├── stpclk_pin_slot1.jpg ├── stpclk_pin_socket370.jpg ├── stpclk_pin_socket7.jpg └── stpclk_pin_socketA.jpg └── kicad ├── ThrottleBlaster.kicad_pcb ├── ThrottleBlaster.lib ├── ThrottleBlaster.pretty └── raspberry_pi_pico.kicad_mod ├── ThrottleBlaster.pro ├── ThrottleBlaster.sch ├── fp-lib-table └── sym-lib-table /.gitignore: -------------------------------------------------------------------------------- 1 | build/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThrottleBlaster 2 | 3 | A Pi Pico-based solution that reduces the effective frequency of fast CPUs by pulling down the STPCLK# pin at a specified frequency and duty-cycle. 4 | 5 | This allows you to play speed-sensitive games, like Digger on a 1200MHz Athlon! 6 | 7 | 8 | 9 | 10 | 11 | Videos: 12 | - Part 3 (rev 0.3): https://www.youtube.com/watch?v=g4OluJwGDEQ 13 | - Part 2: https://www.youtube.com/watch?v=nGy8OmOe_34 14 | - Part 1: https://www.youtube.com/watch?v=9uNml2j6sy0 15 | 16 | - Mounted on a 5.25 panel: https://www.youtube.com/shorts/n1aFvRNtOcw (by Michael Swimm) 17 | - Controllable by serial: https://www.youtube.com/watch?v=71rln-R2mis (by Michael Swimm) 18 | 19 | Download firmware (ThrottleBlaster.uf2): https://github.com/scrapcomputing/ThrottleBlaster/releases 20 | 21 | Download gerbers (ThrottleBlaster_gerbers_rev.X.X.zip): https://github.com/scrapcomputing/ThrottleBlaster/releases 22 | 23 | A front panel design by Michael Swimm: https://www.thingiverse.com/thing:6619261 24 | 25 | # How it works 26 | The Throttle Blaster is a fancy PWM controller that pulls the CPU's STPCLK# to ground. 27 | 28 | 29 | 30 | It is tailored to the needs of vintage PC enthusiasts, so it drives a 4-digit 7-segment display and is pre-loaded with presets that span several hardware generations. 31 | 32 | 33 | # Features 34 | - Four modes of operation to fit your needs, selected by jumpers: 35 | 1. single push-button control. This is great for re-purposing the Turbo button (NOTE: the on/off turbo switch would need to be replaced with a push-button). 36 | 2. Two-button control (left/right). 37 | 3. rotary-encoder with a push-button. This is great for placing it on a drive bay panel. 38 | 4. analogue potentiometer with/without a push-button. This could be used for configurations without a display. 39 | - 7-segment TM1637-based display that shows the effective frequency. This can be considered optional, but is highly recommended. 40 | - Cycle through preset frequencies or select a frequency at a 1MHz granularity. 41 | - Each preset can be tuned/programmed both in terms of the PWM level but also the PWM frequency. 42 | - Configuration changes are permanently stored in the Pico's flash memory. 43 | - The PWM pulses are generated by the Pico's PIOs and are clock-accurate, with no jitter. 44 | 45 | 46 | # Modes of operation 47 | 48 | ## Mode 1: Rotary Encoder (ROT) 49 | 50 | - The devices starts at the "Presets" state (mode). 51 | - Turn the knob to select a frequency 52 | - Short push to switch to fine-grain frequency selection 53 | 54 | 55 | 56 | ## Initial configuration 57 | - The default CPU frequency is 200MHz. Long push to configure the CPU speed. Select the frequency and short push to get back to the presets. 58 | 59 | ## Mode 2: Single-button (1Btn) 60 | 61 | - The device starts at the "Presets" state. 62 | - Three types of push: 63 | 1. Short push (release immediately) 64 | 2. Medium push (for ~0.5 seconds) 65 | 3. Long push (about 2 seconds) 66 | - Cycle through presets with a short push. A medium push brings us back to the default CPU frequency. 67 | - Long push to enter programming mode. 68 | 69 | 70 | 71 | 72 | ## Mode 3: Two-button (2Btn) 73 | 74 | - The device starts at the "Presets" state. 75 | - Two types of push: long and short 76 | - Three types of actions: 77 | 1. Short Left or Right push (release immediately) 78 | 2. Both Left and Right short push 79 | 3. Both Left and Right long push. 80 | - Cycle through presets with a L or R short push. 81 | - Go to 1-1 manual mode with both L and R short push. 82 | - Long push (L or R) to enter programming mode, or to configure the CPU Frequency (CPUF) when showing the maximum frequency. 83 | - Reset to defaults by long press L and R twice. 84 | 85 | 86 | 87 | ## Mode 4: Potentiometer (POT) 88 | 89 | - Can be used in conjunction with the single-button operation. Turning the potentiometer overrides the preset selected by the button. 90 | 91 | ## Serial Mode (UART): Control by the serial port, works in conjunction with all other modes. 92 | 93 | You can connect to the Throttle Blaster via the serial port and set the Frequency and PWM Period. 94 | This is convenient for launching a game with a `.bat` file that first configures the Throttle Blaster and then launches the game. 95 | 96 | ### UART Circuit 97 | - Connect the Throttle Blaster's Tx pin to the PC's serial port Rx pin (that is pin 2 of the serial connector), the Throttle Blaster's Rx pin to serial Tx (pin 3) and ground to ground (pin 5) 98 | 99 | 100 | ### UART Software 101 | - Serial port settings: 9600 8N1, no flow control 102 | - The string to send is in the form `FP\r`, where: 103 | - `` is the desired effective frequency in MHz (float), 104 | - `` is the PWM Period level (1-256), 105 | - `\r` (also seen as `^M`) is the Carriage-Return character (ASCII 13 0x0d). 106 | - For example `F4.77P8` sets the frequency to `4.77MHz` and the PWM period to `8` which is a around 50us. 107 | - In DOS you can use a terminal emulator, like [Kermit](http://www.columbia.edu/kermit/ftp/archives/msk314.zip), to connect and send the command. 108 | - You could use this one-liner in a batch file: `kermit set port COM1, set speed 9600, output F4.77P8\13`. This will set the frequency to 4.77 MHz, the period to 8 and will send a Carriage-Return character (`\13`). 109 | 110 | > **Note** 111 | > Changing the frequency is not instantaneous. So if you are setting the frequency in a batch file, please consider adding a delay before launching the game. 112 | 113 | > **Note** 114 | > Please note that the serial-port functionality is totally optional. You don't need to populate the MAX3232 IC and its capacitors if you are not planning to use it. 115 | 116 | 117 | ## Preset Buttons (since rev 0.7) 118 | Since revision 0.7 you can also directly select a preset by pushing one of the 8 available buttons. 119 | This functionality is compatible with all other modes of operation. 120 | 121 | Please note that you don't need a Rev 0.7 PCB to use this feature. 122 | You can simply connect buttons to the Pico's GPIOs, with one button pin to the GPIO and the other to ground, see table below: 123 | 124 | 125 | GPIO | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 126 | ------------|----|----|----|----|----|----|-----|----- 127 | Freq (MHz) | 4 | 8 | 10 | 25 | 33 | 66 | 133 | Max 128 | 129 | ## Reset Detection (Optional and experimental) (since rev 0.8) 130 | Connect the computer case reset button to `RES1` and another cable from `RES2` to the computer motherboard. 131 | Now when you press the reset button, the Throttle Blaster will detect it and will temporarily set the frequency to maximum for a faster boot. 132 | During this time the MHz display will show "boot" and show a count down until the frequency is restored. 133 | 134 | > **Note** 135 | > Polarity matters: If you connect the reset cables to the RES headers the wrong way your PC won't boot! 136 | 137 | ## Reverse Direction jumper JP3 (since rev 0.4) 138 | Since revision 0.4 the default rotation direction of the knob has been changed and a new jumper JP3 has been added. 139 | Closing the jumper will reverse the direction for both the rotary encoder and the potentiometer. 140 | 141 | 142 | # Presets 143 | 144 | Display | Performance equivalence 145 | ---------|------------------------ 146 | 4 | 4.77 MHz IBM PC 147 | 8 | 8 MHz 148 | 10 | 10 MHz 149 | 25 | 25 MHz 150 | 33 | 33 MHz 486 151 | 66 | 66 MHz 486 152 | 133 | 133 MHz Pentium 153 | 450 | 450 MHz Pentium-II 154 | 733 | 733 MHz Pentium-III 155 | 156 | # Firmware 157 | 158 | ## Dependencies 159 | - (Optional) https://github.com/wahlencraft/TM1637-pico 160 | - C++17 compiler 161 | 162 | ## Build Instructions 163 | - Download release (v1.2.1) https://github.com/wahlencraft/TM1637-pico and extract it. 164 | - `cd firmware/ && mkdir build && cd build` 165 | - `cmake -DCMAKE_BUILD_TYPE=Release -DPICO_SDK_PATH=/path/to/pico-sdk/ -DPICO_TM1637_PATH=/path/to/TM1637-pico/ ../src/ && make` 166 | - This will place the firmware into: `ThrottleBlaster.uf2` in the `build` directory. 167 | 168 | ## Installing the firmware 169 | - Unplug the Pico 170 | - Press and hold the small "BOOTSEL" button on the Pico 171 | - While holding the BOOTSEL button, connect the Pico to your PC with a micro-USB cable 172 | - The Pico should show up as a mass-storage device 173 | - Copy the `ThrottleBlaster.uf2` firmware to the drive associated with the Pico 174 | - Safely eject the mass-storage device 175 | 176 | The Pico should boot and you should see the Pico's LED light up. 177 | 178 | # Circuit 179 | 180 | ## Schematic 181 | 182 | Throttle Blaster Schematic 183 | 184 | The Throttle Blaster circuit is fairly simple: 185 | - The Rotary Encoder, the Push Button and the Potentiometer are connected to the Pico's GPIOs. 186 | - The Display is also connected directly to GPIOs 187 | - The STPCLK# pin is driven by a N-channel MOSFET, a 2N7000, and its gate pin connects to the Pico's GPIO via a 1K resistor and the transistor's drain connects to STPCLK# via a 100 Ohm resistor. 188 | - The circuit is powered directly from the PSU's 5V power supply via diode (preferrably a Schottky). 189 | - The serial port circuit relies on a MAX3232 for converting the RS232 levels to Pi-Pico levels. 190 | 191 | ## PCB 192 | 193 | Throttle Blaster PCB Front 194 | Throttle Blaster PCB Back 195 | 196 | ## Bill Of Materials 197 | 198 | Download gerbers: https://github.com/scrapcomputing/ThrottleBlaster/releases 199 | 200 | Reference | Quantity | Value | Description 201 | ---------------|-------------------|-------------------------------------------------------|------------ 202 | N/A | 1 (recommended) | TM1637 based 4-digit 7-segment display | The display of the Throttle Blaster 203 | D1 | 1 | Through-hole diode (preferrably Schottky 1N5817) | Reverse polarity protection 204 | N/A (for Pico) | 2 | 1x17 female through-hole pin-header 2.54mm pitch (Harwin M20-7821746) | For attaching the Pico to the board. 205 | J1 | 1 (optional) | 1x01 male through-hole angled pin-header 2.54mm pitch | For the STPCLK# cable 206 | RES1,2 | 2 (optional) | 1x02 male through-hole angled pin-header 2.54mm pitch | For Reset detection 207 | J2 | 1 (optional UART) | 1x03 male through-hole pin-header 2.54mm pitch | For controlling the Throttle Blaster via serial. (Requires MAX3232) 208 | JP1/JP2 | 1 | 2x02 (or 2x 1x02) male through-hole pin-header 2.54mm | Selects mode of operation. 209 | JP3 | 1 | 1x02 male through-hole pin-header 2.54mm pitch | For the JP3 jumper that flips the rotation direction 210 | SW1/SW2 | 2 (optional) | 1x02 male through-hole pin-header 2.54mm pitch | For the SW1 and SW2 switches 211 | U1 | 1 (optional) | 1x04 male through-hole angled pin-header 2.54mm pitch | For connecting the TM1637 7-segment display. 212 | Jumpers | 2 | 2.54mm pitch Jumpers | For JP1/JP2 213 | Q1 (optional Q2) | 1 (2 for optional RESET detection) | 2N7000 N-channel MOSFET | Pulls down the CPU's STPCLK# pin 214 | R1 | 1 | 1K Resistor SMD 1206 | For the throttle transistor gate. 215 | R2 | 1 | 100 Ohm Resistor SMD 1206 (P-iii CPUs may need 47 Ohms) | Between the throttle pin and the throttling transistor for additional safety. It's value could be lower. 216 | Pot1 | 1 (mode POT) | 10K linear potentiometer | Selects Frequency in Potentiometer mode. 217 | SW1 | 1 (mode 1Btn) | Push button | Selects Frequency in 1Btn mode. 218 | SW3 | 1 (mode 2Btn) | Push button | The right button in 2Btn mode. 219 | Preset Btns P1-P8| 8 (optional) | 1x02 (or one 8x02) male through-hole pin-header 2.54mm| Headers for the 8 preset push-buttons. 220 | SW2 | 1 (mode ROT) | Rotary Encoder with push-button, (ALPS EC11E-Switch) Vertical | Selects Frequency in Rotary mode. Note: These are widely available online using keywords like: "rotary encoder switch Arduino" and they can also be found in kits with fitting knobs. 221 | U2 | 1 | Raspberry Pi Pico | 222 | U3 | 1 | 1x04 horizontal pin header 2.54mm pitch | For connecting to the floppy power connector, for powering the unit. 223 | C1 | 1 (mode POT) | 100pF Ceramic capacitor SMD 1205 | Used to reduce potentiometer noise. 224 | C2,C3,C4,C5,C6 | 5 (optional UART) | 1uF Ceramic Capacitor SMD 1206 | For MAX3232 (serial port) 225 | U4 | 1 (optional UART) | MAX3232 SOIC-16 5.3x10.2mm (Commonly listed as 16-SOIC 3.90mm width) | For controlling the Throttle Blaster via the serial port. 226 | 227 | ## Using the circuit for the first time 228 | - Select the operation mode using jumpers JP1/JP2. 229 | - Connect J1 to your CPU's STPCLK# pin. 230 | - Power it on and you are good to go. 231 | - You can reverse the knob direction by closing JP3 (since rev.0.4) 232 | 233 | 234 | ## Basic Troubleshooting 235 | - Check that the Throttle Blaster's ground is connected to the motherboard's ground 236 | - Check the voltage at the STPCLK# pin: 237 | - Max frequency: The voltage should be matching the CPU's I/O high ~3.3V for most CPUs 238 | - Any other frequency: You should be seing pulses using an oscilloscope, or values between 0 and ~3.3V using a multimeter. 239 | - Confirm that it's working as expected by running benchmarks from [Phil's DOS Benchmark Pack](https://www.philscomputerlab.com/dos-benchmark-pack.html) 240 | 241 | 242 | ## How to find the `STPCLK#` pin 243 | Just look for it in your CPU's datasheet. 244 | This table lists the STPCLK# pin number for your reference: 245 | 246 | CPU | STPCLK# Pin 247 | ----------------------|------------ 248 | Pentium MMX | V34 249 | Pentium-iii socket | AG35 250 | Pentium-iii slot1 | B6 251 | Athlon XP | AC1 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | # How about older CPUs that don't have a STPCLK pin? 262 | 486 or older CPUs don't usually have a STPCLK# pin. 263 | Instead they have the HOLD pin which has somewhat similar functionality and is active high. 264 | Some boards use PWM on this HOLD pin when the Turbo button is pressed. 265 | 266 | The HOLD pin is not pulled down internally by the CPU. 267 | So it is up to the motherboard to pull it down. 268 | The problem is that this is usually actively pulled down by the CPU chipset, meaning that if we try to pull it up with the Throttle Blaster we may damage the chipset. 269 | So I don't think there is a way to get the Throttle Blaster to work universally with older systems. 270 | 271 | 272 | # Change Log 273 | - Rev 0.8c: Fixes bugs introduced in Rev 0.8b. 274 | - Rev 0.8b: Improves responsiveness of rotary encoder. WARNING: This revision is buggy, please upgrade to Rev 0.8c (thanks @nahimov for reporting the bug). 275 | - Rev 0.8: Adds RESET detction circuit (optional) which will temporarily set the speed to max for 20 seconds. 276 | - Rev 0.7: Adds support for 8 preset buttons. 277 | - Rev 0.6: Firmware bug fixes: (i) fix saving MHz/Period adjustments to flash and (ii) one-button mode frequency glitch. 278 | - Rev 0.5: Replaces potentiometer capacitor with SMD and several firmware fixes. 279 | - Rev 0.4: Reverses knob direction and adds jumper JP3 for selecting direction. 280 | - Rev 0.3: Adds UART support in both firmware and PCB. 281 | - Rev 0.2: Adds two-button mode "2Btn". 282 | - Rev 0.1: Initial release. 283 | 284 | # Acknowledgements 285 | - Many thanks to all of you who have reported bugs or asked for clarifications or features requests. By doing so you are helping improve the project! Special thanks to Michael Swimm for extensive testing, reporting bugs, coming up with awesome ideas for new features, and for sharing fancy mounting hardware for the Throttle Blaster! 286 | 287 | # License 288 | The project is GPLv2 except for `Pwm.pio` which comes from the Pi Pico SDK examples and is under `SPDX-License-Identifier: BSD-3-Clause`. 289 | -------------------------------------------------------------------------------- /firmware/src/Button.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "Button.h" 7 | 8 | /// \Returns true if we "release both", which currently only if both release 9 | /// or if one is released and the other is pressed. 10 | bool bothRelease(ButtonState BS1, ButtonState BS2) { 11 | if ((BS1 == ButtonState::Release || BS1 == ButtonState::MedRelease) && 12 | (BS2 == ButtonState::Release || BS2 == ButtonState::MedRelease)) 13 | return true; 14 | return ((BS1 == ButtonState::Release || BS1 == ButtonState::MedRelease) && 15 | BS2 == ButtonState::Pressed) || 16 | ((BS2 == ButtonState::Release || BS2 == ButtonState::MedRelease) && 17 | BS1 == ButtonState::Pressed); 18 | } 19 | 20 | bool bothLongPress(ButtonState BS1, ButtonState BS2) { 21 | return BS1 == ButtonState::LongPress && BS2 == ButtonState::Pressed; 22 | } 23 | -------------------------------------------------------------------------------- /firmware/src/Button.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __BUTTON_H__ 7 | #define __BUTTON_H__ 8 | 9 | #include "Pico.h" 10 | #include "Utils.h" 11 | 12 | enum class ButtonState { 13 | LongPress, 14 | MedRelease, 15 | Release, 16 | Pressed, 17 | None, 18 | }; 19 | 20 | static constexpr const char *getButtonState(ButtonState State) { 21 | switch (State) { 22 | case ButtonState::LongPress: return "LongPress"; 23 | case ButtonState::MedRelease: return "MedRelease"; 24 | case ButtonState::Release: return "Release"; 25 | case ButtonState::Pressed: return "Pressed"; 26 | case ButtonState::None: return "None"; 27 | } 28 | } 29 | 30 | bool bothRelease(ButtonState BS1, ButtonState BS2); 31 | /// WARNING: Be very careful if you are handling single-button long pressess! 32 | bool bothLongPress(ButtonState BS1, ButtonState BS2); 33 | 34 | template 36 | class Button { 37 | static constexpr const bool OnVal = !OffVal; 38 | int GPIO; 39 | Pico Π 40 | Buffer Buff; 41 | bool LastVal = OffVal; 42 | int LongPressCnt = 0; 43 | bool IgnoreRelease = false; 44 | 45 | public: 46 | Button(int GPIO, Pico &Pi, const char *Name) : GPIO(GPIO), Pi(Pi) { 47 | auto Pull = OffVal == true ? Pico::Pull::Up : Pico::Pull::Down; 48 | Pi.initGPIO(GPIO, GPIO_IN, Pull, Name); 49 | } 50 | 51 | ButtonState get() { 52 | Pi.readGPIO(); 53 | Buff.append(Pi.getGPIO(GPIO)); 54 | bool Val = Buff.getMean(); 55 | 56 | auto GetState = [this](bool Val) { 57 | if (Val == OnVal && LastVal == OffVal) { 58 | LongPressCnt = 0; 59 | return ButtonState::Pressed; 60 | } else if (Val == OffVal && LastVal == OnVal) { 61 | if (!IgnoreRelease) { 62 | if (LongPressCnt >= MedReleaseCntVal) 63 | return ButtonState::MedRelease; 64 | else 65 | return ButtonState::Release; 66 | } 67 | IgnoreRelease = false; 68 | } else if (Val == OnVal && LastVal == OnVal) { 69 | if (++LongPressCnt == LongPressCntVal) { 70 | IgnoreRelease = true; 71 | return ButtonState::LongPress; 72 | } 73 | return ButtonState::Pressed; 74 | } 75 | return ButtonState::None; 76 | }; 77 | auto NewState = GetState(Val); 78 | LastVal = Val; 79 | return NewState; 80 | } 81 | }; 82 | 83 | #endif // __BUTTON_H__ 84 | -------------------------------------------------------------------------------- /firmware/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | # Build 4 | # ----- 5 | # $ mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DPICO_SDK_PATH=/path/to/pico-sdk/ ../src/ && make -j 6 | # For 7-segment display support you need to specify the path to the 7 | # Pico-TM1637 library (https://github.com/wahlencraft/TM1637-pico) 8 | # -DPICO_TM1637_PATH=/path/to/TM1637-pico/ 9 | # 10 | # Options 11 | # ------- 12 | # o -DDISPLAY_SHIFT_LEFT= If the numbers displayed need shifting 13 | # o -DDISPLAY_BRIGHTNESS= (0-7) Sets the brightness 7 is max, default: 1. 14 | # o -DDISABLE_PICO_LED=on to disable the Pico's blinking LED. 15 | # o -DDBGPRINT=on to enable debug messages 16 | # 17 | # This will place the firmware into: build/ThrottleBlaster.uf2 18 | # 19 | # For example: minicom -b 115200 -D /dev/ttyACM0. Serial connection: 115200 8N1 20 | 21 | set(REVISION_MAJOR 0) 22 | set(REVISION_MINOR 8) 23 | 24 | message("PICO_SDK_PATH = ${PICO_SDK_PATH}") 25 | 26 | # initialize the SDK based on PICO_SDK_PATH 27 | # note: this must happen before project() 28 | include(pico_sdk_import.cmake) 29 | 30 | set(PROJECT_NAME ThrottleBlaster) 31 | project( 32 | ${PROJECT_NAME} 33 | LANGUAGES C CXX ASM) 34 | 35 | set(CMAKE_C_STANDARD 11) 36 | set(CMAKE_CXX_STANDARD 17) 37 | 38 | # initialize the Raspberry Pi Pico SDK 39 | pico_sdk_init() 40 | include_directories("${PICO_SDK_PATH}/src/common/pico_stdlib/include") 41 | include_directories("${PICO_SDK_PATH}/src/common/pico_base/include") 42 | include_directories("${PICO_SDK_PATH}/src/rp2_common/hardware_adc/include") 43 | include_directories("${PICO_SDK_PATH}/src/rp2_common/pico_multicore/include") 44 | include_directories("${PROJECT_BINARY_DIR}/") # for build/config.h 45 | if (DEFINED PICO_TM1637_PATH) 46 | include("${PICO_TM1637_PATH}/PicoTM1637.cmake") 47 | set(PICO_TM1637 1) 48 | endif () 49 | 50 | if (NOT DEFINED DISPLAY_SHIFT_LEFT) 51 | set(DISPLAY_SHIFT_LEFT 0) 52 | endif() 53 | 54 | if (NOT DEFINED DISPLAY_BRIGHTNESS) 55 | set(DISPLAY_BRIGHTNESS 1) 56 | endif() 57 | 58 | set(CMAKE_CXX_FLAGS_RELEASE "-O2") 59 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Werror ${EXTRA_DBG_FLAGS}") 60 | 61 | # rest of your project 62 | file(GLOB SOURCES *.c *.cpp *.h *.def) 63 | add_executable(${PROJECT_NAME} ${SOURCES}) 64 | 65 | pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/Pwm.pio) 66 | 67 | target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_LIST_DIR}") 68 | 69 | set(LIBS 70 | pico_stdlib 71 | hardware_adc 72 | pico_multicore 73 | hardware_pio 74 | ) 75 | if (DEFINED PICO_TM1637_PATH) 76 | set(LIBS PicoTM1637 ${LIBS}) 77 | endif () 78 | target_link_libraries(${PROJECT_NAME} ${LIBS}) 79 | 80 | 81 | message("") 82 | message("+---------------+") 83 | message("| Configuration |") 84 | message("+---------------+") 85 | pico_enable_stdio_usb(${PROJECT_NAME} 1) 86 | pico_enable_stdio_uart(${PROJECT_NAME} 0) 87 | if (DISABLE_USB_DBG STREQUAL "0") 88 | pico_enable_stdio_usb(${PROJECT_NAME} 0) 89 | pico_enable_stdio_uart(${PROJECT_NAME} 0) 90 | endif () 91 | message("DISABLE_USB_DBG = ${DISABLE_USB_DBG}") 92 | 93 | message("PICO_LED = ${PICO_LED}") 94 | message("PICO_TM1637_PATH = ${PICO_TM1637_PATH}") 95 | message("LCD_SHIFT_LEFT = ${LCD_SHIFT_LEFT}") 96 | message("MHZ = ${MHZ}") 97 | 98 | # End of configuration 99 | message("") 100 | 101 | configure_file ( 102 | "${PROJECT_SOURCE_DIR}/config.h.in" 103 | "${PROJECT_BINARY_DIR}/config.h" 104 | ) 105 | 106 | # Create map/bin/hex/uf2 in addition to ELF. 107 | pico_add_extra_outputs(${PROJECT_NAME}) 108 | -------------------------------------------------------------------------------- /firmware/src/CommonLogic.cpp: -------------------------------------------------------------------------------- 1 | #include "CommonLogic.h" 2 | #include "Debug.h" 3 | #include "pico/time.h" 4 | #include 5 | 6 | void CommonLogic::setMode(Mode NewMode) { 7 | setModeInit(NewMode); 8 | 9 | BeforeMaxMHz = Presets.getMaxMHz(); 10 | BeforePeriod = Presets.getPeriod(); 11 | BeforeActualKHz = Presets.getActualKHz(); 12 | 13 | CurrMode = NewMode; 14 | DBG_PRINT(std::cout << getModeStr(CurrMode) << "\n";) 15 | } 16 | 17 | void CommonLogic::tryWritePresetsToFlash() { 18 | if (Presets.getActualKHz() != BeforeActualKHz || 19 | Presets.getPeriod() != BeforePeriod || 20 | Presets.getMaxMHz() != BeforeMaxMHz) { 21 | DBG_PRINT(std::cout << "WriteToFlash:\n";) 22 | DBG_PRINT(std::cout << " Before After\n";) 23 | DBG_PRINT(std::cout << "ActualKHz: " << BeforeActualKHz << " " 24 | << Presets.getActualKHz() << "\n";) 25 | DBG_PRINT(std::cout << "Period: " << BeforePeriod << " " 26 | << Presets.getPeriod() << "\n";) 27 | DBG_PRINT(std::cout << "MaxMHz: " << BeforeMaxMHz << " " 28 | << Presets.getMaxMHz() << "\n";) 29 | Presets.writeToFlash(Flash); 30 | } else { 31 | DBG_PRINT( 32 | std::cout << "Not writing to flash (no change in KHz/Period/MaxMHz)\n";) 33 | } 34 | } 35 | 36 | void CommonLogic::printTxtAndSleep(const char *Str) { 37 | Disp.printTxt(Str); 38 | sleep_ms(PrintSleep); 39 | } 40 | 41 | void CommonLogic::updateDisplay() { 42 | #ifdef PICO_TM1637 43 | switch (getMode()) { 44 | case Mode::Presets: 45 | Disp.printKHz(Presets.getKHz()); 46 | break; 47 | case Mode::ConfigMHz: 48 | Disp.printKHz(Presets.getActualKHz()); 49 | break; 50 | case Mode::ConfigPeriod: 51 | Disp.printRaw(Presets.getPeriod()); 52 | break; 53 | case Mode::Manual: 54 | case Mode::Uart: 55 | Disp.printKHz(DC.getKHz()); 56 | break; 57 | case Mode::ConfigMaxMHz: 58 | Disp.printMHz(Presets.getMaxMHz()); 59 | break; 60 | case Mode::ResetToDefaults: 61 | Disp.printTxt(MsgResetToDefaults); 62 | break; 63 | case Mode::Boot: { 64 | auto Now = std::chrono::system_clock::now(); 65 | auto Seconds = 66 | std::chrono::duration_cast(Now - *ResetTimeOpt); 67 | if (Seconds.count() == 0) { 68 | Disp.printTxt(MsgResetDetected); 69 | } else { 70 | int Remaining = ResetMaxSpeedDuration - Seconds.count(); 71 | Disp.printRaw(Remaining); 72 | } 73 | break; 74 | } 75 | } 76 | #endif 77 | } 78 | 79 | void CommonLogic::uartTick(Uart &Uart) { 80 | auto Bytes = Uart.readNonBlocking(); 81 | auto NumBytes = Bytes.size(); 82 | if (NumBytes == 0) 83 | return; 84 | if (NumBytes >= 1) { 85 | for (char Byte : Bytes) { 86 | // Skip some illegal chars. 87 | if (Byte == '\n') 88 | continue; 89 | UartStr += Byte; 90 | } 91 | } 92 | DBG_PRINT(std::cout << "UartStr: " << UartStr << "\n";) 93 | // Reject messages that are too long! 94 | if (UartStr.size() > MaxUartStrSz) { 95 | DBG_PRINT(std::cout << "Uart: Too many chars:'" << UartStr << "'\n";) 96 | printTxtAndSleep(MsgUartErr); 97 | UartStr.clear(); 98 | return; 99 | } 100 | // Look for the EOM character. 101 | if (UartStr.back() != UartEOM) 102 | return; 103 | float MHz = 0; 104 | int Period = 0; 105 | sscanf(UartStr.c_str(), "F%fP%d\r", &MHz, &Period); 106 | int KHz = MHz * 1000; 107 | DBG_PRINT(std::cout << "UART: " 108 | << "MHz=" << MHz << " KHz=" << KHz << " Period=" << Period 109 | << "\n";) 110 | if (KHz == 0) { 111 | setMode(Mode::Uart); 112 | printTxtAndSleep(MsgUartMode); 113 | DC.setMHzToMax(); 114 | } else if ((MHz >= MHzLimitLo && MHz <= MHzLimitHi) && 115 | (Period >= PeriodLimitLo && Period <= PeriodLimitHi)) { 116 | setMode(Mode::Uart); 117 | DC.setKHz(KHz); 118 | DC.setPeriod(Period); 119 | printTxtAndSleep(MsgUartMode); 120 | // Uart.writeBlockingStr("OK\r\n"); 121 | } else { 122 | printTxtAndSleep(MsgUartErr); 123 | } 124 | UartStr.clear(); 125 | } 126 | 127 | void CommonLogic::resetSenseTick() { 128 | if (ResetTimeOpt) { 129 | auto Now = std::chrono::system_clock::now(); 130 | auto Seconds = 131 | std::chrono::duration_cast(Now - *ResetTimeOpt); 132 | if (Seconds.count() > ResetMaxSpeedDuration) { 133 | DBG_PRINT(std::cout << "End of Reset. SvKHz=" << ResetSavedKHz 134 | << " SvPeriod=" << ResetSavedPeriod 135 | << " SvMode=" << getModeStr(ResetSavedMode) << "\n";) 136 | ResetTimeOpt = std::nullopt; 137 | DC.setKHz(ResetSavedKHz); 138 | DC.setPeriodRaw(ResetSavedPeriod); 139 | setMode(ResetSavedMode); 140 | } 141 | } 142 | // Button not pressed, skipping. 143 | if (ResetSense.get() != ButtonState::Pressed) 144 | return; 145 | // Nothing to do if already at max freq. 146 | if (DC.getKHz() == Presets.getMaxKHz() && !ResetTimeOpt) 147 | return; 148 | // If already in booting mode don't save freq/period/mode. 149 | if (getMode() != Mode::Boot) { 150 | ResetSavedKHz = DC.getKHz(); 151 | ResetSavedPeriod = DC.getPeriod(); 152 | ResetSavedMode = getMode(); 153 | } 154 | DBG_PRINT(std::cout << "Reset detected! SvKHz=" << ResetSavedKHz 155 | << " SvPeriod=" << ResetSavedPeriod 156 | << " SvMode=" << getModeStr(ResetSavedMode) << "\n";) 157 | DC.setMHzToMax(); 158 | ResetTimeOpt = std::chrono::system_clock::now(); 159 | setMode(Mode::Boot); 160 | } 161 | 162 | void CommonLogic::presetBtnsTick() { 163 | auto HandleBtn = [this](auto &Btn, int BtnIdx, bool IsLast) { 164 | switch (Btn.get()) { 165 | case ButtonState::Release: 166 | case ButtonState::MedRelease: 167 | case ButtonState::LongPress: { 168 | DBG_PRINT(std::cout << "PresetBtn " << BtnIdx << " IsLast=" << IsLast 169 | << "\n";) 170 | if (!IsLast) 171 | Presets.setIdx(BtnIdx); 172 | else 173 | // The last preset always sets max frequency. 174 | Presets.cycleMax(); 175 | int NewKHz = Presets.getActualKHz(); 176 | int NewPeriod = Presets.getPeriod(); 177 | DBG_PRINT(std::cout << "NewKHz=" << NewKHz << " NewPeriod=" << NewPeriod 178 | << "\n";) 179 | DC.setKHz(NewKHz); 180 | DC.setPeriod(NewPeriod); 181 | DBG_PRINT(DC.dump();) 182 | break; 183 | } 184 | default: 185 | break; 186 | } 187 | }; 188 | 189 | switch (CurrMode) { 190 | case Mode::Presets: 191 | case Mode::Manual:{ 192 | for (int BtnIdx = 0, E = (int)PresetBtns.size(); BtnIdx != E; ++BtnIdx) 193 | HandleBtn(PresetBtns[BtnIdx], BtnIdx, /*IsLast=*/BtnIdx == E - 1); 194 | break; 195 | } 196 | default: 197 | break; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /firmware/src/CommonLogic.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __COMMONLOGIC_H__ 7 | #define __COMMONLOGIC_H__ 8 | 9 | #include "Button.h" 10 | #include "Debug.h" 11 | #include "Display.h" 12 | #include "DutyCycle.h" 13 | #include "Flash.h" 14 | #include "Presets.h" 15 | #include "Uart.h" 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | class CommonLogic { 22 | protected: 23 | int SamplePeriod; 24 | int LoopCntSinceSample = 0; 25 | 26 | Display &Disp; 27 | DutyCycle &DC; 28 | PresetsTable &Presets; 29 | FlashStorage &Flash; 30 | 31 | enum class Mode { 32 | Presets, 33 | ConfigMHz, 34 | ConfigPeriod, 35 | Manual, 36 | ConfigMaxMHz, 37 | ResetToDefaults, 38 | Uart, 39 | Boot, 40 | }; 41 | 42 | Mode CurrMode = Mode::Presets; 43 | 44 | static constexpr const char *getModeStr(Mode Mode) { 45 | switch (Mode) { 46 | case Mode::Presets: return "Presets"; 47 | case Mode::ConfigMHz: return "ConfigMHz"; 48 | case Mode::ConfigPeriod: return "ConfigPeriod"; 49 | case Mode::Manual: return "Manual"; 50 | case Mode::ConfigMaxMHz: return "ConfigMaxMHz"; 51 | case Mode::ResetToDefaults: return "ResetToDefaults"; 52 | case Mode::Uart: return "UART"; 53 | case Mode::Boot: return "Boot"; 54 | } 55 | return "BAD"; 56 | } 57 | /// Called by setMode(). Can be overriden by implementations. 58 | virtual void setModeInit(Mode NewMode) {} 59 | void setMode(Mode NewMode); 60 | void tryWritePresetsToFlash(); 61 | Mode getMode() const { return CurrMode; } 62 | 63 | static constexpr const char *MsgActualFreq = "FrE"; 64 | static constexpr const char *MsgPeriod = "PEr"; 65 | static constexpr const char *MsgMaxMHz = "CPUF"; 66 | static constexpr const char *MsgResetToDefaults = "RES"; 67 | static constexpr const char *MsgEscape = "ESC"; 68 | static constexpr const char *MsgConfirm = "done"; 69 | static constexpr const char *MsgPresets = "PRE"; 70 | static constexpr const char *MsgManual = "1-1"; 71 | static constexpr const char *MsgUartErr = "UErr"; 72 | static constexpr const char *MsgUartMode = "UArt"; 73 | static constexpr const char *MsgResetDetected = "boot"; 74 | 75 | int BeforeMaxMHz = 0; 76 | int BeforeActualKHz = 0; 77 | int BeforePeriod = 0; 78 | 79 | std::vector> 81 | PresetBtns; 82 | 83 | Button 85 | ResetSense; 86 | std::optional> 87 | ResetTimeOpt; 88 | int ResetSavedKHz = 0; 89 | int ResetSavedPeriod = 0; 90 | Mode ResetSavedMode; 91 | 92 | void printTxtAndSleep(const char *Str); 93 | 94 | void updateDisplay(); 95 | 96 | static constexpr const size_t MaxUartStrSz = 10; 97 | std::string UartStr; 98 | void uartTick(Uart &Uart); 99 | void presetBtnsTick(); 100 | void resetSenseTick(); 101 | 102 | virtual void tick() = 0; 103 | 104 | public: 105 | CommonLogic(int SamplePeriod, Display &Disp, DutyCycle &DC, 106 | PresetsTable &Presets, FlashStorage &Flash, Pico &Pi) 107 | : SamplePeriod(SamplePeriod), Disp(Disp), DC(DC), Presets(Presets), 108 | Flash(Flash), ResetSense(ResetSenseGPIO, Pi, "ResetSense") { 109 | for (int BtnIdx = 0, E = PresetBtnGPIOs.size(); BtnIdx != E; ++BtnIdx) { 110 | std::string BtnStr = "PresetBtn." + std::to_string(BtnIdx); 111 | PresetBtns.emplace_back(PresetBtnGPIOs[BtnIdx], Pi, BtnStr.c_str()); 112 | } 113 | } 114 | void tickAll(Uart &Uart) { 115 | tick(); 116 | uartTick(Uart); 117 | presetBtnsTick(); 118 | resetSenseTick(); 119 | } 120 | }; 121 | 122 | #endif // __COMMONLOGIC_H__ 123 | -------------------------------------------------------------------------------- /firmware/src/ConfigOpts.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #ifndef __CONFIGOPTS_H__ 6 | #define __CONFIGOPTS_H__ 7 | 8 | #include 9 | #include 10 | 11 | // GPIOs 12 | // Display 13 | static constexpr const int DisplayClkGPIO = 0; 14 | static constexpr const int DisplayDioGPIO = 1; 15 | // Rotary Encoder 16 | static constexpr const int RotaryClkGPIO = 2; 17 | static constexpr const int RotaryDtGPIO = 3; 18 | static constexpr const int RotarySwGPIO = 4; 19 | // Buttons 20 | static constexpr const int LeftButtonGPIO = RotarySwGPIO; 21 | static constexpr const int RightButtonGPIO = 7; 22 | // Buttons for presets 23 | static std::vector PresetBtnGPIOs = {10, 11, 12, 13, 18, 19, 20, 21}; 24 | // Reset sense 25 | static constexpr const int ResetSenseGPIO = 22; 26 | // Maximum speed for this many seconds. 27 | static constexpr const int ResetMaxSpeedDuration = 20; 28 | 29 | // Throttle pin 30 | static constexpr const int ThrottleGPIO = 27; 31 | // Jumpers 32 | static constexpr const int ModeJP1GPIO = 5; 33 | static constexpr const int ModeJP2GPIO = 6; 34 | // Potentiometer 35 | static constexpr const int PotentiometerGPIO = 28; 36 | // UART 37 | static constexpr const int UartGPIO = 8; // (also UartGPIO+1) 38 | // Reverse knob direction. 39 | static constexpr const int ReverseDirectionGPIO = 26; 40 | 41 | // Uart configuration 9600 8N1 42 | static constexpr const uint32_t UartRequestedBaud = 9600; 43 | static constexpr const uint32_t UartDataBits = 8; 44 | static constexpr const uart_parity_t UartParity = UART_PARITY_NONE; 45 | static constexpr const uint32_t UartStopBits = 1; 46 | static constexpr const uint32_t UartFlowControl = false; 47 | 48 | static constexpr const char UartEOM = '\r'; 49 | 50 | // MHz Settings 51 | static constexpr const int DefaultMaxMHz = 200; 52 | // The maximum MHz we allow. 53 | static constexpr const int MHzLimitHi = 5000; 54 | // The minimum MHz we allow. 55 | static constexpr const int MHzLimitLo = 1; 56 | // This controls the PWM period, the higher the value the larger the period. 57 | static constexpr const int PeriodLimitHi = 256; 58 | static constexpr const int PeriodLimitLo = 1; 59 | 60 | static constexpr const int TwoBtnMaxMHzStep = 50; 61 | 62 | // Input Settings 63 | // After how many loop iterations we are sampling the inputs. 64 | // The lower the value the more responsive it is. 65 | static constexpr const int PotSamplePeriod = 100; 66 | static constexpr const int RotaryEncoderSamplePeriod = 1; 67 | static constexpr const int ButtonSamplePeriod = 100; 68 | 69 | // Turning the Rotary Encoder one step 70 | static constexpr const int RotaryMaxMHzRightStep = 50; 71 | static constexpr const int RotaryMaxMHzLeftStep = 1; 72 | 73 | // Potentiometer settings. 74 | static constexpr const int PotMoveAgainstSavedIdleLimit = 30; 75 | static constexpr const int PotLeftPercent = 40; 76 | static constexpr const int PotRightPercent = 60; 77 | static constexpr const int PotentiometerRampUpIgnoreReadings = 50; 78 | // A dead-zone for the min and max position of the potentiometer. 79 | static constexpr const int ADCDeadZone = 20; 80 | // ADC is noizy so take the average over this many measurements. 81 | static constexpr const unsigned ADCDenoise = 32; 82 | 83 | // Button debounce: Take the average of this many readings. 84 | static constexpr const int ButtonDebounceSz = 2; 85 | 86 | // Push button behavior. 87 | static constexpr const int ButtonMedReleaseCnt = 20; 88 | static constexpr const int ButtonLongPressCnt = 200; 89 | static constexpr const int RotaryLongPressMillis = 1500; 90 | 91 | // TM1637 Display Settings 92 | // Flashing 93 | static constexpr const int DisplayFlashOnPeriod = 100; 94 | static constexpr const int DisplayFlashOffPeriod = 30; 95 | // The delay after printing a message. 96 | static constexpr const int PrintSleep = 1000; 97 | 98 | // Other 99 | // No need to update the PWM period on every UI tick. Skip this many. 100 | static constexpr const int UpdatePWMSamplePeriod = 8; 101 | 102 | #endif // __CONFIGOPTS_H__ 103 | -------------------------------------------------------------------------------- /firmware/src/Debug.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #ifndef __DEBUG_H__ 6 | #define __DEBUG_H__ 7 | 8 | #include 9 | 10 | 11 | #ifdef DBGPRINT 12 | #define DBG_PRINT(...) \ 13 | { __VA_ARGS__ } 14 | #else 15 | #define DBG_PRINT(...) {} 16 | #endif 17 | 18 | #endif // __DEBUG_H__ 19 | -------------------------------------------------------------------------------- /firmware/src/Display.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "Display.h" 7 | #include 8 | #ifdef PICO_TM1637 9 | using uint = unsigned int; 10 | extern "C" { 11 | #include 12 | } 13 | #endif 14 | 15 | bool Display::flashShouldClear() { 16 | if (!Flash) 17 | return false; 18 | ++FlashCnt; 19 | switch (State) { 20 | case FlashState::Show: 21 | if (FlashCnt == FlashOnPeriod) { 22 | FlashCnt = 0; 23 | State = FlashState::Clear; 24 | return true; 25 | } 26 | return false; 27 | case FlashState::Clear: 28 | if (FlashCnt == FlashOffPeriod) { 29 | FlashCnt = 0; 30 | State = FlashState::Show; 31 | return false; 32 | } 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | Display::Display(int CLK_PIN, int DIO_PIN) { 39 | #ifdef PICO_TM1637 40 | TM1637_init(CLK_PIN, DIO_PIN); 41 | TM1637_clear(); 42 | TM1637_set_brightness(DISPLAY_BRIGHTNESS); 43 | #endif 44 | } 45 | 46 | 47 | static int pow10(int Num) { 48 | int Res = 1; 49 | for (int Cnt = 0; Cnt < Num; ++Cnt) 50 | Res = Res * 10; 51 | return Res; 52 | } 53 | 54 | void Display::printRaw(int Num) { 55 | #ifdef PICO_TM1637 56 | if (flashShouldClear()) { 57 | TM1637_clear(); 58 | return; 59 | } 60 | int NumToDisplay = Num * pow10(DISPLAY_SHIFT_LEFT); 61 | TM1637_set_colon(false); 62 | TM1637_display(NumToDisplay, 0); 63 | #endif 64 | } 65 | 66 | void Display::printMHz(int MHz) { 67 | #ifdef PICO_TM1637 68 | if (flashShouldClear()) { 69 | TM1637_clear(); 70 | return; 71 | } 72 | printRaw(MHz); 73 | #endif 74 | } 75 | 76 | void Display::printKHz(int KHz) { 77 | #ifdef PICO_TM1637 78 | if (flashShouldClear()) { 79 | TM1637_clear(); 80 | return; 81 | } 82 | int NumToPrint = KHz / 1000; 83 | printMHz(NumToPrint); 84 | #endif 85 | } 86 | 87 | void Display::printTxt(const char *Str) { 88 | #ifdef PICO_TM1637 89 | if (flashShouldClear()) { 90 | TM1637_clear(); 91 | return; 92 | } 93 | TM1637_display_word((char *)Str, true); 94 | #endif 95 | } 96 | -------------------------------------------------------------------------------- /firmware/src/Display.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __DISPLAY_H__ 7 | #define __DISPLAY_H__ 8 | 9 | #include "ConfigOpts.h" 10 | 11 | class Display { 12 | bool Flash = false; 13 | static constexpr const int FlashOnPeriod = DisplayFlashOnPeriod; 14 | static constexpr const int FlashOffPeriod = DisplayFlashOffPeriod; 15 | int FlashCnt = 0; 16 | enum class FlashState { 17 | Show, 18 | Clear, 19 | }; 20 | FlashState State = FlashState::Show; 21 | 22 | bool flashShouldClear(); 23 | 24 | public: 25 | Display(int CLK_PIN, int DIO_PIN); 26 | Display(const Display &) = delete; 27 | void printRaw(int Num); 28 | void printMHz(int MHz); 29 | void printKHz(int KHz); 30 | void printTxt(const char *Str); 31 | /// Turns on/off flashing of the display. 32 | void setFlash(bool NewState) { Flash = NewState; } 33 | }; 34 | 35 | #endif // __DISPLAY_H__ 36 | -------------------------------------------------------------------------------- /firmware/src/DutyCycle.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #ifndef __DUTYCYCLE_H__ 6 | #define __DUTYCYCLE_H__ 7 | 8 | #include 9 | #include 10 | #include "Presets.h" 11 | #include "ConfigOpts.h" 12 | 13 | class DutyCycle { 14 | int Period = 0; 15 | int KHz = 0; 16 | const PresetsTable &Presets; 17 | 18 | public: 19 | DutyCycle(PresetsTable &Presets) 20 | : Period(1u << 12), KHz(Presets.getMaxKHz()), Presets(Presets) {} 21 | void setMHzToMax() { setKHz(Presets.getMaxKHz()); } 22 | void incrMHz() { 23 | int Step = 1000; 24 | int NextKHz = (KHz / 1000) * 1000 + Step; 25 | setKHz(std::min(Presets.getMaxKHz(), NextKHz)); 26 | } 27 | void decrMHz() { 28 | int Step = 1000; 29 | int PrevKHz = (KHz / 1000) * 1000 - Step; 30 | setKHz(std::max(0, PrevKHz)); 31 | } 32 | int getMHz() const { return (int64_t)KHz * 1000; } 33 | int getKHz() const { return KHz; } 34 | void setKHz(int NewKHz) { KHz = NewKHz; } 35 | void setPeriod(int Num) { Period = (1u << 8) * Num - 1; } 36 | void setPeriodRaw(int RawPeriod) { Period = RawPeriod; } 37 | int getPeriod() const { return Period; } 38 | int getLevel() const { 39 | return Period - (int64_t)KHz * Period / Presets.getMaxKHz(); 40 | } 41 | int getMaxLevel() const { return Period; } 42 | void dump() const { 43 | std::cout << "KHz=" << KHz << " Level=" << getLevel() 44 | << " Period=" << getPeriod() << "\n"; 45 | } 46 | }; 47 | 48 | #endif // __DUTYCYCLE_H__ 49 | -------------------------------------------------------------------------------- /firmware/src/Flash.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "Flash.h" 7 | #include "Debug.h" 8 | #include 9 | #include 10 | 11 | std::vector FlashStorage::getDataVec(const std::vector &Values) { 12 | std::vector TmpDataVec; 13 | int NumEntries = BytesToFlash / sizeof(TmpDataVec[0]); 14 | TmpDataVec.reserve(NumEntries); 15 | TmpDataVec.reserve(MagicNumber.size() + 2 + Values.size()); 16 | // 1. Magic number. 17 | TmpDataVec.insert(TmpDataVec.end(), MagicNumber.begin(), MagicNumber.end()); 18 | // 2. Revision. 19 | TmpDataVec.push_back(REVISION_MAJOR); 20 | TmpDataVec.push_back(REVISION_MINOR); 21 | // 3. Actual values. 22 | TmpDataVec.insert(TmpDataVec.end(), Values.begin(), Values.end()); 23 | // // 5. Fill the rest with zeros. 24 | // for (int Cnt = TmpDataVec.size(); Cnt != NumEntries; ++Cnt) 25 | // TmpDataVec.push_back(0); 26 | return TmpDataVec; 27 | } 28 | 29 | FlashStorage::FlashStorage() { 30 | FlashArray = 31 | (const int *)(XIP_BASE + BaseOffset + /*Revision:*/ 2 * sizeof(int) + 32 | /*MagicNumber:*/ MagicNumber.size() * 33 | sizeof(MagicNumber[0])); 34 | } 35 | 36 | void FlashStorage::write(const std::vector &Values) { 37 | DBG_PRINT(std::cout << "BaseOffset=" << BaseOffset 38 | << " BytesToFlash=" << BytesToFlash << "\n";) 39 | DBG_PRINT(std::cout << "DataVec:\n";) 40 | auto DataVec = getDataVec(Values); 41 | for (int Val : DataVec) 42 | DBG_PRINT(std::cout << Val << "\n";) 43 | 44 | DBG_PRINT(std::cout << "Before save and disable interrupts()\n";) 45 | // When writing to flash we need to stop the other core from running code. 46 | multicore_lockout_start_blocking(); 47 | // We also need to disable interrupts. 48 | uint32_t SvInterrupts = save_and_disable_interrupts(); 49 | flash_range_erase(BaseOffset, BytesToFlash); 50 | flash_range_program(BaseOffset, (const uint8_t *)DataVec.data(), 51 | BytesToFlash); 52 | // Restore interrupts. 53 | restore_interrupts(SvInterrupts); 54 | // Resume execution on other core. 55 | multicore_lockout_end_blocking(); 56 | DBG_PRINT(std::cout << "After interrupts\n";) 57 | } 58 | 59 | std::vector FlashStorage::readMagicNumber() const { 60 | std::vector MagicNum; 61 | int MagicNumberSz = (int)MagicNumber.size(); 62 | for (int Idx = 0; Idx != MagicNumberSz; ++Idx) 63 | MagicNum.push_back(FlashArray[Idx - MagicNumberSz - 2]); 64 | 65 | DBG_PRINT(std::cout << "Read magic number: ";) 66 | for (int V : MagicNum) 67 | DBG_PRINT(std::cout << V << " ";) 68 | DBG_PRINT(std::cout << "\n";) 69 | return MagicNum; 70 | } 71 | 72 | std::pair FlashStorage::readRevision() const { 73 | return {FlashArray[-2], FlashArray[-1]}; 74 | } 75 | 76 | bool FlashStorage::valid() const { return readMagicNumber() == MagicNumber; } 77 | -------------------------------------------------------------------------------- /firmware/src/Flash.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __FLASH_H__ 7 | #define __FLASH_H__ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class FlashStorage { 15 | // Use the last sector (4KB) of the 2MB flash. 16 | static constexpr const int BaseOffset = 2 * (1u << 20) - FLASH_SECTOR_SIZE; 17 | static constexpr const int BytesToFlash = FLASH_SECTOR_SIZE; 18 | std::vector MagicNumber = {11111111, 42, 0, 666, 11111111}; 19 | 20 | /// Points to the first usable int ptr, after the magic number and revision. 21 | const int *FlashArray = nullptr; 22 | 23 | std::vector getDataVec(const std::vector &Values); 24 | 25 | public: 26 | FlashStorage(); 27 | void write(const std::vector &Values); 28 | /// \Reads a value at \p ValueIdx offset (after the magic numbers). 29 | int read(int ValueIdx) const { 30 | return FlashArray[ValueIdx]; 31 | } 32 | std::pair readRevision() const; 33 | std::vector readMagicNumber() const; 34 | bool valid() const; 35 | }; 36 | 37 | 38 | #endif // __FLASH_H__ 39 | -------------------------------------------------------------------------------- /firmware/src/Pico.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "Pico.h" 7 | #include "Debug.h" 8 | #include "Utils.h" 9 | #include "hardware/pll.h" 10 | #include "hardware/structs/rosc.h" 11 | #include "hardware/structs/scb.h" 12 | #include 13 | 14 | #ifndef NDEBUG 15 | static uint32_t AlreadySetMask = 0u; 16 | #endif // NDEBUG 17 | 18 | PinRange::PinRange(uint32_t From, uint32_t To) : From(From), To(To) { 19 | Mask = ((1u << (To + 1 - From)) - 1) << From; 20 | #ifndef NDEBUG 21 | if ((AlreadySetMask & Mask) != 0u) 22 | die("Some pins in range ", From, " to ", To, " are already set!"); 23 | AlreadySetMask |= Mask; 24 | #endif // NDEBUG 25 | } 26 | 27 | void PinRange::dump(std::ostream &OS) const { 28 | auto Flags = OS.flags(); 29 | if (From == To) 30 | OS << std::setw(5) << std::setfill(' ') << From; 31 | else 32 | OS << std::setw(2) << From << "-" << std::setw(2) << To; 33 | OS << " Mask="; 34 | OS << "0x" << std::setw(8) << std::setfill('0') << std::hex << Mask; 35 | OS.flags(Flags); 36 | } 37 | 38 | void PinRange::dump() const { dump(std::cerr); } 39 | 40 | Pico::Pico() { 41 | // Initialize stdio so that we can print debug messages. 42 | stdio_init_all(); 43 | adc_init(); 44 | // Initialize the Pico LED. 45 | gpio_init(PICO_DEFAULT_LED_PIN); 46 | gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); 47 | 48 | // Use pins 16-17 as debugging uart 49 | gpio_set_function(16, GPIO_FUNC_UART); // TX 50 | gpio_set_function(17, GPIO_FUNC_UART); // RX 51 | 52 | // Wait for a bit otherwise this does not show up during serial debug. 53 | DBG_PRINT(sleep_ms(500);) 54 | DBG_PRINT( 55 | std::cerr << "+---------------------------------+\n"; 56 | std::cerr << "| " << PROJECT_NAME << "\n"; 57 | std::cerr << "+---------------------------------+\n"; 58 | std::cerr << "clk_sys = " 59 | << frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS) << "KHz\n"; 60 | std::cerr << "clk_usb = " 61 | << frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB) << "KHz\n"; 62 | std::cerr << "clk_peri = " 63 | << frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI) 64 | << "KHz\n"; 65 | std::cerr << "clk_ref = " 66 | << frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_REF) << "KHz\n"; 67 | std::cerr << "clk_adc = " 68 | << frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC) << "KHz\n"; 69 | std::cerr << "clk_rtc = " 70 | << frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC) 71 | << "KHz\n";) 72 | } 73 | 74 | void Pico::initGPIO(const PinRange &Pins, int Direction, Pull Pull, 75 | const char *Descr) { 76 | DBG_PRINT(std::cout << "Setting up GPIO " << std::setw(5) << std::setfill(' ') << Descr 77 | << " ";) 78 | DBG_PRINT(Pins.dump(std::cout);) 79 | DBG_PRINT(std::cout << " " << (Direction == GPIO_IN ? "IN" : "OUT") << "\n";) 80 | for (uint32_t Pin = Pins.getFrom(), E = Pins.getTo(); Pin <= E; ++Pin) { 81 | gpio_init(Pin); 82 | gpio_set_dir(Pin, Direction); 83 | switch (Pull) { 84 | case Pull::Up: 85 | gpio_pull_up(Pin); 86 | break; 87 | case Pull::Down: 88 | gpio_pull_down(Pin); 89 | break; 90 | case Pull::None: 91 | gpio_disable_pulls(Pin); 92 | break; 93 | } 94 | } 95 | } 96 | 97 | void Pico::initADC(const PinRange &Pins, const char *Descr) { 98 | DBG_PRINT(std::cout << "Setting up ADC " << std::setw(5) << std::setfill(' ') 99 | << Descr << " ";) 100 | DBG_PRINT(Pins.dump(std::cout);) 101 | DBG_PRINT(std::cout << "\n";) 102 | for (uint32_t Pin = Pins.getFrom(), E = Pins.getTo(); Pin <= E; ++Pin) { 103 | adc_gpio_init(Pin); 104 | } 105 | } 106 | 107 | uint16_t Pico::readADC(uint32_t GPIO) const { 108 | if (!(GPIO >= 26 && GPIO < 29)) { 109 | std::cerr << "Bad ADC Pin " << GPIO << ". Good values are 26..29 !\n"; 110 | abort(); 111 | } 112 | int AdcId = GPIO - 26; 113 | adc_select_input(AdcId); 114 | uint16_t Raw_12bit = adc_read(); 115 | return Raw_12bit; 116 | } 117 | -------------------------------------------------------------------------------- /firmware/src/Pico.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __SRC_PICO_H__ 7 | #define __SRC_PICO_H__ 8 | 9 | #include "Utils.h" 10 | #include "hardware/clocks.h" 11 | #include "hardware/vreg.h" 12 | #include "pico/stdlib.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /// A range of GPIO pins. This is useful because it also creates the 20 | /// corresponding mask which is used for setting and resetting pins. 21 | class PinRange { 22 | uint32_t From = 0; 23 | uint32_t To = 0; 24 | uint32_t Mask = 0; 25 | 26 | public: 27 | /// \p From is the first pin and \p To is the last pin in the reange. 28 | PinRange(uint32_t From, uint32_t To); 29 | /// Use this constructor to create a single pin. 30 | PinRange(uint32_t Pin) : PinRange(Pin, Pin) {} 31 | uint32_t getFrom() const { return From; } 32 | uint32_t getTo() const { return To; } 33 | uint32_t getMask() const { return Mask; } 34 | void dump(std::ostream &OS) const; 35 | DUMP_METHOD void dump() const; 36 | }; 37 | 38 | class Pico { 39 | uint32_t State = 0; 40 | 41 | public: 42 | static constexpr const uint16_t ADCMax = 1 << 12; 43 | // Some valid frequencies: 225000, 250000, 270000, 280000, 290400 44 | // Voltages: /src/rp2_common/hardware_vreg/include/hardware/vreg.h 45 | // Examples: VREG_VOLTAGE_0_85 0.85v 46 | // VREG_VOLTAGE_1_30 1.30v 47 | Pico(); 48 | /// Sets \p Pins to GPIO_OUT. 49 | inline void setGPIODirectionOut(const PinRange &Pins) { 50 | gpio_set_dir_out_masked(Pins.getMask()); 51 | } 52 | /// Sets \p Pins to GPIO_IN. 53 | inline void setGPIODirectionIn(const PinRange &Pins) { 54 | gpio_set_dir_in_masked(Pins.getMask()); 55 | } 56 | inline void setGPIODir(int GPIO, bool Out) { 57 | gpio_set_dir(GPIO, Out); 58 | } 59 | /// Sets \p Pins to \p Value. 60 | inline void setGPIOBits(const PinRange &Pins, uint32_t Value) { 61 | gpio_put_masked(Pins.getMask(), Value << Pins.getFrom()); 62 | } 63 | /// Direction can be GPIO_IN GPIO_OUT. 64 | enum Pull { 65 | Up, 66 | Down, 67 | None, 68 | }; 69 | void initGPIO(const PinRange &Pins, int Direction, Pull Pull, const char *Descr); 70 | void initADC(const PinRange &Pins, const char *Descr); 71 | /// Receives the status of all GPIO pins. 72 | inline void readGPIO() { State = gpio_get_all(); } 73 | /// \Returns the state of \p GPIO. Must have run `readGPIO()` first! 74 | inline bool getGPIO(uint32_t GPIO) const { return (State >> GPIO) & 0x1; } 75 | /// Sets \p GPIO to \p Value. 76 | inline void setGPIO(uint32_t GPIO, bool Value) { gpio_put(GPIO, Value); } 77 | inline bool getGPIO(uint32_t GPIO) { return gpio_get(GPIO); } 78 | /// Reads ADC at \p GPIO and returns the 12-bit value. 79 | /// Note: GPIO must be a valid ADC GPIO, i.e., 26-29. 80 | uint16_t readADC(uint32_t GPIO) const; 81 | void ledSet(bool State) { gpio_put(PICO_DEFAULT_LED_PIN, State); } 82 | void ledON() { gpio_put(PICO_DEFAULT_LED_PIN, 1); } 83 | void ledOFF() { gpio_put(PICO_DEFAULT_LED_PIN, 0); } 84 | inline void clear(uint32_t Mask) { gpio_clr_mask(Mask); } 85 | inline void set(uint32_t Mask) { gpio_set_mask(Mask); } 86 | }; 87 | 88 | #endif // __SRC_PICO_H__ 89 | -------------------------------------------------------------------------------- /firmware/src/Potentiometer.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __POTENTIOMETER_H__ 7 | #define __POTENTIOMETER_H__ 8 | 9 | #include "Pico.h" 10 | #include "Utils.h" 11 | 12 | template class Potentiometer { 13 | Buffer MeanADCVals; 14 | int GPIO; 15 | Pico Π 16 | bool ReverseDirection; 17 | 18 | public: 19 | Potentiometer(int GPIO, Pico &Pi, bool ReverseDirection = false, 20 | const char *Name = "Potentiometer") 21 | : GPIO(GPIO), Pi(Pi), ReverseDirection(ReverseDirection) { 22 | Pi.initADC(GPIO, Name); 23 | } 24 | static constexpr const int getMax() { return MaxVal; } 25 | int get() { 26 | MeanADCVals.append(Pi.readADC(GPIO)); 27 | int ADCVal = MeanADCVals.getMean(); 28 | int Val = std::min(MaxVal, std::max(0, ADCVal - DeadZone) * MaxVal / 29 | (Pico::ADCMax - 2 * DeadZone)); 30 | return !ReverseDirection ? Val : MaxVal - Val; 31 | } 32 | }; 33 | 34 | #endif // __POTENTIOMETER_H__ 35 | -------------------------------------------------------------------------------- /firmware/src/PotentiometerLogic.cpp: -------------------------------------------------------------------------------- 1 | #include "PotentiometerLogic.h" 2 | #include "Debug.h" 3 | #include "config.h" 4 | 5 | void PotentiometerLogic::uart(int PotVal, ButtonState BtnState) { 6 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 7 | setMode(Mode::Manual); 8 | return; 9 | } 10 | if (BtnState != ButtonState::None) { 11 | setMode(Mode::Presets); 12 | printTxtAndSleep(MsgPresets); 13 | } 14 | } 15 | 16 | void PotentiometerLogic::manual(int PotVal, ButtonState BtnState) { 17 | switch (BtnState) { 18 | case ButtonState::Release: 19 | savePotVal(PotVal); 20 | setMode(Mode::Presets); 21 | printTxtAndSleep(MsgPresets); 22 | return; 23 | case ButtonState::LongPress: 24 | if (Presets.atMaxKHz()) { 25 | setMode(Mode::ConfigMHz); 26 | printTxtAndSleep(MsgActualFreq); 27 | } else { 28 | setMode(Mode::ConfigMaxMHz); 29 | printTxtAndSleep(MsgMaxMHz); 30 | } 31 | savePotVal(PotVal); 32 | Disp.setFlash(true); 33 | return; 34 | default: 35 | break; 36 | } 37 | if (EnablePot) { 38 | int MaxKHz = Presets.getMaxKHz(); 39 | DC.setKHz(PotVal * MaxKHz / PotMaxVal); 40 | } 41 | } 42 | 43 | void PotentiometerLogic::cyclePresets(int PotVal, ButtonState BtnState) { 44 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 45 | setMode(Mode::Manual); 46 | return; 47 | } 48 | switch (BtnState) { 49 | case ButtonState::Release: 50 | Presets.cyclePrev(); 51 | DC.setKHz(Presets.getKHz()); 52 | DC.setPeriod(Presets.getPeriod()); 53 | break; 54 | case ButtonState::MedRelease: 55 | Presets.cycleMax(); 56 | DC.setKHz(Presets.getKHz()); 57 | DC.setPeriod(Presets.getPeriod()); 58 | break; 59 | case ButtonState::LongPress: 60 | if (Presets.atMaxKHz()) { 61 | setMode(Mode::ConfigMaxMHz); 62 | printTxtAndSleep(MsgMaxMHz); 63 | } else { 64 | setMode(Mode::ConfigMHz); 65 | printTxtAndSleep(MsgActualFreq); 66 | } 67 | savePotVal(PotVal); 68 | Disp.setFlash(true); 69 | break; 70 | default: 71 | break; 72 | } 73 | } 74 | 75 | void PotentiometerLogic::configPeriod(int PotVal, ButtonState BtnState) { 76 | switch (BtnState) { 77 | case ButtonState::Release: 78 | Presets.incrPeriod(); 79 | DC.setPeriod(Presets.getPeriod()); 80 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 81 | return; 82 | case ButtonState::MedRelease: 83 | Presets.decrPeriod(); 84 | DC.setPeriod(Presets.getPeriod()); 85 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 86 | return; 87 | case ButtonState::LongPress: 88 | tryWritePresetsToFlash(); 89 | Disp.setFlash(false); 90 | printTxtAndSleep(MsgConfirm); 91 | savePotVal(PotVal); 92 | setMode(Mode::Presets); 93 | return; 94 | default: 95 | break; 96 | } 97 | 98 | // If we moved the potentiometer enough, use it to control the value. 99 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 100 | switch (getPotDir(PotVal)) { 101 | case PotDir::Right: 102 | Presets.incrPeriod(); 103 | DC.setPeriod(Presets.getPeriod()); 104 | break; 105 | case PotDir::Left: 106 | Presets.decrPeriod(); 107 | DC.setPeriod(Presets.getPeriod()); 108 | break; 109 | default: 110 | break; 111 | } 112 | } 113 | } 114 | 115 | void PotentiometerLogic::configResetToDefaults(int PotVal, 116 | ButtonState BtnState) { 117 | auto DontReset = [this, PotVal]() { 118 | tryWritePresetsToFlash(); 119 | Disp.setFlash(false); 120 | printTxtAndSleep(MsgEscape); 121 | printTxtAndSleep(MsgPresets); 122 | savePotVal(PotVal); 123 | setMode(Mode::Presets); 124 | }; 125 | switch (BtnState) { 126 | case ButtonState::Release: 127 | case ButtonState::MedRelease: 128 | DontReset(); 129 | break; 130 | case ButtonState::LongPress: 131 | Presets.resetToDefaults(Flash); 132 | Disp.setFlash(false); 133 | printTxtAndSleep(MsgConfirm); 134 | savePotVal(PotVal); 135 | setMode(Mode::Presets); 136 | break; 137 | default: 138 | break; 139 | } 140 | if (EnablePot && movedPotComparedToSaved(PotVal)) 141 | DontReset(); 142 | } 143 | 144 | void PotentiometerLogic::configMaxMHz(int PotVal, ButtonState BtnState) { 145 | switch (BtnState) { 146 | case ButtonState::LongPress: 147 | setMode(Mode::ResetToDefaults); 148 | printTxtAndSleep(MsgResetToDefaults); 149 | return; 150 | case ButtonState::Release: { 151 | int Base = Presets.getMaxMHz() - Presets.getMaxMHz() % 100; 152 | Presets.incrMaxMHz(/*Step=*/1, /*Wrap=*/true, /*WrapLo=*/Base, 153 | /*WrapHi=*/Base + 99); 154 | DC.setKHz(Presets.getMaxKHz()); 155 | return; 156 | } 157 | case ButtonState::MedRelease: { 158 | Presets.incrMaxMHz(/*Step=*/100, /*Wrap=*/true); 159 | DC.setKHz(Presets.getMaxKHz()); 160 | return; 161 | } 162 | default: 163 | break; 164 | } 165 | // If we moved the potentiometer enough, use it to control the value. 166 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 167 | switch (getPotDir(PotVal)) { 168 | case PotDir::Right: 169 | Presets.incrMaxMHz(); 170 | break; 171 | case PotDir::Left: 172 | Presets.decrMaxMHz(); 173 | break; 174 | default: 175 | break; 176 | } 177 | } 178 | } 179 | 180 | void PotentiometerLogic::configMHz(int PotVal, ButtonState BtnState) { 181 | switch (BtnState) { 182 | case ButtonState::Release: 183 | Presets.incrActualKHz(); 184 | return; 185 | case ButtonState::MedRelease: 186 | Presets.decrActualKHz(); 187 | return; 188 | case ButtonState::LongPress: 189 | tryWritePresetsToFlash(); 190 | Disp.setFlash(false); 191 | printTxtAndSleep(MsgPeriod); 192 | Disp.setFlash(true); 193 | setMode(Mode::ConfigPeriod); 194 | return; 195 | default: 196 | break; 197 | } 198 | // If we moved the potentiometer enough, use it to control the value. 199 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 200 | switch (getPotDir(PotVal)) { 201 | case PotDir::Right: 202 | Presets.incrActualKHz(); 203 | break; 204 | case PotDir::Left: 205 | Presets.decrActualKHz(); 206 | break; 207 | default: 208 | break; 209 | } 210 | } 211 | } 212 | 213 | void PotentiometerLogic::setModeInit(Mode NewMode) { 214 | if (NewMode == Mode::Uart) { 215 | savePotVal(Pot.get()); 216 | } 217 | } 218 | 219 | void PotentiometerLogic::tick() { 220 | if (++LoopCntSinceSample != SamplePeriod) 221 | return; 222 | LoopCntSinceSample = 0; 223 | 224 | updateDisplay(); 225 | 226 | auto BtnState = Btn.get(); 227 | int PotVal = Pot.get(); 228 | 229 | if (EnablePot && InitRampUp) { 230 | // There is a small capacitor connected to the potentiometer for 231 | // stability, but it causes the values to ramp-up. Ignore them. 232 | if (++InitRampUpCnt < PotentiometerRampUpIgnoreReadings) 233 | return; 234 | InitRampUp = false; 235 | LastPotVal = PotVal; 236 | savePotVal(PotVal); 237 | } 238 | 239 | switch (getMode()) { 240 | case Mode::Manual: 241 | manual(PotVal, BtnState); 242 | break; 243 | case Mode::Presets: 244 | cyclePresets(PotVal, BtnState); 245 | break; 246 | case Mode::ConfigMHz: 247 | configMHz(PotVal, BtnState); 248 | break; 249 | case Mode::ConfigPeriod: 250 | configPeriod(PotVal, BtnState); 251 | break; 252 | case Mode::ConfigMaxMHz: 253 | configMaxMHz(PotVal, BtnState); 254 | break; 255 | case Mode::ResetToDefaults: 256 | configResetToDefaults(PotVal, BtnState); 257 | break; 258 | case Mode::Uart: 259 | uart(PotVal, BtnState); 260 | break; 261 | case Mode::Boot: 262 | break; 263 | } 264 | LastMode = getMode(); 265 | LastPotVal = PotVal; 266 | } 267 | -------------------------------------------------------------------------------- /firmware/src/PotentiometerLogic.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __POTENTIOMETERLOGIC_H__ 7 | #define __POTENTIOMETERLOGIC_H__ 8 | 9 | #include "Button.h" 10 | #include "CommonLogic.h" 11 | #include "ConfigOpts.h" 12 | #include "Display.h" 13 | #include "Flash.h" 14 | #include "Pico.h" 15 | #include "Potentiometer.h" 16 | #include "Presets.h" 17 | 18 | class PotentiometerLogic : public CommonLogic { 19 | static constexpr const int PotMaxVal = 1024; 20 | Button 22 | Btn; 23 | Potentiometer Pot; 24 | Pico Π 25 | int LoopCntSinceSample = 0; 26 | 27 | static constexpr const int LeftPotLimit = PotLeftPercent * PotMaxVal / 100; 28 | static constexpr const int RightPotLimit = PotRightPercent * PotMaxVal / 100; 29 | 30 | CommonLogic::Mode LastMode = Mode::Manual; 31 | 32 | int LastPotVal = -1; 33 | /// The capacitor attached to the potentiometer causes it to ramp-up. 34 | /// We need to ignore these values. 35 | bool InitRampUp = true; 36 | int InitRampUpCnt = 0; 37 | 38 | void manual(int PotVal, ButtonState BtnState); 39 | 40 | void uart(int PotVal, ButtonState BtnState); 41 | 42 | void cyclePresets(int PotVal, ButtonState BtnState); 43 | 44 | void configPeriod(int PotVal, ButtonState BtnState); 45 | 46 | void configResetToDefaults(int PotVal, ButtonState BtnState); 47 | 48 | void configMaxMHz(int PotVal, ButtonState BtnState); 49 | 50 | void configMHz(int PotVal, ButtonState BtnState); 51 | 52 | void setModeInit(Mode NewMode) override; 53 | 54 | int SvPotVal = 0; 55 | bool MovedPot = false; 56 | 57 | void savePotVal(int PotVal) { 58 | SvPotVal = PotVal; 59 | MovedPot = false; 60 | } 61 | bool movedPotComparedToSaved(int PotVal) { 62 | int PotDiff = std::abs(PotVal - SvPotVal); 63 | if (PotDiff > PotMoveAgainstSavedIdleLimit) 64 | MovedPot = true; 65 | return MovedPot; 66 | } 67 | enum class PotDir { 68 | Right, 69 | Mid, 70 | Left, 71 | }; 72 | PotDir getPotDir(int PotVal) const { 73 | if (PotVal > RightPotLimit) 74 | return PotDir::Right; 75 | if (PotVal < LeftPotLimit) 76 | return PotDir::Left; 77 | return PotDir::Mid; 78 | } 79 | 80 | bool EnablePot = false;; 81 | 82 | public: 83 | PotentiometerLogic(int PotGPIO, int ButtonGPIO, int SamplePeriod, Pico &Pi, 84 | Display &Disp, DutyCycle &DC, PresetsTable &Presets, 85 | FlashStorage &Flash, bool EnablePot, bool ReverseDirection) 86 | : CommonLogic(SamplePeriod, Disp, DC, Presets, Flash, Pi), 87 | Btn(ButtonGPIO, Pi, "Pot.Btn"), Pot(PotGPIO, Pi, ReverseDirection), 88 | Pi(Pi), EnablePot(EnablePot) { 89 | // One of the potentiometer's best features is that it remembers its 90 | // positions across rrestarts. So start in manual mode to make use of it. 91 | setMode(Mode::Manual); 92 | } 93 | void tick() final; 94 | }; 95 | 96 | #endif // __POTENTIOMETERLOGIC_H__ 97 | -------------------------------------------------------------------------------- /firmware/src/Presets.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #include "Presets.h" 6 | #include "Debug.h" 7 | #include "Flash.h" 8 | 9 | void PresetsTable::dump() const { 10 | for (const auto &E : Table) 11 | E.dump(); 12 | std::cout << "\n"; 13 | } 14 | 15 | void PresetsTable::incrActualKHz() { 16 | int &Val = Table[Idx].ActualKHz; 17 | Val = std::min(maxKHz(), Val + 1000); 18 | } 19 | 20 | void PresetsTable::decrActualKHz() { 21 | int &Val = Table[Idx].ActualKHz; 22 | Val = std::max(1000, Val - 1000); 23 | } 24 | 25 | void PresetsTable::incrPeriod() { 26 | int &Val = Table[Idx].Period; 27 | Val = std::min(PeriodLimitHi, Val + 1); 28 | } 29 | 30 | void PresetsTable::decrPeriod() { 31 | int &Val = Table[Idx].Period; 32 | Val = std::max(PeriodLimitLo, Val - 1); 33 | } 34 | 35 | void PresetsTable::cyclePrev() { 36 | if (Idx == 0) 37 | Idx = getMaxIdx(); 38 | else 39 | prev(); 40 | } 41 | 42 | void PresetsTable::cycleNext() { 43 | if (Idx == getMaxIdx()) 44 | Idx = 0; 45 | else 46 | next(); 47 | } 48 | 49 | void PresetsTable::cycleMax() { Idx = getMaxIdx(); } 50 | 51 | void PresetsTable::updateMaxMHz(int NewMaxMHz) { 52 | MaxMHz = NewMaxMHz; 53 | int MaxKHz = MaxMHz * 1000; 54 | // If the last entry is not in the immutable table, then it is a custom 55 | // MaxMHz entry. Drop it as we are going to replace it with a new one. 56 | if (!Table.empty()) { 57 | int TableIdx = Table.size() - 1; 58 | if (Table.size() > ImmutableTable.size() || 59 | Table.back().PresetKHz != ImmutableTable[TableIdx].PresetKHz) 60 | Table.pop_back(); 61 | } 62 | // Drop Table entries with KHz > MaxKHz 63 | while (!Table.empty() && Table.back().PresetKHz > MaxKHz) 64 | Table.pop_back(); 65 | // Copy any missing entries from ImmutableMHzTable to MHzTable. 66 | for (const auto &Entry : ImmutableTable) { 67 | if ((Table.empty() || Entry.PresetKHz > Table.back().PresetKHz) && 68 | Entry.PresetKHz <= MaxKHz) 69 | Table.push_back(Entry); 70 | } 71 | // Add a custom entry with the new Max value if required. 72 | if (Table.empty() || Table.back().PresetKHz != MaxKHz) 73 | Table.emplace_back(MaxKHz, MaxKHz, DefaultPeriod); 74 | // Adjust the Idx to point to the last entry. 75 | Idx = (int)Table.size() - 1; 76 | // dump(); 77 | } 78 | 79 | void PresetsTable::incrMaxMHz(int Step, bool Wrap, int WrapLo, int WrapHi) { 80 | int NewMax; 81 | if (Wrap && getMaxMHz() == WrapHi) 82 | NewMax = WrapLo; 83 | else 84 | NewMax = std::min(WrapHi, getMaxMHz() + Step); 85 | updateMaxMHz(NewMax); 86 | } 87 | 88 | void PresetsTable::decrMaxMHz(int Step) { 89 | int NewMax = std::max(MHzLimitLo, getMaxMHz() - Step); 90 | updateMaxMHz(NewMax); 91 | } 92 | 93 | void PresetsTable::writeToFlash(FlashStorage &Flash) const { 94 | std::vector FlashValues; 95 | // First write MaxMHz. 96 | FlashValues.push_back(MaxMHz); 97 | // Next the size of Table. 98 | FlashValues.push_back((int)Table.size()); 99 | // Next the entries one by one. 100 | for (const auto &Entry : Table) 101 | Entry.appendToVec(FlashValues); 102 | // Finally add a checksum. 103 | FlashValues.push_back(checksum(MaxMHz, Table)); 104 | 105 | DBG_PRINT(std::cout << "Presets: Writing to Flash\n";) 106 | Flash.write(FlashValues); 107 | DBG_PRINT(std::cout << "Presets: Done writing to Flash\n";) 108 | } 109 | 110 | int PresetsTable::checksum(int MHz, const std::vector &Entries) const { 111 | unsigned Checksum = 0; 112 | Checksum ^= MHz; 113 | for (const auto &E : Entries) 114 | Checksum ^= E.checksum(); 115 | return (int)Checksum; 116 | } 117 | 118 | void PresetsTable::readFromFlash(FlashStorage &Flash) { 119 | if (!Flash.valid()) 120 | return; 121 | DBG_PRINT(std::cout << "Presets: Reading from Flash\n";) 122 | int FlashOffset = 0; 123 | // 1. First read MaxMHz. 124 | int FlashMaxMHz = Flash.read(FlashOffset++); 125 | if (FlashMaxMHz > MHzLimitHi || FlashMaxMHz < MHzLimitLo) { 126 | DBG_PRINT(std::cout << "Bad FlashMaxMHz=" << FlashMaxMHz << "\n";) 127 | return; 128 | } 129 | updateMaxMHz(FlashMaxMHz); 130 | // 2. Next read the size of Table. 131 | unsigned TableSz = Flash.read(FlashOffset++); 132 | if (TableSz > ImmutableTable.size() + 1) { 133 | DBG_PRINT(std::cout << "Bad TableSz=" << TableSz << "\n";) 134 | return; 135 | } 136 | // 3. Next read the entries one by one. 137 | std::vector FlashTable; 138 | for (unsigned EntryIdx = 0; EntryIdx != TableSz; ++EntryIdx) 139 | FlashTable.emplace_back(Flash, FlashOffset); 140 | // 4. Check checksum. 141 | int ReadChecksum = Flash.read(FlashOffset++); 142 | int ExpectedChecksum = checksum(FlashMaxMHz, FlashTable); 143 | if (ReadChecksum != ExpectedChecksum) { 144 | DBG_PRINT(std::cout << "Checksum error: Got:" << ReadChecksum 145 | << " Expected:" << ExpectedChecksum << "\n";) 146 | return; 147 | } 148 | Table = std::move(FlashTable); 149 | DBG_PRINT(std::cout << "Presets: Done reading from Flash\n";) 150 | DBG_PRINT(dump();) 151 | } 152 | 153 | void PresetsTable::resetToDefaults(FlashStorage &Flash) { 154 | DBG_PRINT(std::cout << "Presets: Reset to defaults...\n";) 155 | Table = ImmutableTable; 156 | updateMaxMHz(DefaultMaxMHz); 157 | writeToFlash(Flash); 158 | } 159 | -------------------------------------------------------------------------------- /firmware/src/Presets.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #ifndef __PRESETS_H__ 6 | #define __PRESETS_H__ 7 | 8 | #include "ConfigOpts.h" 9 | #include "Flash.h" 10 | #include 11 | #include 12 | #include 13 | 14 | class PresetsTable { 15 | int MaxMHz = DefaultMaxMHz; 16 | 17 | struct Entry { 18 | int PresetKHz; 19 | int ActualKHz; 20 | int Period; ///> PWM Period 21 | Entry(int PresetKHz, int ActualKHz, int Period) 22 | : PresetKHz(PresetKHz), ActualKHz(ActualKHz), Period(Period) {} 23 | Entry(FlashStorage &Flash, int &Offset) { 24 | PresetKHz = Flash.read(Offset++); 25 | ActualKHz = Flash.read(Offset++); 26 | Period = Flash.read(Offset++); 27 | } 28 | void dump() const { 29 | std::cout << PresetKHz << ", " << ActualKHz << ", " << Period << "\n"; 30 | } 31 | /// Helper for writing to Flash. 32 | void appendToVec(std::vector &Vec) const { 33 | Vec.push_back(PresetKHz); 34 | Vec.push_back(ActualKHz); 35 | Vec.push_back(Period); 36 | } 37 | unsigned checksum() const { 38 | return (unsigned)PresetKHz ^ (unsigned)ActualKHz ^ (unsigned)Period; 39 | } 40 | }; 41 | 42 | static constexpr const int DefaultPeriod = 8; // ~50us 43 | 44 | // const std::vector ImmutableTable = { 45 | // {4770, 4770, DefaultPeriod}, {8000, 8000, DefaultPeriod}, 46 | // {10000, 10000, DefaultPeriod}, {25000, 25000, DefaultPeriod}, 47 | // {33000, 33000, DefaultPeriod}, {66000, 66000, DefaultPeriod}, 48 | // {100000, 100000, DefaultPeriod}, {133000, 133000, DefaultPeriod}, 49 | // {266000, 266000, DefaultPeriod}, {450000, 450000, DefaultPeriod}, 50 | // {733000, 733000, DefaultPeriod}, {1000000, 1000000, DefaultPeriod}, 51 | // {1666000, 1666000, DefaultPeriod}, {3200000, 3200000, DefaultPeriod}}; 52 | const std::vector ImmutableTable = { 53 | {4770, 4770, DefaultPeriod}, {8000, 8000, DefaultPeriod}, 54 | {10000, 10000, DefaultPeriod}, {25000, 25000, DefaultPeriod}, 55 | {33000, 33000, DefaultPeriod}, {66000, 66000, DefaultPeriod}, 56 | {133000, 133000, DefaultPeriod}, {450000, 450000, DefaultPeriod}, 57 | {733000, 733000, DefaultPeriod}}; 58 | 59 | std::vector Table; 60 | 61 | int Idx; 62 | 63 | int maxKHz() const { return Table.back().PresetKHz; } 64 | int checksum(int MHz, const std::vector &Entries) const; 65 | int getMaxIdx() const { return (int)Table.size() - 1; } 66 | 67 | public: 68 | PresetsTable() { updateMaxMHz(MaxMHz); } 69 | void dump() const; 70 | int getMaxMHz() const { return MaxMHz; } 71 | int getMaxKHz() const { return MaxMHz * 1000; } 72 | int getMinKHz() const { return Table[0].PresetKHz; } 73 | int getKHz() const { return Table[Idx].PresetKHz; } 74 | bool atMaxKHz() const { return getMaxKHz() == getKHz(); } 75 | int getActualKHz() const { return Table[Idx].ActualKHz; } 76 | void incrActualKHz(); 77 | void decrActualKHz(); 78 | int getPeriod() const { return Table[Idx].Period; } 79 | void incrPeriod(); 80 | void decrPeriod(); 81 | void prev() { Idx = std::max(0, Idx - 1); } 82 | void next() { Idx = std::min(getMaxIdx(), Idx + 1); } 83 | void setIdx(int NewIdx) { Idx = std::clamp(NewIdx, 0, getMaxIdx()); } 84 | void cyclePrev(); 85 | void cycleNext(); 86 | void cycleMax(); 87 | void updateMaxMHz(int NewMaxMHz); 88 | void incrMaxMHz(int Step = 1, bool Wrap = false, int WrapLo = MHzLimitLo, 89 | int WrapHi = MHzLimitHi); 90 | void decrMaxMHz(int Step = 1); 91 | void writeToFlash(FlashStorage &Flash) const; 92 | void readFromFlash(FlashStorage &Flash); 93 | void resetToDefaults(FlashStorage &Flash); 94 | }; 95 | 96 | #endif // __PRESETS_H__ 97 | -------------------------------------------------------------------------------- /firmware/src/Pwm.pio: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | ;; 4 | ;; SPDX-License-Identifier: BSD-3-Clause 5 | ;; 6 | ;; Note: This code is based on the Pi Pico SDK pwmn.pio example. 7 | ;; Modified by Scrap Computing. 8 | 9 | .program pwm 10 | .side_set 1 opt 11 | 12 | entry: 13 | pull noblock ; Pull from FIFO to OSR if available, else copy X to OSR. 14 | mov x, osr ; X is the PWM level 15 | mov y, isr ; ISR contains PWM period. Y is the PWM period counter 16 | jmp !x levelZero ; level==0 needs special treatment 17 | jmp x!=y startPWM ; level==max needs special treatment 18 | jmp levelMax 19 | 20 | levelZero: ; level==0 is a special case: Out pin should be 0. 21 | jmp entry side 0 22 | 23 | levelMax: ; level==Max is a special case. Out pin should be 1. 24 | jmp entry side 1 ; Set to 1 and try to pull new values from the queue 25 | 26 | startPWM: 27 | jmp pwmLoop side 0 ; Start by setting the output to 0 28 | pwmLoop: 29 | jmp x!=y noset ; If y < x the pin should remain 0. 30 | jmp skip side 1 ; Set to 1 if x == y 31 | noset: 32 | nop ; Single dummy cycle to keep the two paths the same length 33 | skip: 34 | jmp y-- pwmLoop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO 35 | 36 | % c-sdk { 37 | static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint GPIO) { 38 | pio_gpio_init(pio, GPIO); 39 | pio_sm_set_consecutive_pindirs(pio, sm, GPIO, 1, true); 40 | pio_sm_config c = pwm_program_get_default_config(offset); 41 | sm_config_set_sideset_pins(&c, GPIO); 42 | pio_sm_init(pio, sm, offset, &c); 43 | } 44 | %} 45 | -------------------------------------------------------------------------------- /firmware/src/RotaryEncoder.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __ROTARYENCODER_H__ 7 | #define __ROTARYENCODER_H__ 8 | 9 | #include "Debug.h" 10 | #include "Pico.h" 11 | #include "Utils.h" 12 | #include 13 | #include 14 | #include 15 | 16 | class RotaryEncoder { 17 | int ClkPin; 18 | int DtPin; 19 | int SwPin; 20 | 21 | static constexpr const unsigned DebounceSz = 4; 22 | static constexpr const unsigned SwitchDebounceSz = 16; 23 | Buffer BuffA; 24 | Buffer BuffB; 25 | Buffer BuffC; 26 | 27 | public: 28 | enum class State { 29 | SwPress, 30 | SwRelease, 31 | SwLongPress, 32 | Left, 33 | Right, 34 | None, 35 | }; 36 | 37 | #ifdef DBGPRINT 38 | static const char *stateToStr(State S) { 39 | switch (S) { 40 | case State::SwPress: 41 | return "SwPress"; 42 | case State::SwRelease: 43 | return "SwRelease"; 44 | case State::SwLongPress: 45 | return "SwLongPress"; 46 | case State::Left: 47 | return "Left"; 48 | case State::Right: 49 | return "Right"; 50 | case State::None: 51 | return "None"; 52 | } 53 | return "UNKNOWN"; 54 | } 55 | #endif // DBGPRINT 56 | 57 | private: 58 | bool LastA = false; 59 | bool LastB = false; 60 | bool LastSw = true; 61 | bool IgnoreRelease = false; 62 | 63 | enum class InternalState { 64 | A0B0, 65 | A0B1, 66 | A1B1, 67 | A1B0, 68 | }; 69 | static constexpr const int BuffSz = 4; 70 | Buffer IntState; 71 | 72 | Pico &PICO; 73 | bool ReverseDirection; 74 | 75 | std::chrono::time_point PressStart; 76 | 77 | critical_section Lock; 78 | 79 | std::list StateList; 80 | 81 | void registerState(State); 82 | 83 | public: 84 | RotaryEncoder(int ClkPin, int DtPin, int SwPin, Pico &Pico, 85 | bool ReverseDirection = false); 86 | 87 | void tick(); 88 | 89 | State get(); 90 | }; 91 | 92 | #endif // __ROTARYENCODER_H__ 93 | -------------------------------------------------------------------------------- /firmware/src/RotaryLogic.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "RotaryLogic.h" 7 | #include "Debug.h" 8 | 9 | void RotaryLogic::escapeReset() { 10 | setMode(Mode::Presets); 11 | Disp.setFlash(false); 12 | printTxtAndSleep(MsgEscape); 13 | } 14 | 15 | void RotaryLogic::onSwRelease() { 16 | switch (getMode()) { 17 | case Mode::Presets: 18 | setMode(Mode::Manual); 19 | printTxtAndSleep(MsgManual); 20 | break; 21 | case Mode::Manual: 22 | setMode(Mode::Presets); 23 | DC.setKHz(Presets.getActualKHz()); 24 | printTxtAndSleep(MsgPresets); 25 | break; 26 | case Mode::ConfigMHz: 27 | tryWritePresetsToFlash(); 28 | setMode(Mode::ConfigPeriod); 29 | Disp.setFlash(false); 30 | printTxtAndSleep(MsgPeriod); 31 | Disp.setFlash(true); 32 | break; 33 | case Mode::ConfigPeriod: 34 | tryWritePresetsToFlash(); 35 | setMode(Mode::Presets); 36 | Disp.setFlash(false); 37 | printTxtAndSleep(MsgConfirm); 38 | break; 39 | case Mode::ConfigMaxMHz: 40 | tryWritePresetsToFlash(); 41 | setMode(Mode::Presets); 42 | Disp.setFlash(false); 43 | printTxtAndSleep(MsgConfirm); 44 | break; 45 | case Mode::ResetToDefaults: 46 | escapeReset(); 47 | break; 48 | case Mode::Uart: 49 | setMode(Mode::Presets); 50 | printTxtAndSleep(MsgPresets); 51 | break; 52 | case Mode::Boot: 53 | break; 54 | } 55 | } 56 | 57 | void RotaryLogic::onSwLongPress() { 58 | switch (getMode()) { 59 | case Mode::Manual: 60 | if (Presets.atMaxKHz()) { 61 | setMode(Mode::ConfigMaxMHz); 62 | printTxtAndSleep(MsgMaxMHz); 63 | } 64 | break; 65 | case Mode::Presets: 66 | if (Presets.atMaxKHz()) { 67 | setMode(Mode::ConfigMaxMHz); 68 | printTxtAndSleep(MsgMaxMHz); 69 | } else { 70 | setMode(Mode::ConfigMHz); 71 | printTxtAndSleep(MsgActualFreq); 72 | } 73 | Disp.setFlash(true); 74 | break; 75 | case Mode::ConfigMHz: 76 | case Mode::ConfigPeriod: 77 | setMode(Mode::Presets); 78 | printTxtAndSleep(MsgEscape); 79 | Disp.setFlash(false); 80 | break; 81 | case Mode::ConfigMaxMHz: 82 | setMode(Mode::ResetToDefaults); 83 | printTxtAndSleep(MsgResetToDefaults); 84 | Disp.setFlash(true); 85 | break; 86 | case Mode::ResetToDefaults: 87 | setMode(Mode::Presets); 88 | Presets.resetToDefaults(Flash); 89 | printTxtAndSleep(MsgConfirm); 90 | Disp.setFlash(false); 91 | break; 92 | case Mode::Uart: 93 | setMode(Mode::Presets); 94 | printTxtAndSleep(MsgPresets); 95 | break; 96 | case Mode::Boot: 97 | break; 98 | } 99 | } 100 | 101 | void RotaryLogic::onLeft() { 102 | switch (getMode()) { 103 | case Mode::Presets: 104 | Presets.prev(); 105 | DC.setKHz(Presets.getActualKHz()); 106 | DC.setPeriod(Presets.getPeriod()); 107 | DBG_PRINT(std::cout << "Presets=" << Presets.getKHz() << "\n";) 108 | break; 109 | case Mode::ConfigMHz: 110 | Presets.decrActualKHz(); 111 | DC.setKHz(Presets.getActualKHz()); 112 | DC.setPeriod(Presets.getPeriod()); 113 | DBG_PRINT(std::cout << "ActualKHz=" << Presets.getActualKHz() << "\n";) 114 | break; 115 | case Mode::ConfigPeriod: 116 | Presets.decrPeriod(); 117 | DC.setPeriod(Presets.getPeriod()); 118 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 119 | break; 120 | case Mode::Manual: 121 | DC.decrMHz(); 122 | DBG_PRINT(std::cout << DC.getKHz() << "\n";) 123 | break; 124 | case Mode::ConfigMaxMHz: { 125 | int PrevMHz = Presets.getMaxMHz() - RotaryMaxMHzLeftStep; 126 | if (PrevMHz < MHzLimitLo) 127 | PrevMHz = MHzLimitHi; 128 | Presets.updateMaxMHz(PrevMHz); 129 | DC.setKHz(Presets.getMaxKHz()); 130 | DBG_PRINT(std::cout << "MaxMHz=" << Presets.getMaxMHz() << "\n";) 131 | break; 132 | } 133 | case Mode::ResetToDefaults: 134 | escapeReset(); 135 | break; 136 | case Mode::Uart: 137 | setMode(Mode::Presets); 138 | DC.setKHz(Presets.getActualKHz()); 139 | DC.setPeriod(Presets.getPeriod()); 140 | printTxtAndSleep(MsgPresets); 141 | break; 142 | case Mode::Boot: 143 | break; 144 | } 145 | } 146 | 147 | void RotaryLogic::onRight() { 148 | switch (getMode()) { 149 | case Mode::Presets: 150 | Presets.next(); 151 | DC.setKHz(Presets.getActualKHz()); 152 | DC.setPeriod(Presets.getPeriod()); 153 | DBG_PRINT(std::cout << "Presets=" << Presets.getKHz() << "\n";) 154 | break; 155 | case Mode::ConfigMHz: 156 | Presets.incrActualKHz(); 157 | DC.setKHz(Presets.getActualKHz()); 158 | DC.setPeriod(Presets.getPeriod()); 159 | DBG_PRINT(std::cout << "ActualKHz=" << Presets.getActualKHz() << "\n";) 160 | break; 161 | case Mode::ConfigPeriod: { 162 | Presets.incrPeriod(); 163 | DC.setPeriod(Presets.getPeriod()); 164 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 165 | break; 166 | case Mode::Manual: 167 | DC.incrMHz(); 168 | DBG_PRINT(std::cout << DC.getKHz() << "\n";) 169 | break; 170 | case Mode::ConfigMaxMHz: { 171 | int NextMHz = Presets.getMaxMHz() + RotaryMaxMHzRightStep; 172 | if (NextMHz > MHzLimitHi) 173 | NextMHz = MHzLimitLo; 174 | Presets.updateMaxMHz(NextMHz); 175 | DC.setKHz(Presets.getMaxKHz()); 176 | DBG_PRINT(std::cout << "MaxMHz=" << Presets.getMaxMHz() << "\n";) 177 | break; 178 | } 179 | case Mode::ResetToDefaults: 180 | setMode(Mode::Presets); 181 | Disp.setFlash(false); 182 | printTxtAndSleep(MsgEscape); 183 | break; 184 | case Mode::Uart: 185 | setMode(Mode::Presets); 186 | DC.setKHz(Presets.getActualKHz()); 187 | DC.setPeriod(Presets.getPeriod()); 188 | printTxtAndSleep(MsgPresets); 189 | break; 190 | case Mode::Boot: 191 | break; 192 | } 193 | } 194 | } 195 | 196 | void RotaryLogic::tick() { 197 | if (++LoopCntSinceSample != SamplePeriod) 198 | return; 199 | LoopCntSinceSample = 0; 200 | 201 | updateDisplay(); 202 | 203 | switch (RotEnc.get()) { 204 | case RotaryEncoder::State::SwRelease: { 205 | DBG_PRINT(std::cout << "SwRelase\n";) 206 | onSwRelease(); 207 | break; 208 | } 209 | case RotaryEncoder::State::SwLongPress: { 210 | DBG_PRINT(std::cout << "SwLongPress\n";) 211 | onSwLongPress(); 212 | break; 213 | } 214 | case RotaryEncoder::State::Left: { 215 | DBG_PRINT(std::cout << "Left\n";) 216 | onLeft(); 217 | break; 218 | } 219 | case RotaryEncoder::State::Right: { 220 | DBG_PRINT(std::cout << "Right\n";) 221 | onRight(); 222 | break; 223 | } 224 | default: 225 | break; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /firmware/src/RotaryLogic.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __ROTARYLOGIC_H__ 7 | #define __ROTARYLOGIC_H__ 8 | 9 | #include "CommonLogic.h" 10 | #include "DutyCycle.h" 11 | #include "Flash.h" 12 | #include "Pico.h" 13 | #include "RotaryEncoder.h" 14 | #include 15 | 16 | class RotaryLogic : public CommonLogic { 17 | 18 | RotaryEncoder RotEnc; 19 | 20 | int BeforeMaxMHz = 0; 21 | int BeforeActualKHz = 0; 22 | int BeforePWM = 0; 23 | 24 | void escapeReset(); 25 | 26 | void onSwRelease(); 27 | void onSwLongPress(); 28 | void onLeft(); 29 | void onRight(); 30 | 31 | public: 32 | RotaryLogic(int ClkGPIO, int DtGPIO, int SwGPIO, int SamplePeriod, Pico &Pi, 33 | Display &Disp, PresetsTable &Presets, DutyCycle &DC, 34 | FlashStorage &Flash, bool ReverseDirection) 35 | : CommonLogic(SamplePeriod, Disp, DC, Presets, Flash, Pi), 36 | RotEnc(ClkGPIO, DtGPIO, SwGPIO, Pi, ReverseDirection) { 37 | setMode(Mode::Presets); 38 | } 39 | RotaryLogic(const RotaryLogic &) = delete; 40 | void tick() final; 41 | }; 42 | 43 | #endif // __ROTARYLOGIC_H__ 44 | -------------------------------------------------------------------------------- /firmware/src/TwoButtonLogic.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "TwoButtonLogic.h" 7 | 8 | void TwoButtonLogic::cyclePresets(ButtonState LBtnState, 9 | ButtonState RBtnState) { 10 | auto LongPress = [this]() { 11 | if (Presets.atMaxKHz()) { 12 | setMode(Mode::ConfigMaxMHz); 13 | printTxtAndSleep(MsgMaxMHz); 14 | } else { 15 | setMode(Mode::ConfigMHz); 16 | printTxtAndSleep(MsgActualFreq); 17 | } 18 | Disp.setFlash(true); 19 | }; 20 | 21 | if (bothRelease(LBtnState, RBtnState)) { 22 | setMode(Mode::Manual); 23 | printTxtAndSleep(MsgManual); 24 | return; 25 | } 26 | if (bothLongPress(LBtnState, RBtnState)) { 27 | setMode(Mode::ResetToDefaults); 28 | printTxtAndSleep(MsgResetToDefaults); 29 | return; 30 | } 31 | // Skip if both buttons are pressed. 32 | if (LBtnState == ButtonState::LongPress && RBtnState == ButtonState::Pressed) 33 | return; 34 | if (RBtnState == ButtonState::LongPress && LBtnState == ButtonState::Pressed) 35 | return; 36 | 37 | switch (LBtnState) { 38 | case ButtonState::Release: 39 | case ButtonState::MedRelease: 40 | Presets.prev(); 41 | DC.setKHz(Presets.getKHz()); 42 | DC.setPeriod(Presets.getPeriod()); 43 | break; 44 | case ButtonState::LongPress: 45 | LongPress(); 46 | break; 47 | default: 48 | break; 49 | } 50 | switch (RBtnState) { 51 | case ButtonState::Release: 52 | case ButtonState::MedRelease: 53 | Presets.next(); 54 | DC.setKHz(Presets.getKHz()); 55 | DC.setPeriod(Presets.getPeriod()); 56 | break; 57 | case ButtonState::LongPress: 58 | LongPress(); 59 | break; 60 | default: 61 | break; 62 | } 63 | } 64 | 65 | void TwoButtonLogic::manual(ButtonState LBtnState, ButtonState RBtnState) { 66 | if (bothRelease(LBtnState, RBtnState)) { 67 | setMode(Mode::Presets); 68 | printTxtAndSleep(MsgPresets); 69 | return; 70 | } 71 | switch (LBtnState) { 72 | case ButtonState::Release: 73 | case ButtonState::MedRelease: 74 | DC.decrMHz(); 75 | break; 76 | case ButtonState::LongPress: 77 | break; 78 | default: 79 | break; 80 | } 81 | 82 | switch (RBtnState) { 83 | case ButtonState::Release: 84 | case ButtonState::MedRelease: 85 | DC.incrMHz(); 86 | break; 87 | case ButtonState::LongPress: 88 | break; 89 | default: 90 | break; 91 | } 92 | } 93 | 94 | void TwoButtonLogic::uart(ButtonState LBtnState, ButtonState RBtnState) { 95 | if (LBtnState != ButtonState::None || RBtnState != ButtonState::None) { 96 | setMode(Mode::Presets); 97 | printTxtAndSleep(MsgPresets); 98 | } 99 | } 100 | 101 | void TwoButtonLogic::configPeriod(ButtonState LBtnState, ButtonState RBtnState) { 102 | auto LongPress = [this]() { 103 | // Save 104 | tryWritePresetsToFlash(); 105 | Disp.setFlash(false); 106 | printTxtAndSleep(MsgConfirm); 107 | setMode(Mode::Presets); 108 | }; 109 | 110 | if (bothRelease(LBtnState, RBtnState)) { 111 | // Cancel 112 | setMode(Mode::Presets); 113 | printTxtAndSleep(MsgEscape); 114 | Disp.setFlash(false); 115 | return; 116 | } 117 | switch (LBtnState) { 118 | case ButtonState::Release: 119 | case ButtonState::MedRelease: 120 | Presets.decrPeriod(); 121 | DC.setPeriod(Presets.getPeriod()); 122 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 123 | return; 124 | case ButtonState::LongPress: 125 | LongPress(); 126 | return; 127 | default: 128 | break; 129 | } 130 | 131 | switch (RBtnState) { 132 | case ButtonState::Release: 133 | case ButtonState::MedRelease: 134 | Presets.incrPeriod(); 135 | DC.setPeriod(Presets.getPeriod()); 136 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 137 | return; 138 | case ButtonState::LongPress: 139 | LongPress(); 140 | return; 141 | default: 142 | break; 143 | } 144 | } 145 | 146 | void TwoButtonLogic::configResetToDefaults(ButtonState LBtnState, 147 | ButtonState RBtnState) { 148 | auto DontReset = [this] { 149 | tryWritePresetsToFlash(); 150 | Disp.setFlash(false); 151 | printTxtAndSleep(MsgEscape); 152 | printTxtAndSleep(MsgPresets); 153 | setMode(Mode::Presets); 154 | }; 155 | switch (LBtnState) { 156 | case ButtonState::Release: 157 | DontReset(); 158 | break; 159 | default: 160 | break; 161 | } 162 | switch (RBtnState) { 163 | case ButtonState::Release: 164 | DontReset(); 165 | break; 166 | default: 167 | break; 168 | } 169 | if (bothLongPress(LBtnState, RBtnState)) { 170 | Presets.resetToDefaults(Flash); 171 | Disp.setFlash(false); 172 | printTxtAndSleep(MsgConfirm); 173 | setMode(Mode::Presets); 174 | return; 175 | } 176 | } 177 | 178 | void TwoButtonLogic::configMaxMHz(ButtonState LBtnState, ButtonState RBtnState) { 179 | auto LongPress = [this]() { 180 | // Save 181 | tryWritePresetsToFlash(); 182 | setMode(Mode::Presets); 183 | Disp.setFlash(false); 184 | printTxtAndSleep(MsgConfirm); 185 | }; 186 | if (bothRelease(LBtnState, RBtnState)) { 187 | // Cancel 188 | setMode(Mode::Presets); 189 | printTxtAndSleep(MsgEscape); 190 | Disp.setFlash(false); 191 | return; 192 | } 193 | switch (LBtnState) { 194 | case ButtonState::Release: 195 | case ButtonState::MedRelease: 196 | Presets.decrMaxMHz(/*Step=*/1); 197 | DC.setKHz(Presets.getMaxKHz()); 198 | return; 199 | case ButtonState::LongPress: 200 | LongPress(); 201 | return; 202 | default: 203 | break; 204 | } 205 | 206 | switch (RBtnState) { 207 | case ButtonState::Release: 208 | case ButtonState::MedRelease: 209 | Presets.incrMaxMHz(/*Step=*/TwoBtnMaxMHzStep, /*Wrap=*/true, 210 | /*WrapLo=*/MHzLimitLo, 211 | /*WrapHi=*/MHzLimitHi); 212 | DC.setKHz(Presets.getMaxKHz()); 213 | return; 214 | case ButtonState::LongPress: 215 | LongPress(); 216 | return; 217 | default: 218 | break; 219 | } 220 | } 221 | 222 | void TwoButtonLogic::configMHz(ButtonState LBtnState, ButtonState RBtnState) { 223 | auto LongPress = [this] () { 224 | // Proceed to Period mode 225 | Disp.setFlash(false); 226 | printTxtAndSleep(MsgPeriod); 227 | Disp.setFlash(true); 228 | setMode(Mode::ConfigPeriod); 229 | }; 230 | 231 | if (bothRelease(LBtnState, RBtnState)) { 232 | setMode(Mode::Presets); 233 | printTxtAndSleep(MsgEscape); 234 | Disp.setFlash(false); 235 | return; 236 | } 237 | switch (LBtnState) { 238 | case ButtonState::Release: 239 | case ButtonState::MedRelease: 240 | Presets.decrActualKHz(); 241 | return; 242 | case ButtonState::LongPress: 243 | LongPress(); 244 | return; 245 | default: 246 | break; 247 | } 248 | 249 | switch (RBtnState) { 250 | case ButtonState::Release: 251 | case ButtonState::MedRelease: 252 | Presets.incrActualKHz(); 253 | return; 254 | case ButtonState::LongPress: 255 | LongPress(); 256 | return; 257 | default: 258 | break; 259 | } 260 | } 261 | 262 | void TwoButtonLogic::tick() { 263 | if (++LoopCntSinceSample != SamplePeriod) 264 | return; 265 | LoopCntSinceSample = 0; 266 | 267 | updateDisplay(); 268 | 269 | auto LBtnState = LBtn.get(); 270 | auto RBtnState = RBtn.get(); 271 | 272 | switch (getMode()) { 273 | case Mode::Presets: 274 | cyclePresets(LBtnState, RBtnState); 275 | break; 276 | case Mode::Manual: 277 | manual(LBtnState, RBtnState); 278 | break; 279 | case Mode::ConfigMHz: 280 | configMHz(LBtnState, RBtnState); 281 | break; 282 | case Mode::ConfigPeriod: 283 | configPeriod(LBtnState, RBtnState); 284 | break; 285 | case Mode::ConfigMaxMHz: 286 | configMaxMHz(LBtnState, RBtnState); 287 | break; 288 | case Mode::ResetToDefaults: 289 | configResetToDefaults(LBtnState, RBtnState); 290 | break; 291 | case Mode::Uart: 292 | uart(LBtnState, RBtnState); 293 | break; 294 | case Mode::Boot: 295 | break; 296 | } 297 | LastMode = getMode(); 298 | } 299 | -------------------------------------------------------------------------------- /firmware/src/TwoButtonLogic.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #ifndef __TWOBUTTONLOGIC_H__ 7 | #define __TWOBUTTONLOGIC_H__ 8 | 9 | #include "Button.h" 10 | #include "CommonLogic.h" 11 | #include "ConfigOpts.h" 12 | #include "Display.h" 13 | #include "Flash.h" 14 | #include "Pico.h" 15 | #include "Presets.h" 16 | 17 | class TwoButtonLogic : public CommonLogic { 18 | static constexpr const int PotMaxVal = 1024; 19 | Button 21 | LBtn; 22 | Button 24 | RBtn; 25 | Pico Π 26 | int LoopCntSinceSample = 0; 27 | 28 | CommonLogic::Mode LastMode = Mode::Manual; 29 | 30 | void cyclePresets(ButtonState LBtnState, ButtonState RBtnState); 31 | 32 | void manual(ButtonState LBtnState, ButtonState RBtnState); 33 | 34 | void uart(ButtonState LBtnState, ButtonState RBtnState); 35 | 36 | void configPeriod(ButtonState LBtnState, ButtonState RBtnState); 37 | 38 | void configResetToDefaults(ButtonState LBtnState, ButtonState RBtnState); 39 | 40 | void configMaxMHz(ButtonState LBtnState, ButtonState RBtnState); 41 | 42 | void configMHz(ButtonState LBtnState, ButtonState RBtnState); 43 | 44 | public: 45 | TwoButtonLogic(int LeftButtonGPIO, int RightButtonGPIO, int SamplePeriod, 46 | Pico &Pi, Display &Disp, DutyCycle &DC, PresetsTable &Presets, 47 | FlashStorage &Flash) 48 | : CommonLogic(SamplePeriod, Disp, DC, Presets, Flash, Pi), 49 | LBtn(LeftButtonGPIO, Pi, "LBtn"), RBtn(RightButtonGPIO, Pi, "LBtn"), 50 | Pi(Pi) { 51 | setMode(Mode::Presets); 52 | } 53 | void tick() final; 54 | }; 55 | 56 | #endif // __TWOBUTTONLOGIC_H__ 57 | -------------------------------------------------------------------------------- /firmware/src/Uart.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #include "Uart.h" 6 | #include "Debug.h" 7 | #include "Pico.h" 8 | #include "Utils.h" 9 | #include 10 | 11 | const char *Uart::GetParityStr(uart_parity_t Parity) { 12 | switch (Parity) { 13 | case UART_PARITY_NONE: 14 | return "UART_PARITY_NONE"; 15 | case UART_PARITY_EVEN: 16 | return "UART_PARITY_EVEN"; 17 | case UART_PARITY_ODD: 18 | return "UART_PARITY_ODD"; 19 | } 20 | unreachable("Bad Parity"); 21 | } 22 | 23 | Uart::Uart(int GPIO, uint32_t RequestedBaudrate, uint32_t DataBits, 24 | uint32_t StopBits, uart_parity_t Parity, bool FlowControl) { 25 | /// Instance: is uart0 (GP0-1, GP12-13, GP16-17) or uart1 (GP4-5 or GP8-9). 26 | switch (GPIO) { 27 | case 0: 28 | case 12: 29 | case 16: 30 | Instance = uart0; 31 | break; 32 | case 4: 33 | case 8: 34 | Instance = uart1; 35 | break; 36 | default: 37 | std::cerr << "UART error: Bad GPIO=" << GPIO << "\n"; 38 | } 39 | gpio_set_function(GPIO, GPIO_FUNC_UART); 40 | gpio_set_function(GPIO + 1, GPIO_FUNC_UART); 41 | ActualBaudrate = uart_init(Instance, RequestedBaudrate); 42 | uart_set_hw_flow(Instance, /*cts=*/FlowControl, /*rts=*/FlowControl); 43 | uart_set_format(Instance, /*data_bits=*/DataBits, /*stop_bits=*/StopBits, 44 | /*parity=*/Parity); 45 | uart_set_fifo_enabled(Instance, false); 46 | DBG_PRINT(std::cerr << "Actual Baudrate=" << ActualBaudrate << "\n"; 47 | std::cerr << "DataBits=" << DataBits << "\n"; 48 | std::cerr << "StopBits=" << StopBits << "\n"; 49 | std::cerr << "Parity=" << GetParityStr(Parity) << "\n"; 50 | std::cerr << "FlowControl=" << FlowControl << "\n";) 51 | } 52 | Uart::~Uart() { uart_deinit(Instance); } 53 | 54 | void Uart::writeBlockingStr(const std::string &Str) { 55 | uart_write_blocking(Instance, (const uint8_t *)Str.c_str(), Str.size()); 56 | } 57 | 58 | void Uart::writeBlocking(const std::vector &Bytes) { 59 | uart_write_blocking(Instance, Bytes.data(), Bytes.size()); 60 | } 61 | 62 | std::vector Uart::readNonBlocking() { 63 | size_t ReadyBytes = uart_is_readable(Instance); 64 | if (ReadyBytes == 0) 65 | return Empty; 66 | std::vector Bytes; 67 | Bytes.resize(ReadyBytes); 68 | uart_read_blocking(Instance, Bytes.data(), Bytes.size()); 69 | return Bytes; 70 | } 71 | 72 | const std::vector Uart::Empty; 73 | -------------------------------------------------------------------------------- /firmware/src/Uart.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | 5 | #ifndef __UART_H__ 6 | #define __UART_H__ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class Uart { 13 | static const std::vector Empty; 14 | /// The UART instance (either uart0 or uart1). 15 | uart_inst *Instance = nullptr; 16 | /// The baud rate actually set, which may differe from the requested. 17 | uint32_t ActualBaudrate = 0; 18 | 19 | public: 20 | static const char *GetParityStr(uart_parity_t Parity); 21 | /// Valid GPIOs uart0: (GP0-1, GP12-13, GP16-17), uart1: (GP4-5 or GP8-9). 22 | /// Parity: UART_PARITY_NONE, UART_PARITY_EVEN, UART_PARITY_ODD 23 | Uart(int GPIO, uint32_t RequestedBaudrate, uint32_t DataBits, 24 | uint32_t StopBits, uart_parity_t Parity, bool FlowControl); 25 | ~Uart(); 26 | 27 | void writeBlockingStr(const std::string &Str); 28 | void writeBlocking(const std::vector &Bytes); 29 | 30 | std::vector readNonBlocking(); 31 | }; 32 | 33 | 34 | #endif // __UART_H__ 35 | -------------------------------------------------------------------------------- /firmware/src/Utils.h: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scarp Computing 4 | // 5 | 6 | #ifndef __SRC_UTILS_H__ 7 | #define __SRC_UTILS_H__ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define DUMP_METHOD __attribute__((noinline)) __attribute__((__used__)) 14 | 15 | static inline void sleep_nops(int nops) { 16 | for (int i = 0; i != nops; ++i) 17 | asm volatile("nop:"); /* 8ns each 1 cycle @125MHz */ 18 | } 19 | 20 | #define sleep_80ns() \ 21 | asm volatile("nop\n"); \ 22 | asm volatile("nop\n"); \ 23 | asm volatile("nop\n"); \ 24 | asm volatile("nop\n"); \ 25 | asm volatile("nop\n"); \ 26 | asm volatile("nop\n"); \ 27 | asm volatile("nop\n"); \ 28 | asm volatile("nop\n"); \ 29 | asm volatile("nop\n"); \ 30 | asm volatile("nop\n"); 31 | 32 | #define sleep_800ns() \ 33 | sleep_80ns(); \ 34 | sleep_80ns(); \ 35 | sleep_80ns(); \ 36 | sleep_80ns(); \ 37 | sleep_80ns(); \ 38 | sleep_80ns(); \ 39 | sleep_80ns(); \ 40 | sleep_80ns(); \ 41 | sleep_80ns(); \ 42 | sleep_80ns(); 43 | 44 | template 45 | __attribute__((noreturn)) static inline void dieBase(const T &Val) { 46 | std::cerr << Val << "\n"; 47 | exit(1); 48 | } 49 | 50 | /// Print arguments and exit with exit code 1. 51 | template 52 | __attribute__((noreturn)) static inline void dieBase(const T &Val1, 53 | const Ts... Vals) { 54 | std::cerr << Val1; 55 | dieBase(Vals...); 56 | } 57 | 58 | /// Exit the program, reporting a bug. 59 | #define die(...) \ 60 | dieBase("Error in ", __FILE__, ":", __LINE__, " ", __FUNCTION__, \ 61 | "(): ", __VA_ARGS__, "\n") 62 | 63 | #define unreachable(...) \ 64 | dieBase("Unreachable in ", __FILE__, ":", __LINE__, " ", __FUNCTION__, \ 65 | "(): ", __VA_ARGS__, "\n") 66 | 67 | /// Holds the last several entries. 68 | /// Can return the mean. 69 | template 70 | class Buffer { 71 | std::vector Vec; 72 | 73 | public: 74 | Buffer() { 75 | Vec.resize(Sz); 76 | for (unsigned Cnt = 0; Cnt != Sz; ++Cnt) 77 | Vec[Cnt] = InitVal; 78 | } 79 | void append(T Val) { 80 | // Shift left 81 | for (unsigned Cnt = 0; Cnt + 1 < Sz; ++Cnt) 82 | Vec[Cnt] = Vec[Cnt + 1]; 83 | Vec[Sz - 1] = Val; 84 | } 85 | T getMean() const { 86 | int Sum = 0; 87 | int CurrSz = 0; 88 | for (int Val : Vec) { 89 | Sum += Val; 90 | CurrSz += 1; 91 | } 92 | return (T)(Sum / CurrSz); 93 | } 94 | T operator[](unsigned Idx) const { return Vec[Idx]; } 95 | T back() const { return Vec.back(); } 96 | void clear() { std::fill(Vec.begin(), Vec.end(), InitVal); } 97 | }; 98 | 99 | #endif // __SRC_UTILS_H__ 100 | -------------------------------------------------------------------------------- /firmware/src/config.h.in: -------------------------------------------------------------------------------- 1 | #define PROJECT_NAME "@PROJECT_NAME@" 2 | #define REVISION_MAJOR @REVISION_MAJOR@ 3 | #define REVISION_MINOR @REVISION_MINOR@ 4 | #cmakedefine DISABLE_PICO_LED 5 | #cmakedefine PICO_TM1637 6 | #define DISPLAY_SHIFT_LEFT @DISPLAY_SHIFT_LEFT@ 7 | #define DISPLAY_BRIGHTNESS @DISPLAY_BRIGHTNESS@ 8 | #cmakedefine DBGPRINT 9 | -------------------------------------------------------------------------------- /firmware/src/main.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "ConfigOpts.h" 7 | #include "Debug.h" 8 | #include "DutyCycle.h" 9 | #include "Flash.h" 10 | #include "Pico.h" 11 | #include "PotentiometerLogic.h" 12 | #include "Pwm.pio.h" 13 | #include "RotaryEncoder.h" 14 | #include "RotaryLogic.h" 15 | #include "TwoButtonLogic.h" 16 | #include "Uart.h" 17 | #include "hardware/pio.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | PresetsTable Presets; 26 | 27 | 28 | static void PioPWMSetPeriod(PIO Pio, uint SM, uint32_t Period) { 29 | pio_sm_set_enabled(Pio, SM, false); 30 | pio_sm_put_blocking(Pio, SM, Period); 31 | pio_sm_exec(Pio, SM, pio_encode_pull(false, false)); 32 | pio_sm_exec(Pio, SM, pio_encode_out(pio_isr, 32)); 33 | pio_sm_set_enabled(Pio, SM, true); 34 | } 35 | 36 | static void PioPWMSetLevel(PIO Pio, uint SM, uint32_t Level) { 37 | pio_sm_put_blocking(Pio, SM, Level); 38 | } 39 | 40 | enum class UIMode { 41 | Rotary, 42 | TwoButton, 43 | Button, 44 | ButtonWithPot, 45 | }; 46 | 47 | const char *getUIModeStr(UIMode Mode) { 48 | switch (Mode) { 49 | case UIMode::Rotary: 50 | return "Rotary"; 51 | case UIMode::TwoButton: 52 | return "TwoButton"; 53 | case UIMode::Button: 54 | return "OneButton"; 55 | case UIMode::ButtonWithPot: 56 | return "ButtonWithPot"; 57 | } 58 | return "UNKNOWN"; 59 | } 60 | 61 | static UIMode getUIMode(Pico &Pico) { 62 | Pico.readGPIO(); 63 | bool JP1 = Pico.getGPIO(ModeJP1GPIO); 64 | bool JP2 = Pico.getGPIO(ModeJP2GPIO); 65 | if (JP1 && JP2) 66 | return UIMode::Button; 67 | if (JP1 && !JP2) 68 | return UIMode::ButtonWithPot; 69 | if (!JP1 && !JP2) 70 | return UIMode::TwoButton; 71 | if (!JP1 && JP2) 72 | return UIMode::Rotary; 73 | std::cerr << "Bad Mode!\n"; 74 | exit(1); 75 | } 76 | 77 | class ThrottlePin { 78 | Pico Π 79 | DutyCycle &DC; 80 | PIO Pio; 81 | int SM; 82 | int LastKHz = 0; 83 | int LastPeriod = 0; 84 | int CntTicks = 0; 85 | 86 | public: 87 | ThrottlePin(Pico &Pi, DutyCycle &DC) : Pi(Pi), DC(DC) { 88 | // Start PIO for the throttle pin 89 | Pi.setGPIO(ThrottleGPIO, true); 90 | Pio = pio1; 91 | SM = pio_claim_unused_sm(Pio, true); 92 | uint ProgramOffset = pio_add_program(Pio, &pwm_program); 93 | pwm_program_init(Pio, SM, ProgramOffset, ThrottleGPIO); 94 | DBG_PRINT(DC.dump();) 95 | } 96 | void updatePWM() { 97 | if (++CntTicks != UpdatePWMSamplePeriod) 98 | return; 99 | CntTicks = 0; 100 | 101 | int KHz = DC.getKHz(); 102 | int Period = DC.getPeriod(); 103 | if (KHz != LastKHz || Period != LastPeriod) { 104 | DBG_PRINT(DC.dump();) 105 | int Level = DC.getLevel(); 106 | PioPWMSetPeriod(Pio, SM, Period); 107 | PioPWMSetLevel(Pio, SM, Level); 108 | } 109 | LastKHz = KHz; 110 | LastPeriod = Period; 111 | } 112 | }; 113 | 114 | // Core 0 is handling the UI. 115 | static void core0_main_loop(Pico &Pico, Display &Disp, FlashStorage &Flash, 116 | DutyCycle &DC, Uart &Uart) { 117 | std::unique_ptr UI; 118 | auto Mode = getUIMode(Pico); 119 | DBG_PRINT(std::cout << "Mode=" << getUIModeStr(Mode) << "\n";) 120 | switch (Mode) { 121 | case UIMode::Rotary: { 122 | bool ReverseDirection = Pico.getGPIO(ReverseDirectionGPIO); 123 | UI = std::make_unique( 124 | RotaryClkGPIO, RotaryDtGPIO, RotarySwGPIO, RotaryEncoderSamplePeriod, 125 | Pico, Disp, Presets, DC, Flash, ReverseDirection); 126 | break; 127 | } 128 | case UIMode::Button: 129 | case UIMode::ButtonWithPot: { 130 | bool ReverseDirection = Pico.getGPIO(ReverseDirectionGPIO); 131 | UI = std::make_unique( 132 | PotentiometerGPIO, LeftButtonGPIO, PotSamplePeriod, Pico, Disp, DC, 133 | Presets, Flash, /*EnablePot=*/Mode == UIMode::ButtonWithPot, 134 | ReverseDirection); 135 | break; 136 | } 137 | case UIMode::TwoButton: 138 | UI = std::make_unique(LeftButtonGPIO, RightButtonGPIO, 139 | ButtonSamplePeriod, Pico, Disp, DC, 140 | Presets, Flash); 141 | break; 142 | } 143 | 144 | ThrottlePin TPin(Pico, DC); 145 | 146 | #ifndef DISALBE_PICO_LED 147 | Pico.ledON(); 148 | #endif 149 | while (true) { 150 | // The main entry point for the UI. 151 | UI->tickAll(Uart); 152 | // Update the Throttle pin PWM if needed. 153 | TPin.updatePWM(); 154 | } 155 | } 156 | 157 | // Hack for linking error: undefined reference to `__dso_handle' 158 | static void *__dso_handle = 0; 159 | 160 | int main() { 161 | (void)__dso_handle; 162 | Pico Pico; 163 | DBG_PRINT(sleep_ms(1000);) 164 | DBG_PRINT(std::cout << "ThrottleBlaster rev." << REVISION_MAJOR << "." 165 | << REVISION_MINOR << "\n";) 166 | Pico.initGPIO(PICO_DEFAULT_LED_PIN, GPIO_OUT, Pico::Pull::Down, "LED"); 167 | Pico.initGPIO(ThrottleGPIO, GPIO_OUT, Pico::Pull::Down, "Throttle"); 168 | 169 | Pico.initGPIO(ModeJP1GPIO, GPIO_IN, Pico::Pull::Up, "ModeJP1"); 170 | Pico.initGPIO(ModeJP2GPIO, GPIO_IN, Pico::Pull::Up, "ModeJP2"); 171 | 172 | Pico.initGPIO(ReverseDirectionGPIO, GPIO_IN, Pico::Pull::Up, 173 | "ReverseDirectionJP"); 174 | 175 | Display Disp(DisplayClkGPIO, DisplayDioGPIO); 176 | 177 | // Read Presets from flash. 178 | FlashStorage Flash; 179 | if (Flash.valid()) 180 | Presets.readFromFlash(Flash); 181 | else 182 | DBG_PRINT(std::cout << "Flash not valid!\n";) 183 | 184 | // DutyCycle needs an updated Presets. 185 | DutyCycle DC(Presets); 186 | 187 | Uart Uart(UartGPIO, UartRequestedBaud, UartDataBits, UartStopBits, UartParity, 188 | UartFlowControl); 189 | 190 | core0_main_loop(Pico, Disp, Flash, DC, Uart); 191 | return 0; 192 | } 193 | -------------------------------------------------------------------------------- /firmware/src/pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /img/ThrottleBlaster_PCB_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/ThrottleBlaster_PCB_back.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_PCB_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/ThrottleBlaster_PCB_front.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_breadboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/ThrottleBlaster_breadboard.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_pcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/ThrottleBlaster_pcb.jpg -------------------------------------------------------------------------------- /img/button2_states.fig: -------------------------------------------------------------------------------- 1 | #FIG 3.2 Produced by xfig version 3.2.8b 2 | Landscape 3 | Center 4 | Inches 5 | Letter 6 | 400.00 7 | Single 8 | -2 9 | 1200 2 10 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4987.500 2437.500 5700 3450 5025 3675 4275 3450 11 | 1 1 2.00 60.00 120.00 12 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3640.278 5154.167 2850 6450 2175 5550 3300 3675 13 | 1 1 2.00 60.00 120.00 14 | 5 1 0 2 10 7 50 -1 -1 0.000 0 0 1 0 3150.000 5850.000 3750 5400 3900 5850 3750 6300 15 | 1 1 2.00 60.00 120.00 16 | 5 1 0 2 13 7 50 -1 -1 0.000 0 1 1 0 3637.500 4462.500 4350 5100 4575 4650 4050 3600 17 | 1 1 2.00 60.00 120.00 18 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4912.500 5287.500 4350 3225 4950 3150 5700 3300 19 | 1 1 2.00 60.00 120.00 20 | 5 1 0 2 22 7 50 -1 -1 0.000 0 0 1 0 5208.668 3409.763 3525 3000 4275 1950 5850 1800 21 | 1 1 2.00 60.00 120.00 22 | 5 1 0 2 22 7 50 -1 -1 0.000 0 1 1 0 9367.500 11512.500 5625 2025 4500 2550 3750 3000 23 | 1 1 2.00 60.00 120.00 24 | 5 1 0 2 12 7 50 -1 -1 0.000 0 1 1 0 18637.500 36637.500 6000 2325 5025 2700 4125 3075 25 | 1 1 2.00 60.00 120.00 26 | 5 1 0 2 10 7 50 -1 -1 0.000 0 0 1 0 3599.202 -536.968 6000 2325 5175 2850 4200 3150 27 | 1 1 2.00 60.00 120.00 28 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 4125.000 4162.500 3150 3600 3000 4125 3150 4725 29 | 1 1 2.00 60.00 120.00 30 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 4370.098 5158.088 3150 6300 2700 5100 3600 3675 31 | 1 1 2.00 60.00 120.00 32 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 2041.995 4590.887 525 4725 1125 3375 2850 3300 33 | 1 1 2.00 60.00 120.00 34 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 2258.852 4804.023 825 4650 1200 3825 2925 3525 35 | 1 1 2.00 60.00 120.00 36 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 3086.250 5424.000 3000 3525 1950 3900 1350 4650 37 | 1 1 2.00 60.00 120.00 38 | 6 5550 1650 7200 2400 39 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 6375 2025 750 338 5625 1688 7125 2363 40 | 4 0 0 50 -1 18 14 0.0000 4 180 855 5925 2100 Reset?\001 41 | -6 42 | 6 225 4575 1875 5400 43 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 1036 4985 750 338 286 4648 1786 5323 44 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 525 4950 Configure\001 45 | 4 0 0 50 -1 18 14 0.0000 4 240 1140 525 5175 CPU Freq\001 46 | -6 47 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3600 5025 750 338 2850 4688 4350 5363 48 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 6450 3375 750 338 5700 3038 7200 3713 49 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3602 6601 750 338 2852 6264 4352 6939 50 | 1 2 0 4 0 7 50 -1 -1 0.000 1 0.0000 3600 3337 750 338 2850 3337 4350 3337 51 | 2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 52 | 825 375 5175 375 5175 1575 825 1575 825 375 53 | 4 0 0 50 -1 18 16 0.0000 4 210 1005 3150 3450 Presets\001 54 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3150 6225 LP\001 55 | 4 0 0 50 -1 18 14 0.0000 4 180 825 6075 3450 Manual\001 56 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 6600 Configure\001 57 | 4 0 0 50 -1 18 14 0.0000 4 180 585 3300 6900 PWM\001 58 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3000 5025 Configure\001 59 | 4 0 0 50 -1 18 14 0.0000 4 180 495 3375 5325 MHz\001 60 | 4 0 12 50 -1 18 16 0.0000 4 210 375 4875 3975 BP\001 61 | 4 0 10 50 -1 18 16 0.0000 4 210 345 3900 5700 LP\001 62 | 4 0 12 50 -1 18 16 0.0000 4 210 375 2550 6600 BP\001 63 | 4 0 9 50 -1 18 16 0.0000 4 270 4095 975 750 LP = Long Push (Left or Right) \001 64 | 4 0 12 50 -1 18 16 0.0000 4 210 2040 975 1125 BP = Both Push\001 65 | 4 0 22 50 -1 18 16 0.0000 4 270 2970 975 1500 BLP = Both Long Push\001 66 | 4 0 12 50 -1 18 16 0.0000 4 210 375 4800 3525 BP\001 67 | 4 0 22 50 -1 18 16 0.0000 4 210 540 3075 2775 BLP\001 68 | 4 0 22 50 -1 18 16 0.0000 4 210 540 5025 2100 BLP\001 69 | 4 0 12 50 -1 18 16 0.0000 4 210 375 5475 2400 BP\001 70 | 4 0 9 50 -1 18 16 0.0000 4 210 345 5925 2625 LP\001 71 | 4 0 4 50 -1 18 16 0.0000 4 210 1110 4200 2550 Defaults\001 72 | 4 0 4 50 -1 18 16 0.0000 4 210 1260 4200 2325 Resets to\001 73 | 4 0 4 50 -1 18 16 0.0000 4 210 630 2550 5700 Save\001 74 | 4 0 4 50 -1 18 16 0.0000 4 210 915 1500 5925 Cancel\001 75 | 4 0 4 50 -1 18 16 0.0000 4 210 915 4500 2925 Cancel\001 76 | 4 0 12 50 -1 18 16 0.0000 4 210 375 4200 4800 BP\001 77 | 4 0 4 50 -1 18 16 0.0000 4 210 1065 3900 4500 Cancels\001 78 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2625 3825 LP\001 79 | 4 0 4 50 -1 18 16 0.0000 4 210 915 675 3375 Cancel\001 80 | 4 0 12 50 -1 18 16 0.0000 4 210 375 75 4650 BP\001 81 | 4 0 4 50 -1 18 16 0.0000 4 210 630 1200 3750 Save\001 82 | 4 0 9 50 -1 18 16 0.0000 4 210 345 900 4575 LP\001 83 | 4 0 0 50 -1 18 14 0.0000 4 180 1560 1275 3975 If at Max MHz\001 84 | -------------------------------------------------------------------------------- /img/button2_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/button2_states.png -------------------------------------------------------------------------------- /img/button_states.fig: -------------------------------------------------------------------------------- 1 | #FIG 3.2 Produced by xfig version 3.2.8b 2 | Landscape 3 | Center 4 | Inches 5 | Letter 6 | 100.00 7 | Single 8 | -2 9 | 1200 2 10 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4371.429 4848.214 4200 4800 4500 4725 4350 5025 11 | 1 1 2.00 60.00 120.00 12 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3325.000 4200.000 3825 3675 4050 4200 3825 4725 13 | 1 1 2.00 60.00 120.00 14 | 5 1 0 2 22 7 50 -1 -1 0.000 0 0 1 0 4301.786 5346.429 4350 5175 4425 5475 4125 5325 15 | 1 1 2.00 60.00 120.00 16 | 5 1 0 2 22 7 50 -1 -1 0.000 0 0 1 0 4229.605 2911.184 4050 3075 4350 2700 4275 3150 17 | 1 1 2.00 60.00 120.00 18 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3078.409 2931.818 3000 3150 3075 2700 3300 3000 19 | 1 1 2.00 60.00 120.00 20 | 5 1 0 2 22 7 50 -1 -1 0.000 0 0 1 0 4376.786 7071.429 4425 6900 4500 7200 4200 7050 21 | 1 1 2.00 60.00 120.00 22 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3158.508 5728.046 1125 6375 1275 4725 3000 3600 23 | 1 1 2.00 60.00 120.00 24 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 3675.000 5962.500 3300 5400 3000 5925 3300 6525 25 | 1 1 2.00 60.00 120.00 26 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 3132.065 6667.663 3000 5250 2250 5550 1725 6450 27 | 1 1 2.00 60.00 120.00 28 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 3059.797 4865.878 4125 6525 5025 5025 4350 3375 29 | 1 1 2.00 60.00 120.00 30 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4450.000 6575.000 4275 6600 4575 6450 4425 6750 31 | 1 1 2.00 60.00 120.00 32 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 2664.940 5371.305 975 6450 825 4575 2850 3375 33 | 1 1 2.00 60.00 120.00 34 | 6 -75 1275 2475 2250 35 | 4 0 12 50 -1 18 16 0.0000 4 210 1140 -75 1500 P = Push\001 36 | 4 0 9 50 -1 18 16 0.0000 4 270 2070 -75 2190 LP = Long Push\001 37 | 4 0 22 50 -1 18 16 0.0000 4 210 2505 -75 1845 MP = Medium Push\001 38 | -6 39 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 1350 6712 750 338 600 6375 2100 7050 40 | 1 2 0 4 0 7 50 -1 -1 0.000 1 0.0000 3600 3337 750 338 2850 3337 4350 3337 41 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3637 5072 750 338 2887 4735 4387 5410 42 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3677 6798 750 338 2927 6461 4427 7136 43 | 2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 44 | -150 1200 2550 1200 2550 2325 -150 2325 -150 1200 45 | 4 0 0 50 -1 18 16 0.0000 4 210 1005 3150 3450 Presets\001 46 | 4 0 9 50 -1 18 16 0.0000 4 210 345 4050 3900 LP\001 47 | 4 0 22 50 -1 18 16 0.0000 4 210 420 3600 2925 MP\001 48 | 4 0 12 50 -1 18 16 0.0000 4 210 180 2625 3075 P\001 49 | 4 0 12 50 -1 18 16 0.0000 4 210 180 1125 6300 P\001 50 | 4 0 9 50 -1 18 16 0.0000 4 210 345 450 6450 LP\001 51 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2850 5550 LP\001 52 | 4 0 0 50 -1 18 16 0.0000 4 210 1185 1650 5250 Max MHz\001 53 | 4 0 0 50 -1 18 16 0.0000 4 210 495 2025 4950 If at\001 54 | 4 0 22 50 -1 18 16 0.0000 4 210 420 4500 5475 MP\001 55 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4575 4875 P\001 56 | 4 0 22 50 -1 18 16 0.0000 4 210 420 4575 7200 MP\001 57 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3900 6450 LP\001 58 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4650 6600 P\001 59 | 4 0 4 50 -1 18 12 0.0000 4 165 690 4125 4650 +1MHz\001 60 | 4 0 4 50 -1 18 12 0.0000 4 165 615 4050 5700 -1MHz\001 61 | 4 0 4 50 -1 18 12 0.0000 4 165 180 4275 7425 -1\001 62 | 4 0 4 50 -1 18 12 0.0000 4 165 255 4650 6750 +1\001 63 | 4 0 4 50 -1 18 16 0.0000 4 210 1110 -75 4200 Defaults\001 64 | 4 0 4 50 -1 18 16 0.0000 4 210 1260 -75 3900 Resets to\001 65 | 4 0 4 50 -1 18 16 0.0000 4 210 1530 1950 2625 Next Preset\001 66 | 4 0 4 50 -1 18 16 0.0000 4 210 1185 4050 2625 Max MHz\001 67 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3150 6750 Configure\001 68 | 4 0 0 50 -1 18 14 0.0000 4 180 585 3450 7050 PWM\001 69 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 5025 Configure\001 70 | 4 0 0 50 -1 18 14 0.0000 4 180 495 3450 5325 MHz\001 71 | 4 0 0 50 -1 18 14 0.0000 4 180 855 975 6825 Reset?\001 72 | -------------------------------------------------------------------------------- /img/button_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/button_states.png -------------------------------------------------------------------------------- /img/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/diagram.png -------------------------------------------------------------------------------- /img/rotary_states.fig: -------------------------------------------------------------------------------- 1 | #FIG 3.2 Produced by xfig version 3.2.8b 2 | Landscape 3 | Center 4 | Inches 5 | Letter 6 | 400.00 7 | Single 8 | -2 9 | 1200 2 10 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3740.625 5550.000 3225 7500 1725 5625 3225 3600 11 | 1 1 2.00 60.00 120.00 12 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4987.500 2437.500 5700 3450 5025 3675 4275 3450 13 | 1 1 2.00 60.00 120.00 14 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4961.932 4397.727 4350 3225 4950 3075 5700 3300 15 | 1 1 2.00 60.00 120.00 16 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3295.312 6375.000 4350 5100 4950 6375 4350 7650 17 | 1 1 2.00 60.00 120.00 18 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 4188.221 5126.909 3150 6300 2625 5025 3600 3675 19 | 1 1 2.00 60.00 120.00 20 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3640.278 5154.167 2850 6450 2175 5550 3300 3675 21 | 1 1 2.00 60.00 120.00 22 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3080.556 5630.556 2850 7725 975 5550 3000 3525 23 | 1 1 2.00 60.00 120.00 24 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 3637.500 4462.500 4350 5100 4575 4650 4050 3600 25 | 1 1 2.00 60.00 120.00 26 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 2756.250 4200.000 3600 3675 3750 4200 3600 4725 27 | 1 1 2.00 60.00 120.00 28 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3150.000 5850.000 3750 5400 3900 5850 3750 6300 29 | 1 1 2.00 60.00 120.00 30 | 6 375 2550 2550 3225 31 | 4 0 12 50 -1 18 16 0.0000 4 210 1140 375 2775 P = Push\001 32 | 4 0 9 50 -1 18 16 0.0000 4 270 2145 375 3120 LP = Long Push \001 33 | -6 34 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3600 7800 750 338 2850 7463 4350 8138 35 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3600 5025 750 338 2850 4688 4350 5363 36 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 6450 3375 750 338 5700 3038 7200 3713 37 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3602 6601 750 338 2852 6264 4352 6939 38 | 1 2 0 4 0 7 50 -1 -1 0.000 1 0.0000 3600 3337 750 338 2850 3337 4350 3337 39 | 2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 40 | 225 2475 2625 2475 2625 3225 225 3225 225 2475 41 | 4 0 0 50 -1 18 16 0.0000 4 210 1005 3150 3450 Presets\001 42 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4875 3000 P\001 43 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4875 3975 P\001 44 | 4 0 12 50 -1 18 16 0.0000 4 210 180 3900 5700 P\001 45 | 4 0 12 50 -1 18 16 0.0000 4 210 180 2550 6600 P\001 46 | 4 0 12 50 -1 18 16 0.0000 4 210 180 3000 7425 P\001 47 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3825 4350 LP\001 48 | 4 0 9 50 -1 18 16 0.0000 4 210 345 4500 5175 LP\001 49 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3150 6225 LP\001 50 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2400 7950 LP\001 51 | 4 0 4 50 -1 18 16 0.0000 4 210 1260 375 5625 Resets to\001 52 | 4 0 4 50 -1 18 16 0.0000 4 210 1110 450 5925 Defaults\001 53 | 4 0 4 50 -1 18 16 0.0000 4 210 1065 2400 5700 Cancels\001 54 | 4 0 4 50 -1 18 16 0.0000 4 210 1065 4200 4425 Cancels\001 55 | 4 0 0 50 -1 18 14 0.0000 4 180 855 3150 7875 Reset?\001 56 | 4 0 0 50 -1 18 14 0.0000 4 180 825 6075 3450 Manual\001 57 | 4 0 0 50 -1 18 16 0.0000 4 210 1755 4800 5550 If at Max MHz\001 58 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 6600 Configure\001 59 | 4 0 0 50 -1 18 14 0.0000 4 180 585 3300 6900 PWM\001 60 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3000 5025 Configure\001 61 | 4 0 0 50 -1 18 14 0.0000 4 180 495 3375 5325 MHz\001 62 | -------------------------------------------------------------------------------- /img/rotary_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/rotary_states.png -------------------------------------------------------------------------------- /img/stpclk_pin_slot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/stpclk_pin_slot1.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socket370.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/stpclk_pin_socket370.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socket7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/stpclk_pin_socket7.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socketA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/ecc1783d1b1a2f3c95aabb32d2a47a33b3d3c918/img/stpclk_pin_socketA.jpg -------------------------------------------------------------------------------- /kicad/ThrottleBlaster.lib: -------------------------------------------------------------------------------- 1 | EESchema-LIBRARY Version 2.4 2 | #encoding utf-8 3 | # 4 | # 7SegmentDisplayTM1637 5 | # 6 | DEF 7SegmentDisplayTM1637 U 0 40 Y Y 4 F N 7 | F0 "U" -850 -400 50 H V C CNN 8 | F1 "7SegmentDisplayTM1637" -850 -300 50 H V C CNN 9 | F2 "" 0 0 50 H I C CNN 10 | F3 "" 0 0 50 H I C CNN 11 | DRAW 12 | S -350 -500 -1350 -100 0 1 0 N 13 | S -100 -50 -1500 -550 0 1 0 N 14 | X CLK 1 0 -150 100 L 50 50 1 1 I 15 | X DIO 2 0 -250 100 L 50 50 1 1 I 16 | X GND 3 0 -350 100 L 50 50 1 1 W 17 | X 5V 4 0 -450 100 L 50 50 1 1 W 18 | X DIO 2-UB 0 -250 100 L 50 50 2 1 I 19 | X GND 3-UB 0 -350 100 L 50 50 2 1 W 20 | X 5V 4-UB 0 -450 100 L 50 50 2 1 W 21 | X CLK ~-UB 0 -150 100 L 50 50 2 1 I 22 | X DIO 2-UC 0 -250 100 L 50 50 3 1 I 23 | X GND 3-UC 0 -350 100 L 50 50 3 1 W 24 | X 5V 4-UC 0 -450 100 L 50 50 3 1 W 25 | X CLK ~-UC 0 -150 100 L 50 50 3 1 I 26 | X DIO 2-UD 0 -250 100 L 50 50 4 1 I 27 | X GND 3-UD 0 -350 100 L 50 50 4 1 W 28 | X 5V 4-UD 0 -450 100 L 50 50 4 1 W 29 | X CLK ~-UD 0 -150 100 L 50 50 4 1 I 30 | ENDDRAW 31 | ENDDEF 32 | # 33 | # FloppyPower 34 | # 35 | DEF FloppyPower U 0 40 Y Y 4 F N 36 | F0 "U" 0 0 50 H V C CNN 37 | F1 "FloppyPower" 0 0 50 H V C CNN 38 | F2 "" 0 0 50 H I C CNN 39 | F3 "" 0 0 50 H I C CNN 40 | DRAW 41 | X 5V 1 0 -100 100 L 50 50 1 1 w 42 | X GND 2 0 -200 100 L 50 50 1 1 w 43 | X GND 3 0 -300 100 L 50 50 1 1 w 44 | X 12V 4 0 -400 100 L 50 50 1 1 w 45 | X 5V 1-UB 0 -100 100 L 50 50 2 1 w 46 | X GND 2-UB 0 -200 100 L 50 50 2 1 w 47 | X GND 3-UB 0 -300 100 L 50 50 2 1 w 48 | X 12V 4-UB 0 -400 100 L 50 50 2 1 w 49 | X 5V 1-UC 0 -100 100 L 50 50 3 1 w 50 | X GND 2-UC 0 -200 100 L 50 50 3 1 w 51 | X GND 3-UC 0 -300 100 L 50 50 3 1 w 52 | X 12V 4-UC 0 -400 100 L 50 50 3 1 w 53 | X 5V 1-UD 0 -100 100 L 50 50 4 1 w 54 | X GND 2-UD 0 -200 100 L 50 50 4 1 w 55 | X GND 3-UD 0 -300 100 L 50 50 4 1 w 56 | X 12V 4-UD 0 -400 100 L 50 50 4 1 w 57 | ENDDRAW 58 | ENDDEF 59 | # 60 | # RaspberryPi_Pico 61 | # 62 | DEF RaspberryPi_Pico U 0 40 Y Y 1 F N 63 | F0 "U" 1100 650 50 V V C CNN 64 | F1 "RaspberryPi_Pico" 1100 1050 50 V V C CNN 65 | F2 "" 1350 -1500 50 H I C CNN 66 | F3 "" 1350 -1500 50 H I C CNN 67 | DRAW 68 | S 700 2200 1500 200 0 1 0 N 69 | S 950 1350 1250 1050 0 1 0 N 70 | S 1000 2250 1200 2100 0 1 0 N 71 | X GP0 1 600 2150 100 R 50 50 1 1 B 72 | X GP7 10 600 1250 100 R 50 50 1 1 B 73 | X GP8 11 600 1150 100 R 50 50 1 1 B 74 | X GP9 12 600 1050 100 R 50 50 1 1 B 75 | X GND 13 600 950 100 R 50 50 1 1 W 76 | X GP10 14 600 850 100 R 50 50 1 1 B 77 | X GP11 15 600 750 100 R 50 50 1 1 B 78 | X GP12 16 600 650 100 R 50 50 1 1 B 79 | X GP13 17 600 550 100 R 50 50 1 1 B 80 | X GND 18 600 450 100 R 50 50 1 1 W 81 | X GP14 19 600 350 100 R 50 50 1 1 B 82 | X GP1 2 600 2050 100 R 50 50 1 1 B 83 | X GP15 20 600 250 100 R 50 50 1 1 B 84 | X GP16 21 1600 250 100 L 50 50 1 1 B 85 | X GP17 22 1600 350 100 L 50 50 1 1 B 86 | X GND 23 1600 450 100 L 50 50 1 1 W 87 | X GP18 24 1600 550 100 L 50 50 1 1 B 88 | X GP19 25 1600 650 100 L 50 50 1 1 B 89 | X GP20 26 1600 750 100 L 50 50 1 1 B 90 | X GP21 27 1600 850 100 L 50 50 1 1 B 91 | X GND 28 1600 950 100 L 50 50 1 1 W 92 | X GP22 29 1600 1050 100 L 50 50 1 1 B 93 | X GND 3 600 1950 100 R 50 50 1 1 W 94 | X RUN 30 1600 1150 100 L 50 50 1 1 I 95 | X GP26 31 1600 1250 100 L 50 50 1 1 B 96 | X GP27 32 1600 1350 100 L 50 50 1 1 B 97 | X GND 33 1600 1450 100 L 50 50 1 1 I 98 | X GP28 34 1600 1550 100 L 50 50 1 1 B 99 | X ADC_VREF 35 1600 1650 100 L 50 50 1 1 I 100 | X 3V3OUT 36 1600 1750 100 L 50 50 1 1 w 101 | X 3V3_EN 37 1600 1850 100 L 50 50 1 1 I 102 | X GND 38 1600 1950 100 L 50 50 1 1 W 103 | X VSYS 39 1600 2050 100 L 50 50 1 1 W 104 | X GP2 4 600 1850 100 R 50 50 1 1 B 105 | X VBUS 40 1600 2150 100 L 50 50 1 1 W 106 | X GP3 5 600 1750 100 R 50 50 1 1 B 107 | X GP4 6 600 1650 100 R 50 50 1 1 B 108 | X GP5 7 600 1550 100 R 50 50 1 1 B 109 | X GND 8 600 1450 100 R 50 50 1 1 W 110 | X GP6 9 600 1350 100 R 50 50 1 1 B 111 | ENDDRAW 112 | ENDDEF 113 | # 114 | #End Library 115 | -------------------------------------------------------------------------------- /kicad/ThrottleBlaster.pretty/raspberry_pi_pico.kicad_mod: -------------------------------------------------------------------------------- 1 | (module raspberry_pi_pico (layer F.Cu) (tedit 65F5C328) 2 | (fp_text reference REF** (at 10.16 12.7) (layer F.SilkS) 3 | (effects (font (size 1 1) (thickness 0.15))) 4 | ) 5 | (fp_text value raspberry_pi_pico (at 0 -0.5) (layer F.Fab) 6 | (effects (font (size 1 1) (thickness 0.15))) 7 | ) 8 | (fp_line (start 12.7 0) (end 12.7 -1.27) (layer F.SilkS) (width 0.15)) 9 | (fp_line (start 5.08 0) (end 5.08 -1.27) (layer F.SilkS) (width 0.15)) 10 | (fp_line (start 12.7 2.54) (end 12.7 0) (layer F.SilkS) (width 0.15)) 11 | (fp_line (start 5.08 2.54) (end 12.7 2.54) (layer F.SilkS) (width 0.15)) 12 | (fp_line (start 5.08 0) (end 5.08 2.54) (layer F.SilkS) (width 0.15)) 13 | (fp_line (start -1.27 49.53) (end -1.27 -1.27) (layer F.SilkS) (width 0.15)) 14 | (fp_line (start 19.05 49.53) (end -1.27 49.53) (layer F.SilkS) (width 0.15)) 15 | (fp_line (start 19.05 -1.27) (end 19.05 49.53) (layer F.SilkS) (width 0.15)) 16 | (fp_line (start -1.27 -1.27) (end 19.05 -1.27) (layer F.SilkS) (width 0.15)) 17 | (fp_text user 1 (at -1.778 0) (layer F.SilkS) 18 | (effects (font (size 0.6 0.6) (thickness 0.1))) 19 | ) 20 | (fp_text user 40 (at 20.066 0) (layer F.SilkS) 21 | (effects (font (size 0.6 0.6) (thickness 0.1))) 22 | ) 23 | (pad 2 thru_hole circle (at 0 2.54) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 24 | (pad 3 thru_hole circle (at 0 5.08) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 25 | (pad 4 thru_hole circle (at 0 7.62) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 26 | (pad 5 thru_hole circle (at 0 10.16) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 27 | (pad 6 thru_hole circle (at 0 12.7) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 28 | (pad 7 thru_hole circle (at 0 15.24) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 29 | (pad 8 thru_hole circle (at 0 17.78) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 30 | (pad 9 thru_hole circle (at 0 20.32) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 31 | (pad 10 thru_hole circle (at 0 22.86) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 32 | (pad 11 thru_hole circle (at 0 25.4) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 33 | (pad 12 thru_hole circle (at 0 27.94) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 34 | (pad 13 thru_hole circle (at 0 30.48) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 35 | (pad 14 thru_hole circle (at 0 33.02) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 36 | (pad 15 thru_hole circle (at 0 35.56) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 37 | (pad 16 thru_hole circle (at 0 38.1) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 38 | (pad 17 thru_hole circle (at 0 40.64) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 39 | (pad 18 thru_hole circle (at 0 43.18) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 40 | (pad 19 thru_hole circle (at 0 45.72) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 41 | (pad 20 thru_hole circle (at 0 48.26) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 42 | (pad 21 thru_hole circle (at 17.78 48.26) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 43 | (pad 22 thru_hole circle (at 17.78 45.72) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 44 | (pad 23 thru_hole circle (at 17.78 43.18) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 45 | (pad 24 thru_hole circle (at 17.78 40.64) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 46 | (pad 25 thru_hole circle (at 17.78 38.1) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 47 | (pad 26 thru_hole circle (at 17.78 35.56) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 48 | (pad 27 thru_hole circle (at 17.78 33.02) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 49 | (pad 28 thru_hole circle (at 17.78 30.48) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 50 | (pad 29 thru_hole circle (at 17.78 27.94) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 51 | (pad 30 thru_hole circle (at 17.78 25.4) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 52 | (pad 31 thru_hole circle (at 17.78 22.86) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 53 | (pad 32 thru_hole circle (at 17.78 20.32) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 54 | (pad 33 thru_hole circle (at 17.78 17.78) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 55 | (pad 34 thru_hole circle (at 17.78 15.24) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 56 | (pad 35 thru_hole circle (at 17.78 12.7) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 57 | (pad 36 thru_hole circle (at 17.78 10.16) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 58 | (pad 37 thru_hole circle (at 17.78 7.62) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 59 | (pad 38 thru_hole circle (at 17.78 5.08) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 60 | (pad 39 thru_hole circle (at 17.78 2.54) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 61 | (pad 40 thru_hole circle (at 17.78 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 62 | (pad 1 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask)) 63 | ) 64 | -------------------------------------------------------------------------------- /kicad/ThrottleBlaster.pro: -------------------------------------------------------------------------------- 1 | update=Sun 24 Mar 2024 12:33:23 PM PDT 2 | version=1 3 | last_client=kicad 4 | [general] 5 | version=1 6 | RootSch= 7 | BoardNm= 8 | [pcbnew] 9 | version=1 10 | LastNetListRead= 11 | UseCmpFile=1 12 | PadDrill=0.600000000000 13 | PadDrillOvalY=0.600000000000 14 | PadSizeH=1.500000000000 15 | PadSizeV=1.500000000000 16 | PcbTextSizeV=1.500000000000 17 | PcbTextSizeH=1.500000000000 18 | PcbTextThickness=0.300000000000 19 | ModuleTextSizeV=1.000000000000 20 | ModuleTextSizeH=1.000000000000 21 | ModuleTextSizeThickness=0.150000000000 22 | SolderMaskClearance=0.000000000000 23 | SolderMaskMinWidth=0.000000000000 24 | DrawSegmentWidth=0.200000000000 25 | BoardOutlineThickness=0.100000000000 26 | ModuleOutlineThickness=0.150000000000 27 | [cvpcb] 28 | version=1 29 | NetIExt=net 30 | [eeschema] 31 | version=1 32 | LibDir= 33 | [eeschema/libraries] 34 | [schematic_editor] 35 | version=1 36 | PageLayoutDescrFile= 37 | PlotDirectoryName=/home/cowbob/kafroworks/ThrottleBlaster/img/ 38 | SubpartIdSeparator=0 39 | SubpartFirstId=65 40 | NetFmtName= 41 | SpiceAjustPassiveValues=0 42 | LabSize=50 43 | ERC_TestSimilarLabels=1 44 | -------------------------------------------------------------------------------- /kicad/ThrottleBlaster.sch: -------------------------------------------------------------------------------- 1 | EESchema Schematic File Version 4 2 | EELAYER 30 0 3 | EELAYER END 4 | $Descr A4 11693 8268 5 | encoding utf-8 6 | Sheet 1 1 7 | Title "ThrottleBlaster" 8 | Date "2024-06-06" 9 | Rev "0.8" 10 | Comp "Scrap Computing" 11 | Comment1 "" 12 | Comment2 "" 13 | Comment3 "" 14 | Comment4 "" 15 | $EndDescr 16 | $Comp 17 | L power:GND #PWR0103 18 | U 1 1 65F56C1B 19 | P 6050 2950 20 | F 0 "#PWR0103" H 6050 2700 50 0001 C CNN 21 | F 1 "GND" V 6055 2822 50 0000 R CNN 22 | F 2 "" H 6050 2950 50 0001 C CNN 23 | F 3 "" H 6050 2950 50 0001 C CNN 24 | 1 6050 2950 25 | 0 -1 -1 0 26 | $EndComp 27 | $Comp 28 | L power:GND #PWR0104 29 | U 1 1 65F56E23 30 | P 6050 3450 31 | F 0 "#PWR0104" H 6050 3200 50 0001 C CNN 32 | F 1 "GND" V 6055 3322 50 0000 R CNN 33 | F 2 "" H 6050 3450 50 0001 C CNN 34 | F 3 "" H 6050 3450 50 0001 C CNN 35 | 1 6050 3450 36 | 0 -1 -1 0 37 | $EndComp 38 | $Comp 39 | L Device:D_Schottky D1 40 | U 1 1 65F5820D 41 | P 6200 1900 42 | F 0 "D1" H 6200 2117 50 0000 C CNN 43 | F 1 "D_Schottky" H 6200 2026 50 0000 C CNN 44 | F 2 "Diode_THT:D_5W_P10.16mm_Horizontal" H 6200 1900 50 0001 C CNN 45 | F 3 "~" H 6200 1900 50 0001 C CNN 46 | 1 6200 1900 47 | 0 -1 -1 0 48 | $EndComp 49 | $Comp 50 | L Switch:SW_Push SW1 51 | U 1 1 65F5C34E 52 | P 2250 3850 53 | F 0 "SW1" V 2204 3998 50 0000 L CNN 54 | F 1 "SW_Push" V 2295 3998 50 0000 L CNN 55 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 2250 4050 50 0001 C CNN 56 | F 3 "~" H 2250 4050 50 0001 C CNN 57 | 1 2250 3850 58 | 0 -1 -1 0 59 | $EndComp 60 | $Comp 61 | L Device:R_POT Pot1 62 | U 1 1 65F5DC08 63 | P 7000 3300 64 | F 0 "Pot1" H 6930 3254 50 0000 R CNN 65 | F 1 "10K" H 6930 3345 50 0000 R CNN 66 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical" H 7000 3300 50 0001 C CNN 67 | F 3 "~" H 7000 3300 50 0001 C CNN 68 | 1 7000 3300 69 | -1 0 0 1 70 | $EndComp 71 | $Comp 72 | L power:GND #PWR0105 73 | U 1 1 65F54A14 74 | P 6100 1150 75 | F 0 "#PWR0105" H 6100 900 50 0001 C CNN 76 | F 1 "GND" V 6105 1022 50 0000 R CNN 77 | F 2 "" H 6100 1150 50 0001 C CNN 78 | F 3 "" H 6100 1150 50 0001 C CNN 79 | 1 6100 1150 80 | 1 0 0 -1 81 | $EndComp 82 | NoConn ~ 6000 1150 83 | NoConn ~ 5900 1150 84 | Wire Wire Line 85 | 6050 2850 6200 2850 86 | $Comp 87 | L ThrottleBlaster:FloppyPower U3 88 | U 1 1 65F52717 89 | P 6300 1150 90 | F 0 "U3" V 5800 900 50 0000 C CNN 91 | F 1 "FloppyPower" V 5900 900 50 0000 C CNN 92 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Horizontal" H 6300 1150 50 0001 C CNN 93 | F 3 "" H 6300 1150 50 0001 C CNN 94 | 1 6300 1150 95 | 0 1 1 0 96 | $EndComp 97 | $Comp 98 | L Device:Rotary_Encoder_Switch SW2 99 | U 1 1 65FB8E0E 100 | P 3550 4200 101 | F 0 "SW2" V 3500 3800 50 0000 L CNN 102 | F 1 "Rotary_Encoder_Switch" V 3600 3100 50 0000 L CNN 103 | F 2 "Rotary_Encoder:RotaryEncoder_Alps_EC11E-Switch_Vertical_H20mm" H 3400 4360 50 0001 C CNN 104 | F 3 "~" H 3550 4460 50 0001 C CNN 105 | 1 3550 4200 106 | 0 1 1 0 107 | $EndComp 108 | $Comp 109 | L power:GND #PWR0107 110 | U 1 1 65FEE5A0 111 | P 5050 3450 112 | F 0 "#PWR0107" H 5050 3200 50 0001 C CNN 113 | F 1 "GND" V 5055 3322 50 0000 R CNN 114 | F 2 "" H 5050 3450 50 0001 C CNN 115 | F 3 "" H 5050 3450 50 0001 C CNN 116 | 1 5050 3450 117 | 0 1 1 0 118 | $EndComp 119 | $Comp 120 | L power:GND #PWR0108 121 | U 1 1 65FEE9EC 122 | P 5050 2950 123 | F 0 "#PWR0108" H 5050 2700 50 0001 C CNN 124 | F 1 "GND" V 5055 2822 50 0000 R CNN 125 | F 2 "" H 5050 2950 50 0001 C CNN 126 | F 3 "" H 5050 2950 50 0001 C CNN 127 | 1 5050 2950 128 | 0 1 1 0 129 | $EndComp 130 | $Comp 131 | L power:GND #PWR0109 132 | U 1 1 65FF082D 133 | P 2250 4050 134 | F 0 "#PWR0109" H 2250 3800 50 0001 C CNN 135 | F 1 "GND" H 2255 3877 50 0000 C CNN 136 | F 2 "" H 2250 4050 50 0001 C CNN 137 | F 3 "" H 2250 4050 50 0001 C CNN 138 | 1 2250 4050 139 | 1 0 0 -1 140 | $EndComp 141 | $Comp 142 | L power:GND #PWR0110 143 | U 1 1 65FF6900 144 | P 7000 3450 145 | F 0 "#PWR0110" H 7000 3200 50 0001 C CNN 146 | F 1 "GND" H 7005 3277 50 0000 C CNN 147 | F 2 "" H 7000 3450 50 0001 C CNN 148 | F 3 "" H 7000 3450 50 0001 C CNN 149 | 1 7000 3450 150 | 1 0 0 -1 151 | $EndComp 152 | $Comp 153 | L power:GND #PWR0112 154 | U 1 1 6601F27F 155 | P 3550 3900 156 | F 0 "#PWR0112" H 3550 3650 50 0001 C CNN 157 | F 1 "GND" H 3555 3727 50 0000 C CNN 158 | F 2 "" H 3550 3900 50 0001 C CNN 159 | F 3 "" H 3550 3900 50 0001 C CNN 160 | 1 3550 3900 161 | -1 0 0 1 162 | $EndComp 163 | $Comp 164 | L power:GND #PWR0113 165 | U 1 1 6601FACD 166 | P 3450 4500 167 | F 0 "#PWR0113" H 3450 4250 50 0001 C CNN 168 | F 1 "GND" H 3455 4327 50 0000 C CNN 169 | F 2 "" H 3450 4500 50 0001 C CNN 170 | F 3 "" H 3450 4500 50 0001 C CNN 171 | 1 3450 4500 172 | 1 0 0 -1 173 | $EndComp 174 | Wire Wire Line 175 | 3650 3150 3650 3900 176 | Wire Wire Line 177 | 3450 3900 3450 3050 178 | Wire Wire Line 179 | 3800 3250 3800 4500 180 | Wire Wire Line 181 | 3800 4500 3650 4500 182 | Wire Wire Line 183 | 7400 4300 7400 4200 184 | $Comp 185 | L Device:R R1 186 | U 1 1 65FCF503 187 | P 6950 4000 188 | F 0 "R1" V 7157 4000 50 0000 C CNN 189 | F 1 "1K" V 7066 4000 50 0000 C CNN 190 | F 2 "Resistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolder" V 6880 4000 50 0001 C CNN 191 | F 3 "~" H 6950 4000 50 0001 C CNN 192 | 1 6950 4000 193 | 0 -1 -1 0 194 | $EndComp 195 | $Comp 196 | L Transistor_FET:2N7000 Q1 197 | U 1 1 65F5EF5A 198 | P 7300 4000 199 | F 0 "Q1" H 7504 4046 50 0000 L CNN 200 | F 1 "2N7000" H 7504 3955 50 0000 L CNN 201 | F 2 "Package_TO_SOT_THT:TO-92_Inline" H 7500 3925 50 0001 L CIN 202 | F 3 "https://www.onsemi.com/pub/Collateral/NDS7002A-D.PDF" H 7300 4000 50 0001 L CNN 203 | 1 7300 4000 204 | 1 0 0 -1 205 | $EndComp 206 | Wire Wire Line 207 | 6850 3350 6850 3300 208 | $Comp 209 | L power:GND #PWR0111 210 | U 1 1 66001E51 211 | P 3200 2950 212 | F 0 "#PWR0111" H 3200 2700 50 0001 C CNN 213 | F 1 "GND" V 3205 2822 50 0000 R CNN 214 | F 2 "" H 3200 2950 50 0001 C CNN 215 | F 3 "" H 3200 2950 50 0001 C CNN 216 | 1 3200 2950 217 | 0 -1 -1 0 218 | $EndComp 219 | $Comp 220 | L ThrottleBlaster:7SegmentDisplayTM1637 U1 221 | U 1 1 65F6412F 222 | P 3200 2600 223 | F 0 "U1" H 2350 2250 50 0000 C CNN 224 | F 1 "7SegmentDisplayTM1637" H 2350 2350 50 0000 C CNN 225 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical" H 3200 2600 50 0001 C CNN 226 | F 3 "" H 3200 2600 50 0001 C CNN 227 | 1 3200 2600 228 | 1 0 0 -1 229 | $EndComp 230 | Wire Wire Line 231 | 3200 2750 5050 2750 232 | Wire Wire Line 233 | 3200 2850 5050 2850 234 | Wire Wire Line 235 | 6200 2050 6200 2200 236 | Connection ~ 3800 3250 237 | Wire Wire Line 238 | 3800 3250 5050 3250 239 | Wire Wire Line 240 | 3450 3050 5050 3050 241 | Wire Wire Line 242 | 3650 3150 5050 3150 243 | $Comp 244 | L Jumper:Jumper_2_Open JP1 245 | U 1 1 661001AF 246 | P 4550 3350 247 | F 0 "JP1" H 4596 3262 50 0000 R CNN 248 | F 1 "MODE" H 4450 3300 50 0000 R CNN 249 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 4550 3350 50 0001 C CNN 250 | F 3 "~" H 4550 3350 50 0001 C CNN 251 | 1 4550 3350 252 | 1 0 0 -1 253 | $EndComp 254 | $Comp 255 | L Jumper:Jumper_2_Open JP2 256 | U 1 1 66101426 257 | P 4550 3550 258 | F 0 "JP2" H 4596 3462 50 0000 R CNN 259 | F 1 "POT Enable" H 4450 3500 50 0000 R CNN 260 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 4550 3550 50 0001 C CNN 261 | F 3 "~" H 4550 3550 50 0001 C CNN 262 | 1 4550 3550 263 | 1 0 0 -1 264 | $EndComp 265 | $Comp 266 | L power:GND #PWR01 267 | U 1 1 66111830 268 | P 4350 3350 269 | F 0 "#PWR01" H 4350 3100 50 0001 C CNN 270 | F 1 "GND" V 4355 3177 50 0000 C CNN 271 | F 2 "" H 4350 3350 50 0001 C CNN 272 | F 3 "" H 4350 3350 50 0001 C CNN 273 | 1 4350 3350 274 | 0 1 1 0 275 | $EndComp 276 | $Comp 277 | L power:GND #PWR02 278 | U 1 1 66112007 279 | P 4350 3550 280 | F 0 "#PWR02" H 4350 3300 50 0001 C CNN 281 | F 1 "GND" V 4355 3377 50 0000 C CNN 282 | F 2 "" H 4350 3550 50 0001 C CNN 283 | F 3 "" H 4350 3550 50 0001 C CNN 284 | 1 4350 3550 285 | 0 1 1 0 286 | $EndComp 287 | Wire Wire Line 288 | 6050 3150 6400 3150 289 | Wire Wire Line 290 | 6050 3350 6700 3350 291 | Wire Wire Line 292 | 6050 3550 6500 3550 293 | $Comp 294 | L Connector:Conn_01x01_Male J1 295 | U 1 1 65FDF326 296 | P 8450 3800 297 | F 0 "J1" H 8422 3732 50 0000 R CNN 298 | F 1 "Conn_01x01_Male" H 8422 3823 50 0000 R CNN 299 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Horizontal" H 8450 3800 50 0001 C CNN 300 | F 3 "~" H 8450 3800 50 0001 C CNN 301 | 1 8450 3800 302 | -1 0 0 1 303 | $EndComp 304 | $Comp 305 | L power:GND #PWR0114 306 | U 1 1 65FDD588 307 | P 7400 4300 308 | F 0 "#PWR0114" H 7400 4050 50 0001 C CNN 309 | F 1 "GND" H 7405 4127 50 0000 C CNN 310 | F 2 "" H 7400 4300 50 0001 C CNN 311 | F 3 "" H 7400 4300 50 0001 C CNN 312 | 1 7400 4300 313 | 1 0 0 -1 314 | $EndComp 315 | Wire Wire Line 316 | 7400 3800 7700 3800 317 | Wire Wire Line 318 | 6500 4000 6800 4000 319 | Wire Wire Line 320 | 6500 3550 6500 4000 321 | Wire Wire Line 322 | 3200 3050 3400 3050 323 | Wire Wire Line 324 | 3400 3050 3400 2050 325 | Wire Wire Line 326 | 3400 2050 6200 2050 327 | NoConn ~ 6050 3750 328 | NoConn ~ 6050 4450 329 | NoConn ~ 6050 4550 330 | NoConn ~ 6050 4650 331 | NoConn ~ 5050 4650 332 | NoConn ~ 6050 3250 333 | NoConn ~ 6050 3050 334 | NoConn ~ 6050 2750 335 | Wire Wire Line 336 | 6200 1550 6200 1750 337 | Wire Wire Line 338 | 6200 1150 6200 1550 339 | Connection ~ 6200 1550 340 | $Comp 341 | L power:+5V #PWR0106 342 | U 1 1 65FB6A22 343 | P 6200 1550 344 | F 0 "#PWR0106" H 6200 1400 50 0001 C CNN 345 | F 1 "+5V" V 6215 1678 50 0000 L CNN 346 | F 2 "" H 6200 1550 50 0001 C CNN 347 | F 3 "" H 6200 1550 50 0001 C CNN 348 | 1 6200 1550 349 | 0 1 1 0 350 | $EndComp 351 | Connection ~ 6200 2050 352 | $Comp 353 | L power:PWR_FLAG #FLG0101 354 | U 1 1 6623D785 355 | P 6200 2200 356 | F 0 "#FLG0101" H 6200 2275 50 0001 C CNN 357 | F 1 "PWR_FLAG" V 6200 2328 50 0000 L CNN 358 | F 2 "" H 6200 2200 50 0001 C CNN 359 | F 3 "~" H 6200 2200 50 0001 C CNN 360 | 1 6200 2200 361 | 0 -1 -1 0 362 | $EndComp 363 | $Comp 364 | L Device:C C1 365 | U 1 1 65F774D8 366 | P 6700 3500 367 | F 0 "C1" H 6815 3546 50 0000 L CNN 368 | F 1 "100pF" H 6815 3455 50 0000 L CNN 369 | F 2 "Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder" H 6738 3350 50 0001 C CNN 370 | F 3 "~" H 6700 3500 50 0001 C CNN 371 | 1 6700 3500 372 | 1 0 0 -1 373 | $EndComp 374 | Connection ~ 6700 3350 375 | Wire Wire Line 376 | 6700 3350 6850 3350 377 | $Comp 378 | L power:GND #PWR03 379 | U 1 1 65F77A5D 380 | P 6700 3650 381 | F 0 "#PWR03" H 6700 3400 50 0001 C CNN 382 | F 1 "GND" H 6705 3477 50 0000 C CNN 383 | F 2 "" H 6700 3650 50 0001 C CNN 384 | F 3 "" H 6700 3650 50 0001 C CNN 385 | 1 6700 3650 386 | 1 0 0 -1 387 | $EndComp 388 | Wire Wire Line 389 | 4750 3350 5050 3350 390 | Wire Wire Line 391 | 4750 3550 5050 3550 392 | Wire Wire Line 393 | 2250 3250 2250 3650 394 | Wire Wire Line 395 | 2250 3250 3800 3250 396 | Wire Wire Line 397 | 2700 3650 5050 3650 398 | $Comp 399 | L power:GND #PWR04 400 | U 1 1 65F993B3 401 | P 2700 4050 402 | F 0 "#PWR04" H 2700 3800 50 0001 C CNN 403 | F 1 "GND" H 2705 3877 50 0000 C CNN 404 | F 2 "" H 2700 4050 50 0001 C CNN 405 | F 3 "" H 2700 4050 50 0001 C CNN 406 | 1 2700 4050 407 | 1 0 0 -1 408 | $EndComp 409 | $Comp 410 | L Switch:SW_Push SW3 411 | U 1 1 65F988F7 412 | P 2700 3850 413 | F 0 "SW3" V 2746 3802 50 0000 R CNN 414 | F 1 "SW_Push" V 2655 3802 50 0000 R CNN 415 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 2700 4050 50 0001 C CNN 416 | F 3 "~" H 2700 4050 50 0001 C CNN 417 | 1 2700 3850 418 | 0 -1 -1 0 419 | $EndComp 420 | $Comp 421 | L Interface_UART:MAX3232 U4 422 | U 1 1 660281FF 423 | P 5000 6100 424 | F 0 "U4" V 4954 7344 50 0000 L CNN 425 | F 1 "MAX3232" V 5045 7344 50 0000 L CNN 426 | F 2 "Package_SO:SOIC-16W_5.3x10.2mm_P1.27mm" H 5050 5050 50 0001 L CNN 427 | F 3 "https://datasheets.maximintegrated.com/en/ds/MAX3222-MAX3241.pdf" H 5000 6200 50 0001 C CNN 428 | 1 5000 6100 429 | 0 1 1 0 430 | $EndComp 431 | $Comp 432 | L power:GND #PWR0101 433 | U 1 1 6602AE2B 434 | P 3800 6100 435 | F 0 "#PWR0101" H 3800 5850 50 0001 C CNN 436 | F 1 "GND" H 3805 5927 50 0000 C CNN 437 | F 2 "" H 3800 6100 50 0001 C CNN 438 | F 3 "" H 3800 6100 50 0001 C CNN 439 | 1 3800 6100 440 | 1 0 0 -1 441 | $EndComp 442 | Connection ~ 6200 2200 443 | Wire Wire Line 444 | 6200 2200 6200 2850 445 | $Comp 446 | L ThrottleBlaster:RaspberryPi_Pico U2 447 | U 1 1 65F5261C 448 | P 4450 4900 449 | F 0 "U2" V 5550 6100 50 0000 C CNN 450 | F 1 "RaspberryPi_Pico" V 5550 5500 50 0000 C CNN 451 | F 2 "ThrottleBlaster:raspberry_pi_pico" H 3750 1400 50 0001 C CNN 452 | F 3 "" H 3750 1400 50 0001 C CNN 453 | 1 4450 4900 454 | 1 0 0 -1 455 | $EndComp 456 | Wire Wire Line 457 | 6200 6100 6400 6100 458 | Wire Wire Line 459 | 4500 5300 4500 3850 460 | Wire Wire Line 461 | 4500 3850 5050 3850 462 | NoConn ~ 4300 5300 463 | NoConn ~ 4700 6900 464 | $Comp 465 | L power:GND #PWR0102 466 | U 1 1 6603468D 467 | P 4700 5300 468 | F 0 "#PWR0102" H 4700 5050 50 0001 C CNN 469 | F 1 "GND" H 4705 5127 50 0000 C CNN 470 | F 2 "" H 4700 5300 50 0001 C CNN 471 | F 3 "" H 4700 5300 50 0001 C CNN 472 | 1 4700 5300 473 | -1 0 0 1 474 | $EndComp 475 | $Comp 476 | L power:GND #PWR0115 477 | U 1 1 66034B58 478 | P 4300 6900 479 | F 0 "#PWR0115" H 4300 6650 50 0001 C CNN 480 | F 1 "GND" H 4305 6727 50 0000 C CNN 481 | F 2 "" H 4300 6900 50 0001 C CNN 482 | F 3 "" H 4300 6900 50 0001 C CNN 483 | 1 4300 6900 484 | 1 0 0 -1 485 | $EndComp 486 | $Comp 487 | L Device:C C6 488 | U 1 1 660351CA 489 | P 6400 6250 490 | F 0 "C6" H 6515 6296 50 0000 L CNN 491 | F 1 "1uF" H 6515 6205 50 0000 L CNN 492 | F 2 "Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder" H 6438 6100 50 0001 C CNN 493 | F 3 "~" H 6400 6250 50 0001 C CNN 494 | 1 6400 6250 495 | 1 0 0 -1 496 | $EndComp 497 | $Comp 498 | L power:GND #PWR0116 499 | U 1 1 66035CEC 500 | P 6400 6400 501 | F 0 "#PWR0116" H 6400 6150 50 0001 C CNN 502 | F 1 "GND" H 6405 6227 50 0000 C CNN 503 | F 2 "" H 6400 6400 50 0001 C CNN 504 | F 3 "" H 6400 6400 50 0001 C CNN 505 | 1 6400 6400 506 | 1 0 0 -1 507 | $EndComp 508 | $Comp 509 | L Device:C C4 510 | U 1 1 660361DE 511 | P 5750 5300 512 | F 0 "C4" V 5498 5300 50 0000 C CNN 513 | F 1 "1uF" V 5589 5300 50 0000 C CNN 514 | F 2 "Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder" H 5788 5150 50 0001 C CNN 515 | F 3 "~" H 5750 5300 50 0001 C CNN 516 | 1 5750 5300 517 | 0 1 1 0 518 | $EndComp 519 | $Comp 520 | L Device:C C5 521 | U 1 1 660368AB 522 | P 5750 6900 523 | F 0 "C5" V 5498 6900 50 0000 C CNN 524 | F 1 "1uF" V 5589 6900 50 0000 C CNN 525 | F 2 "Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder" H 5788 6750 50 0001 C CNN 526 | F 3 "~" H 5750 6900 50 0001 C CNN 527 | 1 5750 6900 528 | 0 1 1 0 529 | $EndComp 530 | $Comp 531 | L Device:C C3 532 | U 1 1 660373B4 533 | P 5400 7050 534 | F 0 "C3" H 5285 7004 50 0000 R CNN 535 | F 1 "1uF" H 5285 7095 50 0000 R CNN 536 | F 2 "Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder" H 5438 6900 50 0001 C CNN 537 | F 3 "~" H 5400 7050 50 0001 C CNN 538 | 1 5400 7050 539 | -1 0 0 1 540 | $EndComp 541 | $Comp 542 | L Device:C C2 543 | U 1 1 66038E8A 544 | P 5100 7050 545 | F 0 "C2" H 5215 7096 50 0000 L CNN 546 | F 1 "1uF" H 5215 7005 50 0000 L CNN 547 | F 2 "Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder" H 5138 6900 50 0001 C CNN 548 | F 3 "~" H 5100 7050 50 0001 C CNN 549 | 1 5100 7050 550 | 1 0 0 -1 551 | $EndComp 552 | $Comp 553 | L power:GND #PWR0117 554 | U 1 1 66039913 555 | P 5100 7200 556 | F 0 "#PWR0117" H 5100 6950 50 0001 C CNN 557 | F 1 "GND" H 5105 7027 50 0000 C CNN 558 | F 2 "" H 5100 7200 50 0001 C CNN 559 | F 3 "" H 5100 7200 50 0001 C CNN 560 | 1 5100 7200 561 | 1 0 0 -1 562 | $EndComp 563 | $Comp 564 | L Connector:Conn_01x03_Male J2 565 | U 1 1 6603B4BC 566 | P 3600 7400 567 | F 0 "J2" H 3708 7681 50 0000 C CNN 568 | F 1 "Serial" H 3708 7590 50 0000 C CNN 569 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical" H 3600 7400 50 0001 C CNN 570 | F 3 "~" H 3600 7400 50 0001 C CNN 571 | 1 3600 7400 572 | 1 0 0 -1 573 | $EndComp 574 | Wire Wire Line 575 | 3800 7300 4500 7300 576 | Wire Wire Line 577 | 4500 7300 4500 6900 578 | Wire Wire Line 579 | 3800 7500 4900 7500 580 | Wire Wire Line 581 | 4900 7500 4900 6900 582 | $Comp 583 | L power:GND #PWR0118 584 | U 1 1 6603F193 585 | P 3800 7400 586 | F 0 "#PWR0118" H 3800 7150 50 0001 C CNN 587 | F 1 "GND" V 3805 7272 50 0000 R CNN 588 | F 2 "" H 3800 7400 50 0001 C CNN 589 | F 3 "" H 3800 7400 50 0001 C CNN 590 | 1 3800 7400 591 | 0 -1 -1 0 592 | $EndComp 593 | Text Label 3950 7300 0 50 ~ 0 594 | Rx 595 | Text Label 3950 7500 0 50 ~ 0 596 | Tx 597 | $Comp 598 | L Device:R R2 599 | U 1 1 6607F259 600 | P 7850 3800 601 | F 0 "R2" V 7643 3800 50 0000 C CNN 602 | F 1 "100 Ohms" V 7734 3800 50 0000 C CNN 603 | F 2 "Resistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolder" V 7780 3800 50 0001 C CNN 604 | F 3 "~" H 7850 3800 50 0001 C CNN 605 | 1 7850 3800 606 | 0 1 1 0 607 | $EndComp 608 | Wire Wire Line 609 | 8000 3800 8250 3800 610 | $Comp 611 | L power:GND #PWR0119 612 | U 1 1 660C571F 613 | P 5400 7200 614 | F 0 "#PWR0119" H 5400 6950 50 0001 C CNN 615 | F 1 "GND" H 5405 7027 50 0000 C CNN 616 | F 2 "" H 5400 7200 50 0001 C CNN 617 | F 3 "" H 5400 7200 50 0001 C CNN 618 | 1 5400 7200 619 | 1 0 0 -1 620 | $EndComp 621 | Wire Wire Line 622 | 6400 6100 6400 3150 623 | Connection ~ 6400 6100 624 | Connection ~ 6400 3150 625 | Wire Wire Line 626 | 6400 3150 7000 3150 627 | NoConn ~ 5050 4450 628 | NoConn ~ 5050 4550 629 | $Comp 630 | L Device:Jumper JP3 631 | U 1 1 663F5630 632 | P 6950 4900 633 | F 0 "JP3" H 6950 5164 50 0000 C CNN 634 | F 1 "ReverseDir" H 6950 5073 50 0000 C CNN 635 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 6950 4900 50 0001 C CNN 636 | F 3 "~" H 6950 4900 50 0001 C CNN 637 | 1 6950 4900 638 | 1 0 0 -1 639 | $EndComp 640 | $Comp 641 | L power:GND #PWR0120 642 | U 1 1 663F7820 643 | P 7250 4900 644 | F 0 "#PWR0120" H 7250 4650 50 0001 C CNN 645 | F 1 "GND" H 7255 4727 50 0000 C CNN 646 | F 2 "" H 7250 4900 50 0001 C CNN 647 | F 3 "" H 7250 4900 50 0001 C CNN 648 | 1 7250 4900 649 | 1 0 0 -1 650 | $EndComp 651 | $Comp 652 | L power:GND #PWR0121 653 | U 1 1 666266FF 654 | P 5050 3950 655 | F 0 "#PWR0121" H 5050 3700 50 0001 C CNN 656 | F 1 "GND" V 5055 3822 50 0000 R CNN 657 | F 2 "" H 5050 3950 50 0001 C CNN 658 | F 3 "" H 5050 3950 50 0001 C CNN 659 | 1 5050 3950 660 | 0 1 1 0 661 | $EndComp 662 | $Comp 663 | L power:GND #PWR0122 664 | U 1 1 66626E9F 665 | P 6050 3950 666 | F 0 "#PWR0122" H 6050 3700 50 0001 C CNN 667 | F 1 "GND" V 6055 3822 50 0000 R CNN 668 | F 2 "" H 6050 3950 50 0001 C CNN 669 | F 3 "" H 6050 3950 50 0001 C CNN 670 | 1 6050 3950 671 | 0 -1 -1 0 672 | $EndComp 673 | Text GLabel 5050 4050 0 50 Input ~ 0 674 | PRE1 675 | Text GLabel 5050 4150 0 50 Input ~ 0 676 | PRE2 677 | Text GLabel 5050 4250 0 50 Input ~ 0 678 | PRE3 679 | Text GLabel 5050 4350 0 50 Input ~ 0 680 | PRE4 681 | Text GLabel 6050 4350 2 50 Input ~ 0 682 | PRE5 683 | Text GLabel 6050 4250 2 50 Input ~ 0 684 | PRE6 685 | Text GLabel 6050 4150 2 50 Input ~ 0 686 | PRE7 687 | Text GLabel 6050 4050 2 50 Input ~ 0 688 | PRE8 689 | Wire Wire Line 690 | 6350 3650 6350 4900 691 | Wire Wire Line 692 | 6350 4900 6650 4900 693 | Wire Wire Line 694 | 6050 3650 6350 3650 695 | Wire Wire Line 696 | 4750 3750 4750 5000 697 | Wire Wire Line 698 | 4750 5000 4900 5000 699 | Wire Wire Line 700 | 4900 5000 4900 5300 701 | Wire Wire Line 702 | 4750 3750 5050 3750 703 | $Comp 704 | L Switch:SW_Push P1 705 | U 1 1 6662D7D8 706 | P 7200 5900 707 | F 0 "P1" V 7246 5852 50 0000 R CNN 708 | F 1 "SW_Push" V 7155 5852 50 0000 R CNN 709 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 7200 6100 50 0001 C CNN 710 | F 3 "~" H 7200 6100 50 0001 C CNN 711 | 1 7200 5900 712 | 0 -1 -1 0 713 | $EndComp 714 | Text GLabel 7200 5700 1 50 Input ~ 0 715 | PRE1 716 | $Comp 717 | L Switch:SW_Push P2 718 | U 1 1 6662F096 719 | P 7700 5900 720 | F 0 "P2" V 7746 5852 50 0000 R CNN 721 | F 1 "SW_Push" V 7655 5852 50 0000 R CNN 722 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 7700 6100 50 0001 C CNN 723 | F 3 "~" H 7700 6100 50 0001 C CNN 724 | 1 7700 5900 725 | 0 -1 -1 0 726 | $EndComp 727 | $Comp 728 | L Switch:SW_Push P3 729 | U 1 1 6662FBAD 730 | P 8200 5900 731 | F 0 "P3" V 8246 5852 50 0000 R CNN 732 | F 1 "SW_Push" V 8155 5852 50 0000 R CNN 733 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 8200 6100 50 0001 C CNN 734 | F 3 "~" H 8200 6100 50 0001 C CNN 735 | 1 8200 5900 736 | 0 -1 -1 0 737 | $EndComp 738 | $Comp 739 | L Switch:SW_Push P4 740 | U 1 1 66630452 741 | P 8700 5900 742 | F 0 "P4" V 8746 5852 50 0000 R CNN 743 | F 1 "SW_Push" V 8655 5852 50 0000 R CNN 744 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 8700 6100 50 0001 C CNN 745 | F 3 "~" H 8700 6100 50 0001 C CNN 746 | 1 8700 5900 747 | 0 -1 -1 0 748 | $EndComp 749 | $Comp 750 | L Switch:SW_Push P5 751 | U 1 1 66630CF7 752 | P 9200 5900 753 | F 0 "P5" V 9246 5852 50 0000 R CNN 754 | F 1 "SW_Push" V 9155 5852 50 0000 R CNN 755 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 9200 6100 50 0001 C CNN 756 | F 3 "~" H 9200 6100 50 0001 C CNN 757 | 1 9200 5900 758 | 0 -1 -1 0 759 | $EndComp 760 | $Comp 761 | L Switch:SW_Push P6 762 | U 1 1 666313AC 763 | P 9700 5900 764 | F 0 "P6" V 9746 5852 50 0000 R CNN 765 | F 1 "SW_Push" V 9655 5852 50 0000 R CNN 766 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 9700 6100 50 0001 C CNN 767 | F 3 "~" H 9700 6100 50 0001 C CNN 768 | 1 9700 5900 769 | 0 -1 -1 0 770 | $EndComp 771 | $Comp 772 | L Switch:SW_Push P7 773 | U 1 1 66631A2B 774 | P 10200 5900 775 | F 0 "P7" V 10246 5852 50 0000 R CNN 776 | F 1 "SW_Push" V 10155 5852 50 0000 R CNN 777 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 10200 6100 50 0001 C CNN 778 | F 3 "~" H 10200 6100 50 0001 C CNN 779 | 1 10200 5900 780 | 0 -1 -1 0 781 | $EndComp 782 | $Comp 783 | L Switch:SW_Push P8 784 | U 1 1 6663226F 785 | P 10700 5900 786 | F 0 "P8" V 10746 5852 50 0000 R CNN 787 | F 1 "SW_Push" V 10655 5852 50 0000 R CNN 788 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" H 10700 6100 50 0001 C CNN 789 | F 3 "~" H 10700 6100 50 0001 C CNN 790 | 1 10700 5900 791 | 0 -1 -1 0 792 | $EndComp 793 | Text GLabel 7700 5700 1 50 Input ~ 0 794 | PRE2 795 | Text GLabel 8200 5700 1 50 Input ~ 0 796 | PRE3 797 | Text GLabel 8700 5700 1 50 Input ~ 0 798 | PRE4 799 | Text GLabel 9200 5700 1 50 Input ~ 0 800 | PRE5 801 | Text GLabel 9700 5700 1 50 Input ~ 0 802 | PRE6 803 | Text GLabel 10200 5700 1 50 Input ~ 0 804 | PRE7 805 | Text GLabel 10700 5700 1 50 Input ~ 0 806 | PRE8 807 | $Comp 808 | L power:GND #PWR0123 809 | U 1 1 66637514 810 | P 7200 6100 811 | F 0 "#PWR0123" H 7200 5850 50 0001 C CNN 812 | F 1 "GND" H 7205 5927 50 0000 C CNN 813 | F 2 "" H 7200 6100 50 0001 C CNN 814 | F 3 "" H 7200 6100 50 0001 C CNN 815 | 1 7200 6100 816 | 1 0 0 -1 817 | $EndComp 818 | $Comp 819 | L power:GND #PWR0124 820 | U 1 1 66637A9F 821 | P 7700 6100 822 | F 0 "#PWR0124" H 7700 5850 50 0001 C CNN 823 | F 1 "GND" H 7705 5927 50 0000 C CNN 824 | F 2 "" H 7700 6100 50 0001 C CNN 825 | F 3 "" H 7700 6100 50 0001 C CNN 826 | 1 7700 6100 827 | 1 0 0 -1 828 | $EndComp 829 | $Comp 830 | L power:GND #PWR0125 831 | U 1 1 66638036 832 | P 8200 6100 833 | F 0 "#PWR0125" H 8200 5850 50 0001 C CNN 834 | F 1 "GND" H 8205 5927 50 0000 C CNN 835 | F 2 "" H 8200 6100 50 0001 C CNN 836 | F 3 "" H 8200 6100 50 0001 C CNN 837 | 1 8200 6100 838 | 1 0 0 -1 839 | $EndComp 840 | $Comp 841 | L power:GND #PWR0126 842 | U 1 1 666385D9 843 | P 8700 6100 844 | F 0 "#PWR0126" H 8700 5850 50 0001 C CNN 845 | F 1 "GND" H 8705 5927 50 0000 C CNN 846 | F 2 "" H 8700 6100 50 0001 C CNN 847 | F 3 "" H 8700 6100 50 0001 C CNN 848 | 1 8700 6100 849 | 1 0 0 -1 850 | $EndComp 851 | $Comp 852 | L power:GND #PWR0127 853 | U 1 1 66638B88 854 | P 9200 6100 855 | F 0 "#PWR0127" H 9200 5850 50 0001 C CNN 856 | F 1 "GND" H 9205 5927 50 0000 C CNN 857 | F 2 "" H 9200 6100 50 0001 C CNN 858 | F 3 "" H 9200 6100 50 0001 C CNN 859 | 1 9200 6100 860 | 1 0 0 -1 861 | $EndComp 862 | $Comp 863 | L power:GND #PWR0128 864 | U 1 1 66639143 865 | P 9700 6100 866 | F 0 "#PWR0128" H 9700 5850 50 0001 C CNN 867 | F 1 "GND" H 9705 5927 50 0000 C CNN 868 | F 2 "" H 9700 6100 50 0001 C CNN 869 | F 3 "" H 9700 6100 50 0001 C CNN 870 | 1 9700 6100 871 | 1 0 0 -1 872 | $EndComp 873 | $Comp 874 | L power:GND #PWR0129 875 | U 1 1 6663970A 876 | P 10200 6100 877 | F 0 "#PWR0129" H 10200 5850 50 0001 C CNN 878 | F 1 "GND" H 10205 5927 50 0000 C CNN 879 | F 2 "" H 10200 6100 50 0001 C CNN 880 | F 3 "" H 10200 6100 50 0001 C CNN 881 | 1 10200 6100 882 | 1 0 0 -1 883 | $EndComp 884 | $Comp 885 | L power:GND #PWR0130 886 | U 1 1 66639CDD 887 | P 10700 6100 888 | F 0 "#PWR0130" H 10700 5850 50 0001 C CNN 889 | F 1 "GND" H 10705 5927 50 0000 C CNN 890 | F 2 "" H 10700 6100 50 0001 C CNN 891 | F 3 "" H 10700 6100 50 0001 C CNN 892 | 1 10700 6100 893 | 1 0 0 -1 894 | $EndComp 895 | Text GLabel 6050 3850 2 50 Input ~ 0 896 | RESET 897 | $Comp 898 | L power:GND #PWR0131 899 | U 1 1 6669841E 900 | P 10750 4450 901 | F 0 "#PWR0131" H 10750 4200 50 0001 C CNN 902 | F 1 "GND" H 10755 4277 50 0000 C CNN 903 | F 2 "" H 10750 4450 50 0001 C CNN 904 | F 3 "" H 10750 4450 50 0001 C CNN 905 | 1 10750 4450 906 | 1 0 0 -1 907 | $EndComp 908 | Text GLabel 9400 4350 0 50 Input ~ 0 909 | RESET 910 | $Comp 911 | L Connector:Conn_01x02_Male RES1 912 | U 1 1 66699A06 913 | P 10450 4450 914 | F 0 "RES1" H 10350 4400 50 0000 C CNN 915 | F 1 "Conn_01x02_Male" H 10500 4150 50 0000 C CNN 916 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Horizontal" H 10450 4450 50 0001 C CNN 917 | F 3 "~" H 10450 4450 50 0001 C CNN 918 | 1 10450 4450 919 | -1 0 0 1 920 | $EndComp 921 | $Comp 922 | L power:GND #PWR0132 923 | U 1 1 6669A5C1 924 | P 10250 4450 925 | F 0 "#PWR0132" H 10250 4200 50 0001 C CNN 926 | F 1 "GND" H 10255 4277 50 0000 C CNN 927 | F 2 "" H 10250 4450 50 0001 C CNN 928 | F 3 "" H 10250 4450 50 0001 C CNN 929 | 1 10250 4450 930 | 1 0 0 -1 931 | $EndComp 932 | $Comp 933 | L Transistor_FET:2N7000 Q2 934 | U 1 1 6666B520 935 | P 9600 4250 936 | F 0 "Q2" V 9849 4250 50 0000 C CNN 937 | F 1 "2N7000" V 9940 4250 50 0000 C CNN 938 | F 2 "Package_TO_SOT_THT:TO-92_Inline" H 9800 4175 50 0001 L CIN 939 | F 3 "https://www.onsemi.com/pub/Collateral/NDS7002A-D.PDF" H 9600 4250 50 0001 L CNN 940 | 1 9600 4250 941 | 0 1 1 0 942 | $EndComp 943 | Wire Wire Line 944 | 9800 4350 10250 4350 945 | Wire Wire Line 946 | 10750 4350 10250 4350 947 | Connection ~ 10250 4350 948 | $Comp 949 | L Connector:Conn_01x02_Male RES2 950 | U 1 1 66679526 951 | P 10950 4450 952 | F 0 "RES2" H 10950 4400 50 0000 R CNN 953 | F 1 "Conn_01x02_Male" H 11000 4150 50 0000 R CNN 954 | F 2 "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Horizontal" H 10950 4450 50 0001 C CNN 955 | F 3 "~" H 10950 4450 50 0001 C CNN 956 | 1 10950 4450 957 | -1 0 0 1 958 | $EndComp 959 | Wire Wire Line 960 | 7000 3150 9600 3150 961 | Wire Wire Line 962 | 9600 3150 9600 4050 963 | Connection ~ 7000 3150 964 | $EndSCHEMATC 965 | -------------------------------------------------------------------------------- /kicad/fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (lib (name ThrottleBlaster)(type KiCad)(uri ${KIPRJMOD}/ThrottleBlaster.pretty)(options "")(descr "")) 3 | ) 4 | -------------------------------------------------------------------------------- /kicad/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (lib (name ThrottleBlaster)(type Legacy)(uri ${KIPRJMOD}/ThrottleBlaster.lib)(options "")(descr "")) 3 | ) 4 | --------------------------------------------------------------------------------