├── .gitignore ├── img ├── diagram.png ├── button_states.png ├── rotary_states.png ├── button2_states.png ├── stpclk_pin_slot1.jpg ├── stpclk_pin_socket7.jpg ├── stpclk_pin_socket8.jpg ├── stpclk_pin_socketA.jpg ├── ThrottleBlaster_pcb.jpg ├── stpclk_pin_socket370.jpg ├── ThrottleBlaster_PCB_back.jpg ├── ThrottleBlaster_PCB_front.jpg ├── ThrottleBlaster_breadboard.jpg ├── rotary_states.fig ├── button_states.fig └── button2_states.fig ├── kicad ├── fp-lib-table ├── sym-lib-table ├── ThrottleBlaster.pro ├── ThrottleBlaster.lib ├── ThrottleBlaster.pretty │ └── raspberry_pi_pico.kicad_mod └── ThrottleBlaster.sch ├── firmware ├── src │ ├── config.h.in │ ├── Debug.h │ ├── Button.cpp │ ├── Display.h │ ├── Uart.h │ ├── Potentiometer.h │ ├── Flash.h │ ├── RotaryLogic.h │ ├── DutyCycle.h │ ├── Pwm.pio │ ├── TwoButtonLogic.h │ ├── Display.cpp │ ├── RotaryEncoder.h │ ├── Uart.cpp │ ├── Button.h │ ├── Flash.cpp │ ├── Pico.h │ ├── PotentiometerLogic.h │ ├── pico_sdk_import.cmake │ ├── CMakeLists.txt │ ├── Utils.h │ ├── CommonLogic.h │ ├── Presets.h │ ├── Pico.cpp │ ├── ConfigOpts.h │ ├── RotaryEncoder.cpp │ ├── Presets.cpp │ ├── CommonLogic.cpp │ ├── RotaryLogic.cpp │ ├── main.cpp │ ├── PotentiometerLogic.cpp │ └── TwoButtonLogic.cpp └── build.sh ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build/* -------------------------------------------------------------------------------- /img/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/diagram.png -------------------------------------------------------------------------------- /img/button_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/button_states.png -------------------------------------------------------------------------------- /img/rotary_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/rotary_states.png -------------------------------------------------------------------------------- /img/button2_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/button2_states.png -------------------------------------------------------------------------------- /img/stpclk_pin_slot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/stpclk_pin_slot1.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socket7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/stpclk_pin_socket7.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socket8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/stpclk_pin_socket8.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socketA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/stpclk_pin_socketA.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_pcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/ThrottleBlaster_pcb.jpg -------------------------------------------------------------------------------- /img/stpclk_pin_socket370.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/stpclk_pin_socket370.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_PCB_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/ThrottleBlaster_PCB_back.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_PCB_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/ThrottleBlaster_PCB_front.jpg -------------------------------------------------------------------------------- /img/ThrottleBlaster_breadboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrapcomputing/ThrottleBlaster/HEAD/img/ThrottleBlaster_breadboard.jpg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = {11191111, 42 + 1, 0, 666, 11111191}; 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 { return FlashArray[ValueIdx]; } 30 | std::pair readRevision() const; 31 | std::vector readMagicNumber() const; 32 | bool valid() const; 33 | }; 34 | 35 | 36 | #endif // __FLASH_H__ 37 | -------------------------------------------------------------------------------- /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/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2025 Scrap Computing 4 | # 5 | # A helper script for building the release firmwares for both Pico 1 and Pico 2 6 | # 7 | 8 | set -e 9 | 10 | if [ $# -lt 1 ]; then 11 | echo "USAGE: $(basename ${0}) [TM1637_PATH]" 12 | echo "Example: $(basename ${0}) ~/pico-sdk ~/TM1637-pico-1.2.1" 13 | exit 1 14 | fi 15 | 16 | PICO_SDK_PATH=${1} 17 | TM1637_PATH=${2} 18 | 19 | echo "PICO_SDK_PATH=${PICO_SDK_PATH}" 20 | 21 | BUILD_PICO1=build_pico1 22 | BUILD_PICO2=build_pico2 23 | for dir in "${BUILD_PICO1}" "${BUILD_PICO2}"; do 24 | rm -rf ${dir} 25 | mkdir -p ${dir} 26 | done 27 | 28 | SRC_DIR=src/ 29 | CMAKE_COMMON="-DCMAKE_BUILD_TYPE=Release -DPICO_SDK_PATH=${PICO_SDK_PATH} -DPICO_TM1637_PATH=${TM1637_PATH}" 30 | 31 | echo "=== Pico 1 ===" 32 | cmake -B ${BUILD_PICO1} ${CMAKE_COMMON} -DPICO_BOARD=pico ${SRC_DIR} 33 | make -C ${BUILD_PICO1} -j 34 | 35 | echo "=== Pico 2 ===" 36 | cmake -B ${BUILD_PICO2} ${CMAKE_COMMON} -DPICO_BOARD=pico2 ${SRC_DIR} 37 | make -C ${BUILD_PICO2} -j 38 | 39 | echo "Done!" 40 | for dir in "${BUILD_PICO1}" "${BUILD_PICO2}"; do 41 | ls ${dir}/*.uf2 42 | done 43 | -------------------------------------------------------------------------------- /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/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/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 configDeletePreset(ButtonState LBtnState, ButtonState RBtnState); 39 | 40 | void configResetToDefaults(ButtonState LBtnState, ButtonState RBtnState); 41 | 42 | void configMaxMHz(ButtonState LBtnState, ButtonState RBtnState); 43 | 44 | void configMHz(ButtonState LBtnState, ButtonState RBtnState); 45 | 46 | public: 47 | TwoButtonLogic(int LeftButtonGPIO, int RightButtonGPIO, int SamplePeriod, 48 | Pico &Pi, Display &Disp, DutyCycle &DC, PresetsTable &Presets, 49 | FlashStorage &Flash) 50 | : CommonLogic(SamplePeriod, Disp, DC, Presets, Flash, Pi), 51 | LBtn(LeftButtonGPIO, Pi, "LBtn"), RBtn(RightButtonGPIO, Pi, "LBtn"), 52 | Pi(Pi) { 53 | setMode(Mode::Presets); 54 | } 55 | void tick() final; 56 | }; 57 | 58 | #endif // __TWOBUTTONLOGIC_H__ 59 | -------------------------------------------------------------------------------- /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 | #ifdef PICO_TM1637 48 | static int pow10(int Num) { 49 | int Res = 1; 50 | for (int Cnt = 0; Cnt < Num; ++Cnt) 51 | Res = Res * 10; 52 | return Res; 53 | } 54 | #endif // PICO_TM1637 55 | 56 | void Display::printRaw(int Num) { 57 | #ifdef PICO_TM1637 58 | if (flashShouldClear()) { 59 | TM1637_clear(); 60 | return; 61 | } 62 | int NumToDisplay = Num * pow10(DISPLAY_SHIFT_LEFT); 63 | TM1637_set_colon(false); 64 | TM1637_display(NumToDisplay, 0); 65 | #endif 66 | } 67 | 68 | void Display::printMHz(int MHz) { 69 | #ifdef PICO_TM1637 70 | if (flashShouldClear()) { 71 | TM1637_clear(); 72 | return; 73 | } 74 | printRaw(MHz); 75 | #endif 76 | } 77 | 78 | void Display::printKHz(int KHz) { 79 | #ifdef PICO_TM1637 80 | if (flashShouldClear()) { 81 | TM1637_clear(); 82 | return; 83 | } 84 | int NumToPrint = KHz / 1000; 85 | printMHz(NumToPrint); 86 | #endif 87 | } 88 | 89 | void Display::printTxt(const char *Str) { 90 | #ifdef PICO_TM1637 91 | if (flashShouldClear()) { 92 | TM1637_clear(); 93 | return; 94 | } 95 | TM1637_display_word((char *)Str, true); 96 | #endif 97 | } 98 | -------------------------------------------------------------------------------- /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 | extern bool Core1Enabled; 17 | 18 | class RotaryEncoder { 19 | int ClkPin; 20 | int DtPin; 21 | int SwPin; 22 | 23 | static constexpr const unsigned DebounceSz = 4; 24 | static constexpr const unsigned SwitchDebounceSz = 16; 25 | Buffer BuffA; 26 | Buffer BuffB; 27 | Buffer BuffC; 28 | 29 | public: 30 | enum class State { 31 | SwPress, 32 | SwRelease, 33 | SwLongPress, 34 | Left, 35 | Right, 36 | None, 37 | }; 38 | 39 | #ifdef DBGPRINT 40 | static const char *stateToStr(State S) { 41 | switch (S) { 42 | case State::SwPress: 43 | return "SwPress"; 44 | case State::SwRelease: 45 | return "SwRelease"; 46 | case State::SwLongPress: 47 | return "SwLongPress"; 48 | case State::Left: 49 | return "Left"; 50 | case State::Right: 51 | return "Right"; 52 | case State::None: 53 | return "None"; 54 | } 55 | return "UNKNOWN"; 56 | } 57 | #endif // DBGPRINT 58 | 59 | private: 60 | bool LastA = false; 61 | bool LastB = false; 62 | bool LastSw = true; 63 | bool IgnoreRelease = false; 64 | 65 | enum class InternalState { 66 | A0B0, 67 | A0B1, 68 | A1B1, 69 | A1B0, 70 | }; 71 | static constexpr const int BuffSz = 4; 72 | Buffer IntState; 73 | 74 | Pico &PICO; 75 | bool ReverseDirection; 76 | 77 | std::chrono::time_point PressStart; 78 | 79 | critical_section Lock; 80 | 81 | std::list StateList; 82 | 83 | void registerState(State); 84 | 85 | public: 86 | RotaryEncoder(int ClkPin, int DtPin, int SwPin, Pico &Pico, 87 | bool ReverseDirection = false); 88 | 89 | void tick(); 90 | 91 | State get(); 92 | }; 93 | 94 | #endif // __ROTARYENCODER_H__ 95 | -------------------------------------------------------------------------------- /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/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/Flash.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "Flash.h" 7 | #include "Debug.h" 8 | #include "RotaryEncoder.h" 9 | #include 10 | #include 11 | 12 | std::vector FlashStorage::getDataVec(const std::vector &Values) { 13 | std::vector TmpDataVec; 14 | int NumEntries = BytesToFlash / sizeof(TmpDataVec[0]); 15 | TmpDataVec.reserve(NumEntries); 16 | TmpDataVec.reserve(MagicNumber.size() + 2 + Values.size()); 17 | // 1. Magic number. 18 | TmpDataVec.insert(TmpDataVec.end(), MagicNumber.begin(), MagicNumber.end()); 19 | // 2. Revision. 20 | TmpDataVec.push_back(REVISION_MAJOR); 21 | TmpDataVec.push_back(REVISION_MINOR); 22 | // 3. Actual values. 23 | TmpDataVec.insert(TmpDataVec.end(), Values.begin(), Values.end()); 24 | // // 5. Fill the rest with zeros. 25 | // for (int Cnt = TmpDataVec.size(); Cnt != NumEntries; ++Cnt) 26 | // TmpDataVec.push_back(0); 27 | return TmpDataVec; 28 | } 29 | 30 | FlashStorage::FlashStorage() { 31 | FlashArray = 32 | (const int *)(XIP_BASE + BaseOffset + /*Revision:*/ 2 * sizeof(int) + 33 | /*MagicNumber:*/ MagicNumber.size() * 34 | sizeof(MagicNumber[0])); 35 | } 36 | 37 | void FlashStorage::write(const std::vector &Values) { 38 | DBG_PRINT(std::cout << "BaseOffset=" << BaseOffset 39 | << " BytesToFlash=" << BytesToFlash << "\n";) 40 | DBG_PRINT(std::cout << "DataVec:\n";) 41 | auto DataVec = getDataVec(Values); 42 | for (int Val : DataVec) 43 | DBG_PRINT(std::cout << Val << "\n";) 44 | 45 | DBG_PRINT(std::cout << "Before save and disable interrupts()\n";) 46 | // When writing to flash we need to stop the other core from running code. 47 | if (Core1Enabled) 48 | multicore_lockout_start_blocking(); 49 | // We also need to disable interrupts. 50 | uint32_t SvInterrupts = save_and_disable_interrupts(); 51 | flash_range_erase(BaseOffset, BytesToFlash); 52 | flash_range_program(BaseOffset, (const uint8_t *)DataVec.data(), 53 | BytesToFlash); 54 | // Restore interrupts. 55 | restore_interrupts(SvInterrupts); 56 | // Resume execution on other core. 57 | if (Core1Enabled) 58 | multicore_lockout_end_blocking(); 59 | DBG_PRINT(std::cout << "After interrupts\n";) 60 | } 61 | 62 | std::vector FlashStorage::readMagicNumber() const { 63 | std::vector MagicNum; 64 | int MagicNumberSz = (int)MagicNumber.size(); 65 | for (int Idx = 0; Idx != MagicNumberSz; ++Idx) 66 | MagicNum.push_back(FlashArray[Idx - MagicNumberSz - 2]); 67 | 68 | DBG_PRINT(std::cout << "Read magic number: ";) 69 | for (int V : MagicNum) 70 | DBG_PRINT(std::cout << V << " ";) 71 | DBG_PRINT(std::cout << "\n";) 72 | return MagicNum; 73 | } 74 | 75 | std::pair FlashStorage::readRevision() const { 76 | return {FlashArray[-2], FlashArray[-1]}; 77 | } 78 | 79 | bool FlashStorage::valid() const { return readMagicNumber() == MagicNumber; } 80 | -------------------------------------------------------------------------------- /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/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 configDeletePreset(int PotVal, ButtonState BtnState); 47 | 48 | void configResetToDefaults(int PotVal, ButtonState BtnState); 49 | 50 | void configMaxMHz(int PotVal, ButtonState BtnState); 51 | 52 | void configMHz(int PotVal, ButtonState BtnState); 53 | 54 | void setModeInit(Mode NewMode) override; 55 | 56 | int SvPotVal = 0; 57 | bool MovedPot = false; 58 | 59 | void savePotVal(int PotVal) { 60 | SvPotVal = PotVal; 61 | MovedPot = false; 62 | } 63 | bool movedPotComparedToSaved(int PotVal) { 64 | int PotDiff = std::abs(PotVal - SvPotVal); 65 | if (PotDiff > PotMoveAgainstSavedIdleLimit) 66 | MovedPot = true; 67 | return MovedPot; 68 | } 69 | enum class PotDir { 70 | Right, 71 | Mid, 72 | Left, 73 | }; 74 | PotDir getPotDir(int PotVal) const { 75 | if (PotVal > RightPotLimit) 76 | return PotDir::Right; 77 | if (PotVal < LeftPotLimit) 78 | return PotDir::Left; 79 | return PotDir::Mid; 80 | } 81 | 82 | bool EnablePot = false;; 83 | 84 | public: 85 | PotentiometerLogic(int PotGPIO, int ButtonGPIO, int SamplePeriod, Pico &Pi, 86 | Display &Disp, DutyCycle &DC, PresetsTable &Presets, 87 | FlashStorage &Flash, bool EnablePot, bool ReverseDirection) 88 | : CommonLogic(SamplePeriod, Disp, DC, Presets, Flash, Pi), 89 | Btn(ButtonGPIO, Pi, "Pot.Btn"), Pot(PotGPIO, Pi, ReverseDirection), 90 | Pi(Pi), EnablePot(EnablePot) { 91 | if (EnablePot) 92 | // One of the potentiometer's best features is that it remembers its 93 | // positions across restarts. So start in manual mode to make use of it. 94 | setMode(Mode::Manual); 95 | else 96 | setMode(Mode::Presets); 97 | } 98 | void tick() final; 99 | }; 100 | 101 | #endif // __POTENTIOMETERLOGIC_H__ 102 | -------------------------------------------------------------------------------- /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/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 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 4961.932 4397.727 4350 3225 4950 3075 5700 3300 13 | 1 1 2.00 60.00 120.00 14 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3107.175 6286.464 4200 4800 4950 6375 4350 7650 15 | 1 1 2.00 60.00 120.00 16 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 3929.167 4170.833 4350 4575 4500 4050 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 3487.500 5137.500 3675 4950 3750 5100 3675 5325 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 3487.500 6187.500 3675 6000 3750 6150 3675 6375 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 3217.500 5512.500 2850 7725 975 5550 2850 3300 23 | 1 1 2.00 60.00 120.00 24 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3544.927 5391.370 3225 7500 1425 5625 2850 3375 25 | 1 1 2.00 60.00 120.00 26 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 3525.000 5100.000 2850 6750 1800 5550 2850 3450 27 | 1 1 2.00 60.00 120.00 28 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3781.250 5062.500 2925 6600 2025 5175 2925 3525 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 3549.324 4694.595 2850 5700 2325 4725 3000 3600 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 3637.500 4650.000 2925 5475 2550 4575 3150 3675 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 3393.750 3975.000 3600 3675 3750 3900 3600 4275 35 | 1 1 2.00 60.00 120.00 36 | 6 375 2550 2550 3225 37 | 4 0 12 50 -1 18 16 0.0000 4 210 1140 375 2775 P = Push\001 38 | 4 0 9 50 -1 18 16 0.0000 4 270 2145 375 3120 LP = Long Push \001 39 | -6 40 | 6 2775 4200 4425 4950 41 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3600 4575 750 338 2850 4238 4350 4913 42 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3000 4575 Configure\001 43 | 4 0 0 50 -1 18 14 0.0000 4 180 495 3375 4875 MHz\001 44 | -6 45 | 6 2775 5250 4425 6000 46 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3602 5626 750 338 2852 5289 4352 5964 47 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 5625 Configure\001 48 | 4 0 0 50 -1 18 14 0.0000 4 180 585 3300 5925 PWM\001 49 | -6 50 | 6 2775 6300 4425 7050 51 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3602 6676 750 338 2852 6339 4352 7014 52 | 4 0 0 50 -1 18 14 0.0000 4 180 780 3225 6600 Delete\001 53 | 4 0 0 50 -1 18 14 0.0000 4 180 780 3225 6900 Preset\001 54 | -6 55 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3600 7800 750 338 2850 7463 4350 8138 56 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 6450 3375 750 338 5700 3038 7200 3713 57 | 1 2 0 4 0 7 50 -1 -1 0.000 1 0.0000 3600 3337 750 338 2850 3337 4350 3337 58 | 2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 59 | 225 2475 2625 2475 2625 3225 225 3225 225 2475 60 | 4 0 0 50 -1 18 16 0.0000 4 210 1005 3150 3450 Presets\001 61 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4875 3000 P\001 62 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4875 3975 P\001 63 | 4 0 12 50 -1 18 16 0.0000 4 210 180 3000 7425 P\001 64 | 4 0 9 50 -1 18 16 0.0000 4 210 345 4500 5175 LP\001 65 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2400 7950 LP\001 66 | 4 0 4 50 -1 18 16 0.0000 4 210 1260 375 5625 Resets to\001 67 | 4 0 4 50 -1 18 16 0.0000 4 210 1110 450 5925 Defaults\001 68 | 4 0 0 50 -1 18 14 0.0000 4 180 855 3150 7875 Reset?\001 69 | 4 0 0 50 -1 18 14 0.0000 4 180 825 6075 3450 Manual\001 70 | 4 0 0 50 -1 18 16 0.0000 4 210 1755 4800 5550 If at Max MHz\001 71 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3900 4125 LP\001 72 | 4 0 4 50 -1 18 16 0.0000 4 210 1065 4350 4350 Cancels\001 73 | 4 0 4 50 -1 18 16 0.0000 4 210 1065 2250 5100 Cancels\001 74 | 4 0 12 50 -1 18 16 0.0000 4 210 180 2325 5775 P\001 75 | 4 0 12 50 -1 18 16 0.0000 4 210 180 3900 5250 P\001 76 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2775 5325 LP\001 77 | 4 0 12 50 -1 18 16 0.0000 4 210 180 3825 6225 P\001 78 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2700 6450 LP\001 79 | 4 0 12 50 -1 18 16 0.0000 4 210 180 2550 6900 P\001 80 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/ -DPICO_BOARD= ../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 -DDBGPRINT=on to enable debug messages 15 | # 16 | # This will place the firmware into: build/ThrottleBlaster.uf2 17 | # 18 | # For example: minicom -b 115200 -D /dev/ttyACM0. Serial connection: 115200 8N1 19 | 20 | set(REVISION_MAJOR 0) 21 | set(REVISION_MINOR 8) 22 | 23 | message("PICO_SDK_PATH = ${PICO_SDK_PATH}") 24 | 25 | # initialize the SDK based on PICO_SDK_PATH 26 | # note: this must happen before project() 27 | include(pico_sdk_import.cmake) 28 | 29 | if (PICO_BOARD STREQUAL "pico") 30 | set(NAME_SUFFIX pico1) 31 | set(PICO1 1) 32 | elseif (PICO_BOARD STREQUAL "pico2") 33 | set(PICO2 1) 34 | set(NAME_SUFFIX pico2) 35 | else () 36 | message(FATAL ERROR "ERROR: Please define -DPICO_BOARD pico1 or pico2") 37 | endif () 38 | 39 | set(PROJECT_NAME "ThrottleBlaster_${NAME_SUFFIX}") 40 | project( 41 | ${PROJECT_NAME} 42 | LANGUAGES C CXX ASM) 43 | 44 | set(CMAKE_C_STANDARD 11) 45 | set(CMAKE_CXX_STANDARD 17) 46 | 47 | # initialize the Raspberry Pi Pico SDK 48 | pico_sdk_init() 49 | include_directories("${PICO_SDK_PATH}/src/common/pico_stdlib/include") 50 | include_directories("${PICO_SDK_PATH}/src/common/pico_base/include") 51 | include_directories("${PICO_SDK_PATH}/src/rp2_common/hardware_flash/include") 52 | include_directories("${PICO_SDK_PATH}/src/rp2_common/hardware_adc/include") 53 | include_directories("${PICO_SDK_PATH}/src/rp2_common/pico_multicore/include") 54 | include_directories("${PROJECT_BINARY_DIR}/") # for build/config.h 55 | if (DEFINED PICO_TM1637_PATH) 56 | include("${PICO_TM1637_PATH}/PicoTM1637.cmake") 57 | set(PICO_TM1637 1) 58 | endif () 59 | 60 | if (NOT DEFINED DISPLAY_SHIFT_LEFT) 61 | set(DISPLAY_SHIFT_LEFT 0) 62 | endif() 63 | 64 | if (NOT DEFINED DISPLAY_BRIGHTNESS) 65 | set(DISPLAY_BRIGHTNESS 1) 66 | endif() 67 | 68 | set(CMAKE_CXX_FLAGS_RELEASE "-O2") 69 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Werror ${EXTRA_DBG_FLAGS}") 70 | 71 | # rest of your project 72 | file(GLOB SOURCES *.c *.cpp *.h *.def) 73 | add_executable(${PROJECT_NAME} ${SOURCES}) 74 | 75 | pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/Pwm.pio) 76 | 77 | target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_LIST_DIR}") 78 | 79 | set(LIBS 80 | pico_multicore 81 | pico_stdlib 82 | hardware_adc 83 | hardware_flash 84 | hardware_pio 85 | ) 86 | if (DEFINED PICO_TM1637_PATH) 87 | set(LIBS PicoTM1637 ${LIBS}) 88 | endif () 89 | target_link_libraries(${PROJECT_NAME} ${LIBS}) 90 | 91 | 92 | message("") 93 | message("+---------------+") 94 | message("| Configuration |") 95 | message("+---------------+") 96 | pico_enable_stdio_usb(${PROJECT_NAME} 1) 97 | pico_enable_stdio_uart(${PROJECT_NAME} 0) 98 | if (DISABLE_USB_DBG STREQUAL "0") 99 | pico_enable_stdio_usb(${PROJECT_NAME} 0) 100 | pico_enable_stdio_uart(${PROJECT_NAME} 0) 101 | endif () 102 | message("DISABLE_USB_DBG = ${DISABLE_USB_DBG}") 103 | 104 | message("PICO_TM1637_PATH = ${PICO_TM1637_PATH}") 105 | message("LCD_SHIFT_LEFT = ${LCD_SHIFT_LEFT}") 106 | message("MHZ = ${MHZ}") 107 | 108 | # End of configuration 109 | message("") 110 | 111 | configure_file ( 112 | "${PROJECT_SOURCE_DIR}/config.h.in" 113 | "${PROJECT_BINARY_DIR}/config.h" 114 | ) 115 | 116 | # Create map/bin/hex/uf2 in addition to ELF. 117 | pico_add_extra_outputs(${PROJECT_NAME}) 118 | -------------------------------------------------------------------------------- /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/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 | DeletePreset, 36 | Manual, 37 | ConfigMaxMHz, 38 | ResetToDefaults, 39 | Uart, 40 | Boot, 41 | }; 42 | 43 | Mode CurrMode = Mode::Presets; 44 | 45 | static constexpr const char *getModeStr(Mode Mode) { 46 | switch (Mode) { 47 | case Mode::Presets: return "Presets"; 48 | case Mode::ConfigMHz: return "ConfigMHz"; 49 | case Mode::ConfigPeriod: return "ConfigPeriod"; 50 | case Mode::DeletePreset: return "DeletePreset"; 51 | case Mode::Manual: return "Manual"; 52 | case Mode::ConfigMaxMHz: return "ConfigMaxMHz"; 53 | case Mode::ResetToDefaults: return "ResetToDefaults"; 54 | case Mode::Uart: return "UART"; 55 | case Mode::Boot: return "Boot"; 56 | } 57 | return "BAD"; 58 | } 59 | /// Called by setMode(). Can be overriden by implementations. 60 | virtual void setModeInit(Mode NewMode) {} 61 | void setMode(Mode NewMode); 62 | void tryWritePresetsToFlash(); 63 | Mode getMode() const { return CurrMode; } 64 | 65 | public: 66 | static constexpr const char *MsgActualFreq = "FrE"; 67 | static constexpr const char *MsgPeriod = "PEr"; 68 | static constexpr const char *MsgMaxMHz = "CPUF"; 69 | static constexpr const char *MsgResetToDefaults = "RES"; 70 | static constexpr const char *MsgEscape = "ESC"; 71 | static constexpr const char *MsgConfirm = "done"; 72 | static constexpr const char *MsgPresets = "PRE"; 73 | static constexpr const char *MsgManual = "1-1"; 74 | static constexpr const char *MsgUartErr = "UErr"; 75 | static constexpr const char *MsgUartMode = "UArt"; 76 | static constexpr const char *MsgResetDetected = "boot"; 77 | static constexpr const char *MsgDeletePreset = "dEL"; 78 | static constexpr const char *MsgYes = "yes"; 79 | static constexpr const char *MsgNo = " no"; 80 | 81 | protected: 82 | std::vector> 84 | PresetBtns; 85 | 86 | Button 88 | ResetSense; 89 | std::optional> 90 | ResetTimeOpt; 91 | int ResetSavedKHz = 0; 92 | int ResetSavedPeriod = 0; 93 | Mode ResetSavedMode; 94 | 95 | void printTxtAndSleep(const char *Str); 96 | 97 | void updateDisplay(); 98 | 99 | static constexpr const size_t MaxUartStrSz = 10; 100 | std::string UartStr; 101 | void uartTick(Uart &Uart); 102 | void presetBtnsTick(); 103 | void resetSenseTick(); 104 | 105 | virtual void tick() = 0; 106 | 107 | public: 108 | CommonLogic(int SamplePeriod, Display &Disp, DutyCycle &DC, 109 | PresetsTable &Presets, FlashStorage &Flash, Pico &Pi) 110 | : SamplePeriod(SamplePeriod), Disp(Disp), DC(DC), Presets(Presets), 111 | Flash(Flash), ResetSense(ResetSenseGPIO, Pi, "ResetSense") { 112 | for (int BtnIdx = 0, E = PresetBtnGPIOs.size(); BtnIdx != E; ++BtnIdx) { 113 | std::string BtnStr = "PresetBtn." + std::to_string(BtnIdx); 114 | PresetBtns.emplace_back(PresetBtnGPIOs[BtnIdx], Pi, BtnStr.c_str()); 115 | } 116 | } 117 | void tickAll(Uart &Uart) { 118 | tick(); 119 | uartTick(Uart); 120 | presetBtnsTick(); 121 | resetSenseTick(); 122 | } 123 | }; 124 | 125 | #endif // __COMMONLOGIC_H__ 126 | -------------------------------------------------------------------------------- /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 | bool Deleted = false; ///> If the user has deleted this preset. 22 | Entry(int PresetKHz, int ActualKHz, int Period) 23 | : PresetKHz(PresetKHz), ActualKHz(ActualKHz), Period(Period) {} 24 | Entry(FlashStorage &Flash, int &Offset) { 25 | PresetKHz = Flash.read(Offset++); 26 | ActualKHz = Flash.read(Offset++); 27 | Period = Flash.read(Offset++); 28 | Deleted = Flash.read(Offset++); 29 | } 30 | void dump() const { 31 | std::cout << PresetKHz << ", " << ActualKHz << ", " << Period << ", " 32 | << Deleted << "\n"; 33 | } 34 | /// Helper for writing to Flash. 35 | void appendToVec(std::vector &Vec) const { 36 | Vec.push_back(PresetKHz); 37 | Vec.push_back(ActualKHz); 38 | Vec.push_back(Period); 39 | Vec.push_back(Deleted); 40 | } 41 | unsigned checksum() const { 42 | return (unsigned)PresetKHz ^ (unsigned)ActualKHz ^ (unsigned)Period ^ 43 | (unsigned)Deleted; 44 | } 45 | }; 46 | 47 | static constexpr const int DefaultPeriod = 8; // ~50us 48 | 49 | const std::vector ImmutableTable = { 50 | Entry(4770, 4770, DefaultPeriod), 51 | Entry(8000, 8000, DefaultPeriod), 52 | Entry(10000, 10000, DefaultPeriod), 53 | Entry(25000, 25000, DefaultPeriod), 54 | Entry(33000, 33000, DefaultPeriod), 55 | Entry(66000, 66000, DefaultPeriod), 56 | Entry(133000, 133000, DefaultPeriod), 57 | Entry(450000, 450000, DefaultPeriod), 58 | Entry(733000, 733000, DefaultPeriod)}; 59 | 60 | std::vector Table; 61 | 62 | int Idx; 63 | 64 | int maxKHz() const { return Table.back().PresetKHz; } 65 | int checksum(int MHz, const std::vector &Entries) const; 66 | int getMaxIdx() const { return (int)Table.size() - 1; } 67 | 68 | public: 69 | PresetsTable() { updateMaxMHz(MaxMHz); } 70 | void dump() const; 71 | int getMaxMHz() const { return MaxMHz; } 72 | int getMaxKHz() const { return MaxMHz * 1000; } 73 | int getMinKHz() const { return Table[0].PresetKHz; } 74 | int getKHz() const { return Table[Idx].PresetKHz; } 75 | bool atMaxKHz() const { return getMaxKHz() == getKHz(); } 76 | int getActualKHz() const { return Table[Idx].ActualKHz; } 77 | void incrActualKHz(); 78 | void decrActualKHz(); 79 | int getPeriod() const { return Table[Idx].Period; } 80 | void incrPeriod(); 81 | void toggleDeleted() { Table[Idx].Deleted = !Table[Idx].Deleted; } 82 | bool isDeleted() const { return Table[Idx].Deleted; } 83 | void decrPeriod(); 84 | void prev() { 85 | int FoundIdx = Idx; 86 | do { 87 | --FoundIdx; 88 | } while (FoundIdx >= 0 && Table[FoundIdx].Deleted); 89 | if (FoundIdx >= 0) // if legal 90 | Idx = FoundIdx; 91 | } 92 | void next() { 93 | int FoundIdx = Idx; 94 | do { 95 | ++FoundIdx; 96 | } while (FoundIdx < getMaxIdx() && Table[FoundIdx].Deleted); 97 | if (FoundIdx <= getMaxIdx()) // if legal 98 | Idx = FoundIdx; 99 | } 100 | void setIdx(int NewIdx) { Idx = std::clamp(NewIdx, 0, getMaxIdx()); } 101 | /// Returns the minimum Idx that is not deleted. 102 | int minNonDeleted() const; 103 | void cyclePrev(); 104 | void cycleNext(); 105 | void cycleMax(); 106 | void updateMaxMHz(int NewMaxMHz); 107 | void incrMaxMHz(int Step = 1, bool Wrap = false, int WrapLo = MHzLimitLo, 108 | int WrapHi = MHzLimitHi); 109 | void decrMaxMHz(int Step = 1); 110 | void writeToFlash(FlashStorage &Flash) const; 111 | void readFromFlash(FlashStorage &Flash); 112 | void resetToDefaults(FlashStorage &Flash); 113 | }; 114 | 115 | #endif // __PRESETS_H__ 116 | -------------------------------------------------------------------------------- /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/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 | // Factory defaults press button. 103 | static constexpr const int ResetBtnPressMs = 2000; 104 | static constexpr const int ResetBtnCheckMs = 50; 105 | static constexpr const int ResetDefaultsFlashOnMs = 100; 106 | static constexpr const uint32_t ResetDefaultFlashCnt = 20; 107 | 108 | #endif // __CONFIGOPTS_H__ 109 | -------------------------------------------------------------------------------- /firmware/src/RotaryEncoder.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "ConfigOpts.h" 7 | #include "RotaryEncoder.h" 8 | 9 | class RotaryEncoder; 10 | 11 | class RotaryEncoder *RE; 12 | 13 | /// Helps skip multicore_lockout_start when core 1 is not running. 14 | bool Core1Enabled = false; 15 | 16 | static void core1_tick() { 17 | Core1Enabled = true; 18 | // We need to be able to stop the core when writing to Flash. 19 | multicore_lockout_victim_init(); 20 | 21 | while (true) { 22 | RE->tick(); 23 | } 24 | } 25 | 26 | RotaryEncoder::RotaryEncoder(int ClkPin, int DtPin, int SwPin, Pico &Pico, 27 | bool ReverseDirection) 28 | : ClkPin(ClkPin), DtPin(DtPin), SwPin(SwPin), PICO(Pico), 29 | ReverseDirection(ReverseDirection) { 30 | PICO.initGPIO(ClkPin, GPIO_IN, Pico::Pull::Up, "Rotary.Clk"); 31 | PICO.initGPIO(DtPin, GPIO_IN, Pico::Pull::Up, "Rotary.Dt"); 32 | PICO.initGPIO(SwPin, GPIO_IN, Pico::Pull::Up, "Rotary.Sw"); 33 | 34 | critical_section_init(&Lock); 35 | 36 | // Use the second core for sampling the GPIOs. 37 | RE = this; 38 | multicore_launch_core1(core1_tick); 39 | } 40 | 41 | void RotaryEncoder::registerState(RotaryEncoder::State TheState) { 42 | if (TheState != State::None) { 43 | critical_section_enter_blocking(&Lock); 44 | static constexpr const uint LIST_SZ_LIMIT = 16; 45 | if (StateList.size() > LIST_SZ_LIMIT) { 46 | StateList.pop_front(); 47 | std::cout << "WARNING: List Size Limit!!!\n"; 48 | } 49 | StateList.push_back(TheState); 50 | DBG_PRINT(std::cout << "registerState: " << stateToStr(TheState) << "\n";) 51 | critical_section_exit(&Lock); 52 | } 53 | } 54 | 55 | void RotaryEncoder::tick() { 56 | PICO.readGPIO(); 57 | BuffA.append(PICO.getGPIO(ClkPin)); 58 | BuffB.append(PICO.getGPIO(DtPin)); 59 | BuffC.append(PICO.getGPIO(SwPin)); 60 | 61 | bool A = BuffA.getMean(); 62 | bool B = BuffB.getMean(); 63 | bool Sw = BuffC.getMean(); 64 | 65 | // bool A = PICO.getGPIO(ClkPin); 66 | // bool B = PICO.getGPIO(DtPin); 67 | // bool Sw = PICO.getGPIO(SwPin); 68 | 69 | State TheState = State::None; 70 | if (!Sw && LastSw) { 71 | PressStart = std::chrono::high_resolution_clock::now(); 72 | TheState = State::SwPress; 73 | } else if (Sw && !LastSw) { 74 | if (!IgnoreRelease) 75 | TheState = State::SwRelease; 76 | IgnoreRelease = false; 77 | } else if (!Sw && !LastSw) { 78 | auto Now = std::chrono::high_resolution_clock::now(); 79 | std::chrono::duration Diff = Now - PressStart; 80 | int DiffMillis = Diff.count() * 1024; 81 | if (DiffMillis > RotaryLongPressMillis) { 82 | IgnoreRelease = true; 83 | TheState = State::SwLongPress; 84 | PressStart = Now; 85 | } 86 | } 87 | // Push button has higher priority than rotary. 88 | if (TheState == State::None) { 89 | if (A && !B) { 90 | if (IntState.back() != InternalState::A1B0) 91 | IntState.append(InternalState::A1B0); 92 | } else if (!A && B) { 93 | if (IntState.back() != InternalState::A0B1) 94 | IntState.append(InternalState::A0B1); 95 | } else if (A && B) { 96 | if (IntState.back() != InternalState::A1B1) 97 | IntState.append(InternalState::A1B1); 98 | } else if (!A && !B) { 99 | if (IntState.back() != InternalState::A0B0) 100 | IntState.append(InternalState::A0B0); 101 | } 102 | } 103 | 104 | LastA = A; 105 | LastB = B; 106 | LastSw = Sw; 107 | 108 | if (IntState[0] == InternalState::A0B1 && 109 | IntState[1] == InternalState::A0B0 && 110 | IntState[2] == InternalState::A1B0 && 111 | IntState[3] == InternalState::A1B1) { 112 | IntState.clear(); 113 | TheState = ReverseDirection ? State::Right : State::Left; 114 | } 115 | else if (IntState[0] == InternalState::A1B0 && 116 | IntState[1] == InternalState::A0B0 && 117 | IntState[2] == InternalState::A0B1 && 118 | IntState[3] == InternalState::A1B1) { 119 | IntState.clear(); 120 | TheState = ReverseDirection ? State::Left : State::Right; 121 | } 122 | registerState(TheState); 123 | return; 124 | } 125 | 126 | 127 | RotaryEncoder::State RotaryEncoder::get() { 128 | State RetState = State::None; 129 | critical_section_enter_blocking(&Lock); 130 | if (!StateList.empty()) { 131 | RetState = StateList.back(); 132 | StateList.pop_back(); 133 | } 134 | critical_section_exit(&Lock); 135 | return RetState; 136 | } 137 | -------------------------------------------------------------------------------- /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 22 7 50 -1 -1 0.000 0 0 1 0 4229.605 2911.184 4050 3075 4350 2700 4275 3150 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 3078.409 2931.818 3000 3150 3075 2700 3300 3000 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 3158.508 5728.046 1125 6375 1275 4725 3000 3600 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 2664.940 5371.305 975 6450 825 4575 2850 3375 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 3487.500 3937.500 3825 3675 3900 3825 3825 4200 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 4301.786 6171.429 4350 6000 4425 6300 4125 6150 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 4375.000 5675.000 4200 5700 4500 5550 4350 5850 23 | 1 1 2.00 60.00 120.00 24 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3187.500 5212.500 3675 4875 3750 5025 3675 5550 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 3762.076 4433.263 3975 5625 4950 4200 4350 3375 27 | 1 1 2.00 60.00 120.00 28 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3112.500 6562.500 3600 6225 3675 6375 3600 6900 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 4225.000 6875.000 4050 6900 4350 6750 4200 7050 31 | 1 1 2.00 60.00 120.00 32 | 5 1 0 2 22 7 50 -1 -1 0.000 0 0 1 0 4226.786 7521.429 4275 7350 4350 7650 4050 7500 33 | 1 1 2.00 60.00 120.00 34 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 2224.039 7243.269 2850 7050 2550 6675 2100 6600 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 4040.625 11512.500 3000 6075 2400 6225 1800 6450 37 | 1 1 2.00 60.00 120.00 38 | 5 1 0 2 9 7 50 -1 -1 0.000 0 1 1 0 6450.000 8925.000 3000 4725 2250 5475 1650 6375 39 | 1 1 2.00 60.00 120.00 40 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4371.429 4323.214 4200 4275 4500 4200 4350 4500 41 | 1 1 2.00 60.00 120.00 42 | 5 1 0 2 22 7 50 -1 -1 0.000 0 0 1 0 4301.786 4821.429 4350 4650 4425 4950 4125 4800 43 | 1 1 2.00 60.00 120.00 44 | 6 -75 1275 2475 2250 45 | 4 0 12 50 -1 18 16 0.0000 4 210 1140 -75 1500 P = Push\001 46 | 4 0 9 50 -1 18 16 0.0000 4 270 2070 -75 2190 LP = Long Push\001 47 | 4 0 22 50 -1 18 16 0.0000 4 210 2505 -75 1845 MP = Medium Push\001 48 | -6 49 | 6 2850 4125 4425 4950 50 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3637 4547 750 338 2887 4210 4387 4885 51 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 4500 Configure\001 52 | 4 0 0 50 -1 18 14 0.0000 4 180 495 3450 4800 MHz\001 53 | -6 54 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 1350 6712 750 338 600 6375 2100 7050 55 | 1 2 0 4 0 7 50 -1 -1 0.000 1 0.0000 3600 3337 750 338 2850 3337 4350 3337 56 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3602 5898 750 338 2852 5561 4352 6236 57 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3554 7200 750 338 2804 6863 4304 7538 58 | 2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 59 | -150 1200 2550 1200 2550 2325 -150 2325 -150 1200 60 | 4 0 0 50 -1 18 16 0.0000 4 210 1005 3150 3450 Presets\001 61 | 4 0 22 50 -1 18 16 0.0000 4 210 420 3600 2925 MP\001 62 | 4 0 12 50 -1 18 16 0.0000 4 210 180 2625 3075 P\001 63 | 4 0 12 50 -1 18 16 0.0000 4 210 180 1125 6300 P\001 64 | 4 0 9 50 -1 18 16 0.0000 4 210 345 450 6450 LP\001 65 | 4 0 4 50 -1 18 16 0.0000 4 210 1110 -75 4200 Defaults\001 66 | 4 0 4 50 -1 18 16 0.0000 4 210 1260 -75 3900 Resets to\001 67 | 4 0 4 50 -1 18 16 0.0000 4 210 1530 1950 2625 Next Preset\001 68 | 4 0 4 50 -1 18 16 0.0000 4 210 1185 4050 2625 Max MHz\001 69 | 4 0 0 50 -1 18 14 0.0000 4 180 855 975 6825 Reset?\001 70 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3975 3900 LP\001 71 | 4 0 22 50 -1 18 16 0.0000 4 210 420 4500 6300 MP\001 72 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4575 5700 P\001 73 | 4 0 4 50 -1 18 12 0.0000 4 165 180 4200 6525 -1\001 74 | 4 0 4 50 -1 18 12 0.0000 4 165 255 4575 5850 +1\001 75 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 5850 Configure\001 76 | 4 0 0 50 -1 18 14 0.0000 4 180 585 3375 6150 PWM\001 77 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3825 5475 LP\001 78 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3750 6600 LP\001 79 | 4 0 0 50 -1 18 14 0.0000 4 180 780 3225 7200 Delete\001 80 | 4 0 0 50 -1 18 14 0.0000 4 180 915 3150 7425 Presets\001 81 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4425 6900 P\001 82 | 4 0 22 50 -1 18 16 0.0000 4 210 420 4425 7650 MP\001 83 | 4 0 4 50 -1 18 12 0.0000 4 210 690 4500 7200 yes/no\001 84 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2475 6000 LP\001 85 | 4 0 0 50 -1 18 16 0.0000 4 210 1185 1800 6450 Max MHz\001 86 | 4 0 0 50 -1 18 16 0.0000 4 210 495 2025 6150 If at\001 87 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2775 6900 LP\001 88 | 4 0 22 50 -1 18 16 0.0000 4 210 420 4500 4950 MP\001 89 | 4 0 12 50 -1 18 16 0.0000 4 210 180 4575 4350 P\001 90 | 4 0 4 50 -1 18 12 0.0000 4 165 690 4125 4125 +1MHz\001 91 | 4 0 4 50 -1 18 12 0.0000 4 165 615 4050 5175 -1MHz\001 92 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2850 5175 LP\001 93 | -------------------------------------------------------------------------------- /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 10 7 50 -1 -1 0.000 0 0 1 0 3150.000 5850.000 3750 5400 3900 5850 3750 6300 13 | 1 1 2.00 60.00 120.00 14 | 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 15 | 1 1 2.00 60.00 120.00 16 | 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 17 | 1 1 2.00 60.00 120.00 18 | 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 19 | 1 1 2.00 60.00 120.00 20 | 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 21 | 1 1 2.00 60.00 120.00 22 | 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 23 | 1 1 2.00 60.00 120.00 24 | 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 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 4370.098 5158.088 3150 6300 2700 5100 3600 3675 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 2041.995 4590.887 525 4725 1125 3375 2850 3300 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 2258.852 4804.023 825 4650 1200 3825 2925 3525 31 | 1 1 2.00 60.00 120.00 32 | 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 33 | 1 1 2.00 60.00 120.00 34 | 5 1 0 2 10 7 50 -1 -1 0.000 0 0 1 0 3075.000 7425.000 3675 6975 3825 7425 3675 7875 35 | 1 1 2.00 60.00 120.00 36 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4335.856 5318.901 2850 6450 2475 5475 3450 3675 37 | 1 1 2.00 60.00 120.00 38 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 4899.457 5910.326 3075 8025 2175 6525 3225 3675 39 | 1 1 2.00 60.00 120.00 40 | 5 1 0 2 12 7 50 -1 -1 0.000 0 0 1 0 4453.565 5995.803 2925 8175 1950 6900 3150 3675 41 | 1 1 2.00 60.00 120.00 42 | 5 1 0 2 9 7 50 -1 -1 0.000 0 0 1 0 3175.000 4200.000 3675 3675 3900 4200 3675 4725 43 | 1 1 2.00 60.00 120.00 44 | 6 5550 1650 7200 2400 45 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 6375 2025 750 338 5625 1688 7125 2363 46 | 4 0 0 50 -1 18 14 0.0000 4 180 855 5925 2100 Reset?\001 47 | -6 48 | 6 225 4575 1875 5400 49 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 1036 4985 750 338 286 4648 1786 5323 50 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 525 4950 Configure\001 51 | 4 0 0 50 -1 18 14 0.0000 4 240 1140 525 5175 CPU Freq\001 52 | -6 53 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3600 5025 750 338 2850 4688 4350 5363 54 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 6450 3375 750 338 5700 3038 7200 3713 55 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3602 6601 750 338 2852 6264 4352 6939 56 | 1 2 0 4 0 7 50 -1 -1 0.000 1 0.0000 3600 3337 750 338 2850 3337 4350 3337 57 | 1 2 0 2 0 7 50 -1 -1 0.000 1 0.0000 3664 8263 750 338 2914 7926 4414 8601 58 | 2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 59 | 825 375 5175 375 5175 1575 825 1575 825 375 60 | 4 0 0 50 -1 18 16 0.0000 4 210 1005 3150 3450 Presets\001 61 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3150 6225 LP\001 62 | 4 0 0 50 -1 18 14 0.0000 4 180 825 6075 3450 Manual\001 63 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3075 6600 Configure\001 64 | 4 0 0 50 -1 18 14 0.0000 4 180 585 3300 6900 PWM\001 65 | 4 0 0 50 -1 18 14 0.0000 4 240 1170 3000 5025 Configure\001 66 | 4 0 0 50 -1 18 14 0.0000 4 180 495 3375 5325 MHz\001 67 | 4 0 12 50 -1 18 16 0.0000 4 210 375 4875 3975 BP\001 68 | 4 0 10 50 -1 18 16 0.0000 4 210 345 3900 5700 LP\001 69 | 4 0 12 50 -1 18 16 0.0000 4 210 375 2550 6600 BP\001 70 | 4 0 9 50 -1 18 16 0.0000 4 270 4095 975 750 LP = Long Push (Left or Right) \001 71 | 4 0 12 50 -1 18 16 0.0000 4 210 2040 975 1125 BP = Both Push\001 72 | 4 0 22 50 -1 18 16 0.0000 4 270 2970 975 1500 BLP = Both Long Push\001 73 | 4 0 12 50 -1 18 16 0.0000 4 210 375 4800 3525 BP\001 74 | 4 0 22 50 -1 18 16 0.0000 4 210 540 3075 2775 BLP\001 75 | 4 0 22 50 -1 18 16 0.0000 4 210 540 5025 2100 BLP\001 76 | 4 0 12 50 -1 18 16 0.0000 4 210 375 5475 2400 BP\001 77 | 4 0 9 50 -1 18 16 0.0000 4 210 345 5925 2625 LP\001 78 | 4 0 4 50 -1 18 16 0.0000 4 210 1110 4200 2550 Defaults\001 79 | 4 0 4 50 -1 18 16 0.0000 4 210 1260 4200 2325 Resets to\001 80 | 4 0 4 50 -1 18 16 0.0000 4 210 630 2550 5700 Save\001 81 | 4 0 4 50 -1 18 16 0.0000 4 210 915 1500 5925 Cancel\001 82 | 4 0 4 50 -1 18 16 0.0000 4 210 915 4500 2925 Cancel\001 83 | 4 0 12 50 -1 18 16 0.0000 4 210 375 4200 4800 BP\001 84 | 4 0 4 50 -1 18 16 0.0000 4 210 1065 3900 4500 Cancels\001 85 | 4 0 4 50 -1 18 16 0.0000 4 210 915 675 3375 Cancel\001 86 | 4 0 12 50 -1 18 16 0.0000 4 210 375 75 4650 BP\001 87 | 4 0 4 50 -1 18 16 0.0000 4 210 630 1200 3750 Save\001 88 | 4 0 9 50 -1 18 16 0.0000 4 210 345 900 4575 LP\001 89 | 4 0 0 50 -1 18 14 0.0000 4 180 1560 1275 3975 If at Max MHz\001 90 | 4 0 0 50 -1 18 14 0.0000 4 180 780 3300 8250 Delete\001 91 | 4 0 0 50 -1 18 14 0.0000 4 180 915 3225 8550 Presets\001 92 | 4 0 10 50 -1 18 16 0.0000 4 210 345 3900 7350 LP\001 93 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3075 7875 LP\001 94 | 4 0 12 50 -1 18 16 0.0000 4 210 375 2400 8325 BP\001 95 | 4 0 9 50 -1 18 16 0.0000 4 210 345 2550 3825 LP\001 96 | 4 0 9 50 -1 18 16 0.0000 4 210 345 3900 3975 LP\001 97 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | int PresetsTable::minNonDeleted() const { 36 | for (int Idx = 0, E = getMaxIdx(); Idx <= E; ++Idx) { 37 | if (!Table[Idx].Deleted) 38 | return Idx; 39 | } 40 | std::cerr << "minNonDeleted() beyond max!\n"; 41 | return 0; 42 | } 43 | 44 | void PresetsTable::cyclePrev() { 45 | if (Idx == minNonDeleted()) 46 | Idx = getMaxIdx(); 47 | else 48 | prev(); 49 | } 50 | 51 | void PresetsTable::cycleNext() { 52 | if (Idx == getMaxIdx()) 53 | Idx = 0; 54 | else 55 | next(); 56 | } 57 | 58 | void PresetsTable::cycleMax() { Idx = getMaxIdx(); } 59 | 60 | void PresetsTable::updateMaxMHz(int NewMaxMHz) { 61 | MaxMHz = NewMaxMHz; 62 | int MaxKHz = MaxMHz * 1000; 63 | // If the last entry is not in the immutable table, then it is a custom 64 | // MaxMHz entry. Drop it as we are going to replace it with a new one. 65 | if (!Table.empty()) { 66 | int TableIdx = Table.size() - 1; 67 | if (Table.size() > ImmutableTable.size() || 68 | Table.back().PresetKHz != ImmutableTable[TableIdx].PresetKHz) 69 | Table.pop_back(); 70 | } 71 | // Drop Table entries with KHz > MaxKHz 72 | while (!Table.empty() && Table.back().PresetKHz > MaxKHz) 73 | Table.pop_back(); 74 | // Copy any missing entries from ImmutableMHzTable to MHzTable. 75 | for (const auto &Entry : ImmutableTable) { 76 | if ((Table.empty() || Entry.PresetKHz > Table.back().PresetKHz) && 77 | Entry.PresetKHz <= MaxKHz) 78 | Table.push_back(Entry); 79 | } 80 | // Add a custom entry with the new Max value if required. 81 | if (Table.empty() || Table.back().PresetKHz != MaxKHz) 82 | Table.emplace_back(MaxKHz, MaxKHz, DefaultPeriod); 83 | // Adjust the Idx to point to the last entry. 84 | Idx = (int)Table.size() - 1; 85 | // dump(); 86 | } 87 | 88 | void PresetsTable::incrMaxMHz(int Step, bool Wrap, int WrapLo, int WrapHi) { 89 | int NewMax; 90 | if (Wrap && getMaxMHz() == WrapHi) 91 | NewMax = WrapLo; 92 | else 93 | NewMax = std::min(WrapHi, getMaxMHz() + Step); 94 | updateMaxMHz(NewMax); 95 | } 96 | 97 | void PresetsTable::decrMaxMHz(int Step) { 98 | int NewMax = std::max(MHzLimitLo, getMaxMHz() - Step); 99 | updateMaxMHz(NewMax); 100 | } 101 | 102 | void PresetsTable::writeToFlash(FlashStorage &Flash) const { 103 | std::vector FlashValues; 104 | // First write MaxMHz. 105 | FlashValues.push_back(MaxMHz); 106 | // Next the size of Table. 107 | FlashValues.push_back((int)Table.size()); 108 | // Next the entries one by one. 109 | for (const auto &Entry : Table) 110 | Entry.appendToVec(FlashValues); 111 | // Finally add a checksum. 112 | FlashValues.push_back(checksum(MaxMHz, Table)); 113 | 114 | DBG_PRINT(std::cout << "Presets: Writing to Flash\n";) 115 | Flash.write(FlashValues); 116 | DBG_PRINT(std::cout << "Presets: Done writing to Flash\n";) 117 | } 118 | 119 | int PresetsTable::checksum(int MHz, const std::vector &Entries) const { 120 | unsigned Checksum = 0; 121 | Checksum ^= MHz; 122 | for (const auto &E : Entries) 123 | Checksum ^= E.checksum(); 124 | return (int)Checksum; 125 | } 126 | 127 | void PresetsTable::readFromFlash(FlashStorage &Flash) { 128 | if (!Flash.valid()) 129 | return; 130 | DBG_PRINT(std::cout << "Presets: Reading from Flash\n";) 131 | int FlashOffset = 0; 132 | // 1. First read MaxMHz. 133 | int FlashMaxMHz = Flash.read(FlashOffset++); 134 | if (FlashMaxMHz > MHzLimitHi || FlashMaxMHz < MHzLimitLo) { 135 | DBG_PRINT(std::cout << "Bad FlashMaxMHz=" << FlashMaxMHz << "\n";) 136 | return; 137 | } 138 | updateMaxMHz(FlashMaxMHz); 139 | // 2. Next read the size of Table. 140 | unsigned TableSz = Flash.read(FlashOffset++); 141 | if (TableSz > ImmutableTable.size() + 1) { 142 | DBG_PRINT(std::cout << "Bad TableSz=" << TableSz << "\n";) 143 | return; 144 | } 145 | // 3. Next read the entries one by one. 146 | std::vector FlashTable; 147 | for (unsigned EntryIdx = 0; EntryIdx != TableSz; ++EntryIdx) 148 | FlashTable.emplace_back(Flash, FlashOffset); 149 | // 4. Check checksum. 150 | int ReadChecksum = Flash.read(FlashOffset++); 151 | int ExpectedChecksum = checksum(FlashMaxMHz, FlashTable); 152 | if (ReadChecksum != ExpectedChecksum) { 153 | DBG_PRINT(std::cout << "Checksum error: Got:" << ReadChecksum 154 | << " Expected:" << ExpectedChecksum << "\n";) 155 | return; 156 | } 157 | Table = std::move(FlashTable); 158 | DBG_PRINT(std::cout << "Presets: Done reading from Flash\n";) 159 | DBG_PRINT(dump();) 160 | } 161 | 162 | void PresetsTable::resetToDefaults(FlashStorage &Flash) { 163 | DBG_PRINT(std::cout << "Presets: Reset to defaults...\n";) 164 | Table = ImmutableTable; 165 | updateMaxMHz(DefaultMaxMHz); 166 | writeToFlash(Flash); 167 | } 168 | -------------------------------------------------------------------------------- /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 | CurrMode = NewMode; 10 | DBG_PRINT(std::cout << getModeStr(CurrMode) << "\n";) 11 | } 12 | 13 | void CommonLogic::tryWritePresetsToFlash() { 14 | DBG_PRINT(std::cout << "WriteToFlash:\n";) 15 | DBG_PRINT(std::cout << " Before After\n";) 16 | DBG_PRINT(std::cout << "ActualKHz: " << Presets.getActualKHz() << "\n";) 17 | DBG_PRINT(std::cout << "Period: " << Presets.getPeriod() << "\n";) 18 | DBG_PRINT(std::cout << "MaxMHz: " << Presets.getMaxMHz() << "\n";) 19 | DBG_PRINT(std::cout << "Deleted: " << Presets.isDeleted() << "\n";) 20 | Presets.writeToFlash(Flash); 21 | } 22 | 23 | void CommonLogic::printTxtAndSleep(const char *Str) { 24 | Disp.printTxt(Str); 25 | sleep_ms(PrintSleep); 26 | } 27 | 28 | void CommonLogic::updateDisplay() { 29 | #ifdef PICO_TM1637 30 | switch (getMode()) { 31 | case Mode::Presets: 32 | Disp.printKHz(Presets.getKHz()); 33 | break; 34 | case Mode::ConfigMHz: 35 | Disp.printKHz(Presets.getActualKHz()); 36 | break; 37 | case Mode::ConfigPeriod: 38 | Disp.printRaw(Presets.getPeriod()); 39 | break; 40 | case Mode::DeletePreset: 41 | Disp.printTxt(Presets.isDeleted() ? MsgYes : MsgNo); 42 | break; 43 | case Mode::Manual: 44 | case Mode::Uart: 45 | Disp.printKHz(DC.getKHz()); 46 | break; 47 | case Mode::ConfigMaxMHz: 48 | Disp.printMHz(Presets.getMaxMHz()); 49 | break; 50 | case Mode::ResetToDefaults: 51 | Disp.printTxt(MsgResetToDefaults); 52 | break; 53 | case Mode::Boot: { 54 | auto Now = std::chrono::system_clock::now(); 55 | auto Seconds = 56 | std::chrono::duration_cast(Now - *ResetTimeOpt); 57 | if (Seconds.count() == 0) { 58 | Disp.printTxt(MsgResetDetected); 59 | } else { 60 | int Remaining = ResetMaxSpeedDuration - Seconds.count(); 61 | Disp.printRaw(Remaining); 62 | } 63 | break; 64 | } 65 | } 66 | #endif 67 | } 68 | 69 | void CommonLogic::uartTick(Uart &Uart) { 70 | auto Bytes = Uart.readNonBlocking(); 71 | auto NumBytes = Bytes.size(); 72 | if (NumBytes == 0) 73 | return; 74 | if (NumBytes >= 1) { 75 | for (char Byte : Bytes) { 76 | // Skip some illegal chars. 77 | if (Byte == '\n') 78 | continue; 79 | UartStr += Byte; 80 | } 81 | } 82 | DBG_PRINT(std::cout << "UartStr: " << UartStr << "\n";) 83 | // Reject messages that are too long! 84 | if (UartStr.size() > MaxUartStrSz) { 85 | DBG_PRINT(std::cout << "Uart: Too many chars:'" << UartStr << "'\n";) 86 | printTxtAndSleep(MsgUartErr); 87 | UartStr.clear(); 88 | return; 89 | } 90 | // Look for the EOM character. 91 | if (UartStr.back() != UartEOM) 92 | return; 93 | float MHz = 0; 94 | int Period = 0; 95 | sscanf(UartStr.c_str(), "F%fP%d\r", &MHz, &Period); 96 | int KHz = MHz * 1000; 97 | DBG_PRINT(std::cout << "UART: " 98 | << "MHz=" << MHz << " KHz=" << KHz << " Period=" << Period 99 | << "\n";) 100 | if (KHz == 0) { 101 | setMode(Mode::Uart); 102 | printTxtAndSleep(MsgUartMode); 103 | DC.setMHzToMax(); 104 | } else if ((MHz >= MHzLimitLo && MHz <= MHzLimitHi) && 105 | (Period >= PeriodLimitLo && Period <= PeriodLimitHi)) { 106 | setMode(Mode::Uart); 107 | DC.setKHz(KHz); 108 | DC.setPeriod(Period); 109 | printTxtAndSleep(MsgUartMode); 110 | // Uart.writeBlockingStr("OK\r\n"); 111 | } else { 112 | printTxtAndSleep(MsgUartErr); 113 | } 114 | UartStr.clear(); 115 | } 116 | 117 | void CommonLogic::resetSenseTick() { 118 | if (ResetTimeOpt) { 119 | auto Now = std::chrono::system_clock::now(); 120 | auto Seconds = 121 | std::chrono::duration_cast(Now - *ResetTimeOpt); 122 | if (Seconds.count() > ResetMaxSpeedDuration) { 123 | DBG_PRINT(std::cout << "End of Reset. SvKHz=" << ResetSavedKHz 124 | << " SvPeriod=" << ResetSavedPeriod 125 | << " SvMode=" << getModeStr(ResetSavedMode) << "\n";) 126 | ResetTimeOpt = std::nullopt; 127 | DC.setKHz(ResetSavedKHz); 128 | DC.setPeriodRaw(ResetSavedPeriod); 129 | setMode(ResetSavedMode); 130 | } 131 | } 132 | // Button not pressed, skipping. 133 | if (ResetSense.get() != ButtonState::Pressed) 134 | return; 135 | // Nothing to do if already at max freq. 136 | if (DC.getKHz() == Presets.getMaxKHz() && !ResetTimeOpt) 137 | return; 138 | // If already in booting mode don't save freq/period/mode. 139 | if (getMode() != Mode::Boot) { 140 | ResetSavedKHz = DC.getKHz(); 141 | ResetSavedPeriod = DC.getPeriod(); 142 | ResetSavedMode = getMode(); 143 | } 144 | DBG_PRINT(std::cout << "Reset detected! SvKHz=" << ResetSavedKHz 145 | << " SvPeriod=" << ResetSavedPeriod 146 | << " SvMode=" << getModeStr(ResetSavedMode) << "\n";) 147 | DC.setMHzToMax(); 148 | ResetTimeOpt = std::chrono::system_clock::now(); 149 | setMode(Mode::Boot); 150 | } 151 | 152 | void CommonLogic::presetBtnsTick() { 153 | auto HandleBtn = [this](auto &Btn, int BtnIdx, bool IsLast) { 154 | switch (Btn.get()) { 155 | case ButtonState::Release: 156 | case ButtonState::MedRelease: 157 | case ButtonState::LongPress: { 158 | DBG_PRINT(std::cout << "PresetBtn " << BtnIdx << " IsLast=" << IsLast 159 | << "\n";) 160 | if (!IsLast) 161 | Presets.setIdx(BtnIdx); 162 | else 163 | // The last preset always sets max frequency. 164 | Presets.cycleMax(); 165 | int NewKHz = Presets.getActualKHz(); 166 | int NewPeriod = Presets.getPeriod(); 167 | DBG_PRINT(std::cout << "NewKHz=" << NewKHz << " NewPeriod=" << NewPeriod 168 | << "\n";) 169 | DC.setKHz(NewKHz); 170 | DC.setPeriod(NewPeriod); 171 | DBG_PRINT(DC.dump();) 172 | break; 173 | } 174 | default: 175 | break; 176 | } 177 | }; 178 | 179 | switch (CurrMode) { 180 | case Mode::Presets: 181 | case Mode::Manual:{ 182 | for (int BtnIdx = 0, E = (int)PresetBtns.size(); BtnIdx != E; ++BtnIdx) 183 | HandleBtn(PresetBtns[BtnIdx], BtnIdx, /*IsLast=*/BtnIdx == E - 1); 184 | break; 185 | } 186 | default: 187 | break; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /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 | setMode(Mode::ConfigPeriod); 28 | Disp.setFlash(false); 29 | printTxtAndSleep(MsgPeriod); 30 | Disp.setFlash(true); 31 | break; 32 | case Mode::ConfigPeriod: 33 | setMode(Mode::DeletePreset); 34 | Disp.setFlash(false); 35 | printTxtAndSleep(MsgDeletePreset); 36 | Disp.setFlash(true); 37 | break; 38 | case Mode::DeletePreset: 39 | tryWritePresetsToFlash(); 40 | setMode(Mode::Presets); 41 | Disp.setFlash(false); 42 | printTxtAndSleep(MsgConfirm); 43 | if (Presets.isDeleted()) 44 | Presets.next(); 45 | break; 46 | case Mode::ConfigMaxMHz: 47 | tryWritePresetsToFlash(); 48 | setMode(Mode::Presets); 49 | Disp.setFlash(false); 50 | printTxtAndSleep(MsgConfirm); 51 | break; 52 | case Mode::ResetToDefaults: 53 | escapeReset(); 54 | break; 55 | case Mode::Uart: 56 | setMode(Mode::Presets); 57 | printTxtAndSleep(MsgPresets); 58 | break; 59 | case Mode::Boot: 60 | break; 61 | } 62 | } 63 | 64 | void RotaryLogic::onSwLongPress() { 65 | switch (getMode()) { 66 | case Mode::Manual: 67 | if (Presets.atMaxKHz()) { 68 | setMode(Mode::ConfigMaxMHz); 69 | printTxtAndSleep(MsgMaxMHz); 70 | } 71 | break; 72 | case Mode::Presets: 73 | if (Presets.atMaxKHz()) { 74 | setMode(Mode::ConfigMaxMHz); 75 | printTxtAndSleep(MsgMaxMHz); 76 | } else { 77 | setMode(Mode::ConfigMHz); 78 | printTxtAndSleep(MsgActualFreq); 79 | } 80 | Disp.setFlash(true); 81 | break; 82 | case Mode::ConfigMHz: 83 | case Mode::ConfigPeriod: 84 | case Mode::DeletePreset: 85 | setMode(Mode::Presets); 86 | printTxtAndSleep(MsgEscape); 87 | Disp.setFlash(false); 88 | break; 89 | case Mode::ConfigMaxMHz: 90 | setMode(Mode::ResetToDefaults); 91 | printTxtAndSleep(MsgResetToDefaults); 92 | Disp.setFlash(true); 93 | break; 94 | case Mode::ResetToDefaults: 95 | setMode(Mode::Presets); 96 | Presets.resetToDefaults(Flash); 97 | printTxtAndSleep(MsgConfirm); 98 | Disp.setFlash(false); 99 | break; 100 | case Mode::Uart: 101 | setMode(Mode::Presets); 102 | printTxtAndSleep(MsgPresets); 103 | break; 104 | case Mode::Boot: 105 | break; 106 | } 107 | } 108 | 109 | void RotaryLogic::onLeft() { 110 | switch (getMode()) { 111 | case Mode::Presets: 112 | Presets.prev(); 113 | DC.setKHz(Presets.getActualKHz()); 114 | DC.setPeriod(Presets.getPeriod()); 115 | DBG_PRINT(std::cout << "Presets=" << Presets.getKHz() << "\n";) 116 | break; 117 | case Mode::ConfigMHz: 118 | Presets.decrActualKHz(); 119 | DC.setKHz(Presets.getActualKHz()); 120 | DC.setPeriod(Presets.getPeriod()); 121 | DBG_PRINT(std::cout << "ActualKHz=" << Presets.getActualKHz() << "\n";) 122 | break; 123 | case Mode::ConfigPeriod: 124 | Presets.decrPeriod(); 125 | DC.setPeriod(Presets.getPeriod()); 126 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 127 | break; 128 | case Mode::DeletePreset: 129 | Presets.toggleDeleted(); 130 | DBG_PRINT(std::cout << "DeletePreset=" << Presets.isDeleted() << "\n";) 131 | break; 132 | case Mode::Manual: 133 | DC.decrMHz(); 134 | DBG_PRINT(std::cout << DC.getKHz() << "\n";) 135 | break; 136 | case Mode::ConfigMaxMHz: { 137 | int PrevMHz = Presets.getMaxMHz() - RotaryMaxMHzLeftStep; 138 | if (PrevMHz < MHzLimitLo) 139 | PrevMHz = MHzLimitHi; 140 | Presets.updateMaxMHz(PrevMHz); 141 | DC.setKHz(Presets.getMaxKHz()); 142 | DBG_PRINT(std::cout << "MaxMHz=" << Presets.getMaxMHz() << "\n";) 143 | break; 144 | } 145 | case Mode::ResetToDefaults: 146 | escapeReset(); 147 | break; 148 | case Mode::Uart: 149 | setMode(Mode::Presets); 150 | DC.setKHz(Presets.getActualKHz()); 151 | DC.setPeriod(Presets.getPeriod()); 152 | printTxtAndSleep(MsgPresets); 153 | break; 154 | case Mode::Boot: 155 | break; 156 | } 157 | } 158 | 159 | void RotaryLogic::onRight() { 160 | switch (getMode()) { 161 | case Mode::Presets: 162 | Presets.next(); 163 | DC.setKHz(Presets.getActualKHz()); 164 | DC.setPeriod(Presets.getPeriod()); 165 | DBG_PRINT(std::cout << "Presets=" << Presets.getKHz() << "\n";) 166 | break; 167 | case Mode::ConfigMHz: 168 | Presets.incrActualKHz(); 169 | DC.setKHz(Presets.getActualKHz()); 170 | DC.setPeriod(Presets.getPeriod()); 171 | DBG_PRINT(std::cout << "ActualKHz=" << Presets.getActualKHz() << "\n";) 172 | break; 173 | case Mode::ConfigPeriod: { 174 | Presets.incrPeriod(); 175 | DC.setPeriod(Presets.getPeriod()); 176 | DBG_PRINT(std::cout << "Period=" << Presets.getPeriod() << "\n";) 177 | break; 178 | case Mode::DeletePreset: 179 | Presets.toggleDeleted(); 180 | DBG_PRINT(std::cout << "DeletePreset=" << Presets.isDeleted() << "\n";) 181 | break; 182 | case Mode::Manual: 183 | DC.incrMHz(); 184 | DBG_PRINT(std::cout << DC.getKHz() << "\n";) 185 | break; 186 | case Mode::ConfigMaxMHz: { 187 | int NextMHz = Presets.getMaxMHz() + RotaryMaxMHzRightStep; 188 | if (NextMHz > MHzLimitHi) 189 | NextMHz = MHzLimitLo; 190 | Presets.updateMaxMHz(NextMHz); 191 | DC.setKHz(Presets.getMaxKHz()); 192 | DBG_PRINT(std::cout << "MaxMHz=" << Presets.getMaxMHz() << "\n";) 193 | break; 194 | } 195 | case Mode::ResetToDefaults: 196 | setMode(Mode::Presets); 197 | Disp.setFlash(false); 198 | printTxtAndSleep(MsgEscape); 199 | break; 200 | case Mode::Uart: 201 | setMode(Mode::Presets); 202 | DC.setKHz(Presets.getActualKHz()); 203 | DC.setPeriod(Presets.getPeriod()); 204 | printTxtAndSleep(MsgPresets); 205 | break; 206 | case Mode::Boot: 207 | break; 208 | } 209 | } 210 | } 211 | 212 | void RotaryLogic::tick() { 213 | if (++LoopCntSinceSample != SamplePeriod) 214 | return; 215 | LoopCntSinceSample = 0; 216 | 217 | updateDisplay(); 218 | 219 | switch (RotEnc.get()) { 220 | case RotaryEncoder::State::SwRelease: { 221 | DBG_PRINT(std::cout << "SwRelase\n";) 222 | onSwRelease(); 223 | break; 224 | } 225 | case RotaryEncoder::State::SwLongPress: { 226 | DBG_PRINT(std::cout << "SwLongPress\n";) 227 | onSwLongPress(); 228 | break; 229 | } 230 | case RotaryEncoder::State::Left: { 231 | DBG_PRINT(std::cout << "Left\n";) 232 | onLeft(); 233 | break; 234 | } 235 | case RotaryEncoder::State::Right: { 236 | DBG_PRINT(std::cout << "Right\n";) 237 | onRight(); 238 | break; 239 | } 240 | default: 241 | break; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /firmware/src/main.cpp: -------------------------------------------------------------------------------- 1 | //-*- C++ -*- 2 | // 3 | // Copyright (C) 2024 Scrap Computing 4 | // 5 | 6 | #include "CommonLogic.h" 7 | #include "ConfigOpts.h" 8 | #include "Debug.h" 9 | #include "DutyCycle.h" 10 | #include "Flash.h" 11 | #include "Pico.h" 12 | #include "PotentiometerLogic.h" 13 | #include "Pwm.pio.h" 14 | #include "RotaryEncoder.h" 15 | #include "RotaryLogic.h" 16 | #include "TwoButtonLogic.h" 17 | #include "Uart.h" 18 | #include "hardware/pio.h" 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | PresetsTable Presets; 26 | 27 | static void PioPWMSetPeriod(PIO Pio, uint SM, uint32_t Period) { 28 | pio_sm_set_enabled(Pio, SM, false); 29 | pio_sm_put_blocking(Pio, SM, Period); 30 | pio_sm_exec(Pio, SM, pio_encode_pull(false, false)); 31 | pio_sm_exec(Pio, SM, pio_encode_out(pio_isr, 32)); 32 | pio_sm_set_enabled(Pio, SM, true); 33 | } 34 | 35 | static void PioPWMSetLevel(PIO Pio, uint SM, uint32_t Level) { 36 | pio_sm_put_blocking(Pio, SM, Level); 37 | } 38 | 39 | enum class UIMode { 40 | Rotary, 41 | TwoButton, 42 | Button, 43 | ButtonWithPot, 44 | }; 45 | 46 | static UIMode Mode; 47 | 48 | const char *getUIModeStr(UIMode Mode) { 49 | switch (Mode) { 50 | case UIMode::Rotary: 51 | return "Rotary"; 52 | case UIMode::TwoButton: 53 | return "TwoButton"; 54 | case UIMode::Button: 55 | return "OneButton"; 56 | case UIMode::ButtonWithPot: 57 | return "ButtonWithPot"; 58 | } 59 | return "UNKNOWN"; 60 | } 61 | 62 | static UIMode getUIMode(Pico &Pico) { 63 | Pico.readGPIO(); 64 | bool JP1 = Pico.getGPIO(ModeJP1GPIO); 65 | bool JP2 = Pico.getGPIO(ModeJP2GPIO); 66 | if (JP1 && JP2) 67 | return UIMode::Button; 68 | if (JP1 && !JP2) 69 | return UIMode::ButtonWithPot; 70 | if (!JP1 && !JP2) 71 | return UIMode::TwoButton; 72 | if (!JP1 && JP2) 73 | return UIMode::Rotary; 74 | std::cerr << "Bad Mode!\n"; 75 | exit(1); 76 | } 77 | 78 | class ThrottlePin { 79 | Pico Π 80 | DutyCycle &DC; 81 | PIO Pio; 82 | int SM; 83 | int LastKHz = 0; 84 | int LastPeriod = 0; 85 | int CntTicks = 0; 86 | 87 | public: 88 | ThrottlePin(Pico &Pi, DutyCycle &DC) : Pi(Pi), DC(DC) { 89 | // Start PIO for the throttle pin 90 | Pi.setGPIO(ThrottleGPIO, true); 91 | Pio = pio1; 92 | SM = pio_claim_unused_sm(Pio, true); 93 | uint ProgramOffset = pio_add_program(Pio, &pwm_program); 94 | pwm_program_init(Pio, SM, ProgramOffset, ThrottleGPIO); 95 | DBG_PRINT(DC.dump();) 96 | } 97 | void updatePWM() { 98 | if (++CntTicks != UpdatePWMSamplePeriod) 99 | return; 100 | CntTicks = 0; 101 | 102 | int KHz = DC.getKHz(); 103 | int Period = DC.getPeriod(); 104 | if (KHz != LastKHz || Period != LastPeriod) { 105 | DBG_PRINT(DC.dump();) 106 | int Level = DC.getLevel(); 107 | PioPWMSetPeriod(Pio, SM, Period); 108 | PioPWMSetLevel(Pio, SM, Level); 109 | } 110 | LastKHz = KHz; 111 | LastPeriod = Period; 112 | } 113 | }; 114 | 115 | // Hack for linking error: undefined reference to `__dso_handle' 116 | static void *__dso_handle = 0; 117 | 118 | static bool resetButtonPushed() { 119 | // Get the reset button GPIO depending on operating mode. 120 | int ResetBtn1 = 0; 121 | std::optional ResetBtn2; 122 | switch (Mode) { 123 | case UIMode::Rotary: { 124 | ResetBtn1 = RotarySwGPIO; 125 | break; 126 | } 127 | case UIMode::Button: 128 | case UIMode::ButtonWithPot: 129 | ResetBtn1 = LeftButtonGPIO; 130 | break; 131 | case UIMode::TwoButton: 132 | ResetBtn1 = LeftButtonGPIO; 133 | ResetBtn2 = RightButtonGPIO; 134 | break; 135 | } 136 | 137 | // Check that the user is pressing the reset button for a duration of 138 | // ResetBtnPressMs ms. 139 | for (int Ms = 0; Ms < ResetBtnPressMs; Ms += ResetBtnCheckMs) { 140 | if (!ResetBtn2) { 141 | if (gpio_get(ResetBtn1)) 142 | return false; 143 | } else { 144 | if (gpio_get(ResetBtn1) && gpio_get(*ResetBtn2)) 145 | return false; 146 | } 147 | sleep_ms(ResetBtnCheckMs); 148 | } 149 | return true; 150 | } 151 | 152 | int main() { 153 | (void)__dso_handle; 154 | Pico Pico; 155 | DBG_PRINT(sleep_ms(1000);) 156 | DBG_PRINT(std::cout << "ThrottleBlaster rev." << REVISION_MAJOR << "." 157 | << REVISION_MINOR << "\n";) 158 | Pico.initGPIO(PICO_DEFAULT_LED_PIN, GPIO_OUT, Pico::Pull::Down, "LED"); 159 | Pico.initGPIO(ThrottleGPIO, GPIO_OUT, Pico::Pull::Down, "Throttle"); 160 | 161 | Pico.initGPIO(ModeJP1GPIO, GPIO_IN, Pico::Pull::Up, "ModeJP1"); 162 | Pico.initGPIO(ModeJP2GPIO, GPIO_IN, Pico::Pull::Up, "ModeJP2"); 163 | 164 | Pico.initGPIO(ReverseDirectionGPIO, GPIO_IN, Pico::Pull::Up, 165 | "ReverseDirectionJP"); 166 | 167 | Display Disp(DisplayClkGPIO, DisplayDioGPIO); 168 | 169 | // DutyCycle needs an updated Presets. 170 | DutyCycle DC(Presets); 171 | 172 | Mode = getUIMode(Pico); 173 | DBG_PRINT(std::cout << "Mode=" << getUIModeStr(Mode) << "\n";) 174 | 175 | FlashStorage Flash; 176 | 177 | std::unique_ptr UI; 178 | switch (Mode) { 179 | case UIMode::Rotary: { 180 | bool ReverseDirection = Pico.getGPIO(ReverseDirectionGPIO); 181 | UI = std::make_unique( 182 | RotaryClkGPIO, RotaryDtGPIO, RotarySwGPIO, RotaryEncoderSamplePeriod, 183 | Pico, Disp, Presets, DC, Flash, ReverseDirection); 184 | break; 185 | } 186 | case UIMode::Button: 187 | case UIMode::ButtonWithPot: { 188 | bool ReverseDirection = Pico.getGPIO(ReverseDirectionGPIO); 189 | UI = std::make_unique( 190 | PotentiometerGPIO, LeftButtonGPIO, PotSamplePeriod, Pico, Disp, DC, 191 | Presets, Flash, /*EnablePot=*/Mode == UIMode::ButtonWithPot, 192 | ReverseDirection); 193 | break; 194 | } 195 | case UIMode::TwoButton: 196 | UI = std::make_unique(LeftButtonGPIO, RightButtonGPIO, 197 | ButtonSamplePeriod, Pico, Disp, DC, 198 | Presets, Flash); 199 | break; 200 | } 201 | 202 | // Read Presets from flash. 203 | if (resetButtonPushed()) { 204 | Disp.setFlash(true); 205 | Disp.printTxt(CommonLogic::MsgResetToDefaults); 206 | DBG_PRINT(std::cerr << "*** Resetting to factory defaults ***\n";) 207 | Presets.resetToDefaults(Flash); 208 | // Flash the LED to let the user know that reset was succesfull 209 | for (uint32_t Cnt = 0; Cnt != ResetDefaultFlashCnt; ++Cnt) { 210 | if (Cnt % 2 == 0) 211 | Pico.ledON(); 212 | else 213 | Pico.ledOFF(); 214 | sleep_ms(ResetDefaultsFlashOnMs); 215 | } 216 | Pico.ledON(); 217 | Disp.setFlash(false); 218 | } 219 | if (Flash.valid()) 220 | Presets.readFromFlash(Flash); 221 | else { 222 | DBG_PRINT(std::cout << "Flash not valid, reset to defaults";) 223 | Disp.setFlash(true); 224 | Presets.resetToDefaults(Flash); 225 | sleep_ms(1000); 226 | Disp.setFlash(false); 227 | } 228 | 229 | Uart Uart(UartGPIO, UartRequestedBaud, UartDataBits, UartStopBits, UartParity, 230 | UartFlowControl); 231 | 232 | ThrottlePin TPin(Pico, DC); 233 | Pico.ledON(); 234 | DC.setKHz(Presets.getKHz()); 235 | while (true) { 236 | // The main entry point for the UI. 237 | UI->tickAll(Uart); 238 | // Update the Throttle pin PWM if needed. 239 | TPin.updatePWM(); 240 | } 241 | return 0; 242 | } 243 | -------------------------------------------------------------------------------- /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 | Disp.setFlash(false); 89 | printTxtAndSleep(MsgDeletePreset); 90 | savePotVal(PotVal); 91 | setMode(Mode::DeletePreset); 92 | Disp.setFlash(true); 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::configDeletePreset(int PotVal, ButtonState BtnState) { 116 | switch (BtnState) { 117 | case ButtonState::Release: 118 | case ButtonState::MedRelease: 119 | Presets.toggleDeleted(); 120 | DBG_PRINT(std::cout << "Deleted=" << Presets.isDeleted() << "\n";) 121 | return; 122 | case ButtonState::LongPress: 123 | tryWritePresetsToFlash(); 124 | Disp.setFlash(false); 125 | printTxtAndSleep(MsgConfirm); 126 | savePotVal(PotVal); 127 | setMode(Mode::Presets); 128 | if (Presets.isDeleted()) 129 | Presets.prev(); 130 | return; 131 | default: 132 | break; 133 | } 134 | 135 | // If we moved the potentiometer enough, use it to control the value. 136 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 137 | switch (getPotDir(PotVal)) { 138 | case PotDir::Right: 139 | case PotDir::Left: 140 | Presets.toggleDeleted(); 141 | break; 142 | default: 143 | break; 144 | } 145 | } 146 | } 147 | 148 | void PotentiometerLogic::configResetToDefaults(int PotVal, 149 | ButtonState BtnState) { 150 | auto DontReset = [this, PotVal]() { 151 | tryWritePresetsToFlash(); 152 | Disp.setFlash(false); 153 | printTxtAndSleep(MsgEscape); 154 | printTxtAndSleep(MsgPresets); 155 | savePotVal(PotVal); 156 | setMode(Mode::Presets); 157 | }; 158 | switch (BtnState) { 159 | case ButtonState::Release: 160 | case ButtonState::MedRelease: 161 | DontReset(); 162 | break; 163 | case ButtonState::LongPress: 164 | Presets.resetToDefaults(Flash); 165 | Disp.setFlash(false); 166 | printTxtAndSleep(MsgConfirm); 167 | savePotVal(PotVal); 168 | setMode(Mode::Presets); 169 | break; 170 | default: 171 | break; 172 | } 173 | if (EnablePot && movedPotComparedToSaved(PotVal)) 174 | DontReset(); 175 | } 176 | 177 | void PotentiometerLogic::configMaxMHz(int PotVal, ButtonState BtnState) { 178 | switch (BtnState) { 179 | case ButtonState::LongPress: 180 | setMode(Mode::ResetToDefaults); 181 | printTxtAndSleep(MsgResetToDefaults); 182 | return; 183 | case ButtonState::Release: { 184 | int Base = Presets.getMaxMHz() - Presets.getMaxMHz() % 100; 185 | Presets.incrMaxMHz(/*Step=*/1, /*Wrap=*/true, /*WrapLo=*/Base, 186 | /*WrapHi=*/Base + 99); 187 | DC.setKHz(Presets.getMaxKHz()); 188 | return; 189 | } 190 | case ButtonState::MedRelease: { 191 | Presets.incrMaxMHz(/*Step=*/100, /*Wrap=*/true); 192 | DC.setKHz(Presets.getMaxKHz()); 193 | return; 194 | } 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.incrMaxMHz(); 203 | break; 204 | case PotDir::Left: 205 | Presets.decrMaxMHz(); 206 | break; 207 | default: 208 | break; 209 | } 210 | } 211 | } 212 | 213 | void PotentiometerLogic::configMHz(int PotVal, ButtonState BtnState) { 214 | switch (BtnState) { 215 | case ButtonState::Release: 216 | Presets.incrActualKHz(); 217 | return; 218 | case ButtonState::MedRelease: 219 | Presets.decrActualKHz(); 220 | return; 221 | case ButtonState::LongPress: 222 | tryWritePresetsToFlash(); 223 | Disp.setFlash(false); 224 | printTxtAndSleep(MsgPeriod); 225 | Disp.setFlash(true); 226 | setMode(Mode::ConfigPeriod); 227 | return; 228 | default: 229 | break; 230 | } 231 | // If we moved the potentiometer enough, use it to control the value. 232 | if (EnablePot && movedPotComparedToSaved(PotVal)) { 233 | switch (getPotDir(PotVal)) { 234 | case PotDir::Right: 235 | Presets.incrActualKHz(); 236 | break; 237 | case PotDir::Left: 238 | Presets.decrActualKHz(); 239 | break; 240 | default: 241 | break; 242 | } 243 | } 244 | } 245 | 246 | void PotentiometerLogic::setModeInit(Mode NewMode) { 247 | if (NewMode == Mode::Uart) { 248 | savePotVal(Pot.get()); 249 | } 250 | } 251 | 252 | void PotentiometerLogic::tick() { 253 | if (++LoopCntSinceSample != SamplePeriod) 254 | return; 255 | LoopCntSinceSample = 0; 256 | 257 | updateDisplay(); 258 | 259 | auto BtnState = Btn.get(); 260 | int PotVal = Pot.get(); 261 | 262 | if (EnablePot && InitRampUp) { 263 | // There is a small capacitor connected to the potentiometer for 264 | // stability, but it causes the values to ramp-up. Ignore them. 265 | if (++InitRampUpCnt < PotentiometerRampUpIgnoreReadings) 266 | return; 267 | InitRampUp = false; 268 | LastPotVal = PotVal; 269 | savePotVal(PotVal); 270 | } 271 | 272 | switch (getMode()) { 273 | case Mode::Manual: 274 | manual(PotVal, BtnState); 275 | break; 276 | case Mode::Presets: 277 | cyclePresets(PotVal, BtnState); 278 | break; 279 | case Mode::ConfigMHz: 280 | configMHz(PotVal, BtnState); 281 | break; 282 | case Mode::ConfigPeriod: 283 | configPeriod(PotVal, BtnState); 284 | break; 285 | case Mode::DeletePreset: 286 | configDeletePreset(PotVal, BtnState); 287 | break; 288 | case Mode::ConfigMaxMHz: 289 | configMaxMHz(PotVal, BtnState); 290 | break; 291 | case Mode::ResetToDefaults: 292 | configResetToDefaults(PotVal, BtnState); 293 | break; 294 | case Mode::Uart: 295 | uart(PotVal, BtnState); 296 | break; 297 | case Mode::Boot: 298 | break; 299 | } 300 | LastMode = getMode(); 301 | LastPotVal = PotVal; 302 | } 303 | -------------------------------------------------------------------------------- /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 | if (Presets.atMaxKHz()) { 28 | setMode(Mode::ResetToDefaults); 29 | printTxtAndSleep(MsgResetToDefaults); 30 | } 31 | return; 32 | } 33 | // Skip if both buttons are pressed. 34 | if (LBtnState == ButtonState::LongPress && RBtnState == ButtonState::Pressed) 35 | return; 36 | if (RBtnState == ButtonState::LongPress && LBtnState == ButtonState::Pressed) 37 | return; 38 | 39 | switch (LBtnState) { 40 | case ButtonState::Release: 41 | case ButtonState::MedRelease: 42 | Presets.prev(); 43 | DC.setKHz(Presets.getActualKHz()); 44 | DC.setPeriod(Presets.getPeriod()); 45 | break; 46 | case ButtonState::LongPress: 47 | LongPress(); 48 | break; 49 | default: 50 | break; 51 | } 52 | switch (RBtnState) { 53 | case ButtonState::Release: 54 | case ButtonState::MedRelease: 55 | Presets.next(); 56 | DC.setKHz(Presets.getActualKHz()); 57 | DC.setPeriod(Presets.getPeriod()); 58 | break; 59 | case ButtonState::LongPress: 60 | LongPress(); 61 | break; 62 | default: 63 | break; 64 | } 65 | } 66 | 67 | void TwoButtonLogic::manual(ButtonState LBtnState, ButtonState RBtnState) { 68 | if (bothRelease(LBtnState, RBtnState)) { 69 | setMode(Mode::Presets); 70 | printTxtAndSleep(MsgPresets); 71 | return; 72 | } 73 | switch (LBtnState) { 74 | case ButtonState::Release: 75 | case ButtonState::MedRelease: 76 | DC.decrMHz(); 77 | break; 78 | case ButtonState::LongPress: 79 | break; 80 | default: 81 | break; 82 | } 83 | 84 | switch (RBtnState) { 85 | case ButtonState::Release: 86 | case ButtonState::MedRelease: 87 | DC.incrMHz(); 88 | break; 89 | case ButtonState::LongPress: 90 | break; 91 | default: 92 | break; 93 | } 94 | } 95 | 96 | void TwoButtonLogic::uart(ButtonState LBtnState, ButtonState RBtnState) { 97 | if (LBtnState != ButtonState::None || RBtnState != ButtonState::None) { 98 | setMode(Mode::Presets); 99 | printTxtAndSleep(MsgPresets); 100 | } 101 | } 102 | 103 | void TwoButtonLogic::configPeriod(ButtonState LBtnState, ButtonState RBtnState) { 104 | auto LongPress = [this]() { 105 | // Save 106 | setMode(Mode::DeletePreset); 107 | printTxtAndSleep(MsgDeletePreset); 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::configDeletePreset(ButtonState LBtnState, ButtonState RBtnState) { 147 | auto LongPress = [this]() { 148 | // Save 149 | tryWritePresetsToFlash(); 150 | Disp.setFlash(false); 151 | printTxtAndSleep(MsgConfirm); 152 | setMode(Mode::Presets); 153 | if (Presets.isDeleted()) 154 | Presets.next(); 155 | }; 156 | 157 | if (bothRelease(LBtnState, RBtnState)) { 158 | // Cancel 159 | setMode(Mode::Presets); 160 | printTxtAndSleep(MsgEscape); 161 | Disp.setFlash(false); 162 | return; 163 | } 164 | switch (LBtnState) { 165 | case ButtonState::Release: 166 | case ButtonState::MedRelease: 167 | Presets.toggleDeleted(); 168 | DBG_PRINT(std::cout << "Deleted=" << Presets.isDeleted() << "\n";) 169 | return; 170 | case ButtonState::LongPress: 171 | LongPress(); 172 | return; 173 | default: 174 | break; 175 | } 176 | 177 | switch (RBtnState) { 178 | case ButtonState::Release: 179 | case ButtonState::MedRelease: 180 | Presets.toggleDeleted(); 181 | DBG_PRINT(std::cout << "Deleted=" << Presets.isDeleted() << "\n";) 182 | return; 183 | case ButtonState::LongPress: 184 | LongPress(); 185 | return; 186 | default: 187 | break; 188 | } 189 | } 190 | 191 | void TwoButtonLogic::configResetToDefaults(ButtonState LBtnState, 192 | ButtonState RBtnState) { 193 | auto DontReset = [this] { 194 | tryWritePresetsToFlash(); 195 | Disp.setFlash(false); 196 | printTxtAndSleep(MsgEscape); 197 | printTxtAndSleep(MsgPresets); 198 | setMode(Mode::Presets); 199 | }; 200 | switch (LBtnState) { 201 | case ButtonState::Release: 202 | DontReset(); 203 | break; 204 | default: 205 | break; 206 | } 207 | switch (RBtnState) { 208 | case ButtonState::Release: 209 | DontReset(); 210 | break; 211 | default: 212 | break; 213 | } 214 | if (bothLongPress(LBtnState, RBtnState)) { 215 | Presets.resetToDefaults(Flash); 216 | Disp.setFlash(false); 217 | printTxtAndSleep(MsgConfirm); 218 | setMode(Mode::Presets); 219 | return; 220 | } 221 | } 222 | 223 | void TwoButtonLogic::configMaxMHz(ButtonState LBtnState, ButtonState RBtnState) { 224 | auto LongPress = [this]() { 225 | // Save 226 | tryWritePresetsToFlash(); 227 | setMode(Mode::Presets); 228 | Disp.setFlash(false); 229 | printTxtAndSleep(MsgConfirm); 230 | }; 231 | if (bothRelease(LBtnState, RBtnState)) { 232 | // Cancel 233 | setMode(Mode::Presets); 234 | printTxtAndSleep(MsgEscape); 235 | Disp.setFlash(false); 236 | return; 237 | } 238 | switch (LBtnState) { 239 | case ButtonState::Release: 240 | case ButtonState::MedRelease: 241 | Presets.decrMaxMHz(/*Step=*/1); 242 | DC.setKHz(Presets.getMaxKHz()); 243 | return; 244 | case ButtonState::LongPress: 245 | LongPress(); 246 | return; 247 | default: 248 | break; 249 | } 250 | 251 | switch (RBtnState) { 252 | case ButtonState::Release: 253 | case ButtonState::MedRelease: 254 | Presets.incrMaxMHz(/*Step=*/TwoBtnMaxMHzStep, /*Wrap=*/true, 255 | /*WrapLo=*/MHzLimitLo, 256 | /*WrapHi=*/MHzLimitHi); 257 | DC.setKHz(Presets.getMaxKHz()); 258 | return; 259 | case ButtonState::LongPress: 260 | LongPress(); 261 | return; 262 | default: 263 | break; 264 | } 265 | } 266 | 267 | void TwoButtonLogic::configMHz(ButtonState LBtnState, ButtonState RBtnState) { 268 | auto LongPress = [this] () { 269 | // Proceed to Period mode 270 | Disp.setFlash(false); 271 | printTxtAndSleep(MsgPeriod); 272 | Disp.setFlash(true); 273 | setMode(Mode::ConfigPeriod); 274 | }; 275 | 276 | if (bothRelease(LBtnState, RBtnState)) { 277 | setMode(Mode::Presets); 278 | printTxtAndSleep(MsgEscape); 279 | Disp.setFlash(false); 280 | return; 281 | } 282 | switch (LBtnState) { 283 | case ButtonState::Release: 284 | case ButtonState::MedRelease: 285 | Presets.decrActualKHz(); 286 | DC.setKHz(Presets.getActualKHz()); 287 | return; 288 | case ButtonState::LongPress: 289 | LongPress(); 290 | return; 291 | default: 292 | break; 293 | } 294 | 295 | switch (RBtnState) { 296 | case ButtonState::Release: 297 | case ButtonState::MedRelease: 298 | Presets.incrActualKHz(); 299 | DC.setKHz(Presets.getActualKHz()); 300 | return; 301 | case ButtonState::LongPress: 302 | LongPress(); 303 | return; 304 | default: 305 | break; 306 | } 307 | } 308 | 309 | void TwoButtonLogic::tick() { 310 | if (++LoopCntSinceSample != SamplePeriod) 311 | return; 312 | LoopCntSinceSample = 0; 313 | 314 | updateDisplay(); 315 | 316 | auto LBtnState = LBtn.get(); 317 | auto RBtnState = RBtn.get(); 318 | 319 | switch (getMode()) { 320 | case Mode::Presets: 321 | cyclePresets(LBtnState, RBtnState); 322 | break; 323 | case Mode::Manual: 324 | manual(LBtnState, RBtnState); 325 | break; 326 | case Mode::ConfigMHz: 327 | configMHz(LBtnState, RBtnState); 328 | break; 329 | case Mode::ConfigPeriod: 330 | configPeriod(LBtnState, RBtnState); 331 | break; 332 | case Mode::DeletePreset: 333 | configDeletePreset(LBtnState, RBtnState); 334 | break; 335 | case Mode::ConfigMaxMHz: 336 | configMaxMHz(LBtnState, RBtnState); 337 | break; 338 | case Mode::ResetToDefaults: 339 | configResetToDefaults(LBtnState, RBtnState); 340 | break; 341 | case Mode::Uart: 342 | uart(LBtnState, RBtnState); 343 | break; 344 | case Mode::Boot: 345 | break; 346 | } 347 | LastMode = getMode(); 348 | } 349 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThrottleBlaster 2 | 3 | A Pi Pico-based Turbo circuit with a MHz display 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 | Since rev.0.8e we also support the Pico 2! 8 | 9 | 10 | 11 | 12 | 13 | Videos: 14 | - Part 3 (rev 0.3): https://www.youtube.com/watch?v=g4OluJwGDEQ 15 | - Part 2: https://www.youtube.com/watch?v=nGy8OmOe_34 16 | - Part 1: https://www.youtube.com/watch?v=9uNml2j6sy0 17 | 18 | - Mounted on a 5.25 panel: https://www.youtube.com/shorts/n1aFvRNtOcw (by Michael Swimm) 19 | - Controllable by serial: https://www.youtube.com/watch?v=71rln-R2mis (by Michael Swimm) 20 | 21 | Download firmware ThrottleBlaster_pico1.uf2 (or ThrottleBlaster_pico2.uf2 for the Pico 2): https://github.com/scrapcomputing/ThrottleBlaster/releases 22 | 23 | Download gerbers (ThrottleBlaster_gerbers_rev.X.X.zip): https://github.com/scrapcomputing/ThrottleBlaster/releases 24 | 25 | A front panel design by Michael Swimm: https://www.thingiverse.com/thing:6619261 26 | 27 | # How it works 28 | The Throttle Blaster is a fancy PWM controller that pulls the CPU's STPCLK# to ground. 29 | 30 | 31 | 32 | 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. 33 | 34 | 35 | # Features 36 | - Four modes of operation to fit your needs, selected by jumpers: 37 | 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). 38 | 2. Two-button control (left/right). 39 | 3. rotary-encoder with a push-button. This is great for placing it on a drive bay panel. 40 | 4. analogue potentiometer with/without a push-button. This could be used for configurations without a display. 41 | - 7-segment TM1637-based display that shows the effective frequency. This can be considered optional, but is highly recommended. 42 | - Cycle through preset frequencies or select a frequency at a 1MHz granularity. 43 | - Each preset can be tuned/programmed both in terms of the PWM level but also the PWM frequency. 44 | - Configuration changes are permanently stored in the Pico's flash memory. 45 | - The PWM pulses are generated by the Pico's PIOs and are clock-accurate, with no jitter. 46 | 47 | 48 | # Modes of operation 49 | 50 | ## Mode 1: Rotary Encoder (ROT) 51 | 52 | - The devices starts at the "Presets" state (mode). 53 | - Turn the knob to select a frequency 54 | - Short push to switch to fine-grain frequency selection 55 | - Long push to enter configuration of the current preset: 56 | - Fine-tune actual frequency, short push to switch to PWM period tuning. Long-push escapes. 57 | - Fine-tune PWM period, short push to switch to preset deletion. Long-push escapes. 58 | - Select if preset should be deleted, short push to confirm. 59 | 60 | 61 | 62 | ## Initial configuration 63 | - 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. 64 | 65 | ## Mode 2: Single-button (1Btn) 66 | 67 | - The device starts at the "Presets" state. 68 | - Three types of push: 69 | 1. Short push (release immediately) 70 | 2. Medium push (for ~0.5 seconds) 71 | 3. Long push (about 2 seconds) 72 | - Cycle through presets with a short push. A medium push brings us back to the default CPU frequency. 73 | - Long push to enter programming mode. 74 | 75 | 76 | 77 | 78 | ## Mode 3: Two-button (2Btn) 79 | 80 | - The device starts at the "Presets" state. 81 | - Two types of push: long and short 82 | - Three types of actions: 83 | 1. Short Left or Right push (release immediately) 84 | 2. Both Left and Right short push 85 | 3. Both Left and Right long push. 86 | - Cycle through presets with a L or R short push. 87 | - Go to 1-1 manual mode with both L and R short push. 88 | - Long push (L or R) to enter programming mode, or to configure the CPU Frequency (CPUF) when showing the maximum frequency. 89 | - Reset to defaults by long press L and R twice. 90 | 91 | 92 | 93 | ## Mode 4: Potentiometer (POT) 94 | 95 | - Can be used in conjunction with the single-button operation. Turning the potentiometer overrides the preset selected by the button. 96 | 97 | ## Serial Mode (UART): Control by the serial port, works in conjunction with all other modes. 98 | 99 | You can connect to the Throttle Blaster via the serial port and set the Frequency and PWM Period. 100 | This is convenient for launching a game with a `.bat` file that first configures the Throttle Blaster and then launches the game. 101 | 102 | ## Reset to factory defaults during power on (since rev 0.9) 103 | 104 | Press the button (rotary encoder, or any individual button in other modes) and plug in the ThrottleBlaster to power. The led on the Pico will start flashing fast for around 2 seconds and the MHz display will show "RES". 105 | 106 | This helps revert the deleted presets or any potential flash-related issue. 107 | 108 | ### UART Circuit 109 | - 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) 110 | 111 | 112 | ### UART Software 113 | - Serial port settings: 9600 8N1, no flow control 114 | - The string to send is in the form `FP\r`, where: 115 | - `` is the desired effective frequency in MHz (float), 116 | - `` is the PWM Period level (1-256), 117 | - `\r` (also seen as `^M`) is the Carriage-Return character (ASCII 13 0x0d). 118 | - For example `F4.77P8` sets the frequency to `4.77MHz` and the PWM period to `8` which is a around 50us. 119 | - 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. 120 | - 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`). 121 | 122 | > **Note** 123 | > 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. 124 | 125 | > **Note** 126 | > 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. 127 | 128 | 129 | ## Preset Buttons (since rev 0.7) 130 | Since revision 0.7 you can also directly select a preset by pushing one of the 8 available buttons. 131 | This functionality is compatible with all other modes of operation. 132 | 133 | Please note that you don't need a Rev 0.7 PCB to use this feature. 134 | 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: 135 | 136 | 137 | GPIO | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 138 | ------------|----|----|----|----|----|----|-----|----- 139 | Freq (MHz) | 4 | 8 | 10 | 25 | 33 | 66 | 133 | Max 140 | 141 | ## Reset Detection (Optional and experimental) (since rev 0.8) 142 | Connect the computer case reset button to `RES1` and another cable from `RES2` to the computer motherboard. 143 | 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. 144 | During this time the MHz display will show "boot" and show a count down until the frequency is restored. 145 | 146 | > **Note** 147 | > Polarity matters: If you connect the reset cables to the RES headers the wrong way your PC won't boot! 148 | 149 | ## Reverse Direction jumper JP3 (since rev 0.4) 150 | Since revision 0.4 the default rotation direction of the knob has been changed and a new jumper JP3 has been added. 151 | Closing the jumper will reverse the direction for both the rotary encoder and the potentiometer. 152 | 153 | 154 | # Presets 155 | 156 | Display | Performance equivalence 157 | ---------|------------------------ 158 | 4 | 4.77 MHz IBM PC 159 | 8 | 8 MHz 160 | 10 | 10 MHz 161 | 25 | 25 MHz 162 | 33 | 33 MHz 486 163 | 66 | 66 MHz 486 164 | 133 | 133 MHz Pentium 165 | 450 | 450 MHz Pentium-II 166 | 733 | 733 MHz Pentium-III 167 | 168 | # Firmware 169 | 170 | ## Dependencies 171 | - C++17 cross compiler for pico (see [README](https://github.com/raspberrypi/pico-sdk)) 172 | - [pico-sdk](https://github.com/raspberrypi/pico-sdk) 2.2 or later 173 | - (Optional) [TM1637 display library](https://github.com/wahlencraft/TM1637-pico) 1.2.1 174 | 175 | ## Build Instructions 176 | - Download release (v1.2.1) https://github.com/wahlencraft/TM1637-pico and extract it. 177 | - `cd firmware/ && mkdir build && cd build` 178 | - `cmake -DCMAKE_BUILD_TYPE=Release -DPICO_SDK_PATH=/path/to/pico-sdk/ -DPICO_BOARD= -DPICO_TM1637_PATH=/path/to/TM1637-pico/ ../src/ && make` 179 | - This will place the firmware into: `ThrottleBlaster_pico1.uf2` (or `ThrottleBlaster_pico2.uf2`) in the `build` directory. 180 | 181 | ## Installing the firmware 182 | - Unplug the Pico 183 | - Press and hold the small "BOOTSEL" button on the Pico 184 | - While holding the BOOTSEL button, connect the Pico to your PC with a micro-USB cable 185 | - The Pico should show up as a mass-storage device 186 | - Copy the `ThrottleBlaster_pico1.uf2` (or `ThrottleBlaster_pico2.uf2` for the Pico 2) firmware to the drive associated with the Pico 187 | - Safely eject the mass-storage device 188 | 189 | The Pico should boot and you should see the Pico's LED light up. 190 | 191 | # Circuit 192 | 193 | ## Schematic 194 | 195 | Throttle Blaster Schematic 196 | 197 | The Throttle Blaster circuit is fairly simple: 198 | - The Rotary Encoder, the Push Button and the Potentiometer are connected to the Pico's GPIOs. 199 | - The Display is also connected directly to GPIOs 200 | - 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. 201 | - The circuit is powered directly from the PSU's 5V power supply via diode (preferrably a Schottky). 202 | - The serial port circuit relies on a MAX3232 for converting the RS232 levels to Pi-Pico levels. 203 | 204 | ## PCB 205 | 206 | Throttle Blaster PCB Front 207 | Throttle Blaster PCB Back 208 | 209 | ## Bill Of Materials (BOM) 210 | 211 | Download gerbers: https://github.com/scrapcomputing/ThrottleBlaster/releases 212 | 213 | Reference | Quantity | Value | Description 214 | ---------------|-------------------|-------------------------------------------------------|------------ 215 | N/A | 1 (recommended) | TM1637 based 4-digit 7-segment display | The display of the Throttle Blaster 216 | D1 | 1 | Through-hole diode (preferrably Schottky 1N5817) | Reverse polarity protection 217 | N/A (for Pico) | 2 | 1x17 female through-hole pin-header 2.54mm pitch (Harwin M20-7821746) | For attaching the Pico to the board. 218 | J1 | 1 (optional) | 1x01 male through-hole angled pin-header 2.54mm pitch | For the STPCLK# cable 219 | RES1,2 | 2 (optional) | 1x02 male through-hole angled pin-header 2.54mm pitch | For Reset detection 220 | J2 | 1 (optional UART) | 1x03 male through-hole pin-header 2.54mm pitch | For controlling the Throttle Blaster via serial. (Requires MAX3232) 221 | JP1/JP2 | 1 | 2x02 (or 2x 1x02) male through-hole pin-header 2.54mm | Selects mode of operation. 222 | JP3 | 1 | 1x02 male through-hole pin-header 2.54mm pitch | For the JP3 jumper that flips the rotation direction 223 | SW1/SW2 | 2 (optional) | 1x02 male through-hole pin-header 2.54mm pitch | For the SW1 and SW2 switches 224 | U1 | 1 (optional) | 1x04 male through-hole angled pin-header 2.54mm pitch | For connecting the TM1637 7-segment display. 225 | Jumpers | 2 | 2.54mm pitch Jumpers | For JP1/JP2 226 | Q1 (optional Q2) | 1 (2 for optional RESET detection) | 2N7000 N-channel MOSFET | Pulls down the CPU's STPCLK# pin 227 | R1 | 1 | 1K Resistor SMD 1206 | For the throttle transistor gate. 228 | R2 | 1 | 100 Ohm Resistor SMD 1206 WARNING: P-iii CPUs and VIA MVP3-based boards may need lower values like 47 Ohms | Between the throttle pin and the throttling transistor for additional safety. It's value could be lower. 229 | Pot1 | 1 (mode POT) | 10K linear potentiometer | Selects Frequency in Potentiometer mode. 230 | SW1 | 1 (mode 1Btn) | Push button | Selects Frequency in 1Btn mode. 231 | SW3 | 1 (mode 2Btn) | Push button | The right button in 2Btn mode. 232 | Preset Btns P1-P8| 8 (optional) | 1x02 (or one 8x02) male through-hole pin-header 2.54mm| Headers for the 8 preset push-buttons. 233 | 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. 234 | U2 | 1 | Raspberry Pi Pico | 235 | U3 | 1 | 1x04 horizontal pin header 2.54mm pitch | For connecting to the floppy power connector, for powering the unit. 236 | C1 | 1 (mode POT) | 100pF Ceramic capacitor SMD 1205 | Used to reduce potentiometer noise. 237 | C2,C3,C4,C5,C6 | 5 (optional UART) | 1uF Ceramic Capacitor SMD 1206 | For MAX3232 (serial port) 238 | 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. **WARNING:** There are many fakes/clones of the MAX3232 available online but these are known not to work well or at all. They may need smaller capacitor values as mentioned in [fake max3232 issue](https://github.com/scrapcomputing/ThrottleBlaster/issues/24#). So please avoid them and get a MAX3232 from a known brand and supplier. 239 | 240 | ## Using the circuit for the first time 241 | - Select the operation mode using jumpers JP1/JP2. 242 | - Connect J1 to your CPU's STPCLK# pin. 243 | - Power it on and you are good to go. 244 | - You can reverse the knob direction by closing JP3 (since rev.0.4) 245 | 246 | 247 | ## Basic Troubleshooting 248 | - Check that the Throttle Blaster's ground is connected to the motherboard's ground 249 | - Check the voltage at the STPCLK# pin: 250 | - Max frequency: The voltage should be matching the CPU's I/O high ~3.3V for most CPUs 251 | - Any other frequency: You should be seing pulses using an oscilloscope, or values between 0 and ~3.3V using a multimeter. 252 | - Confirm that it's working as expected by running benchmarks from [Phil's DOS Benchmark Pack](https://www.philscomputerlab.com/dos-benchmark-pack.html) 253 | 254 | 255 | ## How to find the `STPCLK#` pin 256 | Just look for it in your CPU's datasheet. 257 | This table lists the STPCLK# pin number for your reference: 258 | 259 | CPU | STPCLK# Pin 260 | ----------------------|------------ 261 | Pentium MMX | V34 262 | Pentium Pro | A3 263 | Pentium-iii socket | AG35 264 | Pentium-iii slot1 | B6 265 | Athlon XP | AC1 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | # How about older CPUs that don't have a STPCLK pin? 279 | 486 or older CPUs don't usually have a STPCLK# pin. 280 | Instead they have the HOLD pin which has somewhat similar functionality and is active high. 281 | Some boards use PWM on this HOLD pin when the Turbo button is pressed. 282 | 283 | The HOLD pin is not pulled down internally by the CPU. 284 | So it is up to the motherboard to pull it down. 285 | 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. 286 | So I don't think there is a way to get the Throttle Blaster to work universally with older systems. 287 | 288 | 289 | # Change Log 290 | - Rev 0.9: Allows deletion of presets and adds flash reset when button pressed during boot. 291 | - Rev 0.8e: Fixes single/two-button configuration bug (introduced in 0.8b). Adds Pico2 support. 292 | - Rev 0.8d: Fixes reset detection 293 | - Rev 0.8c: Fixes bugs introduced in Rev 0.8b. 294 | - 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). 295 | - Rev 0.8: Adds RESET detction circuit (optional) which will temporarily set the speed to max for 20 seconds. 296 | - Rev 0.7: Adds support for 8 preset buttons. 297 | - Rev 0.6: Firmware bug fixes: (i) fix saving MHz/Period adjustments to flash and (ii) one-button mode frequency glitch. 298 | - Rev 0.5: Replaces potentiometer capacitor with SMD and several firmware fixes. 299 | - Rev 0.4: Reverses knob direction and adds jumper JP3 for selecting direction. 300 | - Rev 0.3: Adds UART support in both firmware and PCB. 301 | - Rev 0.2: Adds two-button mode "2Btn". 302 | - Rev 0.1: Initial release. 303 | 304 | # Acknowledgements 305 | - 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! 306 | 307 | # License 308 | 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`. 309 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------