diff --git a/HAL/pico/include/input/DebouncedGpioButtonInput.hpp b/HAL/pico/include/input/DebouncedGpioButtonInput.hpp index 916c34d9..aa3dfa71 100644 --- a/HAL/pico/include/input/DebouncedGpioButtonInput.hpp +++ b/HAL/pico/include/input/DebouncedGpioButtonInput.hpp @@ -21,13 +21,9 @@ template class DebouncedGpioButtonInput : public GpioButto uint32_t _debounce_period_ms; void UpdateButtonState(InputState &inputs, size_t button_mapping_index, bool pressed) { - bool state_changed = update_debounce_state( - _debounce_state[button_mapping_index], - pressed, - _debounce_period_ms - ); - if (state_changed) { - set_button(inputs.buttons, _button_mappings[button_mapping_index].button, pressed); + update_debounce_state(_debounce_state[button_mapping_index], pressed, _debounce_period_ms); + if (_debounce_state[button_mapping_index].pressed) { + set_button(inputs.buttons, _button_mappings[button_mapping_index].button, true); } } }; diff --git a/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp b/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp index bd6d6dd8..290e0634 100644 --- a/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp +++ b/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp @@ -23,14 +23,9 @@ class DebouncedSwitchMatrixInput : public SwitchMatrixInput uint32_t _debounce_period_ms; void UpdateButtonState(InputState &inputs, size_t col_index, size_t row_index, bool pressed) { - bool state_changed = update_debounce_state( - _debounce_state[col_index][row_index], - pressed, - _debounce_period_ms - ); - if (state_changed) { - Button button = this->_matrix[col_index][row_index]; - set_button(inputs.buttons, button, pressed); + update_debounce_state(_debounce_state[col_index][row_index], pressed, _debounce_period_ms); + if (_debounce_state[col_index][row_index].pressed) { + set_button(inputs.buttons, this->_matrix[col_index][row_index], true); } }; }; diff --git a/HAL/pico/src/input/Pca9671Input.cpp b/HAL/pico/src/input/Pca9671Input.cpp index fd501853..666c656e 100644 --- a/HAL/pico/src/input/Pca9671Input.cpp +++ b/HAL/pico/src/input/Pca9671Input.cpp @@ -31,6 +31,10 @@ InputScanSpeed Pca9671Input::ScanSpeed() { } void Pca9671Input::UpdateInputs(InputState &inputs) { + for (size_t i = 0; i < _button_count; i++) { + set_button(inputs.buttons, _button_mappings[i].button, false); + } + uint16_t pin_values = _pcf.read16(); for (size_t i = 0; i < _button_count; i++) { @@ -43,5 +47,7 @@ void Pca9671Input::UpdateButtonState( size_t button_mapping_index, bool pressed ) { - set_button(inputs.buttons, _button_mappings[button_mapping_index].button, pressed); + if (pressed) { + set_button(inputs.buttons, _button_mappings[button_mapping_index].button, true); + } } \ No newline at end of file diff --git a/HAL/stm32/include/comms/XInputBackend.hpp b/HAL/stm32/include/comms/XInputBackend.hpp new file mode 100644 index 00000000..59a989b0 --- /dev/null +++ b/HAL/stm32/include/comms/XInputBackend.hpp @@ -0,0 +1,20 @@ +#ifndef _COMMS_XINPUTBACKEND_HPP +#define _COMMS_XINPUTBACKEND_HPP + +#include "core/CommunicationBackend.hpp" +#include "core/InputSource.hpp" +#include "stdlib.hpp" + +#include + +class XInputBackend : public CommunicationBackend { + public: + XInputBackend(InputState &inputs, InputSource **input_sources, size_t input_source_count); + CommunicationBackendId BackendId(); + void SendReport(); + + private: + USBXBox360 _xbox360; +}; + +#endif diff --git a/HAL/stm32/include/config_defaults.hpp b/HAL/stm32/include/config_defaults.hpp new file mode 100644 index 00000000..359d3f0d --- /dev/null +++ b/HAL/stm32/include/config_defaults.hpp @@ -0,0 +1,121 @@ +#ifndef _CONFIG_DEFAULTS_HPP +#define _CONFIG_DEFAULTS_HPP + +#include + +// clang-format off + +const Config default_config = { + .game_mode_configs_count = 4, + .game_mode_configs = new GameModeConfig[4] { + GameModeConfig { + .mode_id = MODE_MELEE, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP_NO_REAC }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF4 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP_NO_REAC }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF3 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_ULTIMATE, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF2 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_FGC, + .name = {}, + .socd_pairs_count = 2, + .socd_pairs = new SocdPair[2] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LT1, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = new ButtonRemap[1] { + ButtonRemap { .physical_button = BTN_RT4, .activates = BTN_LT1 }, + }, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF1 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + }, + .communication_backend_configs_count = 1, + .communication_backend_configs = new CommunicationBackendConfig[1] { + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_XINPUT, + .default_mode_config = 4, + .activation_binding_count = 0, + .activation_binding = {}, + .secondary_backends = {}, + }, + }, + .custom_modes_count = 0, + .custom_modes = {}, + .keyboard_modes_count = 0, + .keyboard_modes = {}, + .rgb_configs_count = 0, + .rgb_configs = {}, + .default_backend_config = 1, + .default_usb_backend_config = 1, + .rgb_brightness = 0, + .has_melee_options = true, + .melee_options = { + .crouch_walk_os = false, + .disable_ledgedash_socd_override = false, + .has_custom_airdodge = false, + .custom_airdodge = { .x = 0, .y = 0 }, + }, + .has_project_m_options = true, + .project_m_options = { + .true_z_press = true, + .disable_ledgedash_socd_override = false, + .has_custom_airdodge = false, + .custom_airdodge = { .x = 0, .y = 0 }, + }, +}; + +// clang-format on + +#endif diff --git a/HAL/stm32/include/core/KeyboardMode.hpp b/HAL/stm32/include/core/KeyboardMode.hpp new file mode 100644 index 00000000..974ad30f --- /dev/null +++ b/HAL/stm32/include/core/KeyboardMode.hpp @@ -0,0 +1,25 @@ +#ifndef _CORE_KEYBOARDMODE_HPP +#define _CORE_KEYBOARDMODE_HPP + +#include "core/InputMode.hpp" +#include "core/socd.hpp" +#include "core/state.hpp" + +#include + +class KeyboardMode : public InputMode { + public: + KeyboardMode(); + ~KeyboardMode(); + void SendReport(const InputState &inputs); + + void UpdateOutputs(const InputState &inputs, OutputState &outputs) {} + + protected: + void Press(uint8_t keycode, bool press); + + private: + virtual void UpdateKeys(const InputState &inputs) = 0; +}; + +#endif diff --git a/HAL/stm32/include/gpio.hpp b/HAL/stm32/include/gpio.hpp new file mode 100644 index 00000000..52a15e5b --- /dev/null +++ b/HAL/stm32/include/gpio.hpp @@ -0,0 +1,25 @@ +#ifndef _GPIO_HPP +#define _GPIO_HPP + +#include "stdlib.hpp" + +namespace gpio { + enum class GpioMode { + GPIO_OUTPUT, + GPIO_INPUT, + GPIO_INPUT_PULLUP, + GPIO_INPUT_PULLDOWN, + }; + + void init_pin(uint pin, GpioMode mode); + + inline bool read_digital(uint pin) { + return digitalRead(pin); + } + + inline void write_digital(uint pin, bool value) { + digitalWrite(pin, value); + } +} + +#endif diff --git a/HAL/stm32/include/keycodes.h b/HAL/stm32/include/keycodes.h new file mode 100644 index 00000000..21ebd1c2 --- /dev/null +++ b/HAL/stm32/include/keycodes.h @@ -0,0 +1,180 @@ +#ifndef _KEYCODES_H +#define _KEYCODES_H + +// From TinyUSB: https://github.com/hathach/tinyusb/blob/master/src/class/hid/hid.h +//--------------------------------------------------------------------+ +// HID KEYCODE +//--------------------------------------------------------------------+ +#define HID_KEY_NONE 0x00 +#define HID_KEY_A 0x04 +#define HID_KEY_B 0x05 +#define HID_KEY_C 0x06 +#define HID_KEY_D 0x07 +#define HID_KEY_E 0x08 +#define HID_KEY_F 0x09 +#define HID_KEY_G 0x0A +#define HID_KEY_H 0x0B +#define HID_KEY_I 0x0C +#define HID_KEY_J 0x0D +#define HID_KEY_K 0x0E +#define HID_KEY_L 0x0F +#define HID_KEY_M 0x10 +#define HID_KEY_N 0x11 +#define HID_KEY_O 0x12 +#define HID_KEY_P 0x13 +#define HID_KEY_Q 0x14 +#define HID_KEY_R 0x15 +#define HID_KEY_S 0x16 +#define HID_KEY_T 0x17 +#define HID_KEY_U 0x18 +#define HID_KEY_V 0x19 +#define HID_KEY_W 0x1A +#define HID_KEY_X 0x1B +#define HID_KEY_Y 0x1C +#define HID_KEY_Z 0x1D +#define HID_KEY_1 0x1E +#define HID_KEY_2 0x1F +#define HID_KEY_3 0x20 +#define HID_KEY_4 0x21 +#define HID_KEY_5 0x22 +#define HID_KEY_6 0x23 +#define HID_KEY_7 0x24 +#define HID_KEY_8 0x25 +#define HID_KEY_9 0x26 +#define HID_KEY_0 0x27 +#define HID_KEY_ENTER 0x28 +#define HID_KEY_ESCAPE 0x29 +#define HID_KEY_BACKSPACE 0x2A +#define HID_KEY_TAB 0x2B +#define HID_KEY_SPACE 0x2C +#define HID_KEY_MINUS 0x2D +#define HID_KEY_EQUAL 0x2E +#define HID_KEY_BRACKET_LEFT 0x2F +#define HID_KEY_BRACKET_RIGHT 0x30 +#define HID_KEY_BACKSLASH 0x31 +#define HID_KEY_EUROPE_1 0x32 +#define HID_KEY_SEMICOLON 0x33 +#define HID_KEY_APOSTROPHE 0x34 +#define HID_KEY_GRAVE 0x35 +#define HID_KEY_COMMA 0x36 +#define HID_KEY_PERIOD 0x37 +#define HID_KEY_SLASH 0x38 +#define HID_KEY_CAPS_LOCK 0x39 +#define HID_KEY_F1 0x3A +#define HID_KEY_F2 0x3B +#define HID_KEY_F3 0x3C +#define HID_KEY_F4 0x3D +#define HID_KEY_F5 0x3E +#define HID_KEY_F6 0x3F +#define HID_KEY_F7 0x40 +#define HID_KEY_F8 0x41 +#define HID_KEY_F9 0x42 +#define HID_KEY_F10 0x43 +#define HID_KEY_F11 0x44 +#define HID_KEY_F12 0x45 +#define HID_KEY_PRINT_SCREEN 0x46 +#define HID_KEY_SCROLL_LOCK 0x47 +#define HID_KEY_PAUSE 0x48 +#define HID_KEY_INSERT 0x49 +#define HID_KEY_HOME 0x4A +#define HID_KEY_PAGE_UP 0x4B +#define HID_KEY_DELETE 0x4C +#define HID_KEY_END 0x4D +#define HID_KEY_PAGE_DOWN 0x4E +#define HID_KEY_ARROW_RIGHT 0x4F +#define HID_KEY_ARROW_LEFT 0x50 +#define HID_KEY_ARROW_DOWN 0x51 +#define HID_KEY_ARROW_UP 0x52 +#define HID_KEY_NUM_LOCK 0x53 +#define HID_KEY_KEYPAD_DIVIDE 0x54 +#define HID_KEY_KEYPAD_MULTIPLY 0x55 +#define HID_KEY_KEYPAD_SUBTRACT 0x56 +#define HID_KEY_KEYPAD_ADD 0x57 +#define HID_KEY_KEYPAD_ENTER 0x58 +#define HID_KEY_KEYPAD_1 0x59 +#define HID_KEY_KEYPAD_2 0x5A +#define HID_KEY_KEYPAD_3 0x5B +#define HID_KEY_KEYPAD_4 0x5C +#define HID_KEY_KEYPAD_5 0x5D +#define HID_KEY_KEYPAD_6 0x5E +#define HID_KEY_KEYPAD_7 0x5F +#define HID_KEY_KEYPAD_8 0x60 +#define HID_KEY_KEYPAD_9 0x61 +#define HID_KEY_KEYPAD_0 0x62 +#define HID_KEY_KEYPAD_DECIMAL 0x63 +#define HID_KEY_EUROPE_2 0x64 +#define HID_KEY_APPLICATION 0x65 +#define HID_KEY_POWER 0x66 +#define HID_KEY_KEYPAD_EQUAL 0x67 +#define HID_KEY_F13 0x68 +#define HID_KEY_F14 0x69 +#define HID_KEY_F15 0x6A +#define HID_KEY_F16 0x6B +#define HID_KEY_F17 0x6C +#define HID_KEY_F18 0x6D +#define HID_KEY_F19 0x6E +#define HID_KEY_F20 0x6F +#define HID_KEY_F21 0x70 +#define HID_KEY_F22 0x71 +#define HID_KEY_F23 0x72 +#define HID_KEY_F24 0x73 +#define HID_KEY_EXECUTE 0x74 +#define HID_KEY_HELP 0x75 +#define HID_KEY_MENU 0x76 +#define HID_KEY_SELECT 0x77 +#define HID_KEY_STOP 0x78 +#define HID_KEY_AGAIN 0x79 +#define HID_KEY_UNDO 0x7A +#define HID_KEY_CUT 0x7B +#define HID_KEY_COPY 0x7C +#define HID_KEY_PASTE 0x7D +#define HID_KEY_FIND 0x7E +#define HID_KEY_MUTE 0x7F +#define HID_KEY_VOLUME_UP 0x80 +#define HID_KEY_VOLUME_DOWN 0x81 +#define HID_KEY_LOCKING_CAPS_LOCK 0x82 +#define HID_KEY_LOCKING_NUM_LOCK 0x83 +#define HID_KEY_LOCKING_SCROLL_LOCK 0x84 +#define HID_KEY_KEYPAD_COMMA 0x85 +#define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 +#define HID_KEY_KANJI1 0x87 +#define HID_KEY_KANJI2 0x88 +#define HID_KEY_KANJI3 0x89 +#define HID_KEY_KANJI4 0x8A +#define HID_KEY_KANJI5 0x8B +#define HID_KEY_KANJI6 0x8C +#define HID_KEY_KANJI7 0x8D +#define HID_KEY_KANJI8 0x8E +#define HID_KEY_KANJI9 0x8F +#define HID_KEY_LANG1 0x90 +#define HID_KEY_LANG2 0x91 +#define HID_KEY_LANG3 0x92 +#define HID_KEY_LANG4 0x93 +#define HID_KEY_LANG5 0x94 +#define HID_KEY_LANG6 0x95 +#define HID_KEY_LANG7 0x96 +#define HID_KEY_LANG8 0x97 +#define HID_KEY_LANG9 0x98 +#define HID_KEY_ALTERNATE_ERASE 0x99 +#define HID_KEY_SYSREQ_ATTENTION 0x9A +#define HID_KEY_CANCEL 0x9B +#define HID_KEY_CLEAR 0x9C +#define HID_KEY_PRIOR 0x9D +#define HID_KEY_RETURN 0x9E +#define HID_KEY_SEPARATOR 0x9F +#define HID_KEY_OUT 0xA0 +#define HID_KEY_OPER 0xA1 +#define HID_KEY_CLEAR_AGAIN 0xA2 +#define HID_KEY_CRSEL_PROPS 0xA3 +#define HID_KEY_EXSEL 0xA4 +// RESERVED 0xA5-DF +#define HID_KEY_CONTROL_LEFT 0xE0 +#define HID_KEY_SHIFT_LEFT 0xE1 +#define HID_KEY_ALT_LEFT 0xE2 +#define HID_KEY_GUI_LEFT 0xE3 +#define HID_KEY_CONTROL_RIGHT 0xE4 +#define HID_KEY_SHIFT_RIGHT 0xE5 +#define HID_KEY_ALT_RIGHT 0xE6 +#define HID_KEY_GUI_RIGHT 0xE7 + +#endif \ No newline at end of file diff --git a/HAL/stm32/include/reboot.hpp b/HAL/stm32/include/reboot.hpp new file mode 100644 index 00000000..466f6cf3 --- /dev/null +++ b/HAL/stm32/include/reboot.hpp @@ -0,0 +1,7 @@ +#ifndef _REBOOT_HPP +#define _REBOOT_HPP + +void reboot_firmware(); +void reboot_bootloader(); + +#endif diff --git a/HAL/stm32/include/serial.hpp b/HAL/stm32/include/serial.hpp new file mode 100644 index 00000000..633edd07 --- /dev/null +++ b/HAL/stm32/include/serial.hpp @@ -0,0 +1,15 @@ +#ifndef _SERIAL_HPP +#define _SERIAL_HPP + +#include "stdlib.hpp" + +namespace serial { + void init(unsigned long baudrate); + void close(); + void print(const char *string); + void write(uint8_t byte); + void write(uint8_t *bytes, size_t len); + int available_for_write(); +} + +#endif diff --git a/HAL/stm32/include/stdlib.hpp b/HAL/stm32/include/stdlib.hpp new file mode 100644 index 00000000..98e6ba5d --- /dev/null +++ b/HAL/stm32/include/stdlib.hpp @@ -0,0 +1,8 @@ +#ifndef _HAL_STDLIB_HPP +#define _HAL_STDLIB_HPP + +#include + +typedef unsigned int uint; + +#endif diff --git a/HAL/stm32/include/util/state_util.hpp b/HAL/stm32/include/util/state_util.hpp new file mode 100644 index 00000000..d5330ce2 --- /dev/null +++ b/HAL/stm32/include/util/state_util.hpp @@ -0,0 +1 @@ +#include "../../avr/include/util/state_util.hpp" diff --git a/HAL/stm32/src/comms/XInputBackend.cpp b/HAL/stm32/src/comms/XInputBackend.cpp new file mode 100644 index 00000000..942798bf --- /dev/null +++ b/HAL/stm32/src/comms/XInputBackend.cpp @@ -0,0 +1,64 @@ +#include "comms/XInputBackend.hpp" + +#include "core/CommunicationBackend.hpp" +#include "core/state.hpp" + +#include + +XInputBackend::XInputBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : CommunicationBackend(inputs, input_sources, input_source_count), + _xbox360(0x0738, 0x4726) { + // Disconnect the maple core's default USB serial device first. + USBComposite.clear(); + _xbox360.registerComponent(); + USBComposite.begin(); + while (!USBComposite) + ; + // Suppress auto-send on each setter call; we send once at the end of SendReport(). + _xbox360.setManualReportMode(true); +} + +CommunicationBackendId XInputBackend::BackendId() { + return COMMS_BACKEND_XINPUT; +} + +void XInputBackend::SendReport() { + ScanInputs(InputScanSpeed::SLOW); + ScanInputs(InputScanSpeed::MEDIUM); + ScanInputs(InputScanSpeed::FAST); + + UpdateOutputs(); + + // Digital buttons + _xbox360.button(XBOX_A, _outputs.a); + _xbox360.button(XBOX_B, _outputs.b); + _xbox360.button(XBOX_X, _outputs.x); + _xbox360.button(XBOX_Y, _outputs.y); + _xbox360.button(XBOX_LSHOULDER, _outputs.buttonL); + _xbox360.button(XBOX_RSHOULDER, _outputs.buttonR); + _xbox360.button(XBOX_START, _outputs.start); + _xbox360.button(XBOX_BACK, _outputs.select); + _xbox360.button(XBOX_GUIDE, _outputs.home); + _xbox360.button(XBOX_DUP, _outputs.dpadUp); + _xbox360.button(XBOX_DDOWN, _outputs.dpadDown); + _xbox360.button(XBOX_DLEFT, _outputs.dpadLeft); + _xbox360.button(XBOX_DRIGHT, _outputs.dpadRight); + _xbox360.button(XBOX_L3, _outputs.leftStickClick); + _xbox360.button(XBOX_R3, _outputs.rightStickClick); + + // Triggers + _xbox360.sliderLeft(_outputs.triggerLDigital ? 255 : _outputs.triggerLAnalog); + _xbox360.sliderRight(_outputs.triggerRDigital ? 255 : _outputs.triggerRAnalog); + + // Analog sticks (convert 0-255 range to -32768..32767) + _xbox360.X((_outputs.leftStickX - 128) * 65535 / 255 + 128); + _xbox360.Y((_outputs.leftStickY - 128) * 65535 / 255 + 128); + _xbox360.XRight((_outputs.rightStickX - 128) * 65535 / 255 + 128); + _xbox360.YRight((_outputs.rightStickY - 128) * 65535 / 255 + 128); + + _xbox360.send(); +} diff --git a/HAL/stm32/src/comms/backend_init.cpp b/HAL/stm32/src/comms/backend_init.cpp new file mode 100644 index 00000000..15328d5c --- /dev/null +++ b/HAL/stm32/src/comms/backend_init.cpp @@ -0,0 +1,161 @@ +#include "comms/backend_init.hpp" + +#include "comms/XInputBackend.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "util/config_util.hpp" + +#include + +size_t initialize_backends( + CommunicationBackend **&backends, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout, + backend_config_selector_t get_backend_config, + usb_backend_getter_t get_usb_backend_config, + detect_console_t detect_console, + secondary_backend_initializer_t init_secondary_backends, + primary_backend_initializer_t init_primary_backend +) { + if (get_backend_config == nullptr || get_usb_backend_config == nullptr || + init_primary_backend == nullptr || detect_console == nullptr) { + return 0; + } + + CommunicationBackendConfig backend_config = CommunicationBackendConfig_init_zero; + get_backend_config(backend_config, inputs, config); + + CommunicationBackend *primary_backend = nullptr; + + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED) { + CommunicationBackendConfig usb_backend_config; + get_usb_backend_config(usb_backend_config, config); + init_primary_backend( + primary_backend, + usb_backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + CommunicationBackendId detected_backend_id = detect_console(pinout); + if (detected_backend_id == COMMS_BACKEND_XINPUT) { + backend_config = usb_backend_config; + } else { + backend_config = backend_config_from_id( + detected_backend_id, + config.communication_backend_configs, + config.communication_backend_configs_count + ); + } + } + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED && + config.default_backend_config > 0) { + backend_config = config.communication_backend_configs[config.default_backend_config - 1]; + } + + init_primary_backend( + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + + size_t backend_count = 1; + if (init_secondary_backends != nullptr) { + backend_count = init_secondary_backends( + backends, + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + } + + if (backend_config.default_mode_config > 0) { + GameModeConfig &mode_config = + config.game_mode_configs[backend_config.default_mode_config - 1]; + for (size_t i = 0; i < backend_count; i++) { + set_mode(backends[i], mode_config, config); + } + } + + return backend_count; +} + +void init_primary_backend( + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + switch (backend_id) { + case COMMS_BACKEND_UNSPECIFIED: + case COMMS_BACKEND_XINPUT: + default: + if (primary_backend == nullptr) { + primary_backend = + new XInputBackend(inputs, input_sources, input_source_count); + } + break; + } +} + +size_t init_secondary_backends( + CommunicationBackend **&backends, + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + size_t backend_count = 1; + backends = new CommunicationBackend *[backend_count] { primary_backend }; + return backend_count; +} + +// clang-format off + +backend_config_selector_t get_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const InputState &inputs, + Config &config +) { + backend_config = backend_config_from_buttons( + inputs, + config.communication_backend_configs, + config.communication_backend_configs_count + ); +}; + +usb_backend_getter_t get_usb_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const Config &config +) { + if (config.default_usb_backend_config > 0 && + config.default_usb_backend_config <= config.communication_backend_configs_count) { + backend_config = + config.communication_backend_configs[config.default_usb_backend_config - 1]; + } +}; + +// clang-format on + +primary_backend_initializer_t init_primary_backend_default = &init_primary_backend; +secondary_backend_initializer_t init_secondary_backends_default = &init_secondary_backends; diff --git a/HAL/stm32/src/comms/console_detection.cpp b/HAL/stm32/src/comms/console_detection.cpp new file mode 100644 index 00000000..bc59615f --- /dev/null +++ b/HAL/stm32/src/comms/console_detection.cpp @@ -0,0 +1,11 @@ +#include "comms/console_detection.hpp" + +#include "core/pinout.hpp" +#include "stdlib.hpp" + +#include + +CommunicationBackendId detect_console(const Pinout &pinout) { + // STM32 is always USB — return XInput. + return COMMS_BACKEND_XINPUT; +} diff --git a/HAL/stm32/src/core/KeyboardMode.cpp b/HAL/stm32/src/core/KeyboardMode.cpp new file mode 100644 index 00000000..1e0045de --- /dev/null +++ b/HAL/stm32/src/core/KeyboardMode.cpp @@ -0,0 +1,16 @@ +#include "core/KeyboardMode.hpp" + +#include "core/InputMode.hpp" + +KeyboardMode::KeyboardMode() : InputMode() {} + +KeyboardMode::~KeyboardMode() {} + +void KeyboardMode::SendReport(const InputState &inputs) { + // Keyboard mode is not yet supported on STM32. + // TODO: Implement using USBComposite HID keyboard. +} + +void KeyboardMode::Press(uint8_t keycode, bool press) { + // Stub — no keyboard support yet. +} diff --git a/HAL/stm32/src/core/mode_selection.cpp b/HAL/stm32/src/core/mode_selection.cpp new file mode 100644 index 00000000..cd8af66f --- /dev/null +++ b/HAL/stm32/src/core/mode_selection.cpp @@ -0,0 +1,111 @@ +#include "core/mode_selection.hpp" + +#include "core/state.hpp" +#include "modes/CustomControllerMode.hpp" +#include "modes/CustomKeyboardMode.hpp" +#include "modes/FgcMode.hpp" +#include "modes/Melee20Button.hpp" +#include "modes/ProjectM.hpp" +#include "modes/RivalsOfAether.hpp" +#include "modes/Ultimate.hpp" +#include "util/state_util.hpp" + +#include + +Melee20Button melee_mode; +ProjectM projectm_mode; +Ultimate ultimate_mode; +FgcMode fgc_mode; +RivalsOfAether rivals_mode; +CustomKeyboardMode keyboard_mode; +CustomControllerMode custom_mode; + +uint64_t mode_activation_masks[10]; + +size_t current_mode_index = SIZE_MAX; + +void set_mode(CommunicationBackend *backend, ControllerMode *mode) { + current_kb_mode = nullptr; + backend->SetGameMode(mode); +} + +void set_mode(CommunicationBackend *backend, KeyboardMode *mode) { + // Keyboard mode not supported on STM32 XInput backend. + return; +} + +void set_mode(CommunicationBackend *backend, GameModeConfig &mode_config, Config &config) { + switch (mode_config.mode_id) { + case MODE_MELEE: + melee_mode.SetConfig(mode_config, config.melee_options); + set_mode(backend, &melee_mode); + break; + case MODE_PROJECT_M: + projectm_mode.SetConfig(mode_config, config.project_m_options); + set_mode(backend, &projectm_mode); + break; + case MODE_ULTIMATE: + ultimate_mode.SetConfig(mode_config); + set_mode(backend, &ultimate_mode); + break; + case MODE_FGC: + fgc_mode.SetConfig(mode_config); + set_mode(backend, &fgc_mode); + break; + case MODE_RIVALS_OF_AETHER: + rivals_mode.SetConfig(mode_config); + set_mode(backend, &rivals_mode); + break; + case MODE_KEYBOARD: + // Not supported on STM32. + break; + case MODE_CUSTOM: + if (mode_config.custom_mode_config < 1 || + mode_config.custom_mode_config > config.custom_modes_count) { + break; + } + custom_mode.SetConfig( + mode_config, + config.custom_modes[mode_config.custom_mode_config - 1] + ); + set_mode(backend, &custom_mode); + break; + case MODE_UNSPECIFIED: + default: + break; + } +} + +void set_mode(CommunicationBackend *backend, GameModeId mode_id, Config &config) { + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode = config.game_mode_configs[i]; + if (mode.mode_id == mode_id) { + set_mode(backend, mode, config); + return; + } + } +} + +void select_mode(CommunicationBackend **backends, size_t backends_count, Config &config) { + InputState &inputs = backends[0]->GetInputs(); + + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode_config = config.game_mode_configs[i]; + if (all_buttons_held(inputs.buttons, mode_activation_masks[i]) && i != current_mode_index) { + current_mode_index = i; + for (size_t i = 0; i < backends_count; i++) { + set_mode(backends[i], mode_config, config); + } + return; + } + } +} + +void setup_mode_activation_bindings(const GameModeConfig *mode_configs, size_t mode_configs_count) { + for (size_t i = 0; i < mode_configs_count; i++) { + mode_activation_masks[i] = make_button_mask( + mode_configs[i].activation_binding, + mode_configs[i].activation_binding_count + ); + } +} diff --git a/HAL/stm32/src/gpio.cpp b/HAL/stm32/src/gpio.cpp new file mode 100644 index 00000000..7534f120 --- /dev/null +++ b/HAL/stm32/src/gpio.cpp @@ -0,0 +1,17 @@ +#include "gpio.hpp" + +#include "stdlib.hpp" + +namespace gpio { + void init_pin(uint pin, GpioMode mode) { + if (mode == GpioMode::GPIO_OUTPUT) { + pinMode(pin, OUTPUT); + } else if (mode == GpioMode::GPIO_INPUT_PULLUP) { + pinMode(pin, INPUT_PULLUP); + } else if (mode == GpioMode::GPIO_INPUT_PULLDOWN) { + pinMode(pin, INPUT_PULLDOWN); + } else if (mode == GpioMode::GPIO_INPUT) { + pinMode(pin, INPUT); + } + } +} diff --git a/HAL/stm32/src/reboot.cpp b/HAL/stm32/src/reboot.cpp new file mode 100644 index 00000000..4304f0c7 --- /dev/null +++ b/HAL/stm32/src/reboot.cpp @@ -0,0 +1,19 @@ +#include "reboot.hpp" + +#include "stdlib.hpp" + +#include + +void reboot_firmware() { + nvic_sys_reset(); +} + +void reboot_bootloader() { + // Write magic to top of RAM to signal DFU bootloader entry on reset. + // Linker reserves last 8 bytes (LENGTH = 96K - 8), so __msp_init points there. + // Compatible with davidgfnet/stm32-dfu-bootloader reboot protocol. + extern uint32_t __msp_init; + volatile uint64_t *magic = (volatile uint64_t *)&__msp_init; + *magic = 0xDEADBEEFCC00FFEEULL; + nvic_sys_reset(); +} diff --git a/HAL/stm32/src/serial.cpp b/HAL/stm32/src/serial.cpp new file mode 100644 index 00000000..0a9f81cc --- /dev/null +++ b/HAL/stm32/src/serial.cpp @@ -0,0 +1,29 @@ +#include "serial.hpp" + +#include "stdlib.hpp" + +namespace serial { + void init(unsigned long baudrate) { + Serial.begin(baudrate); + } + + void close() { + Serial.end(); + } + + void print(const char *string) { + Serial.print(string); + } + + void write(uint8_t byte) { + Serial.write(byte); + } + + void write(uint8_t *bytes, size_t len) { + Serial.write(bytes, len); + } + + int available_for_write() { + return Serial.availableForWrite(); + } +} diff --git a/config/jz-stm32-leverless/ButtonMappings.md b/config/jz-stm32-leverless/ButtonMappings.md new file mode 100644 index 00000000..17ec4ac7 --- /dev/null +++ b/config/jz-stm32-leverless/ButtonMappings.md @@ -0,0 +1,67 @@ +# Button Mappings (FGC Mode / XInput) + +Pin assignments verified via stm32pintester 2026-03-12. + +## Physical Layout + +Left hand (movement + utility): +``` + B1(PC0) B2(PC2) B3(PA4) + B4(PD2) B5(PA7) B6(PB7) + B7(PB9) B8(PB11) B9(PB1) B10(PC5) + B11(PB15) B12(PA7) +``` + +Right hand (attacks): +``` + B13(PB12) B14(PC8) B15(PC9) B16(PC10) B17(PC11) + B18(PA5) B19(PA3) B20(PA1) B21(PC3) + B22(PC12) B23(PB13) B24(PC6) B25(PB14) + B26(PC7) B27(PA0) +``` + +Shared pins: B5/B12 share PA7 → both mapped to DPad Up (26 unique pins, 27 buttons). +B8/B11 are NOT shared — B8=PB11, B11=PB15. + +## Button-to-XInput Mapping + +| Button | Pin | BTN_* | XInput | Notes | +|--------|------|----------|------------|--------------------------------| +| B1 | PC0 | BTN_MB1 | Start | | +| B2 | PC2 | BTN_RT3 | Select | | +| B3 | PA4 | BTN_RT2 | Home | Also bootloader entry | +| B4 | PD2 | — | — | Unmapped | +| B5 | PA7 | BTN_LT1 | DPad Up | Shared pin with B12 | +| B6 | PB7 | — | — | Unmapped | +| B7 | PB9 | BTN_LT2 | L3 | | +| B8 | PB11 | BTN_LF3 | DPad Left | | +| B9 | PB1 | BTN_LF2 | DPad Down | | +| B10 | PC5 | BTN_LF1 | DPad Right | | +| B11 | PB15 | BTN_LT2 | L3 | Same as B7 | +| B12 | PA7 | BTN_LT1 | DPad Up | Shared pin with B5 | +| B13 | PB12 | BTN_RT1 | R3 | Same as B27 | +| B14 | PC8 | BTN_MB1 | Start | Same as B1 | +| B15 | PC9 | BTN_RT3 | Select | Same as B2 | +| B16 | PC10 | BTN_RT2 | Home | Same as B3 | +| B17 | PC11 | — | — | Unmapped | +| B18 | PA5 | BTN_RF5 | X | | +| B19 | PA3 | BTN_RF6 | Y | | +| B20 | PA1 | BTN_RF7 | RB | | +| B21 | PC3 | BTN_RF8 | LB | | +| B22 | PC12 | BTN_RF1 | A | | +| B23 | PB13 | BTN_RF2 | B | | +| B24 | PC6 | BTN_RF3 | RT | | +| B25 | PB14 | BTN_RF4 | LT | | +| B26 | PC7 | BTN_LT1 | DPad Up | | +| B27 | PA0 | BTN_RT1 | R3 | | + +## Unmapped Buttons (Available for Future Use) + +B4 (PD2), B6 (PB7), B17 (PC11) + +These 3 unique pins (3 buttons) are not mapped to any BTN_* value. + +## SOCD Toggle + +Hold Start (B1) + Select (B2) + DPad Up (B26) to cycle SOCD modes: +SOCD_NEUTRAL → SOCD_2IP → SOCD_2IP_NO_REAC → SOCD_NEUTRAL diff --git a/config/jz-stm32-leverless/config.cpp b/config/jz-stm32-leverless/config.cpp new file mode 100644 index 00000000..c6fb9a9a --- /dev/null +++ b/config/jz-stm32-leverless/config.cpp @@ -0,0 +1,158 @@ +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "core/state.hpp" +#include "input/GpioButtonInput.hpp" +#include "reboot.hpp" +#include "stdlib.hpp" + +#include + +Config config = default_config; + +// Button-to-pin mapping for jz-stm32-leverless (FGC mode). +// Pin assignments verified via stm32pintester 2026-03-12. +// +// Physical layout: +// Left hand: Right hand: +// B1(Start) B2(Sel) B3(Home) B18(X) B19(Y) B20(RB) B21(LB) +// B4 B5 B6 B22(A) B23(B) B24(RT) B25(LT) +// B7(L3) B8(Left) B9(Down) B10(Right) B26(Up) B27(R3) +// +// Shared pins: B5/B12→PA7 (26 unique pins, 27 buttons) + +const GpioButtonMapping button_mappings[] = { + // Top row — Start / Select / Home + { BTN_MB1, PC0 }, // B1 - Start + { BTN_RT3, PC2 }, // B2 - Select + { BTN_RT2, PA4 }, // B3 - Home (also bootloader entry) + { BTN_MB1, PC8 }, // B14 - Start (same as B1) + { BTN_RT3, PC9 }, // B15 - Select (same as B2) + { BTN_RT2, PC10 }, // B16 - Home (same as B3) + + // Left hand — movement + { BTN_LT1, PA7 }, // B5 - DPad Up (also PA7/B12) + { BTN_LT2, PB9 }, // B7 - L3 + { BTN_LT2, PB15 }, // B11 - L3 (same as B7) + { BTN_LF3, PB11 }, // B8 - DPad Left + { BTN_LF2, PB1 }, // B9 - DPad Down + { BTN_LF1, PC5 }, // B10 - DPad Right + + // Right hand — attack buttons + { BTN_RF5, PA5 }, // B18 - X + { BTN_RF6, PA3 }, // B19 - Y + { BTN_RF7, PA1 }, // B20 - RB + { BTN_RF8, PC3 }, // B21 - LB + { BTN_RF1, PC12 }, // B22 - A + { BTN_RF2, PB13 }, // B23 - B + { BTN_RF3, PC6 }, // B24 - RT + { BTN_RF4, PB14 }, // B25 - LT + + // Bottom row + { BTN_LT1, PC7 }, // B26 - DPad Up + { BTN_RT1, PA0 }, // B27 - R3 + { BTN_RT1, PB12 }, // B13 - R3 (same as B27) +}; +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); + +const Pinout pinout = { + .joybus_data = 0, + .nes_data = -1, + .nes_clock = -1, + .nes_latch = -1, + .mux = -1, + .nunchuk_detect = -1, + .nunchuk_sda = -1, + .nunchuk_scl = -1, +}; + +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + +GameModeConfig *find_fgc_config(Config &cfg); + +void setup() { + // Free PA15, PB3, PB4 from JTAG for GPIO use (keep SWD on PA13/PA14). + afio_cfg_debug_ports(AFIO_DEBUG_SW_ONLY); + + // PA8 is the matrix row driver — drive LOW to activate the diode-to-ground matrix. + pinMode(PA8, OUTPUT); + digitalWrite(PA8, LOW); + + static InputState inputs; + + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); + + // Check bootloader button hold as early as possible. + if (inputs.rt2) { + reboot_bootloader(); + } + + static InputSource *input_sources[] = { &gpio_input }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + // Default FGC vertical SOCD to Up priority (Dir1 = Up wins over Down). + GameModeConfig *fgc = find_fgc_config(config); + if (fgc && fgc->socd_pairs_count >= 2) { + fgc->socd_pairs[1] = { BTN_LT1, BTN_LF2, SOCD_DIR1_PRIORITY }; + } + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); + + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); +} + +GameModeConfig *find_fgc_config(Config &cfg) { + for (size_t i = 0; i < cfg.game_mode_configs_count; i++) { + if (cfg.game_mode_configs[i].mode_id == MODE_FGC) { + return &cfg.game_mode_configs[i]; + } + } + return nullptr; +} + +static SocdType next_socd(SocdType current) { + switch (current) { + case SOCD_DIR1_PRIORITY: return SOCD_NEUTRAL; + case SOCD_NEUTRAL: return SOCD_2IP; + case SOCD_2IP: return SOCD_2IP_NO_REAC; + case SOCD_2IP_NO_REAC: return SOCD_DIR1_PRIORITY; + default: return SOCD_DIR1_PRIORITY; + } +} + +void loop() { + select_mode(backends, backend_count, config); + + // SOCD toggle: Start (BTN_MB1) + Select (BTN_RT3) + Up (BTN_LT1) + static bool combo_was_held = false; + InputState &inputs = backends[0]->GetInputs(); + bool combo_held = inputs.mb1 && inputs.rt3 && inputs.lt1; + + if (combo_held && !combo_was_held) { + GameModeConfig *fgc_cfg = find_fgc_config(config); + if (fgc_cfg != nullptr && fgc_cfg->socd_pairs_count >= 2) { + fgc_cfg->socd_pairs[1].socd_type = next_socd(fgc_cfg->socd_pairs[1].socd_type); + // Re-apply config if FGC mode is currently active. + InputMode *current_mode = backends[0]->CurrentGameMode(); + if (current_mode != nullptr) { + current_mode->SetConfig(*fgc_cfg); + } + } + } + combo_was_held = combo_held; + + for (size_t i = 0; i < backend_count; i++) { + backends[i]->SendReport(); + } + + if (current_kb_mode != nullptr) { + current_kb_mode->SendReport(backends[0]->GetInputs()); + } +} diff --git a/config/jz-stm32-leverless/env.ini b/config/jz-stm32-leverless/env.ini new file mode 100644 index 00000000..836c8187 --- /dev/null +++ b/config/jz-stm32-leverless/env.ini @@ -0,0 +1,21 @@ +[env:jz-stm32-leverless] +extends = stm32_base +; Actual MCU is F103RFT6 (XL-density, 768KB), but maple core only supports up to +; F103RE (high-density, 512KB). Same 64-pin LQFP pinout; firmware fits in 512KB. +board = genericSTM32F103RE +board_upload.maximum_ram_size = 98304 +board_upload.offset_address = 0x08001000 +board_build.ldscript = stm32f103re_bootloader.ld +extra_scripts = + pre:config/jz-stm32-leverless/patch_usb_polling.py + post:config/jz-stm32-leverless/override_vect.py +; DFU bootloader on PA11/PA12: hold B1 + reset to enter DFU mode, then: +; dfu-util -a 0 -s 0x08001000:leave -D .pio/build/jz-stm32-leverless/firmware.bin +upload_protocol = dfu +upload_command = dfu-util -a 0 -s 0x08001000:leave -D $SOURCE +build_flags = + ${stm32_base.build_flags} + -DBOARD_USB_DISC_DEV=NULL +build_src_filter = + ${stm32_base.build_src_filter} + + diff --git a/config/jz-stm32-leverless/override_vect.py b/config/jz-stm32-leverless/override_vect.py new file mode 100644 index 00000000..d63b9626 --- /dev/null +++ b/config/jz-stm32-leverless/override_vect.py @@ -0,0 +1,19 @@ +Import("env") +import os + +# Replace the default VECT_TAB_ADDR=0x8000000 (set by maple build script) +# with 0x08001000 for our custom bootloader offset. +cppdefines = env.get("CPPDEFINES", []) +new_defines = [] +for d in cppdefines: + if isinstance(d, tuple) and d[0] == "VECT_TAB_ADDR": + new_defines.append(("VECT_TAB_ADDR", "0x08001000")) + elif isinstance(d, list) and len(d) == 2 and d[0] == "VECT_TAB_ADDR": + new_defines.append(["VECT_TAB_ADDR", "0x08001000"]) + else: + new_defines.append(d) +env.Replace(CPPDEFINES=new_defines) + +# Add our config dir to linker search path for the custom .ld file +ld_dir = os.path.join(env["PROJECT_DIR"], "config", "jz-stm32-leverless") +env.Prepend(LIBPATH=[ld_dir]) diff --git a/config/jz-stm32-leverless/patch_usb_polling.py b/config/jz-stm32-leverless/patch_usb_polling.py new file mode 100644 index 00000000..779f3de2 --- /dev/null +++ b/config/jz-stm32-leverless/patch_usb_polling.py @@ -0,0 +1,18 @@ +Import("env") +import os, re + +# Patch USBComposite usb_x360w.c to set bInterval=1 (1ms/1000Hz polling). +# The library default is 4ms (250Hz), which causes noticeable input latency. + +libdeps = env.subst("$PROJECT_LIBDEPS_DIR") +target_file = os.path.join(libdeps, env["PIOENV"], + "USBComposite for STM32F1", "usb_x360w.c") + +if os.path.isfile(target_file): + with open(target_file, "r") as f: + src = f.read() + patched = re.sub(r"(\.bInterval\s*=\s*)[48](,)", r"\g<1>1\2", src) + if patched != src: + with open(target_file, "w") as f: + f.write(patched) + print("Patched usb_x360w.c: bInterval → 1ms (1000Hz)") diff --git a/config/jz-stm32-leverless/stm32f103re_bootloader.ld b/config/jz-stm32-leverless/stm32f103re_bootloader.ld new file mode 100644 index 00000000..374fbc6f --- /dev/null +++ b/config/jz-stm32-leverless/stm32f103re_bootloader.ld @@ -0,0 +1,19 @@ +/* + * Linker script for STM32F103RF with 4KB DFU bootloader. + * App starts at 0x08001000 (4KB offset from flash start). + * RAM: 96KB, last 8 bytes reserved for bootloader reboot magic. + */ +MEMORY +{ + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 96K - 8 + rom (rx) : ORIGIN = 0x08001000, LENGTH = 508K +} + +/* Provide memory region aliases for common.inc */ +REGION_ALIAS("REGION_TEXT", rom); +REGION_ALIAS("REGION_DATA", ram); +REGION_ALIAS("REGION_BSS", ram); +REGION_ALIAS("REGION_RODATA", rom); + +/* Let common.inc handle the real work. */ +INCLUDE common.inc diff --git a/include/input/SwitchMatrixInput.hpp b/include/input/SwitchMatrixInput.hpp index d499de64..fa466605 100644 --- a/include/input/SwitchMatrixInput.hpp +++ b/include/input/SwitchMatrixInput.hpp @@ -59,6 +59,11 @@ template class SwitchMatrixInput : public Inp InputScanSpeed ScanSpeed() { return InputScanSpeed::FAST; } void UpdateInputs(InputState &inputs) { + for (size_t i = 0; i < num_rows; i++) { + for (size_t j = 0; j < num_cols; j++) { + set_button(inputs.buttons, _matrix[i][j], false); + } + } for (size_t i = 0; i < _num_outputs; i++) { // Activate the column/row. gpio::init_pin(_output_pins[i], gpio::GpioMode::GPIO_OUTPUT); @@ -94,8 +99,9 @@ template class SwitchMatrixInput : public Inp size_t row_index, bool pressed ) { - Button button = _matrix[col_index][row_index]; - set_button(inputs.buttons, button, pressed); + if (pressed) { + set_button(inputs.buttons, _matrix[col_index][row_index], true); + } }; }; diff --git a/platformio.ini b/platformio.ini index 1b7f820f..d90ed573 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,6 +81,38 @@ lib_deps = https://github.com/JonnyHaystack/ArduinoJoystickLibrary/archive/refs/tags/v0.0.1.zip https://github.com/JonnyHaystack/ArduinoKeyboard/archive/refs/tags/1.0.5.zip +[stm32_base] +platform = ststm32 +framework = arduino +board_build.core = maple +build_unflags = + -std=gnu++11 + -DSERIAL_USB + -DGENERIC_BOOTLOADER +build_flags = + ${env.build_flags} + -std=gnu++17 + -Os + -USERIAL_USB + -UGENERIC_BOOTLOADER + -fdata-sections + -ffunction-sections + -fno-sized-deallocation + -Wl,--gc-sections + -Wno-unused-variable + -I HAL/stm32/include +build_src_filter = + ${env.build_src_filter} + + + - + - +custom_nanopb_options = + ${env.custom_nanopb_options} + --options-file ../../../../HAL/avr/proto/config.options +lib_deps = + ${env.lib_deps} + arpruss/USBComposite for STM32F1 + [arduino_pico_base] platform = https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c framework = arduino diff --git a/src/input/GpioButtonInput.cpp b/src/input/GpioButtonInput.cpp index 69fc2ee1..0f62e654 100644 --- a/src/input/GpioButtonInput.cpp +++ b/src/input/GpioButtonInput.cpp @@ -19,6 +19,9 @@ InputScanSpeed GpioButtonInput::ScanSpeed() { } void GpioButtonInput::UpdateInputs(InputState &inputs) { + for (size_t i = 0; i < _button_count; i++) { + set_button(inputs.buttons, _button_mappings[i].button, false); + } for (size_t i = 0; i < _button_count; i++) { UpdateButtonState(inputs, i, !gpio::read_digital(_button_mappings[i].pin)); } @@ -29,5 +32,7 @@ void GpioButtonInput::UpdateButtonState( size_t button_mapping_index, bool pressed ) { - set_button(inputs.buttons, _button_mappings[button_mapping_index].button, pressed); + if (pressed) { + set_button(inputs.buttons, _button_mappings[button_mapping_index].button, true); + } } diff --git a/src/util/analog_filters.cpp b/src/util/analog_filters.cpp index 74e619df..4108d919 100644 --- a/src/util/analog_filters.cpp +++ b/src/util/analog_filters.cpp @@ -14,7 +14,7 @@ uint8_t apply_deadzone(uint8_t value, uint8_t deadzone, bool scale) { // value is given on the rim. if (scale) { int8_t sign = SIGNUM(post_deadzone); - int8_t post_scaling = min(127, abs(post_deadzone) * 128.0 / (128 - deadzone)) * sign; + int8_t post_scaling = min(127, (int)(abs(post_deadzone) * 128.0 / (128 - deadzone))) * sign; return post_scaling + 128; } return post_deadzone + 128;