236 lines
6.9 KiB
C
236 lines
6.9 KiB
C
// Inter-integrated circuit (I2C) management
|
|
|
|
|
|
#include "i2c.h"
|
|
|
|
|
|
// I2C timeout, about 2ms
|
|
#define I2C_TIMEOUT 200U
|
|
|
|
// Maximum NBYTES value
|
|
#define I2C_NBYTES_MAX 255U
|
|
|
|
|
|
// Count rough delay for timeouts
|
|
static uint32_t i2c_calc_delay(uint32_t delay) {
|
|
uint32_t cnt;
|
|
|
|
if (SystemCoreClock > 1000000U) {
|
|
cnt = (delay * ((SystemCoreClock / 1000000U) + 1U));
|
|
} else {
|
|
cnt = (((delay / 100U) + 1U) * ((SystemCoreClock / 10000U) + 1U));
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
// Check if target device is ready for communication
|
|
// input:
|
|
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
|
// devAddr - target device address
|
|
// trials - number of trials (must not be zero)
|
|
// return:
|
|
// I2C_ERROR if there was a timeout during I2C operations, I2C_SUCCESS otherwise
|
|
i2cstatus i2c_is_device_ready(I2C_TypeDef* I2Cx, uint8_t devAddr, uint32_t trials) {
|
|
volatile uint32_t wait;
|
|
uint32_t delay_val = i2c_calc_delay(I2C_TIMEOUT);
|
|
uint32_t reg;
|
|
|
|
while (trials--) {
|
|
// Clear all flags
|
|
I2Cx->ICR = I2C_ICR_ALL;
|
|
|
|
// Generate START
|
|
i2c_genstart(I2Cx, devAddr);
|
|
|
|
// Wait for STOP, NACK or BERR
|
|
wait = delay_val;
|
|
while (!((reg = I2Cx->ISR) & (I2C_ISR_STOPF | I2C_ISR_NACKF | I2C_ISR_BERR)) && --wait);
|
|
if (wait == 0) { return I2C_ERROR; }
|
|
|
|
// Wait while STOP flag is reset
|
|
wait = delay_val;
|
|
while (!(I2Cx->ISR & I2C_ISR_STOPF) && --wait);
|
|
if (wait == 0) { return I2C_ERROR; }
|
|
|
|
// Clear the NACK, STOP and BERR flags
|
|
I2Cx->ICR = I2C_ICR_STOPCF | I2C_ICR_NACKCF | I2C_ICR_BERRCF;
|
|
|
|
// Check for BERR flag
|
|
if (reg & I2C_ISR_BERR) {
|
|
// Misplaced START/STOP? Perform a software reset of I2C
|
|
i2c_disable(I2Cx);
|
|
i2c_enable(I2Cx);
|
|
} else {
|
|
// Device responded if NACK flag is not set
|
|
if (!(reg & I2C_ISR_NACKF)) { return I2C_SUCCESS; }
|
|
}
|
|
}
|
|
|
|
return I2C_ERROR;
|
|
}
|
|
|
|
// Transmit an amount of data in master mode
|
|
// input:
|
|
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
|
// pBbuf - pointer to the data buffer
|
|
// nbytes - number of bytes to transmit
|
|
// devAddr - address of target device
|
|
// flags - options for transmission, combination of I2C_TX_xx values:
|
|
// I2C_TX_NOSTART - don't generate START condition
|
|
// I2C_TX_NOSTOP - don't generate STOP condition
|
|
// I2C_TX_CONT - this flag indicates that transmission will be continued
|
|
// e.g. by calling this function again with NOSTART flag
|
|
// zero value - generate both START and STOP conditions
|
|
// return:
|
|
// I2C_ERROR if there was a timeout during I2C operations, I2C_SUCCESS otherwise
|
|
i2cstatus i2c_transmit(I2C_TypeDef* I2Cx, uint8_t devAddr, const uint8_t *pBuf, uint32_t nbytes, uint32_t flags) {
|
|
uint32_t reg;
|
|
uint32_t tx_count;
|
|
uint32_t delay_val = i2c_calc_delay(I2C_TIMEOUT);
|
|
volatile uint32_t wait;
|
|
|
|
// Clear all flags
|
|
I2Cx->ICR = I2C_ICR_ALL;
|
|
|
|
// Everything regarding to the transmission is in the CR2 register
|
|
reg = I2Cx->CR2;
|
|
reg &= ~I2C_CR2_ALL;
|
|
|
|
// Slave device address
|
|
reg |= (devAddr & I2C_CR2_SADD);
|
|
|
|
// Whether it need to generate START condition
|
|
if (!(flags & I2C_TX_NOSTART)) { reg |= I2C_CR2_START; }
|
|
|
|
// Whether it need to generate STOP condition
|
|
if ((flags & I2C_TX_CONT) || (nbytes > I2C_NBYTES_MAX)) {
|
|
reg |= I2C_CR2_RELOAD;
|
|
} else {
|
|
if (!(flags & I2C_TX_NOSTOP)) { reg |= I2C_CR2_AUTOEND; }
|
|
}
|
|
|
|
// Transfer length
|
|
tx_count = (nbytes > I2C_NBYTES_MAX) ? I2C_NBYTES_MAX : nbytes;
|
|
nbytes -= tx_count;
|
|
reg |= tx_count << I2C_CR2_NBYTES_Pos;
|
|
|
|
// Write a composed value to the I2C register
|
|
I2Cx->CR2 = reg;
|
|
|
|
// Transmit data
|
|
while (tx_count) {
|
|
// Wait until either TXIS or NACK flag is set
|
|
wait = delay_val;
|
|
while (!((reg = I2Cx->ISR) & (I2C_ISR_TXIS | I2C_ISR_NACKF)) && --wait);
|
|
if ((reg & I2C_ISR_NACKF) || (wait == 0)) { return I2C_ERROR; }
|
|
|
|
// Transmit byte
|
|
I2Cx->TXDR = *pBuf++;
|
|
tx_count--;
|
|
|
|
if ((tx_count == 0) && (nbytes != 0)) {
|
|
// Wait until TCR flag is set (Transfer Complete Reload)
|
|
wait = delay_val;
|
|
while (!(I2Cx->ISR & I2C_ISR_TCR) && --wait);
|
|
if (wait == 0) { return I2C_ERROR; }
|
|
|
|
// Configure next (or last) portion transfer
|
|
reg = I2Cx->CR2;
|
|
reg &= ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD | I2C_CR2_AUTOEND);
|
|
if ((flags & I2C_TX_CONT) || (nbytes > I2C_NBYTES_MAX)) {
|
|
reg |= I2C_CR2_RELOAD;
|
|
} else {
|
|
if (!(flags & I2C_TX_NOSTOP)) { reg |= I2C_CR2_AUTOEND; }
|
|
}
|
|
tx_count = (nbytes > I2C_NBYTES_MAX) ? I2C_NBYTES_MAX : nbytes;
|
|
nbytes -= tx_count;
|
|
reg |= tx_count << I2C_CR2_NBYTES_Pos;
|
|
I2Cx->CR2 = reg;
|
|
}
|
|
}
|
|
|
|
// End of transmission
|
|
wait = delay_val;
|
|
while (!(I2Cx->ISR & (I2C_ISR_TC | I2C_ISR_TCR | I2C_ISR_STOPF)) && --wait);
|
|
|
|
return (wait) ? I2C_SUCCESS : I2C_ERROR;
|
|
}
|
|
|
|
// Receive an amount of data in master mode
|
|
// input:
|
|
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
|
// buf - pointer to the data buffer
|
|
// nbytes - number of bytes to receive
|
|
// devAddr - address of target device
|
|
// return:
|
|
// I2C_ERROR if there was a timeout during I2C operations, I2C_SUCCESS otherwise
|
|
i2cstatus i2c_receive(I2C_TypeDef* I2Cx, uint8_t devAddr, uint8_t *pBuf, uint32_t nbytes) {
|
|
uint32_t reg;
|
|
uint32_t rx_count;
|
|
uint32_t delay_val = i2c_calc_delay(I2C_TIMEOUT);
|
|
volatile uint32_t wait;
|
|
|
|
// Clear all flags
|
|
I2Cx->ICR = I2C_ICR_ALL;
|
|
|
|
// Everything regarding to the transmission is in the CR2 register
|
|
reg = I2Cx->CR2;
|
|
reg &= ~I2C_CR2_ALL;
|
|
|
|
// Configure slave device address, enable START condition and set direction to READ
|
|
reg |= (devAddr & I2C_CR2_SADD) | I2C_CR2_START | I2C_CR2_RD_WRN;
|
|
|
|
// Transfer length
|
|
if (nbytes > I2C_NBYTES_MAX) {
|
|
rx_count = I2C_NBYTES_MAX;
|
|
reg |= I2C_CR2_RELOAD;
|
|
} else {
|
|
rx_count = nbytes;
|
|
reg |= I2C_CR2_AUTOEND;
|
|
}
|
|
reg |= rx_count << I2C_CR2_NBYTES_Pos;
|
|
nbytes -= rx_count;
|
|
|
|
// Write a composed value to the I2C register
|
|
I2Cx->CR2 = reg;
|
|
|
|
// Receive data
|
|
while (rx_count) {
|
|
// Wait until either RXNE or NACK flag is set
|
|
wait = delay_val;
|
|
while (!((reg = I2Cx->ISR) & (I2C_ISR_RXNE | I2C_ISR_NACKF)) && --wait);
|
|
if ((reg & I2C_ISR_NACKF) || (wait == 0)) { return I2C_ERROR; }
|
|
|
|
// Read received data
|
|
*pBuf++ = I2Cx->RXDR;
|
|
rx_count--;
|
|
|
|
if ((rx_count == 0) && (nbytes != 0)) {
|
|
// Wait until TCR flag is set (Transfer Complete Reload)
|
|
wait = delay_val;
|
|
while (!(I2Cx->ISR & I2C_ISR_TCR) && --wait);
|
|
if (wait == 0) { return I2C_ERROR; }
|
|
|
|
// Configure next (or last) portion transfer
|
|
reg = I2Cx->CR2;
|
|
reg &= ~(I2C_CR2_NBYTES | I2C_CR2_AUTOEND | I2C_CR2_RELOAD);
|
|
if (nbytes > I2C_NBYTES_MAX) {
|
|
rx_count = I2C_NBYTES_MAX;
|
|
reg |= I2C_CR2_RELOAD;
|
|
} else {
|
|
rx_count = nbytes;
|
|
reg |= I2C_CR2_AUTOEND;
|
|
}
|
|
reg |= rx_count << I2C_CR2_NBYTES_Pos;
|
|
nbytes -= rx_count;
|
|
I2Cx->CR2 = reg;
|
|
}
|
|
}
|
|
|
|
// Wait for the STOP flag
|
|
wait = delay_val;
|
|
while (!(I2Cx->ISR & I2C_ISR_STOPF) && --wait);
|
|
|
|
return (wait) ? I2C_SUCCESS : I2C_ERROR;
|
|
}
|