diff --git a/CMakeLists.txt b/CMakeLists.txt index 69f3e19..d4297fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,14 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) target_link_libraries( - ${PROJECT_NAME} PUBLIC tinyusb_device tinyusb_board pico_stdlib hardware_adc - pico_multicore pio_ws2812) + ${PROJECT_NAME} + PUBLIC tinyusb_device + tinyusb_board + pico_stdlib + hardware_adc + pico_multicore + pio_ws2812 + mcp23017) pico_enable_stdio_usb(${PROJECT_NAME} 1) pico_enable_stdio_uart(${PROJECT_NAME} 0) diff --git a/include/GlobalConfiguration.h b/include/GlobalConfiguration.h index eed9418..f44b20d 100644 --- a/include/GlobalConfiguration.h +++ b/include/GlobalConfiguration.h @@ -1,9 +1,12 @@ #ifndef _GLOBALCONFIGURATION_H_ #define _GLOBALCONFIGURATION_H_ +#include "peripherals/Buttons.h" #include "peripherals/Drum.h" #include "peripherals/StatusLed.h" +#include "hardware/i2c.h" + namespace Doncon::Config::Default { const Peripherals::Drum::Config drum_config = { @@ -15,10 +18,45 @@ const Peripherals::Drum::Config drum_config = { 3, // Ka Right }, 400, // Trigger threshold - 100, // Double hit threshold + 100, // Double hit threshold 17, // Debounce delay in milliseconds }; +const Peripherals::Buttons::Config button_config = { + // I2C config + { + 6, // SDA Pin + 7, // SCL Pin + i2c1, // Block + 1000000, // Speed + 0x20, // Address + }, + + // Pins + {{ + 0, // Up + 1, // Down + 2, // Left + 3, // Right + }, + { + 8, // North + 9, // East + 10, // South + 11, // West + + 4, // L + 12, // R + + 13, // Start + 5, // Select + 14, // Home + 6, // Share + }}, + + 20, // Debounce delay in milliseconds +}; + const Peripherals::StatusLed::Config led_config = { {255, 0, 0}, // Don Left Color {0, 0, 255}, // Ka Left Color diff --git a/include/peripherals/Buttons.h b/include/peripherals/Buttons.h new file mode 100644 index 0000000..63e8e0f --- /dev/null +++ b/include/peripherals/Buttons.h @@ -0,0 +1,110 @@ +#ifndef _PERIPHERALS_BUTTONS_H_ +#define _PERIPHERALS_BUTTONS_H_ + +#include "utils/InputState.h" + +#include + +#include +#include +#include + +namespace Doncon::Peripherals { + +class Buttons { + public: + struct Config { + + struct { + uint8_t sda_pin; + uint8_t scl_pin; + i2c_inst_t *block; + uint speed_hz; + uint8_t address; + } i2c; + + struct { + struct { + uint8_t up; + uint8_t down; + uint8_t left; + uint8_t right; + } dpad; + + struct { + uint8_t north; + uint8_t east; + uint8_t south; + uint8_t west; + + uint8_t l; + uint8_t r; + + uint8_t start; + uint8_t select; + uint8_t home; + uint8_t share; + } buttons; + } pins; + + uint8_t debounce_delay_ms; + }; + + private: + enum class Id { + UP, + DOWN, + LEFT, + RIGHT, + NORTH, + EAST, + SOUTH, + WEST, + L, + R, + START, + SELECT, + HOME, + SHARE, + }; + + class Button { + private: + uint8_t gpio_pin; + uint16_t gpio_mask; + + uint32_t last_change; + bool active; + + public: + Button(uint8_t pin); + + uint8_t getGpioPin() const { return gpio_pin; }; + uint16_t getGpioMask() const { return gpio_mask; }; + + bool getState() const { return active; }; + void setState(bool state, uint8_t debounce_delay); + }; + + struct SocdState { + Id lastVertical; + Id lastHorizontal; + }; + + Config m_config; + SocdState m_socd_state; + std::map m_buttons; + + std::unique_ptr m_mcp23017; + + void socdClean(Utils::InputState &input_state); + + public: + Buttons(const Config &config); + + void updateInputState(Utils::InputState &input_state); +}; + +} // namespace Doncon::Peripherals + +#endif // _PERIPHERALS_BUTTONS_H_ \ No newline at end of file diff --git a/include/utils/InputState.h b/include/utils/InputState.h index a37bc8b..8596efb 100644 --- a/include/utils/InputState.h +++ b/include/utils/InputState.h @@ -20,8 +20,20 @@ struct InputState { Pad don_left, ka_left, don_right, ka_right; }; + struct DPad { + bool up, down, left, right; + }; + + struct Buttons { + bool north, east, south, west; + bool l, r; + bool start, select, home, share; + }; + public: Drum drum; + DPad dpad; + Buttons buttons; private: xinput_report_t m_xinput_report; diff --git a/src/main.cpp b/src/main.cpp index 3427cdb..00fb8ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "peripherals/Buttons.h" #include "peripherals/Drum.h" #include "peripherals/StatusLed.h" #include "usb/usb_driver.h" @@ -36,6 +37,7 @@ int main() { Utils::InputState input_state; Peripherals::Drum drum(Config::Default::drum_config); + Peripherals::Buttons buttons(Config::Default::button_config); // Move to core 1? usb_mode_t mode = USB_MODE_XBOX360; usb_driver_init(mode); @@ -46,6 +48,7 @@ int main() { while (true) { drum.updateInputState(input_state); + buttons.updateInputState(input_state); usb_driver_send_and_receive_report(input_state.getReport(mode)); usb_driver_task(); diff --git a/src/peripherals/Buttons.cpp b/src/peripherals/Buttons.cpp new file mode 100644 index 0000000..26ca794 --- /dev/null +++ b/src/peripherals/Buttons.cpp @@ -0,0 +1,102 @@ +#include "peripherals/Buttons.h" + +#include "hardware/gpio.h" +#include "pico/time.h" + +namespace Doncon::Peripherals { + +Buttons::Button::Button(uint8_t pin) : gpio_pin(pin), gpio_mask(1 << pin), last_change(0), active(false) {} + +void Buttons::Button::setState(bool state, uint8_t debounce_delay) { + if (active == state) { + return; + } + + // Immediately change the input state, but only allow a change every debounce_delay milliseconds. + uint32_t now = to_ms_since_boot(get_absolute_time()); + if (last_change + debounce_delay <= now) { + active = state; + last_change = now; + } +} + +void Buttons::socdClean(Utils::InputState &input_state) { + + // Last input has priority + if (input_state.dpad.up && input_state.dpad.down) { + if (m_socd_state.lastVertical == Id::DOWN) { + input_state.dpad.down = false; + } else if (m_socd_state.lastVertical == Id::UP) { + input_state.dpad.up = false; + } + } else if (input_state.dpad.up) { + m_socd_state.lastVertical = Id::UP; + } else { + m_socd_state.lastVertical = Id::DOWN; + } + + if (input_state.dpad.left && input_state.dpad.right) { + if (m_socd_state.lastHorizontal == Id::RIGHT) { + input_state.dpad.right = false; + } else if (m_socd_state.lastHorizontal == Id::LEFT) { + input_state.dpad.left = false; + } + } else if (input_state.dpad.left) { + m_socd_state.lastHorizontal = Id::LEFT; + } else { + m_socd_state.lastHorizontal = Id::RIGHT; + } +} + +Buttons::Buttons(const Config &config) : m_config(config), m_socd_state{Id::DOWN, Id::RIGHT} { + i2c_init(m_config.i2c.block, m_config.i2c.speed_hz); + gpio_set_function(m_config.i2c.sda_pin, GPIO_FUNC_I2C); + gpio_set_function(m_config.i2c.scl_pin, GPIO_FUNC_I2C); + gpio_pull_up(m_config.i2c.sda_pin); + gpio_pull_up(m_config.i2c.scl_pin); + + m_mcp23017 = std::make_unique(m_config.i2c.address, m_config.i2c.block); + m_mcp23017->setDirection(0xFFFF); // All inputs + m_mcp23017->setPullup(0xFFFF); // All on + + m_buttons.emplace(Id::UP, config.pins.dpad.up); + m_buttons.emplace(Id::DOWN, config.pins.dpad.down); + m_buttons.emplace(Id::LEFT, config.pins.dpad.left); + m_buttons.emplace(Id::RIGHT, config.pins.dpad.right); + m_buttons.emplace(Id::NORTH, config.pins.buttons.north); + m_buttons.emplace(Id::EAST, config.pins.buttons.east); + m_buttons.emplace(Id::SOUTH, config.pins.buttons.south); + m_buttons.emplace(Id::WEST, config.pins.buttons.west); + m_buttons.emplace(Id::L, config.pins.buttons.l); + m_buttons.emplace(Id::R, config.pins.buttons.r); + m_buttons.emplace(Id::START, config.pins.buttons.start); + m_buttons.emplace(Id::SELECT, config.pins.buttons.select); + m_buttons.emplace(Id::HOME, config.pins.buttons.home); + m_buttons.emplace(Id::SHARE, config.pins.buttons.share); +} + +void Buttons::updateInputState(Utils::InputState &input_state) { + uint16_t gpio_state = ~m_mcp23017->read(); + + for (auto &button : m_buttons) { + button.second.setState(gpio_state & button.second.getGpioMask(), m_config.debounce_delay_ms); + } + + input_state.dpad.up = m_buttons.at(Id::UP).getState(); + input_state.dpad.down = m_buttons.at(Id::DOWN).getState(); + input_state.dpad.left = m_buttons.at(Id::LEFT).getState(); + input_state.dpad.right = m_buttons.at(Id::RIGHT).getState(); + input_state.buttons.north = m_buttons.at(Id::NORTH).getState(); + input_state.buttons.east = m_buttons.at(Id::EAST).getState(); + input_state.buttons.south = m_buttons.at(Id::SOUTH).getState(); + input_state.buttons.west = m_buttons.at(Id::WEST).getState(); + input_state.buttons.l = m_buttons.at(Id::L).getState(); + input_state.buttons.r = m_buttons.at(Id::R).getState(); + input_state.buttons.start = m_buttons.at(Id::START).getState(); + input_state.buttons.select = m_buttons.at(Id::SELECT).getState(); + input_state.buttons.home = m_buttons.at(Id::HOME).getState(); + input_state.buttons.share = m_buttons.at(Id::SHARE).getState(); + + socdClean(input_state); +} +} // namespace Doncon::Peripherals diff --git a/src/utils/InputState.cpp b/src/utils/InputState.cpp index 5515ea6..05adf69 100644 --- a/src/utils/InputState.cpp +++ b/src/utils/InputState.cpp @@ -6,7 +6,8 @@ namespace Doncon::Utils { InputState::InputState() - : drum({{false, 0}, {false, 0}, {false, 0}, {false, 0}}), + : drum({{false, 0}, {false, 0}, {false, 0}, {false, 0}}), dpad({false, false, false, false}), + buttons({false, false, false, false, false, false, false, false, false, false}), m_xinput_report({0x00, sizeof(xinput_report_t), 0, 0, 0, 0, 0, 0, 0, 0, {}}) {} usb_report_t InputState::getReport(usb_mode_t mode) { @@ -28,24 +29,24 @@ usb_report_t InputState::getReport(usb_mode_t mode) { } usb_report_t InputState::getXinputReport() { - m_xinput_report.buttons1 = 0 // - | (false ? (1 << 0) : 0) // Dpad Up - | (drum.don_left.triggered ? (1 << 1) : 0) // Dpad Down - | (drum.ka_left.triggered ? (1 << 2) : 0) // Dpad Left - | (false ? (1 << 3) : 0) // Dpad Right - | (false ? (1 << 4) : 0) // Start - | (false ? (1 << 5) : 0) // Select - | (false ? (1 << 6) : 0) // L3 - | (false ? (1 << 7) : 0); // R3 + m_xinput_report.buttons1 = 0 // + | (dpad.up ? (1 << 0) : 0) // Dpad Up + | ((dpad.down || drum.don_left.triggered) ? (1 << 1) : 0) // Dpad Down + | ((dpad.left || drum.ka_left.triggered) ? (1 << 2) : 0) // Dpad Left + | (dpad.right ? (1 << 3) : 0) // Dpad Right + | (buttons.start ? (1 << 4) : 0) // Start + | (buttons.select ? (1 << 5) : 0) // Select + | (false ? (1 << 6) : 0) // L3 + | (false ? (1 << 7) : 0); // R3 - m_xinput_report.buttons2 = 0 // - | (false ? (1 << 0) : 0) // L1 - | (false ? (1 << 1) : 0) // R1 - | (false ? (1 << 2) : 0) // Guide - | (drum.don_right.triggered ? (1 << 4) : 0) // A - | (drum.ka_right.triggered ? (1 << 5) : 0) // B - | (false ? (1 << 6) : 0) // X - | (false ? (1 << 7) : 0); // Y + m_xinput_report.buttons2 = 0 // + | (buttons.l ? (1 << 0) : 0) // L1 + | (buttons.r ? (1 << 1) : 0) // R1 + | (buttons.home ? (1 << 2) : 0) // Guide + | ((buttons.south || drum.don_right.triggered) ? (1 << 4) : 0) // A + | ((buttons.east || drum.ka_right.triggered) ? (1 << 5) : 0) // B + | (buttons.west ? (1 << 6) : 0) // X + | (buttons.north ? (1 << 7) : 0); // Y m_xinput_report.lt = 0; m_xinput_report.rt = 0;