implement bit-banged I2C driver (#2289)

* bb

* implement

* tweaks

* s

* comment, format

* guard

* headers and comment
This commit is contained in:
Matthew Kennedy 2021-02-06 14:59:06 -08:00 committed by GitHub
parent 89f7ecb366
commit f518b72260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 278 additions and 1 deletions

View File

@ -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

View File

@ -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, &reg, 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

View File

@ -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 <cstdint>
#include <cstddef>
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