summaryrefslogtreecommitdiff
path: root/i2c.c
diff options
context:
space:
mode:
authorjaseg <git@jaseg.net>2019-07-06 11:33:47 +0900
committerjaseg <git@jaseg.net>2019-07-06 11:33:47 +0900
commit4491f72afd80c59d1fc0f1e166b17372b10cdc5a (patch)
tree703d95aa100137c05cda29e1388d3a97a4885c19 /i2c.c
downloadmoargb-4491f72afd80c59d1fc0f1e166b17372b10cdc5a.tar.gz
moargb-4491f72afd80c59d1fc0f1e166b17372b10cdc5a.tar.bz2
moargb-4491f72afd80c59d1fc0f1e166b17372b10cdc5a.zip
Initial commit
Diffstat (limited to 'i2c.c')
-rw-r--r--i2c.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/i2c.c b/i2c.c
new file mode 100644
index 0000000..6c24c04
--- /dev/null
+++ b/i2c.c
@@ -0,0 +1,236 @@
+// 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;
+}