implement bit-banged I2C driver (#2289)
* bb * implement * tweaks * s * comment, format * guard * headers and comment
This commit is contained in:
parent
5499ab5089
commit
a5f269d733
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue