diff --git a/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h b/Firmware/FFBoard/UserExtensions/Inc/SPIButtons.h index 9f8f061df..7c5fe634a 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,10 +96,7 @@ 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; 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..ef533fa8a 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 diff --git a/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp b/Firmware/FFBoard/UserExtensions/Src/SPIButtons.cpp index a82e0d710..7d15417ed 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,24 @@ 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) +} + // 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 +92,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 +112,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 +155,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 +163,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 +209,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 +233,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 +265,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 +278,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 +291,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..80257d632 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, @@ -212,10 +212,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, @@ -236,8 +236,6 @@ 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_LOCAL_BTN_CONF_3, // Pulse mask @@ -410,4 +408,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..6897827dc 100644 --- a/Firmware/Targets/F407VG/Core/Inc/target_constants.h +++ b/Firmware/Targets/F407VG/Core/Inc/target_constants.h @@ -40,6 +40,7 @@ // Extra features #define LOCALBUTTONS #define SPIBUTTONS +#define SPIBUTTONS2 #define SHIFTERBUTTONS #define PCF8574BUTTONS // Requires I2C #define ANALOGAXES diff --git a/Firmware/Targets/F407VG/Core/Src/main.c b/Firmware/Targets/F407VG/Core/Src/main.c index 9026825ba..716996bd8 100644 --- a/Firmware/Targets/F407VG/Core/Src/main.c +++ b/Firmware/Targets/F407VG/Core/Src/main.c @@ -1374,15 +1374,18 @@ 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); + // 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|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); 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..9a5660b3c --- /dev/null +++ b/doc/logitech g27/G27_COMPLETE_WIRING_GUIDE.md @@ -0,0 +1,467 @@ +# 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 (PC3) | - | 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 (PC3) | - | 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 | 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 + +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 | 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 + +> **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 **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 | +|----------|-----------------|---------------|-------| +| 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 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. The upstream solution requires a buffer IC; the fork solution uses a separate SPI bus. + +#### 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 **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`. + +--- + +## 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 ─────── PC3 (Analog 3) │ +│ ├── VCC ────────── 3.3V │ +│ └── GND ────────── GND │ +│ │ +│ SHIFTER (SPI2 + Analog) │ +│ ├── SCK ────────── PB13 │ +│ ├── MISO ───────── PB14 │ +│ ├── CS ─────────── PB12 (SPI2_SS1) │ +│ ├── X Axis ─────── PC0 (Analog 6) │ +│ ├── Y Axis ─────── PC1 (Analog 5) │ +│ ├── 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, PC3 | 3.3V | Standard wiki connection | +| 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 | + +--- + +## 🔧 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**: Wheel rim buttons via SPI3 require the fork firmware (tag `g27-full-working`). The upstream PR includes only the ShifterAnalog fix. + +### 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..ba582f662 --- /dev/null +++ b/doc/logitech g27/G27_SHIFTER_ISSUE_REPORT.md @@ -0,0 +1,369 @@ +# 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) | +| 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 via PC0/PC1) + +--- + +## 🔴 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..31d27ebf1 --- /dev/null +++ b/doc/logitech g27/G27_WHEEL_RIM_BUTTONS_REPORT.md @@ -0,0 +1,407 @@ +# 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 workaround implemented using a separate SPI port (SPI3, fork/tag only). + +--- + +## 📋 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..764ef1863 --- /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. **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. **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. + +--- + +## 📄 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 workaround (fork/tag `g27-full-working` only) | + +--- + +## 🔧 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, PC3 | +| Shifter | SPI2 + Analog | PB12, PB13, PB14, PC0, PC1 | +| Wheel Rim | SPI3 (fork/tag `g27-full-working` only) | 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 *(fork/tag `g27-full-working` only)* + +--- + +## 🎮 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*