i2c slave block transfer driver library

Dependents:   i2c_slave_block_example i2c_lora_slave

I2C block read/write slave

operates with I2C master using the i2c_smbus_read_i2c_block_data() and i2c_smbus_write_i2c_block_data() calls.

Tested with raspberry pi as master.
STM32Lx nucleo as slave.

signal pinmbed slaveRPi master
GND6
SCLD155
SDAD143
IRQany avail pinany avail pin

IRQ pin is implemented in application, not in this library. (see example)

Up to 32byte length per transfer.
Raspberry pi I2C doesnt support SMBUS block read, so I2C block transfers are supported here.
SMBUS block transfers are variable length. Instead, here in this driver, I2C block length is mapped to command byte by const array cmd_to_length[] (see example)

master write to slave

For i2c_smbus_write_i2c_block_data() from master, this driver lets the main loop on slave take the write request. The main loop on slave calls service_i2c(), which then calls service_i2c_write() upon each write from master. This occurs thru a circular buffer, permitting the slave to take its time doing the request, while the master can issue several back-to-back requests without delays.

clock stretching

For i2c_smbus_read_i2c_block_data(): Since raspberry pi doesnt support clock stretching out the the box, providing transmit data must be done in the interrupt service routine. fill_tx_buf() populates the transmit buffer to be sent to master, which must be done immediately because this occurs inside interrupt handler. If I2C master were to support clock stretching, then transmit buffer work could be done in main loop of slave.
/media/uploads/dudmuck/i2c_rpi_nucleo_sm_.png
An STM32 running at 32MHz is unlikely to operate with 400KHz I2C. Use 100KHz speed with 32MHz CPU, or use faster speed CPU for 400KHz I2C.

Revision:
3:c012313ebc13
Parent:
1:914409dc83b1
Child:
4:7ab789db70da
--- a/TARGET_STM/smbus.c	Mon Jan 21 16:58:05 2019 -0800
+++ b/TARGET_STM/smbus.c	Sun Feb 03 16:52:43 2019 -0800
@@ -1,32 +1,72 @@
+#include "hal/pinmap.h"
+#include "PeripheralPins.h"
+#include "i2c_device.h"
 #include "smbus.h"
-#include "device.h"
-#include "smbus_defs.h"
-#include "cmds.h"
-
-
-SMBUS_HandleTypeDef SmbusHandle;
-
-static volatile state_e state;
+#include "mbed_interface.h" // mbed_error_printf
 
 i2c_slave_t i2c;
 
+static DMA_HandleTypeDef hdma_tx;
+static DMA_HandleTypeDef hdma_rx;
+static SMBUS_HandleTypeDef SmbusHandle;
+static IRQn_Type event_i2cIRQ;
+static IRQn_Type error_i2cIRQ;
+
 static inline void put_cbuf(uint8_t o)
 {
     i2c.cbuf[i2c.cbuf_in] = o;
-    if (++i2c.cbuf_in == _RX_BUF_SIZE)
+    if (++i2c.cbuf_in == CBUF_SIZE)
         i2c.cbuf_in = 0;
+
+    if (i2c.cbuf_in == i2c.cbuf_out) {
+        i2c.c_overrun = true;
+    }
 }
 
-static volatile uint8_t i2c_buf_idx;
+static inline void err_irqHandler(uint32_t tmpisrvalue)
+{
+    uint32_t _icr = 0;
+    if (tmpisrvalue & SMBUS_FLAG_BERR) {
+        put_cbuf(CMD_BUSERR);
+        put_cbuf(1);
+        put_cbuf(hdma_tx.Instance->CNDTR); // req[0]
+
+        _icr |= SMBUS_FLAG_BERR;
+    }
+
+    if (tmpisrvalue & SMBUS_FLAG_ARLO) {
+        put_cbuf(CMD_ARLO);
+        put_cbuf(1);
+        put_cbuf(hdma_tx.Instance->CNDTR); // req[0]
+
+        _icr |= SMBUS_FLAG_ARLO;
+    }
+
+    if (tmpisrvalue & SMBUS_FLAG_TIMEOUT) {
+        SmbusHandle.Instance->CR1 &= ~I2C_CR1_TXDMAEN;
+        __HAL_DMA_DISABLE(&hdma_tx);
+        __HAL_UNLOCK(&hdma_tx);
+
+        SmbusHandle.Instance->CR1 &= ~I2C_CR1_RXDMAEN;
+        __HAL_DMA_DISABLE(&hdma_rx);
+        __HAL_UNLOCK(&hdma_rx);
+
+        put_cbuf(CMD_TIMEOUT);
+        put_cbuf(1);
+        put_cbuf(hdma_tx.Instance->CNDTR); // req[0]
+        
+        _icr |= SMBUS_FLAG_TIMEOUT;
+    }
+
+    __HAL_SMBUS_CLEAR_FLAG(&SmbusHandle, _icr);
+}
 
 #ifdef TARGET_STM32L4
 void SMBUSx_ER_IRQHandler()
 {
-    asm("nop"); // TODO
+    err_irqHandler(SMBUS_GET_ISR_REG(&SmbusHandle));
 }
-#endif /* TARGET_STM32L4 */
 
-#ifdef TARGET_STM32L4
 void SMBUSx_EV_IRQHandler(void)
 #else
 void SMBUSx_IRQHandler(void)
@@ -34,64 +74,99 @@
 {
     uint32_t tmpisrvalue = SMBUS_GET_ISR_REG(&SmbusHandle);
     uint32_t icr = 0;
-    static uint8_t xfer_length;
+
     static bool i2cTransferDirection; // true = read
-    static uint8_t cmd;
-    static uint8_t rx_buf_[32];
+    static bool stopWaiting = false;
+
+    static uint8_t _rx_buf[33]; // [0] is cmd
 
-    if (tmpisrvalue & SMBUS_FLAG_RXNE) {
-        uint8_t rxdr = SmbusHandle.Instance->RXDR;
-        if (state == STATE_CMD) {
-            cmd = rxdr;
-            if (cmd_allowed(cmd)) {
-                xfer_length = cmd_to_length[cmd];
-                i2c_buf_idx = 0;
-                state = STATE_XFERING;
-            } else {
-                state = STATE_NONE;
-                __HAL_SMBUS_GENERATE_NACK(&SmbusHandle);
-            }
-        } else if (state == STATE_XFERING && i2cTransferDirection == 0) { // if xfering host write
-            if (i2c_buf_idx < sizeof(rx_buf_))
-                rx_buf_[i2c_buf_idx++] = rxdr;
-        }
+    /* often multiple flags are set simultaneously, yet must still be taken in correct order */
 
-        icr |= SMBUS_FLAG_RXNE;
-    }
-
+#ifdef DEBUG_SMBUS
+    put_cbuf(CMD_ISR);
+    put_cbuf(1);
+    put_cbuf(tmpisrvalue & 0xff);
+#endif /* DEBUG_SMBUS */
 
     if (tmpisrvalue & SMBUS_FLAG_ADDR) {
         i2cTransferDirection = SMBUS_GET_DIR(&SmbusHandle);
-        if (i2cTransferDirection) {
+        if (i2cTransferDirection) { // if host read
             // 1: Read transfer, slave enters transmitter mode..  cmd has already been provided
-            fill_tx_buf(cmd);
+            SmbusHandle.Instance->CR1 &= ~I2C_CR1_RXDMAEN;
+            __HAL_DMA_DISABLE(&hdma_rx);
+            __HAL_UNLOCK(&hdma_rx);
+
+            fill_tx_buf(_rx_buf[0]);
 
-            __HAL_SMBUS_ENABLE_IT(&SmbusHandle, SMBUS_IT_ERRI | SMBUS_IT_TCI | SMBUS_IT_STOPI | SMBUS_IT_NACKI | SMBUS_IT_TXI);
-        } else {
+#ifdef DEBUG_SMBUS
+            put_cbuf(CMD_ADDR_HOST_READ);
+            put_cbuf(2);
+            put_cbuf(cmd_to_length[_rx_buf[0]]);
+            put_cbuf(hdma_tx.Instance->CNDTR);
+#endif /* DEBUG_SMBUS */
+            SmbusHandle.Instance->CR1 |= I2C_CR1_TXDMAEN;
+            HAL_DMA_Start(&hdma_tx, (uint32_t)i2c.tx_buf, (uint32_t)&SmbusHandle.Instance->TXDR, cmd_to_length[_rx_buf[0]]);
+
+            icr |= SMBUS_FLAG_ADDR;
+        } else if (!stopWaiting) {
             // 0: Write transfer, slave enters receiver mode.
-            __HAL_SMBUS_ENABLE_IT(&SmbusHandle, SMBUS_IT_ERRI | SMBUS_IT_TCI | SMBUS_IT_STOPI | SMBUS_IT_NACKI | SMBUS_IT_RXI);
-            if (state == STATE_NONE) {
-                state = STATE_CMD;
-            }
+            SmbusHandle.Instance->CR1 |= I2C_CR1_RXDMAEN;
+            HAL_DMA_Start(&hdma_rx, (uint32_t)&SmbusHandle.Instance->RXDR, (uint32_t)_rx_buf, sizeof(_rx_buf));
+
+#ifdef DEBUG_SMBUS
+            put_cbuf(CMD_ADDR_HOST_WRITE);
+            put_cbuf(1);
+            put_cbuf(sizeof(_rx_buf));
+#endif /* DEBUG_SMBUS */
+
+            icr |= SMBUS_FLAG_ADDR;
         }
 
-        icr |= SMBUS_FLAG_ADDR;
+        stopWaiting = true;
     }
 
+#ifdef TARGET_STM32L0
+    err_irqHandler(tmpisrvalue);
+#endif
+
     if (tmpisrvalue & SMBUS_FLAG_AF) {
         /* last byte the host wants was sent */
+        SmbusHandle.Instance->CR1 &= ~I2C_CR1_TXDMAEN;
+        __HAL_DMA_DISABLE(&hdma_tx);
+        __HAL_UNLOCK(&hdma_tx);
+
+#ifdef DEBUG_SMBUS
+        put_cbuf(CMD_AF);
+        put_cbuf(1);
+        put_cbuf(hdma_tx.Instance->CNDTR);
+#endif /* DEBUG_SMBUS */
+
         icr |= SMBUS_FLAG_AF;
     }
 
     if (tmpisrvalue & SMBUS_FLAG_STOPF) {
+#ifdef DEBUG_SMBUS
+        put_cbuf(CMD_STOPF);
+        put_cbuf(0);
+#endif /* DEBUG_SMBUS */
+
         icr |= SMBUS_FLAG_STOPF;
-        if (i2cTransferDirection == 0) {   // host done writing to buf
-            unsigned i;
-            /* send to mainloop */
-            put_cbuf(cmd);
-            put_cbuf(i2c_buf_idx);
-            for (i = 0; i < i2c_buf_idx; i++)
-                put_cbuf(rx_buf_[i]);
+        if (i2cTransferDirection == 0) {
+            /* host done writing to buf */
+            unsigned i, len;
+
+            SmbusHandle.Instance->CR1 &= ~I2C_CR1_RXDMAEN;
+            __HAL_DMA_DISABLE(&hdma_rx);
+            __HAL_UNLOCK(&hdma_rx);
+
+            len = sizeof(_rx_buf) - hdma_rx.Instance->CNDTR;
+            if (len > 0) {
+                /* send to mainloop */
+                put_cbuf(_rx_buf[0]);
+                put_cbuf(len-1);
+                for (i = 1; i < len; i++)
+                    put_cbuf(_rx_buf[i]);
+            }
         }
 
         if (!(tmpisrvalue & SMBUS_FLAG_TXE)) {
@@ -99,36 +174,130 @@
             SmbusHandle.Instance->ISR = SMBUS_FLAG_TXE;
         }
 
-        state = STATE_NONE;
-    }
-
-    if (tmpisrvalue & SMBUS_FLAG_TXIS ) {
-        if (state == STATE_XFERING && i2cTransferDirection) {  // xfering in host-read direction
-            SmbusHandle.Instance->TXDR = i2c.tx_buf[i2c_buf_idx++];
-            if (i2c_buf_idx == xfer_length) {
-                state = STATE_SEND_DONE;
-                __HAL_SMBUS_DISABLE_IT(&SmbusHandle, SMBUS_IT_TXI);
-            }
-        }
-    } else if (icr == 0) {
-        /* unhandled interrupt flag: halt this irq */
-#ifdef TARGET_STM32L4
-        HAL_NVIC_DisableIRQ(SMBUSx_EV_IRQn);
-#else
-        HAL_NVIC_DisableIRQ(SMBUSx_IRQn);
-#endif
-    }
+        stopWaiting = false;
+    } // ..if (tmpisrvalue & SMBUS_FLAG_STOPF)
 
     __HAL_SMBUS_CLEAR_FLAG(&SmbusHandle, icr);
 }
 
-int smbus_init(uint8_t slaveAddress)
+#define I2C_TIMING              0x10900CD6
+
+int smbus_init( PinName sda, PinName scl, uint8_t slaveAddress)
 {
+    DMA_Channel_TypeDef *dma_ch_tx, *dma_ch_rx;
+    uint32_t dma_req_tx, dma_req_rx;
+    RCC_PeriphCLKInitTypeDef  RCC_PeriphCLKInitStruct;
     uint32_t tmpisr;
-    //HAL_StatusTypeDef status;
+    I2CName i2c;
+
+    I2CName i2c_sda = (I2CName)pinmap_peripheral(sda, PinMap_I2C_SDA);
+    I2CName i2c_scl = (I2CName)pinmap_peripheral(scl, PinMap_I2C_SCL);
+
+    // Configure I2C pins
+    pinmap_pinout(sda, PinMap_I2C_SDA);
+    pinmap_pinout(scl, PinMap_I2C_SCL);
+    pin_mode(sda, OpenDrainNoPull);
+    pin_mode(scl, OpenDrainNoPull);
+
+    i2c = (I2CName)pinmap_merge(i2c_sda, i2c_scl);;
+
+    if (i2c == I2C_1) {
+        SmbusHandle.Instance = I2C1;
+        RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2C1;
+        RCC_PeriphCLKInitStruct.I2c1ClockSelection = RCC_I2C1CLKSOURCE_SYSCLK;
+        __HAL_RCC_I2C1_CLK_ENABLE();
+        event_i2cIRQ = I2C1_EV_IRQn;
+        error_i2cIRQ = I2C1_ER_IRQn;
+
+        __HAL_RCC_DMA1_CLK_ENABLE();
+#ifdef TARGET_STM32L4
+        dma_ch_tx = DMA1_Channel6;
+        dma_ch_rx = DMA1_Channel7;
+        dma_req_tx = DMA_REQUEST_3;
+        dma_req_rx = DMA_REQUEST_3;
+#elif defined(TARGET_STM32L0)
+        dma_ch_tx = DMA1_Channel6;
+        dma_ch_rx = DMA1_Channel7;
+        dma_req_tx = DMA_REQUEST_6;
+        dma_req_rx = DMA_REQUEST_6;
+#endif
+    }
+#if defined I2C2_BASE
+    else if (i2c == I2C_2) {
+        SmbusHandle.Instance = I2C2;
+        RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2C2;
+#ifdef RCC_I2C2CLKSOURCE_SYSCLK
+        RCC_PeriphCLKInitStruct.I2c1ClockSelection = RCC_I2C2CLKSOURCE_SYSCLK;
+#endif /* RCC_I2C2CLKSOURCE_SYSCLK */
+        __HAL_RCC_I2C2_CLK_ENABLE();
+        event_i2cIRQ = I2C2_EV_IRQn;
+        error_i2cIRQ = I2C2_ER_IRQn;
 
-    SmbusHandle.Instance                  = I2Cx;
+        __HAL_RCC_DMA1_CLK_ENABLE();
+#ifdef TARGET_STM32L4
+        dma_ch_tx = DMA1_Channel4;
+        dma_ch_rx = DMA1_Channel5;
+        dma_req_tx = DMA_REQUEST_3;
+        dma_req_rx = DMA_REQUEST_3;
+#elif defined(TARGET_STM32L0)
+        dma_ch_tx = DMA1_Channel4;
+        dma_ch_rx = DMA1_Channel5;
+        dma_req_tx = DMA_REQUEST_7;
+        dma_req_rx = DMA_REQUEST_7;
+#endif
+    }
+#endif /* I2C2_BASE */
+#if defined I2C3_BASE
+    else if (i2c == I2C_3) {
+        SmbusHandle.Instance = I2C3;
+        RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2C3;
+        RCC_PeriphCLKInitStruct.I2c3ClockSelection = RCC_I2C3CLKSOURCE_SYSCLK;
+        __HAL_RCC_I2C3_CLK_ENABLE();
+        event_i2cIRQ = I2C3_EV_IRQn;
+        error_i2cIRQ = I2C3_ER_IRQn;
 
+        __HAL_RCC_DMA1_CLK_ENABLE();
+#ifdef TARGET_STM32L4
+        dma_ch_tx = DMA1_Channel2;
+        dma_ch_rx = DMA1_Channel3;
+        dma_req_tx = DMA_REQUEST_3;
+        dma_req_rx = DMA_REQUEST_3;
+#elif defined(TARGET_STM32L0)
+        dma_ch_tx = DMA1_Channel4;
+        dma_ch_rx = DMA1_Channel5;
+        dma_req_tx = DMA_REQUEST_14;
+        dma_req_rx = DMA_REQUEST_14;
+#endif
+    }
+#endif /* I2C3_BASE */
+#if defined I2C4_BASE
+    else if (i2c == I2C_4) {
+        SmbusHandle.Instance = I2C4;
+        RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2C4;
+        RCC_PeriphCLKInitStruct.I2c4ClockSelection = RCC_I2C4CLKSOURCE_SYSCLK;
+        __HAL_RCC_I2C4_CLK_ENABLE();
+        event_i2cIRQ = I2C4_EV_IRQn;
+        error_i2cIRQ = I2C4_ER_IRQn;
+
+#ifdef TARGET_STM32L4
+        __HAL_RCC_DMA2_CLK_ENABLE();
+        dma_ch_tx = DMA1_Channel1;
+        dma_ch_rx = DMA1_Channel2;
+        dma_req_tx = DMA_REQUEST_0;
+        dma_req_rx = DMA_REQUEST_0;
+#elif defined(TARGET_STM32L0)
+        #error absent
+#endif
+    }
+#endif /* I2C4_BASE */
+    else {
+        mbed_error_printf("unknown i2c %x\r\n", i2c);
+        return -1;
+    }
+
+    HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);
+
+    // use get_i2c_timing() from i2c_device.h?
     SmbusHandle.Init.Timing               = I2C_TIMING;
     SmbusHandle.Init.AnalogFilter         = SMBUS_ANALOGFILTER_ENABLE;
     SmbusHandle.Init.OwnAddress1          = slaveAddress;
@@ -139,25 +308,64 @@
     SmbusHandle.Init.NoStretchMode        = SMBUS_NOSTRETCH_DISABLE;
     SmbusHandle.Init.PacketErrorCheckMode = SMBUS_PEC_DISABLE;
     SmbusHandle.Init.PeripheralMode       = SMBUS_PERIPHERAL_MODE_SMBUS_SLAVE;
-    SmbusHandle.Init.SMBusTimeout         = 0;
+    SmbusHandle.Init.SMBusTimeout         = I2C_TIMEOUTR_TIMOUTEN | 0x186;
 
-    if(HAL_SMBUS_Init(&SmbusHandle) != HAL_OK)
+    if (HAL_SMBUS_Init(&SmbusHandle) != HAL_OK)
     { 
         return -1;
     }
 
-    state = STATE_NONE;
-
     tmpisr = SMBUS_IT_ADDRI | SMBUS_IT_STOPI | SMBUS_IT_NACKI | SMBUS_IT_ERRI;
     __HAL_SMBUS_ENABLE_IT(&SmbusHandle, tmpisr);
 
+#ifdef TARGET_STM32L4
+    HAL_NVIC_SetPriority(event_i2cIRQ, 0, 0);
+    HAL_NVIC_EnableIRQ(event_i2cIRQ);
+    HAL_NVIC_SetPriority(error_i2cIRQ , 0, 0);
+    HAL_NVIC_EnableIRQ(error_i2cIRQ);
+
+    NVIC_SetVector(event_i2cIRQ, (uint32_t)SMBUSx_EV_IRQHandler);
+    NVIC_SetVector(error_i2cIRQ, (uint32_t)SMBUSx_ER_IRQHandler);
+  
+#else
+    HAL_NVIC_SetPriority(event_i2cIRQ, 0, 0);
+    HAL_NVIC_EnableIRQ(event_i2cIRQ);
+
+    NVIC_SetVector(event_i2cIRQ, (uint32_t)SMBUSx_IRQHandler);
+#endif
+
+      /* Configure the DMA handler for Transmission process */
+    hdma_tx.Instance                 = dma_ch_tx;
+    hdma_tx.Init.Request             = dma_req_tx;
+    hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;
+    hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;
+    hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;
+    hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
+    hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
+    hdma_tx.Init.Mode                = DMA_NORMAL;
+    hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;
+
+    HAL_DMA_Init(&hdma_tx);
+
+    hdma_rx.Instance                 = dma_ch_rx;
+    hdma_rx.Init.Request             = dma_req_rx;
+    hdma_rx.Init.Direction           = DMA_PERIPH_TO_MEMORY;
+    hdma_rx.Init.PeriphInc           = DMA_PINC_DISABLE;
+    hdma_rx.Init.MemInc              = DMA_MINC_ENABLE;
+    hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
+    hdma_rx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
+    hdma_rx.Init.Mode                = DMA_NORMAL;
+    hdma_rx.Init.Priority            = DMA_PRIORITY_HIGH;
+
+    HAL_DMA_Init(&hdma_rx);
+    
     return 0;
 }
 
 static inline uint8_t get_cbuf()
 {
     uint8_t ret = i2c.cbuf[i2c.cbuf_out];
-    if (++i2c.cbuf_out == _RX_BUF_SIZE)
+    if (++i2c.cbuf_out == CBUF_SIZE)
         i2c.cbuf_out = 0;
 
     return ret;
@@ -167,10 +375,10 @@
 void service_i2c()
 {
     if (i2c.cbuf_in != i2c.cbuf_out) {
+        uint8_t buf[32];
         uint8_t n, cmd, len;
         cmd = get_cbuf();
         len = get_cbuf();
-        uint8_t buf[32];
         /* host is writing: parse here */
         for (n = 0; n < len; n++)
             buf[n] = get_cbuf();