Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
476 changes: 476 additions & 0 deletions CLAUDE.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ CRC32
FlashPROM
ADS1219
ADS1256
mcp23017
NeoPico
OneBitDisplay
ArduinoJson
Expand Down
36 changes: 36 additions & 0 deletions configs/Pico/BoardConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@
#define GPIO_PIN_14 GpioAction::BUTTON_PRESS_TURBO
#define TURBO_LED_PIN 15

// --------------------- HARDWARE TURBO CONFIGURATION ---------------------
// Hardware turbo controls via I2C GPIO expander (MCP23017)
// Provides 8 physical toggle switches for per-button turbo control
// and optional analog speed dial for adjustable turbo rate.
//
// Requirements:
// - MCP23017 I2C GPIO expander breakout board
// - 8× SPST toggle switches (or DIP-8 switch array)
// - 10kΩ linear potentiometer (optional, for speed dial)
//
// Hardware connections:
// - I2C bus is shared with display (if present)
// - MCP23017 Port A pins (GPA0-GPA7) connect to switches
// - Switches should be wired active-low (pull to GND when closed)
// - MCP23017 has built-in pull-up resistors
//
// Switch mapping (GPA0-GPA7):
// GPA0 → B1, GPA1 → B2, GPA2 → B3, GPA3 → B4
// GPA4 → L1, GPA5 → R1, GPA6 → L2, GPA7 → R2

// I2C GPIO expander for per-button turbo switches
// Enables 8 physical switches (B1-B4, L1-L2, R1-R2)
// Uncomment to enable hardware turbo switches
#define TURBO_I2C_SWITCHES_ENABLED 1
#define TURBO_I2C_SDA_PIN 0 // Must match display I2C pins (if display enabled)
#define TURBO_I2C_SCL_PIN 1 // Must match display I2C pins (if display enabled)
#define TURBO_I2C_BLOCK i2c0 // I2C block instance (i2c0 or i2c1)
#define TURBO_I2C_SPEED 400000 // 400kHz (safe speed for most I2C devices)
#define TURBO_I2C_ADDR 0x27 // MCP23017 I2C address (default: 0x20, avoid display at 0x3C)

// Turbo Speed Dial (Potentiometer)
// Analog input for adjustable turbo speed (2-30 shots/second)
// Set to a valid ADC pin (26, 27, or 28) to enable
// Set to -1 to disable
// #define PIN_SHMUP_DIAL 26

#define BOARD_LEDS_PIN 28
#define LED_BRIGHTNESS_MAXIMUM 100
#define LED_BRIGHTNESS_STEPS 5
Expand Down
39 changes: 38 additions & 1 deletion configs/Pico/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,41 @@ Basic pin setup for a stock Raspberry Pi Pico. Combine with a simple GPIO breako
| GPIO_PIN_18| GpioAction::BUTTON_PRESS_L3 | L3 | LS | LS | L3 | 11 | LS |
| GPIO_PIN_19| GpioAction::BUTTON_PRESS_R3 | R3 | RS | RS | R3 | 12 | RS |
| GPIO_PIN_20| GpioAction::BUTTON_PRESS_A1 | A1 | Guide | Home | PS | 13 | ~ |
| GPIO_PIN_21| GpioAction::BUTTON_PRESS_A2 | A2 | ~ | Capture| ~ | 14 | ~ |
| GPIO_PIN_21| GpioAction::BUTTON_PRESS_A2 | A2 | ~ | Capture| ~ | 14 | ~ |

## Hardware Turbo Controls (Optional)

This board supports optional hardware turbo controls via I2C GPIO expander:

### Pin Assignments
| RP2040 Pin | Function | Notes |
|------------|----------|-------|
| GPIO 0 | I2C SDA | Shared with display |
| GPIO 1 | I2C SCL | Shared with display |
| GPIO 26 | Speed Dial (ADC0) | Optional 10kΩ potentiometer |

### Required Hardware
- MCP23017 I2C GPIO Expander breakout board (I2C address 0x20)
- 8× SPST toggle switches (or DIP-8 switch array)
- 10kΩ linear potentiometer (optional, for adjustable turbo speed)

### Switch Mapping
The MCP23017's Port A pins map to the following buttons:
- GPA0 → B1 (Face button 1)
- GPA1 → B2 (Face button 2)
- GPA2 → B3 (Face button 3)
- GPA3 → B4 (Face button 4)
- GPA4 → L1 (Left shoulder 1)
- GPA5 → R1 (Right shoulder 1)
- GPA6 → L2 (Left shoulder 2)
- GPA7 → R2 (Right shoulder 2)

### Configuration
Enable in `BoardConfig.h`:
```cpp
#define TURBO_I2C_SWITCHES_ENABLED 1
#define TURBO_I2C_ADDR 0x20 // MCP23017 I2C address
#define PIN_SHMUP_DIAL 26 // Optional speed dial
```

See external documentation for complete wiring guide and troubleshooting.
7 changes: 7 additions & 0 deletions headers/addons/turbo.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include "eventmanager.h"
#include "enums.pb.h"

#ifdef TURBO_I2C_SWITCHES_ENABLED
#include "mcp23017.h"
#endif

#ifndef TURBO_ENABLED
#define TURBO_ENABLED 0
#endif
Expand Down Expand Up @@ -138,5 +142,8 @@ class TurboInput : public GPAddon {
uint8_t encoderValue; // Rotary encoder value
bool hasTurboAssigned; // Turbo enabled on a pin.
uint8_t lastShotCount; // Last shot count for comparison
#ifdef TURBO_I2C_SWITCHES_ENABLED
MCP23017* mcp_; // I2C expander instance
#endif
};
#endif // TURBO_H_
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_subdirectory(CRC32)
add_subdirectory(FlashPROM)
add_subdirectory(httpd)
add_subdirectory(lwip-port)
add_subdirectory(mcp23017)
add_subdirectory(nanopb)
add_subdirectory(NeoPico)
add_subdirectory(OneBitDisplay)
Expand Down
4 changes: 4 additions & 0 deletions lib/mcp23017/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
add_library(mcp23017 INTERFACE)
target_sources(mcp23017 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/mcp23017.cpp)
target_include_directories(mcp23017 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(mcp23017 INTERFACE hardware_i2c)
65 changes: 65 additions & 0 deletions lib/mcp23017/mcp23017.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "mcp23017.h"
#include "pico/time.h"

MCP23017::MCP23017(i2c_inst_t* i2c, uint8_t addr)
: i2c_(i2c), addr_(addr) {}

bool MCP23017::init() {
// Configure IOCON register first to ensure we're in the right mode
// IOCON.BANK = 0 (sequential register addresses)
// IOCON.SEQOP = 0 (sequential operation enabled)
writeRegister(MCP23017_IOCON, 0x00);
sleep_ms(10); // Small delay for chip to process

// Configure all pins on Port A as inputs with pull-ups
writeRegister(MCP23017_IODIRA, 0xFF); // All inputs
sleep_ms(1);

// Enable pull-ups on Port A
writeRegister(MCP23017_GPPUA, 0xFF); // All pull-ups enabled
sleep_ms(1);

// Verify the configuration by reading back
uint8_t verifyDir = readRegister(MCP23017_IODIRA);
uint8_t verifyPullup = readRegister(MCP23017_GPPUA);

if (verifyDir != 0xFF || verifyPullup != 0xFF) {
return false; // MCP23017 not configured correctly
}

// Port B unused (all inputs, no pull-ups)
writeRegister(MCP23017_IODIRB, 0xFF);
writeRegister(MCP23017_GPPUB, 0x00);

return true;
}

void MCP23017::writeRegister(uint8_t reg, uint8_t value) {
uint8_t buf[2] = {reg, value};
i2c_write_blocking(i2c_, addr_, buf, 2, false);
}

uint8_t MCP23017::readRegister(uint8_t reg) {
uint8_t value = 0xFF; // Default to all high (unpulled state)
int result = i2c_write_blocking(i2c_, addr_, &reg, 1, true);
if (result == 1) {
i2c_read_blocking(i2c_, addr_, &value, 1, false);
}
return value;
}

uint16_t MCP23017::readAll() {
uint8_t portA = readRegister(MCP23017_GPIOA);
uint8_t portB = readRegister(MCP23017_GPIOB);
return (portB << 8) | portA;
}

bool MCP23017::readPin(uint8_t pin) {
if (pin < 8) {
uint8_t portA = readRegister(MCP23017_GPIOA);
return (portA & (1 << pin)) != 0;
} else {
uint8_t portB = readRegister(MCP23017_GPIOB);
return (portB & (1 << (pin - 8))) != 0;
}
}
109 changes: 109 additions & 0 deletions lib/mcp23017/mcp23017.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* @file mcp23017.h
* @brief Driver for MCP23017 I2C GPIO Expander
*
* Provides simple interface for reading switches connected to MCP23017.
* Used by turbo add-on for hardware per-button turbo controls.
*
* Hardware: MCP23017 16-bit I/O expander
* I2C Address: Configurable via A0-A2 pins (default 0x20)
* Pins Used: Port A (GPA0-GPA7) for 8 switches
*
* Configuration:
* - Port A configured as inputs with pull-up resistors enabled
* - Switches should be wired active-low (closed = LOW, open = HIGH)
* - Port B unused (configured as inputs, no pull-ups)
*
* Register Mode: IOCON.BANK = 0 (sequential addressing mode)
*/

#ifndef MCP23017_H
#define MCP23017_H

#include "hardware/i2c.h"
#include <stdint.h>

// MCP23017 Register Addresses (IOCON.BANK = 0, sequential mode)
#define MCP23017_IODIRA 0x00 // I/O direction A
#define MCP23017_IODIRB 0x01 // I/O direction B
#define MCP23017_IPOLA 0x02 // Input polarity A
#define MCP23017_IPOLB 0x03 // Input polarity B
#define MCP23017_GPINTENA 0x04 // Interrupt-on-change A
#define MCP23017_GPINTENB 0x05 // Interrupt-on-change B
#define MCP23017_DEFVALA 0x06 // Default value A
#define MCP23017_DEFVALB 0x07 // Default value B
#define MCP23017_INTCONA 0x08 // Interrupt control A
#define MCP23017_INTCONB 0x09 // Interrupt control B
#define MCP23017_IOCON 0x0A // I/O expander configuration
#define MCP23017_GPPUA 0x0C // Pull-up A
#define MCP23017_GPPUB 0x0D // Pull-up B
#define MCP23017_INTFA 0x0E // Interrupt flag A
#define MCP23017_INTFB 0x0F // Interrupt flag B
#define MCP23017_INTCAPA 0x10 // Interrupt captured value A
#define MCP23017_INTCAPB 0x11 // Interrupt captured value B
#define MCP23017_GPIOA 0x12 // Port A
#define MCP23017_GPIOB 0x13 // Port B
#define MCP23017_OLATA 0x14 // Output latch A
#define MCP23017_OLATB 0x15 // Output latch B

class MCP23017 {
public:
/**
* @brief Construct a new MCP23017 driver instance
* @param i2c Pointer to I2C hardware instance (i2c0 or i2c1)
* @param addr I2C device address (default 0x20)
*/
MCP23017(i2c_inst_t* i2c, uint8_t addr);

/**
* @brief Initialize the MCP23017 chip
*
* Configures Port A as inputs with pull-ups enabled.
* Port B is configured as inputs with no pull-ups (unused).
*
* @return true if initialization successful, false otherwise
*/
bool init();

/**
* @brief Set pin direction (input/output)
* @param pin Pin number (0-15)
* @param input true for input, false for output
*/
void setPinMode(uint8_t pin, bool input);

/**
* @brief Enable/disable pull-up resistor on a pin
* @param pin Pin number (0-15)
* @param enable true to enable pull-up, false to disable
*/
void setPullup(uint8_t pin, bool enable);

/**
* @brief Read state of a single pin
* @param pin Pin number (0-15)
* @return true if pin is HIGH, false if LOW
*/
bool readPin(uint8_t pin);

/**
* @brief Read all 16 pins at once
* @return 16-bit value with pin states (bit 0 = GPA0, bit 15 = GPB7)
*/
uint16_t readAll();

/**
* @brief Read a MCP23017 register directly
* @param reg Register address
* @return Register value
*/
uint8_t readRegister(uint8_t reg);

private:
i2c_inst_t* i2c_;
uint8_t addr_;

void writeRegister(uint8_t reg, uint8_t value);
};

#endif // MCP23017_H
1 change: 1 addition & 0 deletions pico-sdk-2.1.1
Submodule pico-sdk-2.1.1 added at bddd20
46 changes: 46 additions & 0 deletions src/addons/turbo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ void TurboInput::setup(){
nextTimer = getMicro();
encoderValue = shotCount;
updateTurboShotCount(shotCount, false);

#ifdef TURBO_I2C_SWITCHES_ENABLED
// Initialize MCP23017 I2C GPIO expander for hardware turbo switches
// Note: I2C bus should already be initialized by display addon or I2C0_ENABLED config
// The MCP23017 shares the I2C bus with other devices (e.g., OLED displays)
// Initialization failure is graceful - turbo will still work via software controls
mcp_ = new MCP23017(TURBO_I2C_BLOCK, TURBO_I2C_ADDR);

if (!mcp_->init()) {
// MCP23017 not detected or initialization failed
// This is non-fatal - hardware turbo switches simply won't be available
delete mcp_;
mcp_ = nullptr;
}
#endif
}

/**
Expand All @@ -132,6 +147,37 @@ void TurboInput::process()

if (!options.enabled && (!hasTurboAssigned == true)) return;

#ifdef TURBO_I2C_SWITCHES_ENABLED
// Hardware Turbo Switches: Read physical switch states from MCP23017 I2C GPIO expander
// This provides per-button turbo control via 8 physical toggle switches
if (mcp_) {
// Read all 8 switches from MCP23017 Port A
// Switches are active-low: bit=0 means switch is closed (turbo enabled)
// bit=1 means switch is open (turbo disabled)
uint8_t switchStates = mcp_->readRegister(MCP23017_GPIOA);

// Build turbo mask from switch states
// Only buttons with closed switches will have turbo enabled
uint16_t hardwareTurboMask = 0;

// Map MCP23017 pins to button masks (active-low logic)
// Each GPA pin corresponds to one button's turbo control
if (!(switchStates & 0x01)) hardwareTurboMask |= GAMEPAD_MASK_B1; // GPA0 → B1 (Face button 1)
if (!(switchStates & 0x02)) hardwareTurboMask |= GAMEPAD_MASK_B2; // GPA1 → B2 (Face button 2)
if (!(switchStates & 0x04)) hardwareTurboMask |= GAMEPAD_MASK_B3; // GPA2 → B3 (Face button 3)
if (!(switchStates & 0x08)) hardwareTurboMask |= GAMEPAD_MASK_B4; // GPA3 → B4 (Face button 4)
if (!(switchStates & 0x10)) hardwareTurboMask |= GAMEPAD_MASK_L1; // GPA4 → L1 (Left shoulder 1)
if (!(switchStates & 0x20)) hardwareTurboMask |= GAMEPAD_MASK_R1; // GPA5 → R1 (Right shoulder 1)
if (!(switchStates & 0x40)) hardwareTurboMask |= GAMEPAD_MASK_L2; // GPA6 → L2 (Left shoulder 2)
if (!(switchStates & 0x80)) hardwareTurboMask |= GAMEPAD_MASK_R2; // GPA7 → R2 (Right shoulder 2)

// Hardware switches have priority: they override software/web configurator turbo settings
// This ensures physical switches always have immediate, reliable control
turboButtonsMask = hardwareTurboMask;
gamepad->turboState.buttons = turboButtonsMask;
}
#endif

// Check if shotCount changed externally (e.g., via hotkey)
if (options.shotCount != lastShotCount){
updateTurboShotCount(options.shotCount, false);
Expand Down
Loading