From f2c8df3f2320c0e397597c2573bfa896f92090cf Mon Sep 17 00:00:00 2001 From: Orlando Eduardo Pereira Date: Wed, 15 Apr 2026 11:48:00 -0300 Subject: [PATCH 1/4] Add Logitech G27 shifter fix and SPI3 support for wheel rim buttons ## ShifterAnalog G27 Mode Fix - Fix SPI clock configuration for 74HC165 (CLKPhase, CLKPolarity) - Add CS polarity configuration for active-low operation - Change to synchronous SPI read for reliable button reading - Fix CS pin index off-by-one error in setMode() and setCSPin() - Add startRead() method to trigger SPI before processing data ## SPI Buttons 3 (SPI3 Support) - Add SPI_Buttons_3 class using SPI3 peripheral for separate button source - Required because 74HC165 lacks tri-state output (cannot share MISO) - Allows G27 shifter (SPI2) and wheel rim (SPI3) to work simultaneously - SPI3 pins: PC10 (SCK), PC11 (MISO), PA15 (CS1) ## Hardware Configuration (F407VG) - Reduce SPI3 baud rate for reliable 74HC165 communication - Initialize SPI CS pins HIGH (inactive) to prevent bus contention - Enable SPIBUTTONS3 feature flag ## Documentation - Add complete wiring guide for all G27 components - Add technical reports documenting bugs and solutions - Bilingual documentation (English/Portuguese) Tested with Logitech G27 on STM32F407VET6 board. Made-with: Cursor --- .../FFBoard/UserExtensions/Inc/SPIButtons.h | 46 +- .../UserExtensions/Inc/ShifterAnalog.h | 7 +- .../UserExtensions/Inc/eeprom_addresses.h | 48 +- .../UserExtensions/Src/ButtonSources.cpp | 3 + .../FFBoard/UserExtensions/Src/SPIButtons.cpp | 189 +++++++- .../UserExtensions/Src/ShifterAnalog.cpp | 80 +++- .../UserExtensions/Src/eeprom_addresses.c | 48 +- .../F407VG/Core/Inc/target_constants.h | 4 +- .../F407VG/Core/Src/cpp_target_config.cpp | 7 +- Firmware/Targets/F407VG/Core/Src/main.c | 15 +- doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md | 453 ++++++++++++++++++ doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md | 367 ++++++++++++++ .../G27_WHEEL_RIM_BUTTONS_REPORT.md | 405 ++++++++++++++++ doc/logitech g27/README.md | 161 +++++++ 14 files changed, 1736 insertions(+), 97 deletions(-) create mode 100644 doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md create mode 100644 doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md create mode 100644 doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md create mode 100644 doc/logitech g27/README.md diff --git a/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h b/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h index 9f8f061df..b131ee941 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h +++ b/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h @@ -35,7 +35,7 @@ struct ButtonSourceConfig{ class SPI_Buttons: public ButtonSource,public CommandHandler,public SPIDevice { enum class SPIButtons_commands : uint32_t { - mode,btncut,btnpol,btnnum,cs,spispeed + mode,btncut,btnpol,btnnum,cs,spispeed,debug,syncread }; public: @@ -45,13 +45,14 @@ class SPI_Buttons: public ButtonSource,public CommandHandler,public SPIDevice { virtual ~SPI_Buttons(); uint8_t readButtons(uint64_t* buf); + uint16_t getBtnNum() override; // Override to return conf.numButtons CommandStatus command(const ParsedCommand& cmd,std::vector& replies) override; void registerCommands(); virtual std::string getHelpstring(){return "SPI 2 Button";} void saveFlash(); - void restoreFlash(); + virtual void restoreFlash(); const uint8_t maxButtons = 64; std::string printModes(const std::vector& names); @@ -63,32 +64,29 @@ class SPI_Buttons: public ButtonSource,public CommandHandler,public SPIDevice { void setSpiSpeed(uint8_t speedPreset); protected: - SPI_Buttons(uint16_t configuration_address, uint16_t configuration_address_2); - -private: + SPI_Buttons(uint16_t configuration_address, uint16_t configuration_address_2, SPIPort* spiPort = &external_spi, uint8_t instance = 0); uint16_t configuration_address; uint16_t configuration_address_2; - bool ready = false; void setConfig(ButtonSourceConfig config); virtual ButtonSourceConfig* getConfig(); - void process(uint64_t* buf); - uint8_t bytes = 4; + +private: + bool ready = false; uint64_t mask = 0xff; uint8_t offset = 0; - ButtonSourceConfig conf; + static constexpr std::array speedPresets= {SPI_BAUDRATEPRESCALER_16,SPI_BAUDRATEPRESCALER_32,SPI_BAUDRATEPRESCALER_64}; +protected: + void process(uint64_t* buf); + uint8_t bytes = 4; uint8_t spi_buf[4] = {0}; - - static constexpr std::array speedPresets= {SPI_BAUDRATEPRESCALER_16,SPI_BAUDRATEPRESCALER_32,SPI_BAUDRATEPRESCALER_64}; + ButtonSourceConfig conf; }; class SPI_Buttons_1 : public SPI_Buttons { public: - SPI_Buttons_1() - : SPI_Buttons{ADR_SPI_BTN_1_CONF, ADR_SPI_BTN_1_CONF_2} { - setInstance(0); - } + SPI_Buttons_1(); const ClassIdentifier getInfo() override; static ClassIdentifier info; @@ -98,14 +96,24 @@ class SPI_Buttons_1 : public SPI_Buttons { class SPI_Buttons_2 : public SPI_Buttons { public: - SPI_Buttons_2() - : SPI_Buttons{ADR_SPI_BTN_2_CONF, ADR_SPI_BTN_2_CONF_2} { - setInstance(1); - } + SPI_Buttons_2(); + + const ClassIdentifier getInfo() override; + static ClassIdentifier info; + static bool isCreatable(); +}; + +class SPI_Buttons_3 : public SPI_Buttons { +public: + SPI_Buttons_3(); const ClassIdentifier getInfo() override; static ClassIdentifier info; static bool isCreatable(); + + void restoreFlash() override; // Override to set default values + uint8_t readButtons(uint64_t* buf) override; // Override to use synchronous read for SPI3 + std::string getHelpstring() override {return "SPI 3 Button (SPI3)";} }; #endif /* SPIBUTTONS_H_ */ diff --git a/Firmware/FFBoard/UserExtensions/Inc/ShifterAnalog.h b/Firmware/FFBoard/UserExtensions/Inc/ShifterAnalog.h index 39a0931fb..3f0638fc5 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/ShifterAnalog.h +++ b/Firmware/FFBoard/UserExtensions/Inc/ShifterAnalog.h @@ -63,11 +63,14 @@ enum class ShifterAnalog_commands : uint32_t{ G27ShifterButtonClient(OutputPin& csPin); static constexpr int numUserButtons{12}; + static constexpr int bytesToRead{2}; // 16 buttons = 2 bytes (same as SPI Buttons with 16 buttons) uint16_t getUserButtons(); bool getReverseButton(); - private: - uint16_t buttonStates{0}; + void startRead(); // Start a new DMA read (like SPI_Buttons does) + void spiRxCompleted(SPIPort* port) override; // Called when DMA completes + + uint8_t spi_buf[2]{0}; // Buffer for SPI read (2 bytes for 16 buttons) - DMA writes directly here }; diff --git a/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h b/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h index d05638476..40755abb3 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h +++ b/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h @@ -1,13 +1,13 @@ -/* - * eeprom_addresses.h - * - * Created on: 24.01.2020 - * Author: Yannick - * - * /!\ Generated from the file memory_map.csv - / ! \ DO NOT EDIT THIS DIRECTLY !!! - */ - +/* + * eeprom_addresses.h + * + * Created on: 24.01.2020 + * Author: Yannick + * + * /!\ Generated from the file memory_map.csv + / ! \ DO NOT EDIT THIS DIRECTLY !!! + */ + #ifndef EEPROM_ADDRESSES_H_ #define EEPROM_ADDRESSES_H_ @@ -21,19 +21,19 @@ extern const uint16_t VirtAddVarTab[NB_OF_VAR]; extern const uint16_t exportableFlashAddresses[NB_EXPORTABLE_ADR]; - -/* Add your addresses here. 0xffff is invalid as it marks an erased field. -Anything below 0x00ff is reserved for system variables. - -Use ranges that are clear to distinguish between configurations. Address ranges can have gaps. -Label the names clearly. -Example: 0x0100 - 0x01ff for one class and 0x0200-0x02ff for another class would be reasonable even if they each need only 3 variables - - -Important: Add your variable to the VirtAddVarTab[NB_OF_VAR] array in eeprom_addresses.c! - -Tip to check if a cell is intialized: -uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data) will return 1 if the address is not found or 0 if it was found. + +/* Add your addresses here. 0xffff is invalid as it marks an erased field. +Anything below 0x00ff is reserved for system variables. + +Use ranges that are clear to distinguish between configurations. Address ranges can have gaps. +Label the names clearly. +Example: 0x0100 - 0x01ff for one class and 0x0200-0x02ff for another class would be reasonable even if they each need only 3 variables + + +Important: Add your variable to the VirtAddVarTab[NB_OF_VAR] array in eeprom_addresses.c! + +Tip to check if a cell is intialized: +uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data) will return 1 if the address is not found or 0 if it was found. */ // System variables #define ADR_HW_VERSION 1 @@ -58,6 +58,8 @@ uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data) will return 1 if #define ADR_SPI_BTN_2_CONF 0x205 #define ADR_SPI_BTN_1_CONF_2 0x206 #define ADR_SPI_BTN_2_CONF_2 0x207 +#define ADR_SPI_BTN_3_CONF 0x209 +#define ADR_SPI_BTN_3_CONF_2 0x20A #define ADR_LOCAL_BTN_CONF_3 0x208 // Pulse mask // Local encoder #define ADR_ENCLOCAL_CPR 0x210 diff --git a/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp b/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp index 554560c7f..b189dc693 100644 --- a/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp @@ -24,6 +24,9 @@ const std::vector> ButtonSource::all_buttonsources = #ifdef SPIBUTTONS2 add_class(2), #endif +#ifdef SPIBUTTONS3 + add_class(6), +#endif #ifdef SHIFTERBUTTONS add_class(3), #endif diff --git a/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp b/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp index a82e0d710..5193ce70b 100644 --- a/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp @@ -30,6 +30,11 @@ bool SPI_Buttons_1::isCreatable(){ return (external_spi.hasFreePins()); } +SPI_Buttons_1::SPI_Buttons_1() + : SPI_Buttons{ADR_SPI_BTN_1_CONF, ADR_SPI_BTN_1_CONF_2, &external_spi, 0} { + restoreFlash(); // Call base class version (SPI_Buttons_1 doesn't override) +} + ClassIdentifier SPI_Buttons_2::info = { .name = "SPI Buttons 2" , .id=CLSID_BTN_SPI, @@ -42,14 +47,99 @@ bool SPI_Buttons_2::isCreatable(){ return false;//(external_spi.hasFreePins(); } +SPI_Buttons_2::SPI_Buttons_2() + : SPI_Buttons{ADR_SPI_BTN_2_CONF, ADR_SPI_BTN_2_CONF_2, &external_spi, 1} { + restoreFlash(); // Call base class version (SPI_Buttons_2 doesn't override) +} + +ClassIdentifier SPI_Buttons_3::info = { + .name = "SPI Buttons 3" , + .id=CLSID_BTN_SPI, + }; +const ClassIdentifier SPI_Buttons_3::getInfo(){ + return info; +} + +bool SPI_Buttons_3::isCreatable(){ +#ifdef EXT3_SPI_PORT + return (ext3_spi.hasFreePins()); +#else + return false; +#endif +} + +SPI_Buttons_3::SPI_Buttons_3() + : SPI_Buttons{ADR_SPI_BTN_3_CONF, ADR_SPI_BTN_3_CONF_2, &ext3_spi, 2} { + // Constructor body runs AFTER base class is fully constructed + // Now we can safely call our overridden restoreFlash() + SPI_Buttons_3::restoreFlash(); // Explicitly call our version, not virtual dispatch +} + +void SPI_Buttons_3::restoreFlash(){ + // Always apply G27 wheel rim defaults for SPI_Buttons_3 (hardcoded) + // Force these values every time, regardless of flash contents + ButtonSourceConfig config; + config.numButtons = 8; // 8 buttons on wheel rim - HARDCODED + config.mode = SPI_BtnMode::PISOSR; // 74xx165 mode - HARDCODED + config.cs_num = 1; // CS pin 1 (PA15 - SPI3_SS1) - HARDCODED + config.spi_speed = 2; // Slow speed (prescaler 64) - HARDCODED + config.invert = false; + config.cutRight = false; + + // Always apply hardcoded configuration + setConfig(config); + + // Force CS to correct value after setConfig + this->conf.cs_num = 1; + + // Explicitly ensure btnnum is set (multiple attempts for debug) + this->btnnum = 8; + ButtonSource::btnnum = 8; +} + +// SPI_Buttons_3 uses synchronous read because DMA has timing issues with SPI3 +uint8_t SPI_Buttons_3::readButtons(uint64_t* buf){ + // Copy last buffer to output + memcpy(buf, this->spi_buf, std::min(this->bytes, 8)); + process(buf); + + // Check if SPI is available + if(spiPort.isTaken()) + return this->conf.numButtons; + + // Use direct HAL_SPI_Receive which works correctly for SPI3 + SPI_HandleTypeDef* hspi = spiPort.getPortHandle(); + + // Get CS pin for this device (cs_num is 1-indexed, array is 0-indexed) + OutputPin* cs_pin = spiPort.getCsPin(this->conf.cs_num > 0 ? this->conf.cs_num - 1 : 0); + + if(cs_pin != nullptr) { + // Pulse CS low then high to latch data into 74HC165 + cs_pin->write(false); // CS LOW - load parallel data + // Small delay for latch + for(volatile int i = 0; i < 10; i++) {} + cs_pin->write(true); // CS HIGH - enable shift + } + + // Read data via SPI + HAL_SPI_Receive(hspi, spi_buf, bytes, 10); + + return this->conf.numButtons; +} + // TODO check if pin is free -SPI_Buttons::SPI_Buttons(uint16_t configuration_address, uint16_t configuration_address_2) - : CommandHandler("spibtn",CLSID_BTN_SPI,0), SPIDevice(external_spi,external_spi.getFreeCsPins()[0]){ +SPI_Buttons::SPI_Buttons(uint16_t configuration_address, uint16_t configuration_address_2, SPIPort* spiPort, uint8_t instance) + : CommandHandler("spibtn",CLSID_BTN_SPI,instance), SPIDevice(*spiPort,spiPort->getFreeCsPins()[0]){ this->configuration_address = configuration_address; this->configuration_address_2 = configuration_address_2; - restoreFlash(); + // NOTE: restoreFlash() is NOT called here - it must be called by derived class constructors + // This is because calling virtual functions in base class constructor doesn't dispatch to derived class + + // Initialize with default settings - will be overwritten by restoreFlash() in derived class + this->spiConfig.peripheral.BaudRatePrescaler = speedPresets[1]; // Medium speed default + this->spiConfig.peripheral.FirstBit = SPI_FIRSTBIT_LSB; registerCommands(); this->setCommandsEnabled(true); @@ -77,6 +167,8 @@ void SPI_Buttons::registerCommands(){ registerCommand("btnnum", SPIButtons_commands::btnnum, "Number of buttons",CMDFLAG_GET | CMDFLAG_SET); registerCommand("cs", SPIButtons_commands::cs, "SPI CS pin",CMDFLAG_GET | CMDFLAG_SET); registerCommand("spispeed", SPIButtons_commands::spispeed, "SPI speed preset",CMDFLAG_INFOSTRING | CMDFLAG_GET | CMDFLAG_SET); + registerCommand("debug", SPIButtons_commands::debug, "Debug raw SPI data",CMDFLAG_GET); + registerCommand("syncread", SPIButtons_commands::syncread, "Test synchronous read",CMDFLAG_GET); } /** @@ -95,31 +187,41 @@ void SPI_Buttons::setConfig(ButtonSourceConfig config){ OutputPin* newPin = spiPort.getCsPin(config.cs_num-1); // TODO update internal pin number if requested pin is blocked if(newPin != nullptr){ this->spiConfig.cs = *newPin; - + spiPort.reserveCsPin(this->spiConfig.cs); + }else{ + // CS pin not found - this is an error condition + // Try to use first free pin as fallback + auto& freePins = spiPort.getFreeCsPins(); + if(!freePins.empty()){ + this->spiConfig.cs = freePins[0]; + spiPort.reserveCsPin(this->spiConfig.cs); + } } - spiPort.reserveCsPin(this->spiConfig.cs); // Setup presets if(conf.mode == SPI_BtnMode::TM){ this->spiConfig.cspol = true; this->conf.cutRight = true; - this->spiConfig.peripheral.FirstBit = SPI_FIRSTBIT_LSB; - this->spiConfig.peripheral.CLKPhase = SPI_PHASE_1EDGE; this->spiConfig.peripheral.CLKPolarity = SPI_POLARITY_LOW; + this->spiConfig.peripheral.CLKPhase = SPI_PHASE_1EDGE; + }else if(conf.mode == SPI_BtnMode::PISOSR){ this->spiConfig.cspol = false; this->conf.cutRight = false; - this->spiConfig.peripheral.FirstBit = SPI_FIRSTBIT_LSB; this->spiConfig.peripheral.CLKPhase = SPI_PHASE_2EDGE; this->spiConfig.peripheral.CLKPolarity = SPI_POLARITY_HIGH; // its actually shifting on the rising edge but 165 will have the first output set even before clocking. First clock cycle is actually second bit so we sample at the falling edge and skip the first bit with that. } - this->spiConfig.peripheral.BaudRatePrescaler = speedPresets[this->conf.spi_speed]; +// spiPort.takeSemaphore(); +// spiPort.configurePort(&this->spiConfig.peripheral); +// spiPort.giveSemaphore(); initSPI(); if(config.numButtons == 64){ // Special case mask = 0xffffffffffffffff; }else{ mask = (uint64_t)pow(2,config.numButtons)-(uint64_t)1; // Must be done completely in 64 bit! } - offset = 8 - (config.numButtons % 8); + // Calculate offset: if numButtons is multiple of 8, offset is 0 + // Otherwise, offset is 8 - (numButtons % 8) + offset = (config.numButtons % 8 == 0) ? 0 : (8 - (config.numButtons % 8)); // Thrustmaster uses extra bits for IDs if(config.mode == SPI_BtnMode::TM){ @@ -128,6 +230,7 @@ void SPI_Buttons::setConfig(ButtonSourceConfig config){ bytes = 1+((config.numButtons-1)/8); } + // Update ButtonSource::btnnum so getBtnNum() returns correct value this->btnnum = config.numButtons; } @@ -135,6 +238,12 @@ ButtonSourceConfig* SPI_Buttons::getConfig(){ return &this->conf; } +uint16_t SPI_Buttons::getBtnNum(){ + // Always return conf.numButtons as the source of truth + // btnnum inheritance issue causes it to not update correctly + return this->conf.numButtons; +} + void SPI_Buttons::setSpiSpeed(uint8_t speedPreset){ speedPreset = clip(speedPreset,0,this->speedPresets.size()); this->conf.spi_speed = speedPreset; @@ -175,12 +284,12 @@ uint8_t SPI_Buttons::readButtons(uint64_t* buf){ process(buf); // give back last buffer if(spiPort.isTaken() || !ready) - return this->btnnum; // Don't wait. + return this->conf.numButtons; // Return conf.numButtons instead of btnnum // CS pin and semaphore managed by spi port spiPort.receive_DMA(spi_buf, bytes, this); - return this->btnnum; + return this->conf.numButtons; // Return conf.numButtons instead of btnnum } std::string SPI_Buttons::printModes(const std::vector& names){ @@ -199,6 +308,7 @@ CommandStatus SPI_Buttons::command(const ParsedCommand& cmd,std::vectorgetConfig(); c->numButtons = cmd.val; this->setConfig(*c); + this->saveFlash(); // Save to flash immediately }else if(cmd.type == CMDtype::get){ replies.emplace_back(this->getBtnNum()); }else{ @@ -230,6 +340,7 @@ CommandStatus SPI_Buttons::command(const ParsedCommand& cmd,std::vectorsaveFlash(); // Save to flash immediately }else if(cmd.type == CMDtype::get){ replies.emplace_back((uint8_t)this->conf.mode); }else if(cmd.type == CMDtype::info){ @@ -242,6 +353,7 @@ CommandStatus SPI_Buttons::command(const ParsedCommand& cmd,std::vectorsaveFlash(); // Save to flash immediately }else if(cmd.type == CMDtype::get){ replies.emplace_back((uint8_t)this->conf.spi_speed); }else if(cmd.type == CMDtype::info){ @@ -254,6 +366,59 @@ CommandStatus SPI_Buttons::command(const ParsedCommand& cmd,std::vectorconf.cs_num) == CommandStatus::OK ) { setConfig(this->conf); + this->saveFlash(); // Save to flash immediately + } + break; + + case SPIButtons_commands::debug: + if(cmd.type == CMDtype::get){ + // Return raw SPI buffer data in hex format for debugging + std::string debug_data = "Raw:"; + for(uint8_t i = 0; i < this->bytes; i++){ + char hex[4]; + sprintf(hex, "%02X", this->spi_buf[i]); + debug_data += hex; + if(i < this->bytes - 1) debug_data += " "; + } + debug_data += " offset:" + std::to_string(this->offset); + debug_data += " mask:0x" + std::to_string(this->mask); + debug_data += " inv:" + std::to_string(this->conf.invert); + debug_data += " cut:" + std::to_string(this->conf.cutRight); + replies.emplace_back(debug_data); + }else{ + return CommandStatus::ERR; + } + break; + + case SPIButtons_commands::syncread: + if(cmd.type == CMDtype::get){ + // Detailed diagnostic of SPI communication + std::string result = ""; + + // Show which SPI port is being used + SPI_HandleTypeDef* hspi = spiPort.getPortHandle(); + if(hspi->Instance == SPI2) result += "SPI2 "; + else if(hspi->Instance == SPI3) result += "SPI3 "; + else result += "SPI? "; + + // Show CS pin info + result += "CS:" + std::to_string(this->conf.cs_num) + " "; + + // Force a synchronous read + uint8_t test_buf[4] = {0xAA, 0xAA, 0xAA, 0xAA}; // Pre-fill with known pattern + HAL_StatusTypeDef status = HAL_SPI_Receive(hspi, test_buf, this->bytes, 100); + + result += "HAL:" + std::to_string(status) + " "; + result += "Data:"; + for(uint8_t i = 0; i < this->bytes; i++){ + char hex[4]; + sprintf(hex, "%02X", test_buf[i]); + result += hex; + if(i < this->bytes - 1) result += " "; + } + replies.emplace_back(result); + }else{ + return CommandStatus::ERR; } break; diff --git a/Firmware/FFBoard/UserExtensions/Src/ShifterAnalog.cpp b/Firmware/FFBoard/UserExtensions/Src/ShifterAnalog.cpp index 4334f7653..3bf153b16 100644 --- a/Firmware/FFBoard/UserExtensions/Src/ShifterAnalog.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/ShifterAnalog.cpp @@ -121,15 +121,24 @@ int ShifterAnalog::getUserButtons(uint64_t* buf) { uint8_t ShifterAnalog::readButtons(uint64_t* buf){ updateAdc(); - updateReverseState(); + if (g27ShifterButtonClient) { + // Synchronous read - waits for bus and reads immediately + g27ShifterButtonClient->startRead(); + + // Now process the freshly read data + updateReverseState(); + getUserButtons(buf); + } else { + updateReverseState(); + *buf = 0; + } + calculateGear(); - *buf = 0; // User buttons go first so that switching between sequential and H-pattern // doesn't affect user button assignments. - auto numUserButtons{getUserButtons(buf)}; - if(gear > 0){ + uint64_t numUserButtons = g27ShifterButtonClient ? g27ShifterButtonClient->numUserButtons : 0; *buf |= 1 << (gear - 1 + numUserButtons); } @@ -194,7 +203,19 @@ void ShifterAnalog::setMode(ShifterMode newMode) { g27ShifterButtonClient.reset(); g27ShifterButtonClient = nullptr; } else if (!g27ShifterButtonClient && isG27Mode(newMode)) { - g27ShifterButtonClient = std::make_unique(external_spi.getFreeCsPins()[0]); + // Use the configured CS pin (cs_pin_num is 1-indexed, getCsPin uses 0-indexed) + OutputPin* csPin = external_spi.getCsPin(cs_pin_num - 1); + if (csPin == nullptr) { + // Fallback to first free pin if configured pin is invalid + auto& freePins = external_spi.getFreeCsPins(); + if (!freePins.empty()) { + csPin = &freePins[0]; + } + } + if (csPin != nullptr) { + g27ShifterButtonClient = std::make_unique(*csPin); + // Initial read will happen on first readButtons() call + } } mode = newMode; @@ -209,7 +230,11 @@ void ShifterAnalog::setCSPin(uint8_t new_cs_pin_num) { cs_pin_num = new_cs_pin_num; if (g27ShifterButtonClient) { - g27ShifterButtonClient->updateCSPin(*external_spi.getCsPin(cs_pin_num)); + // cs_pin_num is 1-indexed, getCsPin uses 0-indexed + OutputPin* newPin = external_spi.getCsPin(cs_pin_num - 1); + if (newPin != nullptr) { + g27ShifterButtonClient->updateCSPin(*newPin); + } } } @@ -239,7 +264,14 @@ CommandStatus ShifterAnalog::command(const ParsedCommand& cmd,std::vector(&buttonStates), sizeof(buttonStates),this); + // Read directly from spi_buf (DMA writes directly here, like SPI_Buttons) + // Combine the 2 bytes into uint16_t (LSB first) + uint16_t buttonStates = (uint16_t)spi_buf[0] | ((uint16_t)spi_buf[1] << 8); return buttonStates & 0x02; } uint16_t ShifterAnalog::G27ShifterButtonClient::getUserButtons() { + // Read directly from spi_buf (DMA writes directly here, like SPI_Buttons) + // Combine the 2 bytes into uint16_t (LSB first) + uint16_t buttonStates = (uint16_t)spi_buf[0] | ((uint16_t)spi_buf[1] << 8); return buttonStates >> 4; } +void ShifterAnalog::G27ShifterButtonClient::spiRxCompleted(SPIPort* port) { + // DMA completed - data is now in spi_buf + // Nothing else needed - data will be read on next readButtons() call +} diff --git a/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c b/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c index d111d712a..0026f3d48 100644 --- a/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c +++ b/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c @@ -1,21 +1,21 @@ -/* - * eeprom_addresses.c - * - * Created on: 24.01.2020 - * Author: Yannick - * - * /!\ Generated from the file memory_map.csv - / ! \ DO NOT EDIT THIS DIRECTLY !!! - */ - -#include "eeprom_addresses.h" +/* + * eeprom_addresses.c + * + * Created on: 24.01.2020 + * Author: Yannick + * + * /!\ Generated from the file memory_map.csv + / ! \ DO NOT EDIT THIS DIRECTLY !!! + */ -/* -Add all used addresses to the VirtAddVarTab[] array. This is important for the eeprom emulation to correctly transfer between pages. -This ensures that addresses that were once used are not copied again in a page transfer if they are not in this array. -*/ - -const uint16_t VirtAddVarTab[NB_OF_VAR] = +#include "eeprom_addresses.h" + +/* +Add all used addresses to the VirtAddVarTab[] array. This is important for the eeprom emulation to correctly transfer between pages. +This ensures that addresses that were once used are not copied again in a page transfer if they are not in this array. +*/ + +const uint16_t VirtAddVarTab[NB_OF_VAR] = { // System variables ADR_HW_VERSION, @@ -40,6 +40,8 @@ const uint16_t VirtAddVarTab[NB_OF_VAR] = ADR_SPI_BTN_2_CONF, ADR_SPI_BTN_1_CONF_2, ADR_SPI_BTN_2_CONF_2, + ADR_SPI_BTN_3_CONF, + ADR_SPI_BTN_3_CONF_2, ADR_LOCAL_BTN_CONF_3, // Pulse mask // Local encoder ADR_ENCLOCAL_CPR, @@ -212,10 +214,10 @@ const uint16_t VirtAddVarTab[NB_OF_VAR] = ADR_ADS111X_MAX_3, }; -/** - * Variables to be included in a flash dump - */ -const uint16_t exportableFlashAddresses[NB_EXPORTABLE_ADR] = +/** + * Variables to be included in a flash dump + */ +const uint16_t exportableFlashAddresses[NB_EXPORTABLE_ADR] = { // System variables // ADR_HW_VERSION, @@ -240,6 +242,8 @@ const uint16_t exportableFlashAddresses[NB_EXPORTABLE_ADR] = ADR_SPI_BTN_2_CONF, ADR_SPI_BTN_1_CONF_2, ADR_SPI_BTN_2_CONF_2, + ADR_SPI_BTN_3_CONF, + ADR_SPI_BTN_3_CONF_2, ADR_LOCAL_BTN_CONF_3, // Pulse mask // Local encoder ADR_ENCLOCAL_CPR, @@ -410,4 +414,4 @@ const uint16_t exportableFlashAddresses[NB_EXPORTABLE_ADR] = ADR_ADS111X_MAX_2, ADR_ADS111X_MIN_3, ADR_ADS111X_MAX_3, -}; \ No newline at end of file +}; diff --git a/Firmware/Targets/F407VG/Core/Inc/target_constants.h b/Firmware/Targets/F407VG/Core/Inc/target_constants.h index 0cc62e9f8..c43d5983f 100644 --- a/Firmware/Targets/F407VG/Core/Inc/target_constants.h +++ b/Firmware/Targets/F407VG/Core/Inc/target_constants.h @@ -40,6 +40,8 @@ // Extra features #define LOCALBUTTONS #define SPIBUTTONS +#define SPIBUTTONS2 +#define SPIBUTTONS3 #define SHIFTERBUTTONS #define PCF8574BUTTONS // Requires I2C #define ANALOGAXES @@ -69,8 +71,6 @@ #define TIM_USER htim9 // Timer with full core clock speed available for the mainclass #define TIM_TMC htim6 // Timer running at half clock speed #define TIM_TMC_BCLK SystemCoreClock / 2 -#define TIM_TMC_ARR 250 // 4khz -#define TIM_FFB htim13 extern UART_HandleTypeDef huart1; #define UART_PORT_EXT huart1 // main uart port diff --git a/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp b/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp index 5b481f7f8..fe41ac90c 100644 --- a/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp +++ b/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp @@ -11,7 +11,12 @@ extern SPI_HandleTypeDef hspi1; SPIPort motor_spi{hspi1,motor_spi_cspins,84000000,false}; #ifdef EXT3_SPI_PORT -static const std::vector ext3_spi_cspins{OutputPin(*SPI3_SS1_GPIO_Port, SPI3_SS1_Pin), OutputPin(*SPI3_SS2_GPIO_Port, SPI3_SS2_Pin),OutputPin(*SPI3_SS3_GPIO_Port, SPI3_SS3_Pin)}; +// SPI3 CS pins - native SPI3 pins only +static const std::vector ext3_spi_cspins{ + OutputPin(*SPI3_SS1_GPIO_Port, SPI3_SS1_Pin), // PA15 - CS1 (used for G27 wheel rim) + OutputPin(*SPI3_SS2_GPIO_Port, SPI3_SS2_Pin), // PD2 - CS2 + OutputPin(*SPI3_SS3_GPIO_Port, SPI3_SS3_Pin) // PD3 - CS3 +}; extern SPI_HandleTypeDef EXT3_SPI_PORT; SPIPort ext3_spi{hspi3,ext3_spi_cspins,42000000,true}; #endif diff --git a/Firmware/Targets/F407VG/Core/Src/main.c b/Firmware/Targets/F407VG/Core/Src/main.c index 9026825ba..5758fdcf9 100644 --- a/Firmware/Targets/F407VG/Core/Src/main.c +++ b/Firmware/Targets/F407VG/Core/Src/main.c @@ -677,7 +677,7 @@ static void MX_SPI3_Init(void) hspi3.Init.CLKPolarity = SPI_POLARITY_LOW; hspi3.Init.CLKPhase = SPI_PHASE_1EDGE; hspi3.Init.NSS = SPI_NSS_SOFT; - hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; + hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // Slower for reliable 74HC165 communication hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi3.Init.TIMode = SPI_TIMODE_DISABLE; hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -1374,18 +1374,23 @@ static void MX_GPIO_Init(void) HAL_GPIO_WritePin(SPI1_SS1_GPIO_Port, SPI1_SS1_Pin, GPIO_PIN_SET); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOB, SPI1_SS2_Pin|SPI1_SS3_Pin|SPI2_NSS_Pin, GPIO_PIN_RESET); + // SPI CS pins initialized HIGH (inactive) - important for 74HC165 shift registers + HAL_GPIO_WritePin(SPI2_NSS_GPIO_Port, SPI2_NSS_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(GPIOB, SPI1_SS2_Pin|SPI1_SS3_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOE, DRV_ENABLE_Pin|DRV_BRAKE_Pin|DRV_GP1_Pin|LED_CLIP_Pin |LED_ERR_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOD, SPI2_SS2_Pin|SPI2_SS3_Pin|SPI3_SS2_Pin|SPI3_SS3_Pin - |CAN_S_Pin|GP1_Pin|LED_SYS_Pin, GPIO_PIN_RESET); + // SPI CS pins initialized HIGH (inactive) - important for 74HC165 shift registers + HAL_GPIO_WritePin(GPIOD, SPI2_SS2_Pin|SPI2_SS3_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(GPIOD, SPI3_SS2_Pin|SPI3_SS3_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(GPIOD, CAN_S_Pin|GP1_Pin|LED_SYS_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(SPI3_SS1_GPIO_Port, SPI3_SS1_Pin, GPIO_PIN_RESET); + // SPI3 CS1 initialized HIGH (inactive) - used for G27 wheel rim buttons + HAL_GPIO_WritePin(SPI3_SS1_GPIO_Port, SPI3_SS1_Pin, GPIO_PIN_SET); /*Configure GPIO pins : DIN7_Pin DIN6_Pin DIN5_Pin DIN4_Pin DIN3_Pin */ diff --git a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md new file mode 100644 index 000000000..eed65cc89 --- /dev/null +++ b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md @@ -0,0 +1,453 @@ +# Logitech G27 Complete Wiring Guide for OpenFFBoard STM32F407VET6 + +# Guia Completo de Ligações do Logitech G27 para OpenFFBoard STM32F407VET6 + +--- + +## ⚡ Required: PA9 Pull-up Resistor / Resistor Pull-up no PA9 + +> **EN:** On the generic **STM32F407VET6 board**, a **10kΩ pull-up resistor must be soldered between PA9 and 5V** for the main firmware (F407VG) to boot correctly. Without this resistor, only the F407VG_DISCO firmware works. This is required due to VBUS sensing differences. +> +> **PT:** Na placa genérica **STM32F407VET6**, um **resistor pull-up de 10kΩ deve ser soldado entre PA9 e 5V** para o firmware principal (F407VG) iniciar corretamente. Sem esse resistor, apenas o firmware F407VG_DISCO funciona. Isso é necessário devido a diferenças na detecção VBUS. + +| Connection | Description | +|------------|-------------| +| **PA9** ↔ **5V** | 10kΩ resistor (pull-up for VBUS sensing) | + +--- + +## 📋 Table of Contents / Índice + +1. [Overview / Visão Geral](#overview--visão-geral) +2. [⚡ PA9 Pull-up Resistor (Required)](#-required-pa9-pull-up-resistor--resistor-pull-up-no-pa9) +3. [Motor Connection / Conexão do Motor](#motor-connection--conexão-do-motor) +4. [Encoder Connection / Conexão do Encoder](#encoder-connection--conexão-do-encoder) +5. [Pedals Connection / Conexão dos Pedais](#pedals-connection--conexão-dos-pedais) +6. [Shifter Connection / Conexão do Câmbio](#shifter-connection--conexão-do-câmbio) +7. [Wheel Rim Buttons / Botões do Aro](#wheel-rim-buttons--botões-do-aro) +8. [Complete Pinout Summary / Resumo Completo da Pinagem](#complete-pinout-summary--resumo-completo-da-pinagem) + +--- + +## Overview / Visão Geral + +### EN - English + +This guide documents all connections made to integrate a **Logitech G27 Racing Wheel** with a generic **[STM32F407VET6 board](http://pt.aliexpress.com/item/1005006882009420.html)** running OpenFFBoard firmware. The G27 consists of: + +- **Wheel Base**: DC motor with optical encoder +- **Shifter**: H-pattern with analog axes + 16 SPI buttons (2x 74HC165) +- **Wheel Rim**: 8 buttons (1x 74HC165) + LEDs (1x HC595) +- **Pedals**: 3 analog axes (throttle, brake, clutch) + +### PT - Português + +Este guia documenta todas as conexões feitas para integrar um **Volante Logitech G27** com uma placa genérica **[STM32F407VET6](http://pt.aliexpress.com/item/1005006882009420.html)** rodando firmware OpenFFBoard. O G27 consiste em: + +- **Base do Volante**: Motor DC com encoder óptico +- **Câmbio**: H-pattern com eixos analógicos + 16 botões SPI (2x 74HC165) +- **Aro do Volante**: 8 botões (1x 74HC165) + LEDs (1x HC595) +- **Pedais**: 3 eixos analógicos (acelerador, freio, embreagem) + +--- + +## Motor Connection / Conexão do Motor + +### EN - Motor Wiring + +The G27 uses a **DC motor** controlled via PWM through an H-bridge driver (like BTS7960). + +| Function | OpenFFBoard Pin | Wire Color (typical) | Notes | +|----------|-----------------|---------------------|-------| +| PWM | PE11 | - | TIM1_CH2 PWM output | +| Direction | PE9 | - | Motor direction control | +| Enable | PE10 | - | Motor enable (DRV_BRAKE) | +| Motor + | BTS7960 M+ | Red/Orange | To H-bridge output | +| Motor - | BTS7960 M- | Black/Brown | To H-bridge output | + +#### 🎮 G27 Motor Torque Curve Tuning + +The G27 motors have specific characteristics that benefit from torque curve adjustment. For good force feedback response with minimal dead zone/backlash: + +| Parameter | Recommended Value | Notes | +|-----------|-------------------|-------| +| Torque Curve | **0.60 ~ 0.70** | Reduces dead zone, improves response | + +> **EN:** Setting the torque curve to 0.60-0.70 provides good FFB response with the G27 motors, reducing the "slack" feeling at center position. +> +> **PT:** Configurar a curva de torque em 0.60-0.70 proporciona boa resposta do FFB com os motores G27, reduzindo a sensação de "folga" na posição central. + +### PT - Ligação do Motor + +O G27 usa um **motor DC** controlado via PWM através de um driver H-bridge (como BTS7960). + +| Função | Pino OpenFFBoard | Cor do Fio (típico) | Notas | +|--------|------------------|---------------------|-------| +| PWM | PE11 | - | Saída PWM TIM1_CH2 | +| Direção | PE9 | - | Controle de direção | +| Enable | PE10 | - | Habilitação (DRV_BRAKE) | +| Motor + | BTS7960 M+ | Vermelho/Laranja | Saída do H-bridge | +| Motor - | BTS7960 M- | Preto/Marrom | Saída do H-bridge | + +--- + +## Encoder Connection / Conexão do Encoder + +### EN - Encoder Wiring + +The G27 originally uses an **optical quadrature encoder** (600 PPR) for position feedback. + +| Function | OpenFFBoard Pin | G27 Wire | Notes | +|----------|-----------------|----------|-------| +| Encoder A | PA0 | Green | Quadrature signal A | +| Encoder B | PA1 | White | Quadrature signal B | +| Index (Z) | - | - | Not used on G27 | +| VCC | 5V | Red | Encoder power | +| GND | GND | Black | Ground | + +#### Option A: Original G27 Encoder (600 PPR) +- Encoder Type: **ABN** (Quadrature) +- CPR (Counts Per Revolution): **2400** (600 PPR x4) + +#### Option B: Magnetic Encoder Upgrade (MT6835) ⭐ Recommended + +I replaced the original encoder with a **MT6835 ABZ magnetic encoder** mounted on the motor shaft. This provides much higher resolution and better FFB precision. + +| Parameter | Value | Notes | +|-----------|-------|-------| +| Encoder | MT6835 ABZ | Magnetic, high resolution | +| CPR | **65535** | Maximum resolution | +| Gear Ratio | **11:180** | Motor gear:Wheel gear teeth | +| Mounting | [Thingiverse 1270001](https://www.thingiverse.com/thing:1270001) | 3D printed adapter (works with 555 motors too) | + +The gear ratio 11:180 represents the relationship between the motor pinion (11 teeth) and the main wheel gear (180 teeth). + +### PT - Ligação do Encoder + +O G27 originalmente usa um **encoder óptico em quadratura** (600 PPR) para feedback de posição. + +| Função | Pino OpenFFBoard | Fio G27 | Notas | +|--------|------------------|---------|-------| +| Encoder A | PA0 | Verde | Sinal de quadratura A | +| Encoder B | PA1 | Branco | Sinal de quadratura B | +| Index (Z) | - | - | Não usado no G27 | +| VCC | 5V | Vermelho | Alimentação do encoder | +| GND | GND | Preto | Terra | + +#### Opção A: Encoder Original do G27 (600 PPR) +- Tipo do Encoder: **ABN** (Quadratura) +- CPR (Contagens Por Revolução): **2400** (600 PPR x4) + +#### Opção B: Upgrade com Encoder Magnético (MT6835) ⭐ Recomendado + +Substituí o encoder original por um **encoder magnético MT6835 ABZ** montado no eixo do motor. Isso proporciona resolução muito maior e melhor precisão do FFB. + +| Parâmetro | Valor | Notas | +|-----------|-------|-------| +| Encoder | MT6835 ABZ | Magnético, alta resolução | +| CPR | **65535** | Resolução máxima | +| Relação de Engrenagens | **11:180** | Dentes engrenagem motor:aro | +| Montagem | [Thingiverse 1270001](https://www.thingiverse.com/thing:1270001) | Adaptador impresso 3D (funciona com motores 555 também) | + +A relação 11:180 representa a proporção entre o pinhão do motor (11 dentes) e a engrenagem principal do aro (180 dentes). + +--- + +## Pedals Connection / Conexão dos Pedais + +### EN - Pedals Wiring + +The G27 pedals use **3 potentiometers** for throttle, brake, and clutch. This connection follows the **standard OpenFFBoard wiki guide** - no modifications needed. + +| Function | OpenFFBoard Pin | G27 Pedal Wire | Notes | +|----------|-----------------|----------------|-------| +| Throttle | Analog 1 (PA2) | - | Accelerator pedal | +| Brake | Analog 2 (PA3) | - | Brake pedal | +| Clutch | Analog 3 (PA6) | - | Clutch pedal | +| VCC | 3.3V | Red | Potentiometer power | +| GND | GND | Black | Ground | + +> **Note:** This is the **standard connection** as documented in the [OpenFFBoard Wiki - Pinouts and Peripherals](https://github.com/Ultrawipf/OpenFFBoard/wiki/Pinouts-and-peripherals). No custom modifications were required. + +#### Configuration / Configuração +- Analog Source: **Local Analog** +- Number of axes: **3** +- Calibrate each axis in the OpenFFBoard Configurator + +### PT - Ligação dos Pedais + +Os pedais do G27 usam **3 potenciômetros** para acelerador, freio e embreagem. Esta conexão segue o **guia padrão da wiki do OpenFFBoard** - nenhuma modificação necessária. + +| Função | Pino OpenFFBoard | Fio Pedal G27 | Notas | +|--------|------------------|---------------|-------| +| Acelerador | Analog 1 (PA2) | - | Pedal do acelerador | +| Freio | Analog 2 (PA3) | - | Pedal do freio | +| Embreagem | Analog 3 (PA6) | - | Pedal da embreagem | +| VCC | 3.3V | Vermelho | Alimentação dos potenciômetros | +| GND | GND | Preto | Terra | + +> **Nota:** Esta é a **conexão padrão** conforme documentado na [Wiki do OpenFFBoard - Pinouts and Peripherals](https://github.com/Ultrawipf/OpenFFBoard/wiki/Pinouts-and-peripherals). Nenhuma modificação customizada foi necessária. + +--- + +## Shifter Connection / Conexão do Câmbio + +### EN - Shifter Wiring + +The G27 shifter has: +- **Analog axes** (X/Y) for gear position detection +- **2x 74HC165** shift registers for 16 buttons + +| Function | OpenFFBoard Pin | G27 Connector Pin | Notes | +|----------|-----------------|-------------------|-------| +| VCC | 3.3V | 1 | Power (3.3V!) | +| SCK | PB13 | 2 | SPI Clock | +| MISO | PB14 | 4 | SPI Data | +| CS/Latch | PB12 | 6 | Chip Select (SPI2_SS1) | +| X Axis | Analog In | 7 | Analog X position | +| Y Axis | Analog In | 3 | Analog Y position | +| GND | GND | 9 | Ground | + +#### Configuration / Configuração +- Mode: **G27 Shifter H-pattern** (ShifterAnalog mode 2) +- CS Pin: **1** (PB12) + +### PT - Ligação do Câmbio + +O câmbio G27 tem: +- **Eixos analógicos** (X/Y) para detecção da marcha +- **2x 74HC165** registradores de deslocamento para 16 botões + +| Função | Pino OpenFFBoard | Pino Conector G27 | Notas | +|--------|------------------|-------------------|-------| +| VCC | 3.3V | 1 | Alimentação (3.3V!) | +| SCK | PB13 | 2 | Clock SPI | +| MISO | PB14 | 4 | Dados SPI | +| CS/Latch | PB12 | 6 | Chip Select (SPI2_SS1) | +| Eixo X | Analog In | 7 | Posição analógica X | +| Eixo Y | Analog In | 3 | Posição analógica Y | +| GND | GND | 9 | Terra | + +--- + +## Wheel Rim Buttons / Botões do Aro + +### EN - Wheel Rim Wiring + +The G27 wheel rim has: +- **1x 74HC165** for 8 buttons +- **1x HC595AG** for LEDs (optional, not implemented) + +> **Important:** The wheel rim buttons **cannot share the same SPI bus** with the shifter because the 74HC165 lacks tri-state output. A **separate SPI port (SPI3)** must be used. + +| Function | OpenFFBoard Pin | Wheel Rim Pin | Notes | +|----------|-----------------|---------------|-------| +| VCC | 3.3V | 1 | Power (3.3V!) | +| SCK | PC10 | 4 | SPI3 Clock | +| MISO | PC11 | 6 | SPI3 Data | +| CS/Latch | PA15 | 2 | SPI3_SS1 | +| GND | GND | 7 | Ground | +| MOSI | - | 3 | For LEDs (not used) | +| LED Latch | - | 5 | For LEDs (not used) | + +#### Why SPI3? / Por que SPI3? + +The 74HC165 shift register does **not have tri-state output**. When two 74HC165 chips share the same MISO line, they create electrical contention - both try to drive the line simultaneously. Using a separate SPI bus (SPI3) avoids this problem. + +#### Configuration / Configuração +- Class: **SPI Buttons 3** (custom, uses SPI3) +- Buttons: **8** +- Mode: **74HC165** (PISOSR) +- CS Pin: **1** (PA15) + +### PT - Ligação dos Botões do Aro + +O aro do G27 tem: +- **1x 74HC165** para 8 botões +- **1x HC595AG** para LEDs (opcional, não implementado) + +> **Importante:** Os botões do aro **não podem compartilhar o mesmo barramento SPI** com o câmbio porque o 74HC165 não tem saída tri-state. Um **porta SPI separada (SPI3)** deve ser usada. + +| Função | Pino OpenFFBoard | Pino do Aro | Notas | +|--------|------------------|-------------|-------| +| VCC | 3.3V | 1 | Alimentação (3.3V!) | +| SCK | PC10 | 4 | Clock SPI3 | +| MISO | PC11 | 6 | Dados SPI3 | +| CS/Latch | PA15 | 2 | SPI3_SS1 | +| GND | GND | 7 | Terra | +| MOSI | - | 3 | Para LEDs (não usado) | +| LED Latch | - | 5 | Para LEDs (não usado) | + +#### Por que SPI3? + +O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compartilham a mesma linha MISO, eles criam conflito elétrico - ambos tentam controlar a linha simultaneamente. Usar um barramento SPI separado (SPI3) evita esse problema. + +--- + +## Complete Pinout Summary / Resumo Completo da Pinagem + +### STM32F407VET6 Pin Assignment / Atribuição de Pinos + +``` +┌─────────────────────────────────────────────────────────────┐ +│ STM32F407VET6 - G27 CONNECTIONS │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ⚡ REQUIRED FOR F407VG FIRMWARE TO BOOT │ +│ └── PA9 ────────── 5V (via 10kΩ resistor) │ +│ │ +│ MOTOR (via BTS7960) │ +│ ├── PWM ────────── PE11 │ +│ ├── Direction ──── PE9 │ +│ └── Enable ─────── PE10 (DRV_BRAKE) │ +│ │ +│ ENCODER │ +│ ├── Encoder A ──── PA0 │ +│ ├── Encoder B ──── PA1 │ +│ ├── VCC ────────── 5V │ +│ └── GND ────────── GND │ +│ │ +│ PEDALS (Standard Wiki Connection) │ +│ ├── Throttle ───── PA2 (Analog 1) │ +│ ├── Brake ──────── PA3 (Analog 2) │ +│ ├── Clutch ─────── PA6 (Analog 3) │ +│ ├── VCC ────────── 3.3V │ +│ └── GND ────────── GND │ +│ │ +│ SHIFTER (SPI2) │ +│ ├── SCK ────────── PB13 │ +│ ├── MISO ───────── PB14 │ +│ ├── CS ─────────── PB12 (SPI2_SS1) │ +│ ├── X Axis ─────── Analog Input │ +│ ├── Y Axis ─────── Analog Input │ +│ ├── VCC ────────── 3.3V │ +│ └── GND ────────── GND │ +│ │ +│ WHEEL RIM BUTTONS (SPI3) - Separate bus required! │ +│ ├── SCK ────────── PC10 │ +│ ├── MISO ───────── PC11 │ +│ ├── CS ─────────── PA15 (SPI3_SS1) │ +│ ├── VCC ────────── 3.3V │ +│ └── GND ────────── GND │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Summary Table / Tabela Resumo + +| Component | Interface | Pins | Power | Notes | +|-----------|-----------|------|-------|-------| +| **VBUS Fix** | Resistor | PA9 → 5V | - | ⚡ **10kΩ required for F407VG firmware** | +| Motor | PWM | PE11 | External PSU | H-bridge required, torque curve 0.60-0.70 | +| Encoder (Original) | Quadrature | PA0, PA1 | 5V | ABN type, CPR 2400 | +| Encoder (MT6835) | Quadrature | PA0, PA1 | 5V | ABN type, CPR 65535, ratio 11:180 ⭐ | +| Pedals | Analog | PA2, PA3, PA6 | 3.3V | Standard wiki connection | +| Shifter | SPI2 | PB13, PB14, PB12 | 3.3V | G27 mode | +| Wheel Rim | SPI3 | PC10, PC11, PA15 | 3.3V | Custom firmware required | + +--- + +## 🔧 OpenFFBoard Configurator Settings / Configurações + +### EN - Configuration Steps + +1. **Motor Driver** + - Type: PWM + - PWM Pin: PE11 + - **Torque Curve: 0.60 ~ 0.70** (reduces dead zone) + +2. **Encoder** + - Type: Local ABN + - **Option A (Original):** CPR: 2400 + - **Option B (MT6835):** CPR: 65535, Gear Ratio: 11:180 + +3. **Pedals (Local Analog)** + - Analog Source: Local Analog + - Axes: 3 (Throttle, Brake, Clutch) + - Calibrate in configurator + - ✅ Standard wiki connection - no modifications + +4. **Shifter (Analog Shifter)** + - Mode: G27 Shifter H-pattern (mode 2) + - CS Pin: 1 + +5. **Wheel Rim (SPI Buttons 3)** + - Buttons: 8 + - Mode: 74HC165 + - CS: 1 + - (Requires custom firmware with SPI_Buttons_3 class) + +### PT - Passos de Configuração + +1. **Driver do Motor** + - Tipo: PWM + - Pino PWM: PE11 + - **Curva de Torque: 0.60 ~ 0.70** (reduz zona morta) + +2. **Encoder** + - Tipo: Local ABN + - **Opção A (Original):** CPR: 2400 + - **Opção B (MT6835):** CPR: 65535, Relação: 11:180 + +3. **Pedais (Local Analog)** + - Fonte Analógica: Local Analog + - Eixos: 3 (Acelerador, Freio, Embreagem) + - Calibrar no configurador + - ✅ Conexão padrão da wiki - sem modificações + +4. **Câmbio (Analog Shifter)** + - Modo: G27 Shifter H-pattern (modo 2) + - CS Pin: 1 + +5. **Aro (SPI Buttons 3)** + - Botões: 8 + - Modo: 74HC165 + - CS: 1 + - (Requer firmware customizado com classe SPI_Buttons_3) + +--- + +## ⚠️ Warnings / Avisos + +### EN - Important Warnings + +1. **PA9 Resistor (STM32F407VET6)**: A **10kΩ pull-up resistor between PA9 and 5V is required** for the F407VG firmware to boot on generic STM32F407VET6 boards. Without it, only the DISCO firmware works. +2. **Voltage**: The shifter and wheel rim electronics work with **3.3V**. Do not apply 5V! +3. **SPI Bus Separation**: The wheel rim **must** use a separate SPI bus (SPI3) due to 74HC165 limitations. +4. **Firmware**: The wheel rim buttons require custom firmware modifications (SPI_Buttons_3 class). + +### PT - Avisos Importantes + +1. **Resistor PA9 (STM32F407VET6)**: Um **resistor pull-up de 10kΩ entre PA9 e 5V é obrigatório** para o firmware F407VG iniciar em placas genéricas STM32F407VET6. Sem ele, apenas o firmware DISCO funciona. +2. **Tensão**: A eletrônica do câmbio e aro funciona com **3.3V**. Não aplique 5V! +3. **Separação do Barramento SPI**: O aro **deve** usar um barramento SPI separado (SPI3) devido às limitações do 74HC165. +4. **Firmware**: Os botões do aro requerem modificações customizadas no firmware (classe SPI_Buttons_3). + +--- + +## 📊 Final Result / Resultado Final + +| Component / Componente | Status | Windows Axes/Buttons | +|------------------------|--------|----------------------| +| Motor / Motor | ✅ Working / Funcionando | FFB enabled (torque curve 0.60-0.70) | +| Encoder / Encoder | ✅ Working / Funcionando | Steering axis (MT6835 65535 CPR) | +| Pedals / Pedais | ✅ Working / Funcionando | 3 axes (throttle, brake, clutch) | +| Shifter Gears / Marchas | ✅ Working / Funcionando | Buttons 1-7 (6 gears + reverse) | +| Shifter Buttons / Botões Câmbio | ✅ Working / Funcionando | Buttons 8-19 (12 buttons) | +| Wheel Rim / Aro | ✅ Working / Funcionando | Buttons 20-27 (8 buttons) | + +**Total: 27 buttons + 7 gears (including reverse) + 3 pedal axes + FFB motor** + +### Encoder Upgrade Notes / Notas do Upgrade do Encoder + +> **EN:** The magnetic encoder MT6835 mounted with [this 3D printed adapter](https://www.thingiverse.com/thing:1270001) provides much better resolution and FFB feel compared to the original 600 PPR encoder. The adapter also works with 555 motors. +> +> **PT:** O encoder magnético MT6835 montado com [este adaptador impresso 3D](https://www.thingiverse.com/thing:1270001) proporciona resolução muito melhor e sensação do FFB comparado ao encoder original de 600 PPR. O adaptador também funciona com motores 555. + +--- + +*Document Version: 1.1* +*Date: January 2026* +*Board: Generic STM32F407VET6* +*Hardware: Logitech G27 Racing Wheel* +*Encoder Upgrade: MT6835 ABZ Magnetic Encoder* diff --git a/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md b/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md new file mode 100644 index 000000000..70b3dd252 --- /dev/null +++ b/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md @@ -0,0 +1,367 @@ +# G27 Shifter - ShifterAnalog G27 Mode Bug Report & Fix + +## 🎯 Summary + +The **G27 Shifter buttons did not work** when using the official **ShifterAnalog "G27 Shifter H-pattern" mode** on a generic [STM32F407VET6 board](http://pt.aliexpress.com/item/1005006882009420.html) running OpenFFBoard firmware. After code analysis and modifications, the issue was identified and fixed. + +--- + +## 📋 Hardware Setup + +| Function | Pin | Description | +|----------|-----|-------------| +| SCK | PB13 | SPI2 Clock | +| MISO | PB14 | SPI2 Data In | +| CS/Latch | PB12 | SPI2_SS1 (Shifter Chip Select) | +| VCC | 3.3V | Power | +| GND | GND | Ground | + +The G27 shifter contains: +- **2x 74HC165** shift registers (16 buttons total) +- **Analog axes** for X/Y position (gear detection) + +--- + +## 🔴 Original Problem + +### Symptoms +- ✅ **Analog axes worked** - Gear positions detected correctly +- ❌ **Buttons did NOT work** - No button presses registered in G27 mode +- ✅ **Same hardware worked with SPI Buttons** - When configured as standalone "SPI Buttons" with 16 buttons, mode 74HC165, CS 1, all buttons worked + +--- + +## 🔍 Root Cause Analysis + +After analyzing the original code, **5 bugs** were identified in `ShifterAnalog.cpp`: + +### Bug 1: Wrong SPI Clock Configuration + +**Original Code:** +```cpp +spiConfig.peripheral.CLKPhase = SPI_PHASE_1EDGE; +spiConfig.peripheral.CLKPolarity = SPI_POLARITY_LOW; +``` + +**Problem:** The 74HC165 shift register requires specific SPI timing. The original configuration didn't match the chip's requirements. + +**Fix:** +```cpp +// 74HC165 configuration: sample on falling edge (phase 2, polarity high) +spiConfig.peripheral.CLKPhase = SPI_PHASE_2EDGE; +spiConfig.peripheral.CLKPolarity = SPI_POLARITY_HIGH; +``` + +--- + +### Bug 2: Missing CS Polarity Configuration + +**Original Code:** +```cpp +// No cspol configuration +``` + +**Problem:** The CS (chip select) polarity was not explicitly set, causing incorrect latch behavior for the 74HC165. + +**Fix:** +```cpp +// CS polarity LOW (active low) for 74HC165 mode +spiConfig.cspol = false; +``` + +--- + +### Bug 3: Asynchronous Read Without Waiting for Data + +**Original Code:** +```cpp +bool ShifterAnalog::G27ShifterButtonClient::getReverseButton() { + external_spi.receive_DMA(reinterpret_cast(&buttonStates), sizeof(buttonStates), this); + return buttonStates & 0x02; // Returns BEFORE DMA completes! +} +``` + +**Problem:** The function returned the button state **immediately after starting** the DMA transfer, not after it completed. This meant it always read stale/zero data. + +**Fix:** Created a separate `startRead()` function and read from buffer after completion: +```cpp +void ShifterAnalog::G27ShifterButtonClient::startRead() { + // Use synchronous read - waits for completion + external_spi.receive(spi_buf, bytesToRead, this, 2); // 2ms timeout +} + +bool ShifterAnalog::G27ShifterButtonClient::getReverseButton() { + // Read from buffer AFTER DMA completed + uint16_t buttonStates = (uint16_t)spi_buf[0] | ((uint16_t)spi_buf[1] << 8); + return buttonStates & 0x02; +} +``` + +--- + +### Bug 4: readButtons() Didn't Trigger SPI Read + +**Original Code:** +```cpp +uint8_t ShifterAnalog::readButtons(uint64_t* buf){ + updateAdc(); + updateReverseState(); // This triggered DMA but didn't wait + calculateGear(); + *buf = 0; + auto numUserButtons{getUserButtons(buf)}; // Read stale data + // ... +} +``` + +**Problem:** The read sequence was wrong - it didn't wait for SPI data to be ready before processing. + +**Fix:** +```cpp +uint8_t ShifterAnalog::readButtons(uint64_t* buf){ + updateAdc(); + + if (g27ShifterButtonClient) { + // Synchronous read - waits for bus and reads immediately + g27ShifterButtonClient->startRead(); + + // Now process the freshly read data + updateReverseState(); + getUserButtons(buf); + } else { + updateReverseState(); + *buf = 0; + } + + calculateGear(); + // ... +} +``` + +--- + +### Bug 5: CS Pin Index Off-by-One Error + +**Original Code:** +```cpp +void ShifterAnalog::setMode(ShifterMode newMode) { + // ... + g27ShifterButtonClient = std::make_unique(external_spi.getFreeCsPins()[0]); +} + +void ShifterAnalog::setCSPin(uint8_t new_cs_pin_num) { + // ... + g27ShifterButtonClient->updateCSPin(*external_spi.getCsPin(cs_pin_num)); // Wrong index! +} +``` + +**Problem:** +1. `setMode()` always used the first free CS pin instead of the configured `cs_pin_num` +2. `setCSPin()` used `cs_pin_num` directly, but `getCsPin()` is 0-indexed while `cs_pin_num` is 1-indexed + +**Fix:** +```cpp +void ShifterAnalog::setMode(ShifterMode newMode) { + // ... + // Use the configured CS pin (cs_pin_num is 1-indexed, getCsPin uses 0-indexed) + OutputPin* csPin = external_spi.getCsPin(cs_pin_num - 1); + if (csPin == nullptr) { + // Fallback to first free pin if configured pin is invalid + auto& freePins = external_spi.getFreeCsPins(); + if (!freePins.empty()) { + csPin = &freePins[0]; + } + } + if (csPin != nullptr) { + g27ShifterButtonClient = std::make_unique(*csPin); + } +} + +void ShifterAnalog::setCSPin(uint8_t new_cs_pin_num) { + // ... + // cs_pin_num is 1-indexed, getCsPin uses 0-indexed + OutputPin* newPin = external_spi.getCsPin(cs_pin_num - 1); + if (newPin != nullptr) { + g27ShifterButtonClient->updateCSPin(*newPin); + } +} +``` + +--- + +## 📁 Modified Files + +### ShifterAnalog.h + +```diff + class G27ShifterButtonClient : public SPIDevice { + G27ShifterButtonClient(OutputPin& csPin); + + static constexpr int numUserButtons{12}; ++ static constexpr int bytesToRead{2}; // 16 buttons = 2 bytes + + uint16_t getUserButtons(); + bool getReverseButton(); +- private: +- uint16_t buttonStates{0}; ++ void startRead(); ++ void spiRxCompleted(SPIPort* port) override; ++ ++ uint8_t spi_buf[2]{0}; // Buffer for SPI read + }; +``` + +### ShifterAnalog.cpp + +**G27ShifterButtonClient Constructor:** +```diff + ShifterAnalog::G27ShifterButtonClient::G27ShifterButtonClient(OutputPin& csPin) + :SPIDevice(external_spi,csPin) { + spiConfig.peripheral.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; + spiConfig.peripheral.FirstBit = SPI_FIRSTBIT_LSB; +- spiConfig.peripheral.CLKPhase = SPI_PHASE_1EDGE; +- spiConfig.peripheral.CLKPolarity = SPI_POLARITY_LOW; ++ spiConfig.peripheral.CLKPhase = SPI_PHASE_2EDGE; ++ spiConfig.peripheral.CLKPolarity = SPI_POLARITY_HIGH; ++ spiConfig.cspol = false; + } +``` + +**New startRead() function:** +```cpp +void ShifterAnalog::G27ShifterButtonClient::startRead() { + external_spi.receive(spi_buf, bytesToRead, this, 2); // 2ms timeout +} +``` + +**getReverseButton() - Read from buffer instead of triggering DMA:** +```diff + bool ShifterAnalog::G27ShifterButtonClient::getReverseButton() { +- external_spi.receive_DMA(reinterpret_cast(&buttonStates), sizeof(buttonStates),this); ++ uint16_t buttonStates = (uint16_t)spi_buf[0] | ((uint16_t)spi_buf[1] << 8); + return buttonStates & 0x02; + } +``` + +**getUserButtons() - Read from buffer:** +```diff + uint16_t ShifterAnalog::G27ShifterButtonClient::getUserButtons() { ++ uint16_t buttonStates = (uint16_t)spi_buf[0] | ((uint16_t)spi_buf[1] << 8); + return buttonStates >> 4; + } +``` + +**readButtons() - Proper read sequence:** +```diff + uint8_t ShifterAnalog::readButtons(uint64_t* buf){ + updateAdc(); + +- updateReverseState(); ++ if (g27ShifterButtonClient) { ++ g27ShifterButtonClient->startRead(); ++ updateReverseState(); ++ getUserButtons(buf); ++ } else { ++ updateReverseState(); ++ *buf = 0; ++ } ++ + calculateGear(); + +- *buf = 0; +- auto numUserButtons{getUserButtons(buf)}; +- + if(gear > 0){ ++ uint64_t numUserButtons = g27ShifterButtonClient ? g27ShifterButtonClient->numUserButtons : 0; + *buf |= 1 << (gear - 1 + numUserButtons); + } + + return this->btnnum; + } +``` + +**setMode() - Use configured CS pin:** +```diff + void ShifterAnalog::setMode(ShifterMode newMode) { + // ... +- g27ShifterButtonClient = std::make_unique(external_spi.getFreeCsPins()[0]); ++ OutputPin* csPin = external_spi.getCsPin(cs_pin_num - 1); ++ if (csPin == nullptr) { ++ auto& freePins = external_spi.getFreeCsPins(); ++ if (!freePins.empty()) { ++ csPin = &freePins[0]; ++ } ++ } ++ if (csPin != nullptr) { ++ g27ShifterButtonClient = std::make_unique(*csPin); ++ } + } +``` + +**setCSPin() - Fix off-by-one error:** +```diff + void ShifterAnalog::setCSPin(uint8_t new_cs_pin_num) { + // ... + if (g27ShifterButtonClient) { +- g27ShifterButtonClient->updateCSPin(*external_spi.getCsPin(cs_pin_num)); ++ OutputPin* newPin = external_spi.getCsPin(cs_pin_num - 1); ++ if (newPin != nullptr) { ++ g27ShifterButtonClient->updateCSPin(*newPin); ++ } + } + } +``` + +--- + +## ✅ Result After Fix + +| Feature | Status | +|---------|--------| +| Gear 1-6 detection | ✅ Working | +| Reverse gear | ✅ Working | +| 12 user buttons | ✅ Working | +| CS pin configuration | ✅ Working | + +--- + +## 📊 Test Environment + +| Parameter | Value | +|-----------|-------| +| Board | Generic STM32F407VET6 | +| Shifter | Logitech G27 H-Pattern | +| Connection | PB12 (CS), PB13 (SCK), PB14 (MISO) | +| Voltage | 3.3V | + +--- + +## 🇧🇷 Resumo em Português + +### Problema +Os **botões do câmbio G27 não funcionavam** no modo oficial "G27 Shifter H-pattern" do ShifterAnalog. + +### Causa (5 bugs encontrados) +1. **Configuração SPI errada** - CLKPhase e CLKPolarity incorretos para o 74HC165 +2. **Polaridade do CS não definida** - cspol não estava configurado +3. **Leitura assíncrona sem esperar dados** - getReverseButton() retornava antes do DMA completar +4. **readButtons() não disparava leitura SPI** - sequência de leitura incorreta +5. **Erro de índice no CS pin** - off-by-one error (1-indexed vs 0-indexed) + +### Solução +Foram feitas correções em `ShifterAnalog.cpp` e `ShifterAnalog.h`: +- Corrigida configuração SPI (CLKPhase, CLKPolarity, cspol) +- Criada função `startRead()` com leitura síncrona +- Corrigida sequência de leitura em `readButtons()` +- Corrigido índice do CS pin (cs_pin_num - 1) + +### Resultado +Após as correções, o câmbio G27 funciona perfeitamente: +- ✅ 6 marchas + ré +- ✅ 12 botões extras +- ✅ Configuração do CS pin funcional + +--- + +*Report Date: January 2026* +*Board: Generic STM32F407VET6* +*Affected Feature: ShifterAnalog - G27 Shifter H-pattern mode* diff --git a/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md b/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md new file mode 100644 index 000000000..15f752038 --- /dev/null +++ b/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md @@ -0,0 +1,405 @@ +# G27 Wheel Rim Buttons - SPI Bus Conflict Report & Solution + +## 🎯 Summary + +The **G27 steering wheel rim buttons** (8 buttons via 74HC165) worked correctly when used **alone** on SPI2, but **failed when used simultaneously** with the G27 shifter on the same SPI bus. This document explains the root cause and the solution implemented using a separate SPI port. + +--- + +## 📋 Hardware Setup + +### G27 Wheel Rim +| Function | Pin (Original) | Description | +|----------|----------------|-------------| +| SCK | PB13 | SPI2 Clock | +| MISO | PB14 | SPI2 Data In | +| CS/Latch | PD8 | SPI2_SS2 (Wheel Rim CS) | +| VCC | 3.3V | Power | +| GND | GND | Ground | + +The wheel rim contains: +- **1x 74HC165** shift register (8 buttons) +- **1x HC595AG** shift register (LEDs - not used) + +### G27 Shifter (for reference) +| Function | Pin | Description | +|----------|-----|-------------| +| SCK | PB13 | SPI2 Clock (shared) | +| MISO | PB14 | SPI2 Data In (shared) | +| CS/Latch | PB12 | SPI2_SS1 (Shifter CS) | + +--- + +## ✅ What Worked + +### Wheel Rim Alone on CS2 +When the wheel rim was connected **without the shifter**, using SPI Buttons with: +- `btnnum = 8` +- `mode = 1` (74HC165) +- `cs = 2` (PD8) + +**Result:** ✅ All 8 buttons worked perfectly + +### Shifter Alone on CS1 +When the shifter was connected **without the wheel rim**, using ShifterAnalog G27 mode: +- `mode = 2` (G27 H-pattern) +- `cspin = 1` (PB12) + +**Result:** ✅ All gears + reverse + 12 buttons worked perfectly + +--- + +## ❌ What Didn't Work + +### Both Devices on Same SPI Bus +When both devices were connected simultaneously: + +| Configuration | Shifter | Wheel Rim | +|--------------|---------|-----------| +| ShifterAnalog CS1 + SPI Buttons CS2 | ❌ Buttons stopped working | ❌ Buttons stopped working | +| Only ShifterAnalog active | ❌ Buttons not reading | - | +| Only SPI Buttons active | - | ✅ Working | +| Wheel rim physically disconnected | ✅ Working | - | + +**Key observation:** Simply having the wheel rim **physically connected** to the MISO line caused the shifter buttons to stop working, even with SPI Buttons disabled! + +--- + +## 🔍 Root Cause: 74HC165 Has No Tri-State Output + +### The Problem + +The **74HC165 shift register does NOT have a tri-state (high-impedance) output**. This is a critical hardware limitation. + +#### Normal SPI Device Behavior +Most SPI devices have tri-state outputs: +- When CS is **inactive** → Output goes **high-impedance** (disconnected) +- When CS is **active** → Output drives the MISO line + +#### 74HC165 Behavior +The 74HC165 is different: +- When CS (SH/LD) is **inactive** → Output **still drives** the MISO line +- When CS is **active** → Output drives the MISO line + +``` +┌─────────────────────────────────────────────────────────────┐ +│ SPI BUS CONTENTION │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 74HC165 #1 (Shifter) 74HC165 #2 (Wheel Rim) │ +│ ┌───┐ ┌───┐ │ +│ Q7 ──┤ ├──┐ Q7 ──┤ ├──┐ │ +│ └───┘ │ └───┘ │ │ +│ │ │ │ +│ └────────┬───────────────┘ │ +│ │ │ +│ ▼ │ +│ MISO (PB14) ← CONFLICT! │ +│ │ +│ Both chips ALWAYS drive MISO, regardless of CS state. │ +│ This creates electrical contention and corrupted data. │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Why It Seemed to Work Sometimes + +When only one device was actively being read, the other device's output might coincidentally not interfere. But as soon as both are polled (even at different times), the constant output driving causes: +- Data corruption +- Unpredictable readings +- One device "winning" over the other + +--- + +## ✅ Solution: Use Separate SPI Bus + +Since the 74HC165 chips cannot share a MISO line, the solution is to use a **completely separate SPI peripheral** for the wheel rim. + +### New Hardware Configuration + +| Device | Function | Pin | SPI Bus | +|--------|----------|-----|---------| +| **Shifter** | SCK | PB13 | SPI2 | +| **Shifter** | MISO | PB14 | SPI2 | +| **Shifter** | CS | PB12 | SPI2_SS1 | +| **Wheel Rim** | SCK | PC10 | SPI3 | +| **Wheel Rim** | MISO | PC11 | SPI3 | +| **Wheel Rim** | CS | PA15 | SPI3_SS1 | +| **Wheel Rim** | VCC | 3.3V | - | +| **Wheel Rim** | GND | GND | - | + +``` +┌─────────────────────────────────────────────────────────────┐ +│ SEPARATE SPI BUSES (SOLUTION) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ SPI2 Bus (Shifter) SPI3 Bus (Wheel Rim) │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ 74HC165 │ │ 74HC165 │ │ +│ │ (x2) │ │ (x1) │ │ +│ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ +│ SCK ───┼─── PB13 SCK ────┼─── PC10 │ +│ MISO ──┼─── PB14 MISO ───┼─── PC11 │ +│ CS ────┼─── PB12 CS ─────┼─── PA15 │ +│ │ │ │ +│ ▼ ▼ │ +│ ShifterAnalog SPI_Buttons_3 │ +│ (G27 Mode) (8 buttons) │ +│ │ +│ ✅ No contention - completely separate MISO lines │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 🛠️ Firmware Modifications + +### 1. Created `SPI_Buttons_3` Class + +**File:** `SPIButtons.h` +```cpp +class SPI_Buttons_3 : public SPI_Buttons { +public: + SPI_Buttons_3(); + + const ClassIdentifier getInfo() override; + static ClassIdentifier info; + static bool isCreatable(); + + void restoreFlash() override; + uint8_t readButtons(uint64_t* buf) override; // Custom read for SPI3 + std::string getHelpstring() override {return "SPI 3 Button (SPI3)";} +}; +``` + +### 2. Custom `readButtons()` for SPI3 + +The standard DMA-based read had timing issues with SPI3. Implemented synchronous read with manual CS pulse: + +**File:** `SPIButtons.cpp` +```cpp +uint8_t SPI_Buttons_3::readButtons(uint64_t* buf){ + // Return last buffer immediately (non-blocking) + memcpy(buf, this->spi_buf, std::min(this->bytes, 8)); + process(buf); + + if(spiPort.isTaken()) + return this->conf.numButtons; + + SPI_HandleTypeDef* hspi = spiPort.getPortHandle(); + + // Get CS pin (PA15) + OutputPin* cs_pin = spiPort.getCsPin(this->conf.cs_num > 0 ? this->conf.cs_num - 1 : 0); + + if(cs_pin != nullptr) { + // Pulse CS to latch parallel data into 74HC165 + cs_pin->write(false); // CS LOW - load parallel data + for(volatile int i = 0; i < 10; i++) {} // Small delay + cs_pin->write(true); // CS HIGH - enable shift + } + + // Synchronous SPI read + HAL_SPI_Receive(hspi, spi_buf, bytes, 10); + + return this->conf.numButtons; +} +``` + +### 3. Hardcoded Configuration for Wheel Rim + +```cpp +void SPI_Buttons_3::restoreFlash(){ + ButtonSourceConfig config; + config.numButtons = 8; // 8 buttons on wheel rim + config.mode = SPI_BtnMode::PISOSR; // 74HC165 mode + config.cs_num = 1; // CS pin 1 (PA15) + config.spi_speed = 2; // Slow speed + config.invert = false; + config.cutRight = false; + + setConfig(config); + this->conf.cs_num = 1; + this->btnnum = 8; +} +``` + +### 4. SPI3 Port Configuration + +**File:** `cpp_target_config.cpp` +```cpp +#ifdef EXT3_SPI_PORT +static const std::vector ext3_spi_cspins{ + OutputPin(*SPI3_SS1_GPIO_Port, SPI3_SS1_Pin), // PA15 - CS1 + OutputPin(*SPI3_SS2_GPIO_Port, SPI3_SS2_Pin), // PD2 - CS2 + OutputPin(*SPI3_SS3_GPIO_Port, SPI3_SS3_Pin) // PD3 - CS3 +}; +extern SPI_HandleTypeDef EXT3_SPI_PORT; +SPIPort ext3_spi{hspi3, ext3_spi_cspins, 42000000, true}; +#endif +``` + +### 5. SPI3 Hardware Init + +**File:** `main.c` +```c +static void MX_SPI3_Init(void) +{ + hspi3.Instance = SPI3; + hspi3.Init.Mode = SPI_MODE_MASTER; + hspi3.Init.Direction = SPI_DIRECTION_2LINES; + hspi3.Init.DataSize = SPI_DATASIZE_8BIT; + hspi3.Init.CLKPolarity = SPI_POLARITY_LOW; + hspi3.Init.CLKPhase = SPI_PHASE_1EDGE; + hspi3.Init.NSS = SPI_NSS_SOFT; + hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; + hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB; + // ... +} +``` + +--- + +## 📁 Modified Files Summary + +| File | Changes | +|------|---------| +| `SPIButtons.h` | Added `SPI_Buttons_3` class with custom `readButtons()` override | +| `SPIButtons.cpp` | Implemented `SPI_Buttons_3` with synchronous read + manual CS pulse | +| `cpp_target_config.cpp` | Defined SPI3 CS pins (PA15, PD2, PD3) | +| `main.c` | Configured SPI3 hardware (CLKPolarity, CLKPhase, BaudRate) | +| `stm32f4xx_hal_msp.c` | Adjusted SPI3 DMA/IRQ priorities | +| `OpenFFBoard_F407VG.ioc` | Updated SPI3 CubeMX configuration | + +--- + +## ✅ Final Result + +| Component | Status | Button Range | +|-----------|--------|--------------| +| G27 Shifter (SPI2) | ✅ Working | Buttons 1-19 (gears + 12 buttons) | +| G27 Wheel Rim (SPI3) | ✅ Working | Buttons 20-27 (8 buttons) | + +--- + +## 💡 Alternative Solutions (Not Implemented) + +If SPI3 pins are not available, other options include: + +1. **External Tri-State Buffer** (e.g., 74HC125) + - Add a buffer IC between each 74HC165 and the shared MISO line + - Enable/disable buffer with CS signal + - Allows sharing single MISO line + +2. **Directly daisy-chain the 74HC165s** + - Connect Q7' (serial out) of shifter's last 74HC165 to DS (serial in) of wheel rim's 74HC165 + - Read all buttons in one long SPI transaction + - Requires firmware changes to handle 24 bits (3 bytes) + +--- + +## 🔑 Key Takeaway + +**74HC165 shift registers cannot share an SPI MISO line** due to lack of tri-state output. Each 74HC165 (or group of daisy-chained 74HC165s) requires either: +- A **dedicated SPI bus**, or +- An **external tri-state buffer** on the MISO line + +--- + +# 🇧🇷 Relatório em Português + +## Resumo + +Os **botões do aro do volante G27** (8 botões via 74HC165) funcionavam corretamente quando usados **sozinhos** no SPI2, mas **falhavam quando usados simultaneamente** com o câmbio G27 no mesmo barramento SPI. + +--- + +## O Que Funcionou + +### Aro Sozinho no CS2 +- Configuração: SPI Buttons, 8 botões, modo 74HC165, CS 2 (PD8) +- **Resultado:** ✅ Todos os 8 botões funcionaram + +### Câmbio Sozinho no CS1 +- Configuração: ShifterAnalog modo G27, CS 1 (PB12) +- **Resultado:** ✅ Todas as marchas + ré + 12 botões funcionaram + +--- + +## O Que Não Funcionou + +### Ambos no Mesmo Barramento SPI +Quando ambos estavam conectados: +- Apenas conectar fisicamente o aro já fazia os botões do câmbio pararem +- Os dois dispositivos não conseguiam coexistir no mesmo MISO + +--- + +## Causa Raiz: 74HC165 Não Tem Saída Tri-State + +O **74HC165 sempre mantém sua saída ativa**, mesmo quando o CS está inativo. Isso causa conflito elétrico quando dois 74HC165 compartilham a mesma linha MISO. + +``` +Dispositivo SPI normal: + CS inativo → Saída em alta impedância (desconectada) + CS ativo → Saída conectada + +74HC165: + CS inativo → Saída CONTINUA conectada ❌ + CS ativo → Saída conectada +``` + +Quando dois 74HC165 estão na mesma linha MISO, ambos tentam controlar a linha simultaneamente, causando: +- Corrupção de dados +- Leituras imprevisíveis +- Um dispositivo "vencendo" sobre o outro + +--- + +## Solução: Usar Barramento SPI Separado + +### Nova Configuração de Hardware + +| Dispositivo | Função | Pino | Barramento | +|-------------|--------|------|------------| +| **Câmbio** | SCK | PB13 | SPI2 | +| **Câmbio** | MISO | PB14 | SPI2 | +| **Câmbio** | CS | PB12 | SPI2_SS1 | +| **Aro** | SCK | PC10 | SPI3 | +| **Aro** | MISO | PC11 | SPI3 | +| **Aro** | CS | PA15 | SPI3_SS1 | +| **Aro** | VCC | 3.3V | - | +| **Aro** | GND | GND | - | + +--- + +## Modificações no Firmware + +1. **Criada classe `SPI_Buttons_3`** para usar o SPI3 +2. **Implementada leitura síncrona** com pulso manual do CS (o DMA tinha problemas de timing no SPI3) +3. **Configuração hardcoded** para 8 botões, modo 74HC165, CS 1 (PA15) +4. **Configurado SPI3** com os parâmetros corretos (CLKPolarity, CLKPhase, BaudRate) + +--- + +## Resultado Final + +| Componente | Status | Botões no Windows | +|------------|--------|-------------------| +| Câmbio G27 (SPI2) | ✅ Funcionando | Botões 1-19 | +| Aro G27 (SPI3) | ✅ Funcionando | Botões 20-27 | + +--- + +## Lição Aprendida + +**Registradores 74HC165 não podem compartilhar a linha MISO do SPI** devido à falta de saída tri-state. Cada 74HC165 (ou grupo em cadeia) precisa de: +- Um **barramento SPI dedicado**, ou +- Um **buffer tri-state externo** (como 74HC125) na linha MISO + +--- + +*Report Date: January 2026* +*Board: Generic STM32F407VET6* +*Components: G27 Wheel Rim (8 buttons) + G27 Shifter* diff --git a/doc/logitech g27/README.md b/doc/logitech g27/README.md new file mode 100644 index 000000000..fd0269499 --- /dev/null +++ b/doc/logitech g27/README.md @@ -0,0 +1,161 @@ +# Logitech G27 Support for OpenFFBoard + +# Suporte ao Logitech G27 para OpenFFBoard + +--- + +## 📋 Overview / Visão Geral + +This folder contains documentation and guides for integrating a **Logitech G27 Racing Wheel** with **OpenFFBoard** firmware. + +Esta pasta contém documentação e guias para integrar um **Volante Logitech G27** com o firmware **OpenFFBoard**. + +--- + +## 🛒 Hardware Requirements / Requisitos de Hardware + +### Essential / Essencial + +| Component | Description | Example | +|-----------|-------------|---------| +| **STM32F407 Board** | Main controller | [STM32F407VET6 (AliExpress)](http://pt.aliexpress.com/item/1005006882009420.html) | +| **H-Bridge Driver** | Motor driver for FFB | BTS7960 43A | +| **Power Supply** | 12-24V for motor | 12V 5A minimum | +| **USB Cable** | Micro USB or USB-C | For STM32 connection | + +### From G27 / Do G27 + +| Component | What to use | +|-----------|-------------| +| **Motor** | Original G27 DC motor | +| **Encoder** | Original optical encoder (600 PPR) or upgrade to MT6835 | +| **Pedals** | Original 3-axis potentiometers | +| **Shifter** | Original H-pattern with 2x 74HC165 | +| **Wheel Rim** | Original buttons with 1x 74HC165 | + +### Optional Upgrades / Upgrades Opcionais + +| Component | Description | +|-----------|-------------| +| **MT6835 Encoder** | High resolution magnetic encoder (65535 CPR) | +| **3D Printed Adapter** | [Thingiverse 1270001](https://www.thingiverse.com/thing:1270001) | + +--- + +## ⚠️ Important Notes / Notas Importantes + +### STM32F407VET6 Board Fix + +> **EN:** Generic STM32F407VET6 boards require a **10kΩ pull-up resistor between PA9 and 5V** for the F407VG firmware to boot correctly. +> +> **PT:** Placas genéricas STM32F407VET6 requerem um **resistor pull-up de 10kΩ entre PA9 e 5V** para o firmware F407VG iniciar corretamente. + +### Wheel Rim + Shifter Together + +> **EN:** The G27 wheel rim and shifter both use 74HC165 shift registers which **cannot share the same SPI bus** due to lack of tri-state output. This fork adds **SPI3 support** to allow both to work simultaneously. +> +> **PT:** O aro e o câmbio do G27 usam 74HC165 que **não podem compartilhar o mesmo barramento SPI** devido à falta de saída tri-state. Este fork adiciona **suporte a SPI3** para ambos funcionarem simultaneamente. + +--- + +## 📄 Documentation / Documentação + +### For Users / Para Usuários + +| Document | Description | +|----------|-------------| +| [**G27_COMPLETE_WIRING_GUIDE.md**](G27_COMPLETE_WIRING_GUIDE.md) | Complete wiring guide for all G27 components / Guia completo de ligações | + +### Technical Reports / Relatórios Técnicos + +| Document | Description | +|----------|-------------| +| [**G27_SHIFTER_ISSUE_REPORT.md**](G27_SHIFTER_ISSUE_REPORT.md) | Analysis of ShifterAnalog bugs and fixes / Análise dos bugs do câmbio | +| [**G27_WHEEL_RIM_BUTTONS_REPORT.md**](G27_WHEEL_RIM_BUTTONS_REPORT.md) | SPI bus conflict analysis and SPI3 solution / Conflito SPI e solução | + +--- + +## 🔧 Quick Start / Início Rápido + +### 1. Flash Firmware / Gravar Firmware + +```bash +# Using DFU mode (USB) +# 1. Hold BOOT0 button while connecting USB +# 2. Use STM32CubeProgrammer or dfu-util +dfu-util -a 0 -s 0x08000000 -D OpenFFBoard_F407VG.bin +``` + +### 2. Wire Components / Conectar Componentes + +See [G27_COMPLETE_WIRING_GUIDE.md](G27_COMPLETE_WIRING_GUIDE.md) for detailed pinout. + +| Component | Interface | Main Pins | +|-----------|-----------|-----------| +| Motor | PWM | PE9, PE11 | +| Encoder | ABN | PA0, PA1 | +| Pedals | Analog | PA2, PA3, PA6 | +| Shifter | SPI2 | PB12, PB13, PB14 | +| Wheel Rim | SPI3 | PA15, PC10, PC11 | + +### 3. Configure / Configurar + +Using OpenFFBoard Configurator: + +1. **Motor Driver**: PWM mode, Torque Curve 0.60-0.70 +2. **Encoder**: Local ABN, CPR 2400 (or 65535 for MT6835) +3. **Analog**: 3 axes for pedals +4. **Shifter**: ShifterAnalog, Mode G27 H-pattern +5. **Wheel Rim**: SPI Buttons 3, 8 buttons, Mode 74HC165 + +--- + +## 🎮 Final Result / Resultado Final + +| Component | Status | Windows Output | +|-----------|--------|----------------| +| Motor | ✅ Working | Force Feedback enabled | +| Encoder | ✅ Working | Steering axis | +| Pedals | ✅ Working | 3 axes (throttle, brake, clutch) | +| Shifter Gears | ✅ Working | Buttons 1-7 | +| Shifter Buttons | ✅ Working | Buttons 8-19 | +| Wheel Rim | ✅ Working | Buttons 20-27 | + +**Total: 27 buttons + 7 gears + 3 pedal axes + FFB** + +--- + +## 🐛 Troubleshooting / Solução de Problemas + +### Firmware doesn't boot / Firmware não inicia + +- **Cause**: Missing PA9 pull-up resistor on generic boards +- **Solution**: Solder 10kΩ between PA9 and 5V + +### Shifter buttons not working / Botões do câmbio não funcionam + +- **Cause**: Original firmware SPI timing bug +- **Solution**: Use this fork with the ShifterAnalog fix + +### Wheel rim buttons not working with shifter / Botões do aro não funcionam com câmbio + +- **Cause**: 74HC165 bus contention on shared SPI +- **Solution**: Use SPI3 for wheel rim (this fork) + +### Motor doesn't move / Motor não gira + +- Check H-bridge connections and power supply +- Verify PWM pin configuration in Configurator +- Check encoder is reading position correctly + +--- + +## 📚 References / Referências + +- [OpenFFBoard Wiki](https://github.com/Ultrawipf/OpenFFBoard/wiki) +- [OpenFFBoard Discord](https://discord.gg/gHtnEcP) +- [Original Project](https://github.com/Ultrawipf/OpenFFBoard) + +--- + +*Last updated: April 2026* From 8947edf3611f926390f651117158edec8f70f03c Mon Sep 17 00:00:00 2001 From: Orlando Eduardo Pereira Date: Fri, 8 May 2026 16:37:04 -0300 Subject: [PATCH 2/4] Fix incorrect analog pin mapping in documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed Clutch pedal pin from PA6 to PC3 (AIN2) to match the actual F407VG hardware configuration in main.h: - AIN0 = PA3 (Brake) - AIN1 = PA2 (Throttle) - AIN2 = PC3 (Clutch) ← was incorrectly listed as PA6 Co-authored-by: Cursor --- doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md | 8 ++++---- doc/logitech g27/README.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md index eed65cc89..1843dfe16 100644 --- a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md +++ b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md @@ -163,7 +163,7 @@ The G27 pedals use **3 potentiometers** for throttle, brake, and clutch. This co |----------|-----------------|----------------|-------| | Throttle | Analog 1 (PA2) | - | Accelerator pedal | | Brake | Analog 2 (PA3) | - | Brake pedal | -| Clutch | Analog 3 (PA6) | - | Clutch pedal | +| Clutch | Analog 3 (PC3) | - | Clutch pedal | | VCC | 3.3V | Red | Potentiometer power | | GND | GND | Black | Ground | @@ -182,7 +182,7 @@ Os pedais do G27 usam **3 potenciômetros** para acelerador, freio e embreagem. |--------|------------------|---------------|-------| | Acelerador | Analog 1 (PA2) | - | Pedal do acelerador | | Freio | Analog 2 (PA3) | - | Pedal do freio | -| Embreagem | Analog 3 (PA6) | - | Pedal da embreagem | +| Embreagem | Analog 3 (PC3) | - | Pedal da embreagem | | VCC | 3.3V | Vermelho | Alimentação dos potenciômetros | | GND | GND | Preto | Terra | @@ -310,7 +310,7 @@ O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compart │ PEDALS (Standard Wiki Connection) │ │ ├── Throttle ───── PA2 (Analog 1) │ │ ├── Brake ──────── PA3 (Analog 2) │ -│ ├── Clutch ─────── PA6 (Analog 3) │ +│ ├── Clutch ─────── PC3 (Analog 3) │ │ ├── VCC ────────── 3.3V │ │ └── GND ────────── GND │ │ │ @@ -341,7 +341,7 @@ O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compart | Motor | PWM | PE11 | External PSU | H-bridge required, torque curve 0.60-0.70 | | Encoder (Original) | Quadrature | PA0, PA1 | 5V | ABN type, CPR 2400 | | Encoder (MT6835) | Quadrature | PA0, PA1 | 5V | ABN type, CPR 65535, ratio 11:180 ⭐ | -| Pedals | Analog | PA2, PA3, PA6 | 3.3V | Standard wiki connection | +| Pedals | Analog | PA2, PA3, PC3 | 3.3V | Standard wiki connection | | Shifter | SPI2 | PB13, PB14, PB12 | 3.3V | G27 mode | | Wheel Rim | SPI3 | PC10, PC11, PA15 | 3.3V | Custom firmware required | diff --git a/doc/logitech g27/README.md b/doc/logitech g27/README.md index fd0269499..5063ec86d 100644 --- a/doc/logitech g27/README.md +++ b/doc/logitech g27/README.md @@ -94,7 +94,7 @@ See [G27_COMPLETE_WIRING_GUIDE.md](G27_COMPLETE_WIRING_GUIDE.md) for detailed pi |-----------|-----------|-----------| | Motor | PWM | PE9, PE11 | | Encoder | ABN | PA0, PA1 | -| Pedals | Analog | PA2, PA3, PA6 | +| Pedals | Analog | PA2, PA3, PC3 | | Shifter | SPI2 | PB12, PB13, PB14 | | Wheel Rim | SPI3 | PA15, PC10, PC11 | From b9c4b9dd150826c20e26de39087a2eddf3465324 Mon Sep 17 00:00:00 2001 From: Orlando Eduardo Pereira Date: Fri, 8 May 2026 16:42:14 -0300 Subject: [PATCH 3/4] Add shifter analog pin mapping to documentation Documented the G27 shifter X/Y analog axis connections: - X Axis: PC0 (Analog 6) - configurable via shifter.xchan - Y Axis: PC1 (Analog 5) - configurable via shifter.ychan Updated files: - G27_COMPLETE_WIRING_GUIDE.md: Added pin info to tables, diagram - G27_SHIFTER_ISSUE_REPORT.md: Added analog pins to hardware setup - README.md: Updated shifter pin summary Co-authored-by: Cursor --- doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md | 24 ++++++++++++------- doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md | 4 +++- doc/logitech g27/README.md | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md index 1843dfe16..5e9e2f0e7 100644 --- a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md +++ b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md @@ -204,13 +204,15 @@ The G27 shifter has: | SCK | PB13 | 2 | SPI Clock | | MISO | PB14 | 4 | SPI Data | | CS/Latch | PB12 | 6 | Chip Select (SPI2_SS1) | -| X Axis | Analog In | 7 | Analog X position | -| Y Axis | Analog In | 3 | Analog Y position | +| X Axis | PC0 (Analog 6) | 7 | Analog X position | +| Y Axis | PC1 (Analog 5) | 3 | Analog Y position | | GND | GND | 9 | Ground | #### Configuration / Configuração - Mode: **G27 Shifter H-pattern** (ShifterAnalog mode 2) - CS Pin: **1** (PB12) +- X Channel: **6** (PC0) - configurable via `shifter.xchan` +- Y Channel: **5** (PC1) - configurable via `shifter.ychan` ### PT - Ligação do Câmbio @@ -224,10 +226,16 @@ O câmbio G27 tem: | SCK | PB13 | 2 | Clock SPI | | MISO | PB14 | 4 | Dados SPI | | CS/Latch | PB12 | 6 | Chip Select (SPI2_SS1) | -| Eixo X | Analog In | 7 | Posição analógica X | -| Eixo Y | Analog In | 3 | Posição analógica Y | +| Eixo X | PC0 (Analog 6) | 7 | Posição analógica X | +| Eixo Y | PC1 (Analog 5) | 3 | Posição analógica Y | | GND | GND | 9 | Terra | +#### Configuração +- Modo: **G27 Shifter H-pattern** (ShifterAnalog modo 2) +- CS Pin: **1** (PB12) +- X Channel: **6** (PC0) - configurável via `shifter.xchan` +- Y Channel: **5** (PC1) - configurável via `shifter.ychan` + --- ## Wheel Rim Buttons / Botões do Aro @@ -314,12 +322,12 @@ O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compart │ ├── VCC ────────── 3.3V │ │ └── GND ────────── GND │ │ │ -│ SHIFTER (SPI2) │ +│ SHIFTER (SPI2 + Analog) │ │ ├── SCK ────────── PB13 │ │ ├── MISO ───────── PB14 │ │ ├── CS ─────────── PB12 (SPI2_SS1) │ -│ ├── X Axis ─────── Analog Input │ -│ ├── Y Axis ─────── Analog Input │ +│ ├── X Axis ─────── PC0 (Analog 6) │ +│ ├── Y Axis ─────── PC1 (Analog 5) │ │ ├── VCC ────────── 3.3V │ │ └── GND ────────── GND │ │ │ @@ -342,7 +350,7 @@ O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compart | Encoder (Original) | Quadrature | PA0, PA1 | 5V | ABN type, CPR 2400 | | Encoder (MT6835) | Quadrature | PA0, PA1 | 5V | ABN type, CPR 65535, ratio 11:180 ⭐ | | Pedals | Analog | PA2, PA3, PC3 | 3.3V | Standard wiki connection | -| Shifter | SPI2 | PB13, PB14, PB12 | 3.3V | G27 mode | +| Shifter | SPI2 + Analog | PB13, PB14, PB12, PC0, PC1 | 3.3V | G27 mode, X/Y analog axes | | Wheel Rim | SPI3 | PC10, PC11, PA15 | 3.3V | Custom firmware required | --- diff --git a/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md b/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md index 70b3dd252..ba582f662 100644 --- a/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md +++ b/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md @@ -13,12 +13,14 @@ The **G27 Shifter buttons did not work** when using the official **ShifterAnalog | SCK | PB13 | SPI2 Clock | | MISO | PB14 | SPI2 Data In | | CS/Latch | PB12 | SPI2_SS1 (Shifter Chip Select) | +| X Axis | PC0 | Analog 6 (configurable) | +| Y Axis | PC1 | Analog 5 (configurable) | | VCC | 3.3V | Power | | GND | GND | Ground | The G27 shifter contains: - **2x 74HC165** shift registers (16 buttons total) -- **Analog axes** for X/Y position (gear detection) +- **Analog axes** for X/Y position (gear detection via PC0/PC1) --- diff --git a/doc/logitech g27/README.md b/doc/logitech g27/README.md index 5063ec86d..ee03b207e 100644 --- a/doc/logitech g27/README.md +++ b/doc/logitech g27/README.md @@ -95,7 +95,7 @@ See [G27_COMPLETE_WIRING_GUIDE.md](G27_COMPLETE_WIRING_GUIDE.md) for detailed pi | Motor | PWM | PE9, PE11 | | Encoder | ABN | PA0, PA1 | | Pedals | Analog | PA2, PA3, PC3 | -| Shifter | SPI2 | PB12, PB13, PB14 | +| Shifter | SPI2 + Analog | PB12, PB13, PB14, PC0, PC1 | | Wheel Rim | SPI3 | PA15, PC10, PC11 | ### 3. Configure / Configurar From aa73c382446faf72d1436399f441d1729985c95d Mon Sep 17 00:00:00 2001 From: Orlando Eduardo Pereira Date: Sat, 16 May 2026 13:53:11 -0300 Subject: [PATCH 4/4] Remove SPI3 wheel rim changes per review; keep ShifterAnalog fix Per maintainer feedback on PR #182: SPI2 is the preferred bus for buttons/addons; SPI3 is reserved for high-speed encoders (BISS-C, SSI). Removed from this PR: - SPI_Buttons_3 class and SPIBUTTONS3 define - EEPROM addresses ADR_SPI_BTN_3_CONF / _CONF_2 - Slow SPI3 prescaler (reverted to upstream PRESCALER_4) - Restore TIM_TMC_ARR and TIM_FFB accidentally removed in initial commit - G27-specific SPI3 comments in cpp_target_config.cpp Kept: - ShifterAnalog G27 H-pattern fix (cs_pin indexing, DMA read, SPI config) - SPI2 CS pins initialized HIGH (PB12, PD8, PD9) for correct operation - SPIBUTTONS2 define SPI3 wheel rim workaround preserved in tag g27-full-working for personal use. Upstream preferred solution: SPI2 CS2 with tri-state buffer on MISO. --- .../FFBoard/UserExtensions/Inc/SPIButtons.h | 13 ---- .../UserExtensions/Inc/eeprom_addresses.h | 2 - .../UserExtensions/Src/ButtonSources.cpp | 3 - .../FFBoard/UserExtensions/Src/SPIButtons.cpp | 75 ------------------- .../UserExtensions/Src/eeprom_addresses.c | 6 -- .../F407VG/Core/Inc/target_constants.h | 3 +- .../F407VG/Core/Src/cpp_target_config.cpp | 7 +- Firmware/Targets/F407VG/Core/Src/main.c | 10 +-- doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md | 48 ++++++------ .../G27_WHEEL_RIM_BUTTONS_REPORT.md | 4 +- doc/logitech g27/README.md | 10 +-- 11 files changed, 42 insertions(+), 139 deletions(-) diff --git a/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h b/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h index b131ee941..7c5fe634a 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h +++ b/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h @@ -103,17 +103,4 @@ class SPI_Buttons_2 : public SPI_Buttons { static bool isCreatable(); }; -class SPI_Buttons_3 : public SPI_Buttons { -public: - SPI_Buttons_3(); - - const ClassIdentifier getInfo() override; - static ClassIdentifier info; - static bool isCreatable(); - - void restoreFlash() override; // Override to set default values - uint8_t readButtons(uint64_t* buf) override; // Override to use synchronous read for SPI3 - std::string getHelpstring() override {return "SPI 3 Button (SPI3)";} -}; - #endif /* SPIBUTTONS_H_ */ diff --git a/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h b/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h index 40755abb3..ef533fa8a 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h +++ b/Firmware/FFBoard/UserExtensions/Inc/eeprom_addresses.h @@ -58,8 +58,6 @@ uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data) will return 1 if #define ADR_SPI_BTN_2_CONF 0x205 #define ADR_SPI_BTN_1_CONF_2 0x206 #define ADR_SPI_BTN_2_CONF_2 0x207 -#define ADR_SPI_BTN_3_CONF 0x209 -#define ADR_SPI_BTN_3_CONF_2 0x20A #define ADR_LOCAL_BTN_CONF_3 0x208 // Pulse mask // Local encoder #define ADR_ENCLOCAL_CPR 0x210 diff --git a/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp b/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp index b189dc693..554560c7f 100644 --- a/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/ButtonSources.cpp @@ -24,9 +24,6 @@ const std::vector> ButtonSource::all_buttonsources = #ifdef SPIBUTTONS2 add_class(2), #endif -#ifdef SPIBUTTONS3 - add_class(6), -#endif #ifdef SHIFTERBUTTONS add_class(3), #endif diff --git a/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp b/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp index 5193ce70b..7d15417ed 100644 --- a/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp @@ -52,81 +52,6 @@ SPI_Buttons_2::SPI_Buttons_2() restoreFlash(); // Call base class version (SPI_Buttons_2 doesn't override) } -ClassIdentifier SPI_Buttons_3::info = { - .name = "SPI Buttons 3" , - .id=CLSID_BTN_SPI, - }; -const ClassIdentifier SPI_Buttons_3::getInfo(){ - return info; -} - -bool SPI_Buttons_3::isCreatable(){ -#ifdef EXT3_SPI_PORT - return (ext3_spi.hasFreePins()); -#else - return false; -#endif -} - -SPI_Buttons_3::SPI_Buttons_3() - : SPI_Buttons{ADR_SPI_BTN_3_CONF, ADR_SPI_BTN_3_CONF_2, &ext3_spi, 2} { - // Constructor body runs AFTER base class is fully constructed - // Now we can safely call our overridden restoreFlash() - SPI_Buttons_3::restoreFlash(); // Explicitly call our version, not virtual dispatch -} - -void SPI_Buttons_3::restoreFlash(){ - // Always apply G27 wheel rim defaults for SPI_Buttons_3 (hardcoded) - // Force these values every time, regardless of flash contents - ButtonSourceConfig config; - config.numButtons = 8; // 8 buttons on wheel rim - HARDCODED - config.mode = SPI_BtnMode::PISOSR; // 74xx165 mode - HARDCODED - config.cs_num = 1; // CS pin 1 (PA15 - SPI3_SS1) - HARDCODED - config.spi_speed = 2; // Slow speed (prescaler 64) - HARDCODED - config.invert = false; - config.cutRight = false; - - // Always apply hardcoded configuration - setConfig(config); - - // Force CS to correct value after setConfig - this->conf.cs_num = 1; - - // Explicitly ensure btnnum is set (multiple attempts for debug) - this->btnnum = 8; - ButtonSource::btnnum = 8; -} - -// SPI_Buttons_3 uses synchronous read because DMA has timing issues with SPI3 -uint8_t SPI_Buttons_3::readButtons(uint64_t* buf){ - // Copy last buffer to output - memcpy(buf, this->spi_buf, std::min(this->bytes, 8)); - process(buf); - - // Check if SPI is available - if(spiPort.isTaken()) - return this->conf.numButtons; - - // Use direct HAL_SPI_Receive which works correctly for SPI3 - SPI_HandleTypeDef* hspi = spiPort.getPortHandle(); - - // Get CS pin for this device (cs_num is 1-indexed, array is 0-indexed) - OutputPin* cs_pin = spiPort.getCsPin(this->conf.cs_num > 0 ? this->conf.cs_num - 1 : 0); - - if(cs_pin != nullptr) { - // Pulse CS low then high to latch data into 74HC165 - cs_pin->write(false); // CS LOW - load parallel data - // Small delay for latch - for(volatile int i = 0; i < 10; i++) {} - cs_pin->write(true); // CS HIGH - enable shift - } - - // Read data via SPI - HAL_SPI_Receive(hspi, spi_buf, bytes, 10); - - return this->conf.numButtons; -} - // TODO check if pin is free SPI_Buttons::SPI_Buttons(uint16_t configuration_address, uint16_t configuration_address_2, SPIPort* spiPort, uint8_t instance) diff --git a/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c b/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c index 0026f3d48..80257d632 100644 --- a/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c +++ b/Firmware/FFBoard/UserExtensions/Src/eeprom_addresses.c @@ -40,8 +40,6 @@ const uint16_t VirtAddVarTab[NB_OF_VAR] = ADR_SPI_BTN_2_CONF, ADR_SPI_BTN_1_CONF_2, ADR_SPI_BTN_2_CONF_2, - ADR_SPI_BTN_3_CONF, - ADR_SPI_BTN_3_CONF_2, ADR_LOCAL_BTN_CONF_3, // Pulse mask // Local encoder ADR_ENCLOCAL_CPR, @@ -238,12 +236,8 @@ const uint16_t exportableFlashAddresses[NB_EXPORTABLE_ADR] = ADR_SPI_BTN_1_CONF, ADR_SHIFTERANALOG_CONF, ADR_LOCAL_BTN_CONF, // Pin mask - ADR_LOCAL_BTN_CONF_2, // Misc settings - ADR_SPI_BTN_2_CONF, ADR_SPI_BTN_1_CONF_2, ADR_SPI_BTN_2_CONF_2, - ADR_SPI_BTN_3_CONF, - ADR_SPI_BTN_3_CONF_2, ADR_LOCAL_BTN_CONF_3, // Pulse mask // Local encoder ADR_ENCLOCAL_CPR, diff --git a/Firmware/Targets/F407VG/Core/Inc/target_constants.h b/Firmware/Targets/F407VG/Core/Inc/target_constants.h index c43d5983f..6897827dc 100644 --- a/Firmware/Targets/F407VG/Core/Inc/target_constants.h +++ b/Firmware/Targets/F407VG/Core/Inc/target_constants.h @@ -41,7 +41,6 @@ #define LOCALBUTTONS #define SPIBUTTONS #define SPIBUTTONS2 -#define SPIBUTTONS3 #define SHIFTERBUTTONS #define PCF8574BUTTONS // Requires I2C #define ANALOGAXES @@ -71,6 +70,8 @@ #define TIM_USER htim9 // Timer with full core clock speed available for the mainclass #define TIM_TMC htim6 // Timer running at half clock speed #define TIM_TMC_BCLK SystemCoreClock / 2 +#define TIM_TMC_ARR 250 // 4khz +#define TIM_FFB htim13 extern UART_HandleTypeDef huart1; #define UART_PORT_EXT huart1 // main uart port diff --git a/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp b/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp index fe41ac90c..5b481f7f8 100644 --- a/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp +++ b/Firmware/Targets/F407VG/Core/Src/cpp_target_config.cpp @@ -11,12 +11,7 @@ extern SPI_HandleTypeDef hspi1; SPIPort motor_spi{hspi1,motor_spi_cspins,84000000,false}; #ifdef EXT3_SPI_PORT -// SPI3 CS pins - native SPI3 pins only -static const std::vector ext3_spi_cspins{ - OutputPin(*SPI3_SS1_GPIO_Port, SPI3_SS1_Pin), // PA15 - CS1 (used for G27 wheel rim) - OutputPin(*SPI3_SS2_GPIO_Port, SPI3_SS2_Pin), // PD2 - CS2 - OutputPin(*SPI3_SS3_GPIO_Port, SPI3_SS3_Pin) // PD3 - CS3 -}; +static const std::vector ext3_spi_cspins{OutputPin(*SPI3_SS1_GPIO_Port, SPI3_SS1_Pin), OutputPin(*SPI3_SS2_GPIO_Port, SPI3_SS2_Pin),OutputPin(*SPI3_SS3_GPIO_Port, SPI3_SS3_Pin)}; extern SPI_HandleTypeDef EXT3_SPI_PORT; SPIPort ext3_spi{hspi3,ext3_spi_cspins,42000000,true}; #endif diff --git a/Firmware/Targets/F407VG/Core/Src/main.c b/Firmware/Targets/F407VG/Core/Src/main.c index 5758fdcf9..716996bd8 100644 --- a/Firmware/Targets/F407VG/Core/Src/main.c +++ b/Firmware/Targets/F407VG/Core/Src/main.c @@ -677,7 +677,7 @@ static void MX_SPI3_Init(void) hspi3.Init.CLKPolarity = SPI_POLARITY_LOW; hspi3.Init.CLKPhase = SPI_PHASE_1EDGE; hspi3.Init.NSS = SPI_NSS_SOFT; - hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // Slower for reliable 74HC165 communication + hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi3.Init.TIMode = SPI_TIMODE_DISABLE; hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -1383,14 +1383,12 @@ static void MX_GPIO_Init(void) |LED_ERR_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ - // SPI CS pins initialized HIGH (inactive) - important for 74HC165 shift registers + // SPI2 CS pins initialized HIGH (inactive) - required for correct SPI_Buttons operation HAL_GPIO_WritePin(GPIOD, SPI2_SS2_Pin|SPI2_SS3_Pin, GPIO_PIN_SET); - HAL_GPIO_WritePin(GPIOD, SPI3_SS2_Pin|SPI3_SS3_Pin, GPIO_PIN_SET); - HAL_GPIO_WritePin(GPIOD, CAN_S_Pin|GP1_Pin|LED_SYS_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(GPIOD, SPI3_SS2_Pin|SPI3_SS3_Pin|CAN_S_Pin|GP1_Pin|LED_SYS_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ - // SPI3 CS1 initialized HIGH (inactive) - used for G27 wheel rim buttons - HAL_GPIO_WritePin(SPI3_SS1_GPIO_Port, SPI3_SS1_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(SPI3_SS1_GPIO_Port, SPI3_SS1_Pin, GPIO_PIN_RESET); /*Configure GPIO pins : DIN7_Pin DIN6_Pin DIN5_Pin DIN4_Pin DIN3_Pin */ diff --git a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md index 5e9e2f0e7..9a5660b3c 100644 --- a/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md +++ b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md @@ -240,13 +240,29 @@ O câmbio G27 tem: ## Wheel Rim Buttons / Botões do Aro +> **Upstream PR vs Fork note:** +> - **PR #182 (upstream):** Contains only the ShifterAnalog G27 H-pattern fix. Wheel rim via SPI2 CS2 is the preferred upstream approach, but requires a **tri-state buffer** on MISO because the stock G27 74HC165 has no tri-state output. +> - **Tag `g27-full-working` (fork):** Contains SPI3 support for the wheel rim. Use this tag to build and flash firmware if you need both shifter + wheel rim working on stock G27 hardware today. + ### EN - Wheel Rim Wiring The G27 wheel rim has: - **1x 74HC165** for 8 buttons - **1x HC595AG** for LEDs (optional, not implemented) -> **Important:** The wheel rim buttons **cannot share the same SPI bus** with the shifter because the 74HC165 lacks tri-state output. A **separate SPI port (SPI3)** must be used. +> **Important:** The wheel rim buttons **cannot share the same SPI bus** with the shifter because the **stock G27 74HC165 lacks tri-state output**. The upstream preferred approach (SPI2 CS2) requires adding a tri-state buffer (e.g. 74HC125) on the MISO line. The fork workaround uses a **separate SPI port (SPI3)** — available in tag `g27-full-working`. + +#### Option A: Upstream approach (SPI2 CS2, requires tri-state buffer) + +| Function | OpenFFBoard Pin | Notes | +|----------|-----------------|-------| +| SCK | PB13 | Shared with shifter | +| MISO | PB14 | Via 74HC125 tri-state buffer | +| CS/Latch | PD8 | SPI2_SS2 | +| VCC | 3.3V | Power | +| GND | GND | Ground | + +#### Option B: Fork workaround (SPI3, tag `g27-full-working`) | Function | OpenFFBoard Pin | Wheel Rim Pin | Notes | |----------|-----------------|---------------|-------| @@ -258,37 +274,27 @@ The G27 wheel rim has: | MOSI | - | 3 | For LEDs (not used) | | LED Latch | - | 5 | For LEDs (not used) | -#### Why SPI3? / Por que SPI3? +#### Why the conflict? / Por que há conflito? -The 74HC165 shift register does **not have tri-state output**. When two 74HC165 chips share the same MISO line, they create electrical contention - both try to drive the line simultaneously. Using a separate SPI bus (SPI3) avoids this problem. +The 74HC165 shift register does **not have tri-state output**. When two 74HC165 chips share the same MISO line, they create electrical contention - both try to drive the line simultaneously. The upstream solution requires a buffer IC; the fork solution uses a separate SPI bus. -#### Configuration / Configuração -- Class: **SPI Buttons 3** (custom, uses SPI3) +#### Configuration for Option B / Configuração para Opção B +- Class: **SPI Buttons 3** (fork only, uses SPI3) - Buttons: **8** - Mode: **74HC165** (PISOSR) - CS Pin: **1** (PA15) ### PT - Ligação dos Botões do Aro +> **PR upstream vs Fork:** +> - **PR #182 (upstream):** Contém apenas o fix do câmbio ShifterAnalog. Aro via SPI2 CS2 é a abordagem preferida upstream, mas exige **buffer tri-state** no MISO porque o G27 stock não tem saída tri-state. +> - **Tag `g27-full-working` (fork):** Contém suporte a SPI3 para o aro. Use essa tag para compilar e gravar se precisar de câmbio + aro funcionando no hardware G27 stock hoje. + O aro do G27 tem: - **1x 74HC165** para 8 botões - **1x HC595AG** para LEDs (opcional, não implementado) -> **Importante:** Os botões do aro **não podem compartilhar o mesmo barramento SPI** com o câmbio porque o 74HC165 não tem saída tri-state. Um **porta SPI separada (SPI3)** deve ser usada. - -| Função | Pino OpenFFBoard | Pino do Aro | Notas | -|--------|------------------|-------------|-------| -| VCC | 3.3V | 1 | Alimentação (3.3V!) | -| SCK | PC10 | 4 | Clock SPI3 | -| MISO | PC11 | 6 | Dados SPI3 | -| CS/Latch | PA15 | 2 | SPI3_SS1 | -| GND | GND | 7 | Terra | -| MOSI | - | 3 | Para LEDs (não usado) | -| LED Latch | - | 5 | Para LEDs (não usado) | - -#### Por que SPI3? - -O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compartilham a mesma linha MISO, eles criam conflito elétrico - ambos tentam controlar a linha simultaneamente. Usar um barramento SPI separado (SPI3) evita esse problema. +> **Importante:** Os botões do aro **não podem compartilhar o mesmo barramento SPI** com o câmbio porque o **G27 stock 74HC165 não tem saída tri-state**. A abordagem upstream (SPI2 CS2) requer adicionar um buffer tri-state (ex: 74HC125). O workaround fork usa **SPI3** — disponível na tag `g27-full-working`. --- @@ -422,7 +428,7 @@ O registrador 74HC165 **não tem saída tri-state**. Quando dois 74HC165 compart 1. **PA9 Resistor (STM32F407VET6)**: A **10kΩ pull-up resistor between PA9 and 5V is required** for the F407VG firmware to boot on generic STM32F407VET6 boards. Without it, only the DISCO firmware works. 2. **Voltage**: The shifter and wheel rim electronics work with **3.3V**. Do not apply 5V! 3. **SPI Bus Separation**: The wheel rim **must** use a separate SPI bus (SPI3) due to 74HC165 limitations. -4. **Firmware**: The wheel rim buttons require custom firmware modifications (SPI_Buttons_3 class). +4. **Firmware**: Wheel rim buttons via SPI3 require the fork firmware (tag `g27-full-working`). The upstream PR includes only the ShifterAnalog fix. ### PT - Avisos Importantes diff --git a/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md b/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md index 15f752038..31d27ebf1 100644 --- a/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md +++ b/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md @@ -1,8 +1,10 @@ # G27 Wheel Rim Buttons - SPI Bus Conflict Report & Solution +> **Upstream PR note:** The upstream maintainer prefers using **SPI2 with a second CS pin** (e.g. PD8) for the wheel rim, which is the correct approach when a tri-state buffer is present on the MISO line. The **stock G27 wheel rim has no tri-state output**, so SPI2 CS2 fails when both devices are connected simultaneously. The SPI3 workaround documented here is available in the tag **`g27-full-working`** (fork only). **PR #182 contains only the ShifterAnalog fix** — the SPI3 wheel rim code is not included upstream. + ## 🎯 Summary -The **G27 steering wheel rim buttons** (8 buttons via 74HC165) worked correctly when used **alone** on SPI2, but **failed when used simultaneously** with the G27 shifter on the same SPI bus. This document explains the root cause and the solution implemented using a separate SPI port. +The **G27 steering wheel rim buttons** (8 buttons via 74HC165) worked correctly when used **alone** on SPI2, but **failed when used simultaneously** with the G27 shifter on the same SPI bus. This document explains the root cause and the workaround implemented using a separate SPI port (SPI3, fork/tag only). --- diff --git a/doc/logitech g27/README.md b/doc/logitech g27/README.md index ee03b207e..764ef1863 100644 --- a/doc/logitech g27/README.md +++ b/doc/logitech g27/README.md @@ -52,9 +52,9 @@ Esta pasta contém documentação e guias para integrar um **Volante Logitech G2 ### Wheel Rim + Shifter Together -> **EN:** The G27 wheel rim and shifter both use 74HC165 shift registers which **cannot share the same SPI bus** due to lack of tri-state output. This fork adds **SPI3 support** to allow both to work simultaneously. +> **EN:** The G27 wheel rim and shifter both use 74HC165 shift registers which **cannot share the same SPI bus** due to lack of tri-state output. **PR #182 (upstream)** includes only the G27 shifter fix in ShifterAnalog. The wheel rim via SPI3 is a fork-only workaround available in tag **`g27-full-working`** — use it to flash if you need both shifter + wheel rim working today. > -> **PT:** O aro e o câmbio do G27 usam 74HC165 que **não podem compartilhar o mesmo barramento SPI** devido à falta de saída tri-state. Este fork adiciona **suporte a SPI3** para ambos funcionarem simultaneamente. +> **PT:** O aro e o câmbio do G27 usam 74HC165 que **não podem compartilhar o mesmo barramento SPI** devido à falta de saída tri-state. **O PR #182 (upstream)** inclui apenas o fix do câmbio no ShifterAnalog. O aro via SPI3 é um workaround de fork disponível na tag **`g27-full-working`** — use-a para gravar se você precisa do câmbio + aro funcionando hoje. --- @@ -71,7 +71,7 @@ Esta pasta contém documentação e guias para integrar um **Volante Logitech G2 | Document | Description | |----------|-------------| | [**G27_SHIFTER_ISSUE_REPORT.md**](G27_SHIFTER_ISSUE_REPORT.md) | Analysis of ShifterAnalog bugs and fixes / Análise dos bugs do câmbio | -| [**G27_WHEEL_RIM_BUTTONS_REPORT.md**](G27_WHEEL_RIM_BUTTONS_REPORT.md) | SPI bus conflict analysis and SPI3 solution / Conflito SPI e solução | +| [**G27_WHEEL_RIM_BUTTONS_REPORT.md**](G27_WHEEL_RIM_BUTTONS_REPORT.md) | SPI bus conflict analysis and SPI3 workaround (fork/tag `g27-full-working` only) | --- @@ -96,7 +96,7 @@ See [G27_COMPLETE_WIRING_GUIDE.md](G27_COMPLETE_WIRING_GUIDE.md) for detailed pi | Encoder | ABN | PA0, PA1 | | Pedals | Analog | PA2, PA3, PC3 | | Shifter | SPI2 + Analog | PB12, PB13, PB14, PC0, PC1 | -| Wheel Rim | SPI3 | PA15, PC10, PC11 | +| Wheel Rim | SPI3 (fork/tag `g27-full-working` only) | PA15, PC10, PC11 | ### 3. Configure / Configurar @@ -106,7 +106,7 @@ Using OpenFFBoard Configurator: 2. **Encoder**: Local ABN, CPR 2400 (or 65535 for MT6835) 3. **Analog**: 3 axes for pedals 4. **Shifter**: ShifterAnalog, Mode G27 H-pattern -5. **Wheel Rim**: SPI Buttons 3, 8 buttons, Mode 74HC165 +5. **Wheel Rim**: SPI Buttons 3, 8 buttons, Mode 74HC165 *(fork/tag `g27-full-working` only)* ---