## [STM32 I2C] - Study of I2C slave task
**Github:** https://github.com/CarterWu-M/STM32_I2C_SLAVE
**Board:** NUCLEO-F072RB
**IDE:** STM32CubeIDE

---
### Introduction
---
This article explains how to use two I2C interfaces connected to each other, with one acting as the master and the other as the slave. The focus is primarily on the slave side. In this example, pressing a button triggers the master to send a command to the slave and read a response back. The main APIs used in this example are:
* HAL_I2C_Master_Seq_Transmit_DMA()
* HAL_I2C_Master_Seq_Receive_DMA()
* HAL_I2C_Slave_Seq_Transmit_DMA()
* HAL_I2C_Slave_Seq_Receive_DMA()
The command includes three parts: header, data, and CRC. This example also explains how to first read the header, parse the data length, and then read the remaining data and CRC.
---
### STM32CubeMX settings
---

**PB6,PB7:** Configure I2C1 for the slave
**PB10,PB11:** Configure I2C2 for the master
**PC13:** Configure GPIO EXTI for a button

Configure the I2C1 speed to 100 kHz and the address length to 7 bits

Enable the I2C1 global interrupt

Enable I2C1 RX and TX with DMA

Configure the I2C2 speed to 100 kHz and the address length to 7 bits

Enable the I2C2 global interrupt

Enable I2C2 RX and TX with DMA
---
### Code Structure
---




 
---
### API/CRC
---
```c=
//CRC.h
#ifndef INC_API_CRC_H_
#define INC_API_CRC_H_
uint32_t CRC32_calculate(uint8_t *pData, uint16_t len);
#endif /* INC_API_CRC_H_ */
```
```c=
//CRC.c
#include <string.h>
#include "main.h"
#include "API/CRC.h"
extern CRC_HandleTypeDef hcrc;
uint32_t CRC32_calculate(uint8_t *pData, uint16_t len)
{
if (!pData || !len) {
return -1;
}
uint32_t crcResult;
// Check if length is a multiple of 4
uint16_t words = len / 4; // Full 32-bit words
uint16_t remainder = len % 4;
// Process full 32-bit words
crcResult = HAL_CRC_Calculate(&hcrc, (uint32_t *)pData, words);
// Handle remaining bytes (if any)
if (remainder > 0) {
uint32_t lastWord = 0;
memcpy(&lastWord, &pData[words * 4], remainder); // Copy remaining bytes
crcResult = HAL_CRC_Accumulate(&hcrc, &lastWord, 1); // Accumulate remainder
}
return crcResult;
}
```
---
### API/CmdPackage
---

The command consists of three parts: header, data, and CRC32.
Header has an element called dataLen; it indicates the actual data length.
CRC32 calculation ranges from the header to the data.

* **Cmd_pack():** Fill the data into pData; it will assemble the header and CRC32 and return the result in pCmd
* **Cmd_unpack():** Fill the command into pCmd; it will disassemble the header and CRC32 and return the data in pData
```c=
//CmdPackage.h
#ifndef INC_API_CMDPACKAGE_H_
#define INC_API_CMDPACKAGE_H_
typedef struct {
uint8_t addr;
const uint8_t ver;
uint8_t cmdId;
uint8_t dataLen;
const uint16_t magicNum;
} CmdHdr;
// cmd = | header (6 bytes) | data (dataLen bytes) | CRC32 (4 bytes) |
#define CRC_LEN (4)
int Cmd_pack(uint8_t cmdId, uint8_t *pData, uint8_t dataLen, uint8_t *pCmd, uint8_t *pCmdLen);
int Cmd_unpack(uint8_t *pCmdId, uint8_t *pCmd, uint8_t cmdLen, uint8_t *pData, uint8_t *pDataLen);
#endif /* INC_API_CMDPACKAGE_H_ */
```
```c=
//CmdPackage.c
#include <string.h>
#include "main.h"
#include "API/CRC.h"
#include "API/CmdPackage.h"
static CmdHdr cmdHeader = {
.ver = 0x01,
.magicNum = 0xaabb,
};
#define MAX_CMD_LEN (255)
#define MAX_DATA_LEN (MAX_CMD_LEN - sizeof(cmdHeader) - CRC_LEN)
int Cmd_pack(uint8_t cmdId, uint8_t *pData, uint8_t dataLen, uint8_t *pCmd, uint8_t *pCmdLen)
{
if (!pCmd || !pCmdLen) {
return -1;
}
if (0 != dataLen && !pData) {
return -1;
}
if (MAX_DATA_LEN < dataLen) {
return -1;
}
cmdHeader.addr = 0x10; //I2C slave address
cmdHeader.cmdId = cmdId;
cmdHeader.dataLen = dataLen;
memcpy(pCmd, &cmdHeader, sizeof(CmdHdr));
memcpy(pCmd + sizeof(CmdHdr), pData, dataLen);
uint32_t crc = CRC32_calculate(pCmd, sizeof(CmdHdr) + dataLen);
memcpy(pCmd + sizeof(CmdHdr) + dataLen, &crc, CRC_LEN);
*pCmdLen = sizeof(CmdHdr) + dataLen + CRC_LEN;
return 0;
}
int Cmd_unpack(uint8_t *pCmdId, uint8_t *pCmd, uint8_t cmdLen, uint8_t *pData, uint8_t *pDataLen)
{
if (!pCmdId || !pCmd || sizeof(CmdHdr) + CRC_LEN > cmdLen || !pData || !pDataLen) {
return -1;
}
uint32_t crcCalc = CRC32_calculate(pCmd, cmdLen - CRC_LEN);
uint32_t crcParse;
memcpy(&crcParse, pCmd + cmdLen - CRC_LEN, CRC_LEN);
if (crcCalc != crcParse) {
return -2; //CRC check error
}
CmdHdr hdr;
memcpy(&hdr, pCmd, sizeof(CmdHdr));
if (hdr.magicNum != cmdHeader.magicNum) {
return -3; //check magic number error
}
*pCmdId = hdr.cmdId;
memcpy(pData, pCmd + sizeof(CmdHdr), hdr.dataLen);
*pDataLen = hdr.dataLen;
return 0;
}
```
---
### API/CmdProc
---
**Cmd_process(...)**
* **cmdId:** CMD_R_FAN_SPEED / CMD_W_FAN_PWM
* **parrReq:** the request data
* **pReqLen:** the request data length
* **parrRes:** the response data
* **pResLen:** the response data length
If cmdId is CMD_W_FAN_PWM, it's a write command. parrReq contains the PWM data to set, pReqLen is expected to be 1, and pResLen will return 0, which indicates no response data for the master.
If cmdId is CMD_R_FAN_SPEED , it's a read command. parrReq should be null, pReqLen is expected to be 0, and pResLen will return 1, which indicates that there is response data for the master.
```c=
//CmdProc.h
#ifndef INC_API_CMDPROC_H_
#define INC_API_CMDPROC_H_
//command ID
#define CMD_R_FAN_SPEED (0x01)
#define CMD_W_FAN_PWM (0x02)
int Cmd_process(uint8_t cmdId, uint8_t *parrReq, uint8_t *pReqLen, uint8_t *parrRes, uint8_t *pResLen);
#endif /* INC_API_CMDPROC_H_ */
```
```c=
//CmdProc.c
#include "main.h"
#include "API/CmdProc.h"
static int getFanSpeed(uint8_t *parrRes, uint8_t *pResLen);
static int setFanPWM(uint8_t *parrReq, uint8_t *pReqLen);
enum CmdType {
CMD_WRITE,
CMD_READ,
CMD_TYPE_NUM
};
static struct {
uint8_t cmdId;
enum CmdType cmdType;
int (*pProcFun)(uint8_t *parrData, uint8_t *pDataLen);
} arrT[] = {
{CMD_R_FAN_SPEED, CMD_READ, getFanSpeed},
{CMD_W_FAN_PWM, CMD_WRITE, setFanPWM},
};
#define TBL_NUM (sizeof(arrT) / sizeof(arrT[0]))
int Cmd_process(uint8_t cmdId, uint8_t *parrReq, uint8_t *pReqLen, uint8_t *parrRes, uint8_t *pResLen)
{
if (!parrReq || !parrRes || !pResLen) {
return -1;
}
uint8_t idx;
for (idx = 0; TBL_NUM > idx; idx++) {
if (arrT[idx].cmdId == cmdId) {
break;
}
}
if (TBL_NUM == idx) {
return -1; //the cmdId is not found
}
if (CMD_WRITE == arrT[idx].cmdType) {
arrT[idx].pProcFun(parrReq, pReqLen);
*pResLen = 0;
}
else {//CMD_READ
arrT[idx].pProcFun(parrRes, pResLen);
}
return 0;
}
static int getFanSpeed(uint8_t *parrRes, uint8_t *pResLen)
{
if (!parrRes || !pResLen) {
return -1;
}
//example
parrRes[0] = 145;
*pResLen = 1;
return 0;
}
static int setFanPWM(uint8_t *parrReq, uint8_t *pReqLen)
{
if (!parrReq || !pReqLen) {
return -1;
}
//example
uint8_t fanPwm = parrReq[0];
return 0;
}
```
---
### main.c
---
```c=
//main.c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_I2C2_Init();
MX_CRC_Init();
I2CSlave_init(); //init the I2C address
while (1)
{
I2CSlave_task(); //run the I2C slave task
}
}
```
---
### Services/btnDetect (I2C master)
---

* **HAL_I2C_Master_Seq_Transmit_DMA**(..., **I2C_FIRST_FRAME**)
=> Write the request command to the slave
* **HAL_I2C_Master_Seq_Receive_DMA**(..., **I2C_NEXT_FRAME**)
=> Read the (header) of the response command from the slave
* **HAL_I2C_Master_Seq_Receive_DMA**(..., **I2C_LAST_FRAME**)
=> Read the (data + CRC) of the response command from the slave
```c=
//btnDetect.c
#include "main.h"
#include "API/CmdPackage.h"
extern I2C_HandleTypeDef hi2c2;
#define BUF_SIZE (50)
static uint8_t arrMaRx[BUF_SIZE];
static uint8_t arrMaTx[BUF_SIZE];
static uint8_t rxCnt = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_PIN_13 == GPIO_Pin) {
if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, GPIO_Pin)) {
// When the button is pressed,
// the master starts to send the command to the slave.
uint8_t arrD[] = {50};//PWM 50%
uint8_t txLen;
//Wrap the command into the buffer arrMaTx[]
if (Cmd_pack(CMD_W_FAN_PWM, arrD, 0, arrMaTx, &txLen)) {
return;
}
//The master sends the command to the slave
if (HAL_OK != HAL_I2C_Master_Seq_Transmit_DMA(&hi2c2, 0x10 << 1, arrMaTx, txLen, I2C_FIRST_FRAME)) {
return;
}
}
}
}
```
```c=
//btnDetect.c
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
// After the master writes the command to the slave and it is completed,
// it then starts to read the response from the slave
if (I2C2 == hi2c->Instance) {
rxCnt = 0;
if (HAL_OK != HAL_I2C_Master_Seq_Receive_DMA(&hi2c2, 0x10 << 1, arrMaRx, sizeof(CmdHdr), I2C_NEXT_FRAME)) {
return;
}
}
}
```
```c=
//btnDetect.c
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
static uint8_t dataLen = 0;
if (I2C2 == hi2c->Instance) {
if (0 == rxCnt) {
// When the command header is received, it will enter this case to parse the dataLen information,
// then use that information to receive the remaining data from the slave.
rxCnt += sizeof(CmdHdr);
CmdHdr *pHdr = (CmdHdr*)arrMaRx;
dataLen = pHdr->dataLen;
if (HAL_OK != HAL_I2C_Master_Seq_Receive_DMA(hi2c, 0x10 << 1, arrMaRx + sizeof(CmdHdr), dataLen + CRC_LEN, I2C_LAST_FRAME)) {
return;
}
}
else {
// When the master has received the data and CRC,
// it will enter this case to unpack the command and check whether the return value indicates success or failure
rxCnt += (dataLen + CRC_LEN);
//read finished
uint8_t cmdId;
uint8_t arrRes[BUF_SIZE] = {0};
uint8_t resLen;
if (Cmd_unpack(&cmdId, arrMaRx, rxCnt, arrRes, &resLen)) {
return;
}
if (0 != arrRes[0]) {
//cmd error
return;
}
}
}
}
```
---
### Services/I2C_slave
---
```c=
//I2C_slave.c
int I2CSlave_init(void)
{
hi2c1.Init.OwnAddress1 = 0x10 << 1; //assign I2C slave address
if (HAL_OK != HAL_I2C_Init(&hi2c1)) {
return -1;
}
HAL_I2C_EnableListen_IT(&hi2c1);//enable HAL_I2C_AddrCallback() interrupt
return 0;
}
```

**R:** SDA 1 => master wants to read data from slave
**W:** SDA 0 => master wants to write data to slave
**HAL_I2C_AddrCallback()**
**R:** TransferDirection = I2C_DIRECTION_RECEIVE,
use the HAL_I2C_Slave_Seq_Transmit_DMA() to transmit data to the master
**W:** TransferDirection = I2C_DIRECTION_TRANSMIT,
use the HAL_I2C_Slave_Seq_Receive_DMA() to receive data from the master
```c=
//I2C_slave.c
#include <stdbool.h>
#include <string.h>
#include "main.h"
#include "Services/I2C_slave.h"
#include "API/CmdPackage.h"
#include "API/CmdProc.h"
extern I2C_HandleTypeDef hi2c1;
static uint8_t arrSlRx[50];
static uint8_t arrSlTx[50];
static bool isRxDone = false;
static uint8_t rxCnt = 0;
static uint8_t txCnt = 0;
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
if (I2C1 == hi2c->Instance) {
if (I2C_DIRECTION_TRANSMIT == TransferDirection) {
// When the master starts to write the command to the slave, this case will be triggered,
// and the slave will start to receive the command header of the data
rxCnt = 0;
if (HAL_OK != HAL_I2C_Slave_Seq_Receive_DMA(hi2c, arrSlRx, sizeof(CmdHdr), I2C_FIRST_FRAME)) {
return;
}
}
else {//I2C_DIRECTION_RECEIVE
// when the master starts to read the data from the slave,
// this case will be triggered, and the arrSlTx[] data will be sent to the master
if (HAL_OK != HAL_I2C_Slave_Seq_Transmit_DMA(hi2c, arrSlTx, txCnt, I2C_FIRST_AND_LAST_FRAME)) {
return;
}
}
}
}
```
* **HAL_I2C_Slave_Seq_Receive_DMA**(..., **I2C_FIRST_FRAME**)
=> Read the (header) of the request command from the master
* **HAL_I2C_Slave_Seq_Receive_DMA**(..., **I2C_LAST_FRAME**)
=> Read the (data + CRC) of the request command from the master
```c=
//I2C_slave.c
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
static uint8_t dataLen = 0;
if (0 == rxCnt) {
// When the command header has been received,
// parse the dataLen information and use it to continue receiving the remaining data and CRC
rxCnt += sizeof(CmdHdr);
CmdHdr *pHdr = (CmdHdr*)arrSlRx;
dataLen = pHdr->dataLen;
if (HAL_OK != HAL_I2C_Slave_Seq_Receive_DMA(hi2c, arrSlRx + sizeof(CmdHdr), dataLen + CRC_LEN, I2C_LAST_FRAME)) {
return;
}
}
else {
// When the data and CRC have been received,
// set the isRxDone flag to true
rxCnt += (dataLen + CRC_LEN);
isRxDone = true;
}
}
```
```c=
//I2C_slave.c
int I2CSlave_task(void)
{
int ret = 0;
if (isRxDone) {
isRxDone = false;
uint8_t cmdId;
uint8_t arrReq[BUF_SIZE] = {0};
uint8_t arrRes[BUF_SIZE] = {0};
uint8_t reqLen;
uint8_t resLen;
// Parse the command to get the command ID (cmdId),
// data (arrReq[]), and data length (reqLen)
if (Cmd_unpack(&cmdId, arrSlRx, rxCnt, arrReq, &reqLen)) {
return -1;
}
// Fill in the cmdId, arrReq[], and reqLen to process the data,
// and the response data will be placed in arrRes[],
// with the response data length filled in resLen.
ret = Cmd_process(cmdId, arrReq, &reqLen, arrRes, &resLen);
if (resLen) {
// for the read command response
if (Cmd_pack(cmdId, arrRes, resLen, arrSlTx, &txCnt)) {
return -1;
}
}
else {
// for the write command response
if (Cmd_pack(cmdId, (uint8_t*)&ret, 1, arrSlTx, &txCnt)) {
return -1;
}
}
// clear slave rx buffer
memset(arrSlRx, 0, sizeof(arrSlRx));
}
return 0;
}
```
When the arrSlTx[] buffer is ready, simply wait for the master to read it. It will be read in the HAL_I2C_AddrCallback() function under the I2C_DIRECTION_RECEIVE case.
---
### Reference
---
* https://www.youtube.com/watch?v=CAvawEcxoPU&ab_channel=RohdeSchwarz
* https://www.youtube.com/watch?v=X7WZQka2FHQ&list=PLfIJKC1ud8gj_P7Qb28aTr0t92uk_vwg0&ab_channel=ControllersTech