From a5f269d733f1b28e341c45f815daa24be9e2afb6 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Sat, 6 Feb 2021 14:59:06 -0800 Subject: [PATCH] implement bit-banged I2C driver (#2289) * bb * implement * tweaks * s * comment, format * guard * headers and comment --- firmware/hw_layer/hw_layer.mk | 4 +- firmware/hw_layer/i2c_bb.cpp | 213 ++++++++++++++++++++++++++++++++++ firmware/hw_layer/i2c_bb.h | 62 ++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 firmware/hw_layer/i2c_bb.cpp create mode 100644 firmware/hw_layer/i2c_bb.h diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk index bf103e7c8f..3467a3788f 100644 --- a/firmware/hw_layer/hw_layer.mk +++ b/firmware/hw_layer/hw_layer.mk @@ -41,7 +41,9 @@ HW_LAYER_EMS_CPP = $(HW_LAYER_EGT_CPP) \ $(PROJECT_DIR)/hw_layer/io_pins.cpp \ $(PROJECT_DIR)/hw_layer/rtc_helper.cpp \ $(PROJECT_DIR)/hw_layer/cdm_ion_sense.cpp \ - $(PROJECT_DIR)/hw_layer/debounce.cpp + $(PROJECT_DIR)/hw_layer/debounce.cpp \ + $(PROJECT_DIR)/hw_layer/i2c_bb.cpp \ + # # '-include' is a magic kind of 'include' which would survive if file to be included is not found diff --git a/firmware/hw_layer/i2c_bb.cpp b/firmware/hw_layer/i2c_bb.cpp new file mode 100644 index 0000000000..1ea1ba7350 --- /dev/null +++ b/firmware/hw_layer/i2c_bb.cpp @@ -0,0 +1,213 @@ +/** + * @file i2c_bb.cpp + * @brief Bit-banged I2C driver + * + * @date February 6, 2020 + * @author Matthew Kennedy, (c) 2020 + */ + +#include "i2c_bb.h" + +#if EFI_PROD_CODE + +#include "io_pins.h" +#include "efi_gpio.h" + +void BitbangI2c::sda_high() { + palSetPad(m_sdaPort, m_sdaPin); +} + +void BitbangI2c::sda_low() { + palClearPad(m_sdaPort, m_sdaPin); +} + +void BitbangI2c::scl_high() { + palSetPad(m_sclPort, m_sclPin); +} + +void BitbangI2c::scl_low() { + palClearPad(m_sclPort, m_sclPin); +} + +void BitbangI2c::init(brain_pin_e scl, brain_pin_e sda) { + if (m_sdaPort) return; + + efiSetPadMode("i2c", scl, PAL_MODE_OUTPUT_OPENDRAIN); //PAL_STM32_OTYPE_OPENDRAIN + efiSetPadMode("i2c", sda, PAL_MODE_OUTPUT_OPENDRAIN); + + m_sclPort = getHwPort("i2c", scl); + m_sclPin = getHwPin("i2c", scl); + + m_sdaPort = getHwPort("i2c", sda); + m_sdaPin = getHwPin("i2c", sda); + + // Both lines idle high + scl_high(); + sda_high(); +} + +void BitbangI2c::start() { + // Start with both lines high (bus idle) + sda_high(); + waitQuarterBit(); + scl_high(); + waitQuarterBit(); + + // SDA goes low while SCL is high + sda_low(); + waitQuarterBit(); + scl_low(); + waitQuarterBit(); +} + +void BitbangI2c::stop() { + scl_low(); + waitQuarterBit(); + sda_low(); + waitQuarterBit(); + scl_high(); + waitQuarterBit(); + // SDA goes high while SCL is high + sda_high(); +} + +void BitbangI2c::sendBit(bool val) { + waitQuarterBit(); + + // Write the bit (write while SCL is low) + if (val) { + sda_high(); + } else { + sda_low(); + } + + // Data setup time (~100ns min) + waitQuarterBit(); + + // Strobe the clock + scl_high(); + waitQuarterBit(); + scl_low(); + waitQuarterBit(); +} + +bool BitbangI2c::readBit() { + waitQuarterBit(); + + scl_high(); + + waitQuarterBit(); + waitQuarterBit(); + + // Read just before we set the clock low (ie, as late as possible) + bool val = palReadPad(m_sdaPort, m_sdaPin); + + scl_low(); + waitQuarterBit(); + + return val; +} + +bool BitbangI2c::writeByte(uint8_t data) { + // write out 8 data bits + for (size_t i = 0; i < 8; i++) { + // Send the MSB + sendBit((data & 0x80) != 0); + + data = data << 1; + } + + // Force a release of the data line so the slave can ACK + sda_high(); + + // Read the ack bit + bool ackBit = readBit(); + + // 0 -> ack + // 1 -> nack + return !ackBit; +} + +uint8_t BitbangI2c::readByte(bool ack) { + uint8_t result = 0; + + // Read in 8 data bits + for (size_t i = 0; i < 8; i++) { + result = result << 1; + + result |= readBit() ? 1 : 0; + } + + // 0 -> ack + // 1 -> nack + sendBit(!ack); + + return result; +} + +void BitbangI2c::waitQuarterBit() { + // This yields a bitrate of about 320khz on a 168MHz F4 + for (size_t i = 0; i < 30; i++) { + __asm__ volatile ("nop"); + } +} + +void BitbangI2c::write(uint8_t addr, const uint8_t* writeData, size_t writeSize) { + start(); + + // Address + write + writeByte(addr << 1 | 0); + + // Write outbound bytes + for (size_t i = 0; i < writeSize; i++) { + writeByte(writeData[i]); + } + + stop(); +} + +void BitbangI2c::writeRead(uint8_t addr, const uint8_t* writeData, size_t writeSize, uint8_t* readData, size_t readSize) { + start(); + + // Address + write + writeByte(addr << 1 | 0); + + // Write outbound bytes + for (size_t i = 0; i < writeSize; i++) { + writeByte(writeData[i]); + } + + // Send a repeated start bit to indicate transition to read + start(); + + // Address + read + writeByte(addr << 1 | 1); + + for (size_t i = 0; i < readSize - 1; i++) { + // All but the last byte send ACK to indicate we're still reading + readData[i] = readByte(true); + } + + // last byte sends NAK to indicate we're done reading + readData[readSize - 1] = readByte(false); + + stop(); +} + +uint8_t BitbangI2c::readRegister(uint8_t addr, uint8_t reg) { + uint8_t retval; + + writeRead(addr, ®, 1, &retval, 1); + + return retval; +} + +void BitbangI2c::writeRegister(uint8_t addr, uint8_t reg, uint8_t val) { + uint8_t buf[2]; + buf[0] = reg; + buf[1] = val; + + write(addr, buf, 2); +} + +#endif // EFI_PROD_CODE diff --git a/firmware/hw_layer/i2c_bb.h b/firmware/hw_layer/i2c_bb.h new file mode 100644 index 0000000000..1f30af8b7a --- /dev/null +++ b/firmware/hw_layer/i2c_bb.h @@ -0,0 +1,62 @@ +/** + * @file i2c_bb.h + * @brief Bit-banged I2C driver + * + * @date February 6, 2020 + * @author Matthew Kennedy, (c) 2020 + */ + +#pragma once + +#if EFI_PROD_CODE + +#include "hal.h" +#include "rusefi_hw_enums.h" +#include +#include + +class BitbangI2c { +public: + // Initialize the I2C driver + void init(brain_pin_e scl, brain_pin_e sda); + + // Write a sequence of bytes to the specified device + void write(uint8_t addr, const uint8_t* data, size_t size); + // Write some bytes then read some bytes back after a repeated start bit + void writeRead(uint8_t addr, const uint8_t* writeData, size_t writeSize, uint8_t* readData, size_t readSize); + + // Read a register at the specified address and register index + uint8_t readRegister(uint8_t addr, uint8_t reg); + // Write a register at the specified address and register index + void writeRegister(uint8_t addr, uint8_t reg, uint8_t val); + +private: + // Returns true if the remote device acknowledged the transmission + bool writeByte(uint8_t data); + uint8_t readByte(bool ack); + + void sda_low(); + void sda_high(); + void scl_low(); + void scl_high(); + + // Send an I2C start condition + void start(); + // Send an I2C stop condition + void stop(); + + // Send a single bit + void sendBit(bool val); + // Read a single bit + bool readBit(); + + // Wait for 1/4 of a bit time + void waitQuarterBit(); + + ioportid_t m_sclPort = 0; + ioportmask_t m_sclPin = 0; + ioportid_t m_sdaPort = 0; + ioportmask_t m_sdaPin = 0; +}; + +#endif // EFI_PROD_CODE