summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George <damien.p.george@gmail.com>2018-05-14 13:19:03 +1000
committerDamien George <damien.p.george@gmail.com>2018-05-14 13:19:03 +1000
commitb21415ed4f4bcc66b9a76313e3b47483d279da11 (patch)
tree06f3ba6eb6e5f8bf2164d52c92b6997c240dc762
parentca366454103e28d6a85823454e64c21302627555 (diff)
stm32/i2c: Add new hardware I2C driver for F4 MCUs.
This driver uses low-level register access to control the I2C peripheral (ie it doesn't rely on the ST HAL) and provides the same C-level API as the existing F7 hardware driver.
-rw-r--r--ports/stm32/i2c.c240
1 files changed, 237 insertions, 3 deletions
diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c
index 63bd09ae5..a717ca7b3 100644
--- a/ports/stm32/i2c.c
+++ b/ports/stm32/i2c.c
@@ -30,10 +30,240 @@
#if MICROPY_HW_ENABLE_HW_I2C
-#if defined(STM32F7)
-
#define I2C_POLL_TIMEOUT_MS (50)
+#if defined(STM32F4)
+
+int i2c_init(i2c_t *i2c, mp_hal_pin_obj_t scl, mp_hal_pin_obj_t sda, uint32_t freq) {
+ uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
+
+ // Init pins
+ if (!mp_hal_pin_config_alt(scl, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, AF_FN_I2C, i2c_id + 1)) {
+ return -MP_EPERM;
+ }
+ if (!mp_hal_pin_config_alt(sda, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, AF_FN_I2C, i2c_id + 1)) {
+ return -MP_EPERM;
+ }
+
+ // Force reset I2C peripheral
+ RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST << i2c_id;
+ RCC->APB1RSTR &= ~(RCC_APB1RSTR_I2C1RST << i2c_id);
+
+ // Enable I2C peripheral clock
+ RCC->APB1ENR |= RCC_APB1ENR_I2C1EN << i2c_id;
+ volatile uint32_t tmp = RCC->APB1ENR; // delay after RCC clock enable
+ (void)tmp;
+
+ uint32_t PCLK1 = HAL_RCC_GetPCLK1Freq();
+
+ // Initialise I2C peripheral
+ i2c->CR1 = 0;
+ i2c->CR2 = PCLK1 / 1000000;
+ i2c->OAR1 = 0;
+ i2c->OAR2 = 0;
+
+ freq = MIN(freq, 400000);
+
+ // SM: MAX(4, PCLK1 / (F * 2))
+ // FM, 16:9 duty: 0xc000 | MAX(1, (PCLK1 / (F * (16 + 9))))
+ if (freq <= 100000) {
+ i2c->CCR = MAX(4, PCLK1 / (freq * 2));
+ } else {
+ i2c->CCR = 0xc000 | MAX(1, PCLK1 / (freq * 25));
+ }
+
+ // SM: 1000ns / (1/PCLK1) + 1 = PCLK1 * 1e-6 + 1
+ // FM: 300ns / (1/PCLK1) + 1 = 300e-3 * PCLK1 * 1e-6 + 1
+ if (freq <= 100000) {
+ i2c->TRISE = PCLK1 / 1000000 + 1; // 1000ns rise time in SM
+ } else {
+ i2c->TRISE = PCLK1 / 1000000 * 3 / 10 + 1; // 300ns rise time in FM
+ }
+
+ #if defined(I2C_FLTR_ANOFF)
+ i2c->FLTR = 0; // analog filter on, digital filter off
+ #endif
+
+ return 0;
+}
+
+STATIC int i2c_wait_sr1_set(i2c_t *i2c, uint32_t mask) {
+ uint32_t t0 = HAL_GetTick();
+ while (!(i2c->SR1 & mask)) {
+ if (HAL_GetTick() - t0 >= I2C_POLL_TIMEOUT_MS) {
+ i2c->CR1 &= ~I2C_CR1_PE;
+ return -MP_ETIMEDOUT;
+ }
+ }
+ return 0;
+}
+
+STATIC int i2c_wait_stop(i2c_t *i2c) {
+ uint32_t t0 = HAL_GetTick();
+ while (i2c->CR1 & I2C_CR1_STOP) {
+ if (HAL_GetTick() - t0 >= I2C_POLL_TIMEOUT_MS) {
+ i2c->CR1 &= ~I2C_CR1_PE;
+ return -MP_ETIMEDOUT;
+ }
+ }
+ i2c->CR1 &= ~I2C_CR1_PE;
+ return 0;
+}
+
+// For write: len = 0, 1 or N
+// For read: len = 1, 2 or N; stop = true
+int i2c_start_addr(i2c_t *i2c, int rd_wrn, uint16_t addr, size_t next_len, bool stop) {
+ if (!(i2c->CR1 & I2C_CR1_PE) && (i2c->SR2 & I2C_SR2_MSL)) {
+ // The F4 I2C peripheral can sometimes get into a bad state where it's disabled
+ // (PE low) but still an active master (MSL high). It seems the best way to get
+ // out of this is a full reset.
+ uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
+ RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST << i2c_id;
+ RCC->APB1RSTR &= ~(RCC_APB1RSTR_I2C1RST << i2c_id);
+ }
+
+ // It looks like it's possible to terminate the reading by sending a
+ // START condition instead of STOP condition but we don't support that.
+ if (rd_wrn) {
+ if (!stop) {
+ return -MP_EINVAL;
+ }
+ }
+
+ // Repurpose OAR1 to hold stop flag
+ i2c->OAR1 = stop;
+
+ // Enable peripheral and send START condition
+ i2c->CR1 |= I2C_CR1_PE;
+ i2c->CR1 |= I2C_CR1_START;
+
+ // Wait for START to be sent
+ int ret;
+ if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_SB))) {
+ return ret;
+ }
+
+ // Send the 7-bit address with read/write bit
+ i2c->DR = addr << 1 | rd_wrn;
+
+ // Wait for address to be sent
+ if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_AF | I2C_SR1_ADDR))) {
+ return ret;
+ }
+
+ // Check if the slave responded or not
+ if (i2c->SR1 & I2C_SR1_AF) {
+ // Got a NACK
+ i2c->CR1 |= I2C_CR1_STOP;
+ i2c_wait_stop(i2c); // Don't leak errors from this call
+ return -MP_ENODEV;
+ }
+
+ if (rd_wrn) {
+ // For reading, set up ACK/NACK control based on number of bytes to read (at least 1 byte)
+ if (next_len <= 1) {
+ // NACK next received byte
+ i2c->CR1 &= ~I2C_CR1_ACK;
+ } else if (next_len <= 2) {
+ // NACK second received byte
+ i2c->CR1 |= I2C_CR1_POS;
+ i2c->CR1 &= ~I2C_CR1_ACK;
+ } else {
+ // ACK next received byte
+ i2c->CR1 |= I2C_CR1_ACK;
+ }
+ }
+
+ // Read SR2 to clear SR1_ADDR
+ uint32_t sr2 = i2c->SR2;
+ (void)sr2;
+
+ return 0;
+}
+
+// next_len = 0 or N (>=2)
+int i2c_read(i2c_t *i2c, uint8_t *dest, size_t len, size_t next_len) {
+ if (len == 0) {
+ return -MP_EINVAL;
+ }
+ if (next_len == 1) {
+ return -MP_EINVAL;
+ }
+
+ size_t remain = len + next_len;
+ if (remain == 1) {
+ // Special case
+ i2c->CR1 |= I2C_CR1_STOP;
+ int ret;
+ if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_RXNE))) {
+ return ret;
+ }
+ *dest = i2c->DR;
+ } else {
+ for (; len; --len) {
+ remain = len + next_len;
+ int ret;
+ if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_BTF))) {
+ return ret;
+ }
+ if (remain == 2) {
+ // In this case next_len == 0 (it's not allowed to be 1)
+ i2c->CR1 |= I2C_CR1_STOP;
+ *dest++ = i2c->DR;
+ *dest = i2c->DR;
+ break;
+ } else if (remain == 3) {
+ // NACK next received byte
+ i2c->CR1 &= ~I2C_CR1_ACK;
+ }
+ *dest++ = i2c->DR;
+ }
+ }
+
+ if (!next_len) {
+ // We sent a stop above, just wait for it to be finished
+ return i2c_wait_stop(i2c);
+ }
+
+ return 0;
+}
+
+// next_len = 0 or N
+int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len) {
+ int ret;
+ if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_AF | I2C_SR1_TXE))) {
+ return ret;
+ }
+
+ // Write out the data
+ int num_acks = 0;
+ while (len--) {
+ i2c->DR = *src++;
+ if ((ret = i2c_wait_sr1_set(i2c, I2C_SR1_AF | I2C_SR1_BTF))) {
+ return ret;
+ }
+ if (i2c->SR1 & I2C_SR1_AF) {
+ // Slave did not respond to byte so stop sending
+ break;
+ }
+ ++num_acks;
+ }
+
+ if (!next_len) {
+ if (i2c->OAR1) {
+ // Send a STOP and wait for it to finish
+ i2c->CR1 |= I2C_CR1_STOP;
+ if ((ret = i2c_wait_stop(i2c))) {
+ return ret;
+ }
+ }
+ }
+
+ return num_acks;
+}
+
+#elif defined(STM32F7)
+
int i2c_init(i2c_t *i2c, mp_hal_pin_obj_t scl, mp_hal_pin_obj_t sda, uint32_t freq) {
uint32_t i2c_id = ((uint32_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE);
@@ -214,6 +444,10 @@ int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len) {
return num_acks;
}
+#endif
+
+#if defined(STM32F4) || defined(STM32F7)
+
int i2c_readfrom(i2c_t *i2c, uint16_t addr, uint8_t *dest, size_t len, bool stop) {
int ret;
if ((ret = i2c_start_addr(i2c, 1, addr, len, stop))) {
@@ -230,6 +464,6 @@ int i2c_writeto(i2c_t *i2c, uint16_t addr, const uint8_t *src, size_t len, bool
return i2c_write(i2c, src, len, 0);
}
-#endif // defined(STM32F7)
+#endif
#endif // MICROPY_HW_ENABLE_HW_I2C