Understanding I2C Communication Methods

Understanding I2C Communication Methods

Overview:The I2C communication interface is the most commonly used MCU peripheral in our daily applications. Initially, before the MCU had hardware I2C, I2C communication was achieved by simulating the I2C timing through GPIO ports. Later, with the introduction of hardware I2C peripheral interfaces in MCUs, the use of hardware I2C became commonplace. More importantly, it simplified operations while saving MCU resources. Subsequently, due to market demand changes, MCUs began to support multiple slave address communications, keeping I2C applications in line with market needs.

Although it is known from the I2C characteristics that devices with different I2C addresses can be connected to the same I2C bus for communication, what if the addresses of the I2C devices we need to operate conflict? What if the number of hardware I2C interfaces on the MCU is insufficient? Or if the MCU’s I2C does not support multiple slave address communications? In such cases, we still need to simulate I2C timing through GPIO ports to complete the I2C master/slave functions. Therefore, the existence of hardware I2C does not eliminate the need for software I2C; rather, software and hardware implementations coexist and complement each other.

Understanding I2C Communication Methods

This article will discuss the implementation of I2C on the MM32F032/MM32F0140 series MCUs from the following four aspects, as well as the practical testing using I2C tools (Timos USB2XXX bus adapter):
  1. Hardware I2C Master Communication
  2. Software Simulated I2C Master Communication
  3. Hardware I2C Slave Communication
  4. Software Simulated I2C Slave Communication (Advanced)
The MM32F032 series MCU has one hardware I2C interface, supporting two operating speed modes: standard mode (data transfer rate of 0~100kbps) and fast mode (maximum data transfer rate of 400kbps). Its main features are as follows:
  1. I2C bus protocol converter/parallel bus;
  2. Half-duplex synchronous operation;
  3. Supports master/slave mode;
  4. Supports 7-bit and 10-bit addresses;
  5. Supports standard mode 100kbps and fast mode 400kbps;
  6. Generates Start, Stop, Repeated Start, and Acknowledge signal detection;
  7. Only one master is supported in master mode;
  8. Has separate 2-byte send and receive buffers;
  9. Includes glitch-free circuits on SCL and SDA;
  10. Supports DAM, interrupt, and polling operation modes;
  11. The MM32F0140 series MCU has more extensive I2C functionalities based on the MM32F032, supporting multiple slave address communications, clock stretching, etc. For specifics, refer to the official datasheet.

1. Hardware I2C Master Communication

The hardware I2C of MM32 is the simplest to operate in the code program segment I have used so far; there is no need to consider START signals, ACK signals, and various EVENT events… These complex operations or those that can be omitted are handled by the official lower-level library and chip IP, simplifying our variable designs in driver programming.
For configuring the hardware I2C master, we only need to specify the GPIO ports to be reused, the I2C communication parameters, and the slave address; we can then program to read/write I2C slave devices. The initialization configuration and implementation code for reading and writing to the I2C slave device is as follows:
void hI2C_MASTER_Init(uint8_t SlaveAddress){    GPIO_InitTypeDef GPIO_InitStructure;    I2C_InitTypeDef  I2C_InitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);
    I2C_StructInit(&I2C_InitStructure);    I2C_InitStructure.I2C_Mode       = I2C_Mode_MASTER;    I2C_InitStructure.I2C_OwnAddress = 0;    I2C_InitStructure.I2C_Speed      = I2C_Speed_STANDARD;    I2C_InitStructure.I2C_ClockSpeed = 100000;    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_Send7bitAddress(I2C1, SlaveAddress, I2C_Direction_Transmitter);    I2C_Cmd(I2C1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1);    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;    GPIO_Init(GPIOB, &GPIO_InitStructure);}
void hI2C_MASTER_Read(uint8_t Address, uint8_t *Buffer, uint8_t Length){    uint8_t flag = 0, count = 0;
    I2C_SendData(I2C1, Address);    while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));
    for(uint8_t i = 0; i < Length; i++)    {        while(1)        {            if((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0))            {                I2C_ReadCmd(I2C1);   count++;                if(count == Length) flag = 1;            }
            if(I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE))            {                Buffer[i] = I2C_ReceiveData(I2C1);     break;            }        }    }
    I2C_GenerateSTOP(I2C1, ENABLE);    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_STOP_DET));}
void hI2C_MASTER_Write(uint8_t Address, uint8_t *Buffer, uint8_t Length){    I2C_SendData(I2C1, Address);    while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));
    for(uint8_t i = 0; i < Length; i++)    {        I2C_SendData(I2C1, *Buffer++);        while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));    }
    I2C_GenerateSTOP(I2C1, ENABLE);    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_STOP_DET));}
void hI2C_MASTER_SHELL_Handler(uint8_t Mode){    uint8_t Buffer[10] = {0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0xAA};
    if(Mode == 1)    {        hI2C_MASTER_Write(0x00, Buffer, sizeof(Buffer));    }    else    {        hI2C_MASTER_Read(0x00, Buffer, sizeof(Buffer));
        printf("\r\nhI2C Master Read : \r\n");
        for(uint8_t i = 0; i < sizeof(Buffer); i++)        {            printf("0x%02x ", Buffer[i]);        }
        printf("\r\n");    }}
SHELL_EXPORT_CMD(HI2C_MASTER, hI2C_MASTER_SHELL_Handler, Hardware I2C Master Read And Write);
The actual test results are as follows:
Understanding I2C Communication Methods

2. Software Simulated I2C Master Communication

The implementation of software simulated I2C master communication mainly involves controlling the high and low levels of GPIO port pins to meet the timing requirements of I2C communication for reading and writing operations on I2C slave devices; when implementing software simulated I2C master, it is necessary to correctly generate Start conditions, Stop conditions, and Restart conditions; appropriate input/output configurations of GPIO port pins are needed to correctly determine ACK and NACK responses; and the format of the sent bytes must be correctly operated to ensure the address and data contents are correctly recognized…

The following software simulated I2C master implementation defines an operation structure, allowing the software simulated I2C master program to implement object-oriented programming through instance passing. With the same implementation code, multiple software simulated I2C master communication interfaces can be realized simultaneously, greatly saving code space and enhancing code portability.

typedef struct{    uint32_t      SCL_RCC;    GPIO_TypeDef *SCL_GPIO;    uint16_t      SCL_PIN;
    uint32_t      SDA_RCC;    GPIO_TypeDef *SDA_GPIO;    uint16_t      SDA_PIN;
    uint32_t      TIME;    uint8_t       SlaveAddress;} sI2C_MASTER_TypeDef;

sI2C_MASTER_TypeDef sI2C_MASTER = {    RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_6,    RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_7,    100,    0xA0};

#define sI2C_MASTER_SCL_H(sI2C)     GPIO_WriteBit(sI2C->SCL_GPIO, sI2C->SCL_PIN, Bit_SET)#define sI2C_MASTER_SCL_L(sI2C)     GPIO_WriteBit(sI2C->SCL_GPIO, sI2C->SCL_PIN, Bit_RESET)
#define sI2C_MASTER_SDA_H(sI2C)     GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_SET)#define sI2C_MASTER_SDA_L(sI2C)     GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_RESET)
#define sI2C_MASTER_SCL_GET(sI2C)   GPIO_ReadOutputDataBit(sI2C->SCL_GPIO, sI2C->SCL_PIN)#define sI2C_MASTER_SDA_GET(sI2C)   GPIO_ReadInputDataBit( sI2C->SDA_GPIO, sI2C->SDA_PIN)

void sI2C_MASTER_Delay(uint32_t Tick){    while(Tick--);}
void sI2C_MASTER_SDA_SetDirection(sI2C_MASTER_TypeDef *sI2C, uint8_t Direction){    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHBPeriphClockCmd(sI2C->SDA_RCC, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = sI2C->SDA_PIN;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    if(Direction)   /* Input */    {        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;    }    else            /* Output */    {        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;    }
    GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);}
void sI2C_MASTER_SCL_SetDirection(sI2C_MASTER_TypeDef *sI2C, uint8_t Direction){    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHBPeriphClockCmd(sI2C->SCL_RCC, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = sI2C->SCL_PIN;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    if(Direction)   /* Input */    {        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;    }    else            /* Output */    {        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;    }
    GPIO_Init(sI2C->SCL_GPIO, &GPIO_InitStructure);}
void sI2C_MASTER_GenerateStart(sI2C_MASTER_TypeDef *sI2C){    sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SDA_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}
void sI2C_MASTER_GenerateStop(sI2C_MASTER_TypeDef *sI2C){    sI2C_MASTER_SDA_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}
void sI2C_MASTER_PutACK(sI2C_MASTER_TypeDef *sI2C, uint8_t ack){    if(ack) sI2C_MASTER_SDA_H(sI2C);    /* NACK */    else    sI2C_MASTER_SDA_L(sI2C);    /* ACK  */
    sI2C_MASTER_Delay(sI2C->TIME);
    sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}
uint8_t sI2C_MASTER_GetACK(sI2C_MASTER_TypeDef *sI2C){    uint8_t ack = 0;
    sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);
    sI2C_MASTER_SDA_SetDirection(sI2C, 1);
    sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);
    ack = sI2C_MASTER_SDA_GET(sI2C);
    sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);
    sI2C_MASTER_SDA_SetDirection(sI2C, 0);
    return ack;}
uint8_t sI2C_MASTER_ReadByte(sI2C_MASTER_TypeDef *sI2C){    uint8_t Data = 0;
    sI2C_MASTER_SDA_H(sI2C); /* Must set SDA before read */
    sI2C_MASTER_SDA_SetDirection(sI2C, 1);
    for(uint8_t i = 0; i < 8; i++)    {        sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);
        Data <<= 1;
        if(sI2C_MASTER_SDA_GET(sI2C)) Data |= 0x01;
        sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    }
    sI2C_MASTER_SDA_SetDirection(sI2C, 0);
    return Data;}
void sI2C_MASTER_WriteByte(sI2C_MASTER_TypeDef *sI2C, uint8_t Data){    for(uint8_t i = 0; i < 8; i++)    {        if(Data & 0x80) sI2C_MASTER_SDA_H(sI2C);        else            sI2C_MASTER_SDA_L(sI2C);
        Data <<= 1;
        sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);        sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    }}
void sI2C_MASTER_Init(sI2C_MASTER_TypeDef *sI2C){    sI2C_MASTER_SDA_SetDirection(sI2C, 0);    sI2C_MASTER_SCL_SetDirection(sI2C, 0);
    sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);    sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}
uint8_t sI2C_MASTER_Read(sI2C_MASTER_TypeDef *sI2C, uint8_t Address, uint8_t *Buffer, uint8_t Length){    if(Length == 0) return 0;
    sI2C_MASTER_GenerateStart(sI2C);
    sI2C_MASTER_WriteByte(sI2C, sI2C->SlaveAddress);
    if(sI2C_MASTER_GetACK(sI2C))    {        sI2C_MASTER_GenerateStop(sI2C); return 1;    }
    sI2C_MASTER_WriteByte(sI2C, Address);
    if(sI2C_MASTER_GetACK(sI2C))    {        sI2C_MASTER_GenerateStop(sI2C); return 1;    }
    sI2C_MASTER_GenerateStart(sI2C);
    sI2C_MASTER_WriteByte(sI2C, sI2C->SlaveAddress + 1);
    if(sI2C_MASTER_GetACK(sI2C))    {        sI2C_MASTER_GenerateStop(sI2C); return 1;    }
    while(1)    {        *Buffer++ = sI2C_MASTER_ReadByte(sI2C);
        if(--Length == 0)        {            sI2C_MASTER_PutACK(sI2C, 1); break;        }
        sI2C_MASTER_PutACK(sI2C, 0);    }
    sI2C_MASTER_GenerateStop(sI2C);
    return 0;}
uint8_t sI2C_MASTER_Write(sI2C_MASTER_TypeDef *sI2C, uint8_t Address, uint8_t *Buffer, uint8_t Length){    uint8_t i = 0;
    if(Length == 0) return 0;
    sI2C_MASTER_GenerateStart(sI2C);
    sI2C_MASTER_WriteByte(sI2C, sI2C->SlaveAddress);
    if(sI2C_MASTER_GetACK(sI2C))    {        sI2C_MASTER_GenerateStop(sI2C); return 1;    }
    sI2C_MASTER_WriteByte(sI2C, Address);
    if(sI2C_MASTER_GetACK(sI2C))    {        sI2C_MASTER_GenerateStop(sI2C); return 1;    }
    for(i = 0; i < Length; i++)    {        sI2C_MASTER_WriteByte(sI2C, *Buffer++);
        if(sI2C_MASTER_GetACK(sI2C))     break;    }
    sI2C_MASTER_GenerateStop(sI2C);
    if(i == Length) return 0;    else            return 1;}
void sI2C_MASTER_SHELL_Handler(uint8_t Mode){    uint8_t Buffer[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
    if(Mode == 1)    {        sI2C_MASTER_Write(&sI2C_MASTER, 0x00, Buffer, sizeof(Buffer));    }    else    {        sI2C_MASTER_Read(&sI2C_MASTER, 0x00, Buffer, sizeof(Buffer));
        printf("\r\nsI2C Master Read : \r\n");
        for(uint8_t i = 0; i < sizeof(Buffer); i++)        {            printf("0x%02x ", Buffer[i]);        }
        printf("\r\n");    }}
SHELL_EXPORT_CMD(SI2C_MASTER, sI2C_MASTER_SHELL_Handler, Software I2C Master Read And Write);
The actual test results are as follows:
Understanding I2C Communication Methods

3. Hardware I2C Slave Communication

For hardware I2C slave communication, an interrupt response method is often used to avoid the program waiting indefinitely at a certain point for I2C master operations; polling methods can easily miss I2C requests or events. Therefore, the following hardware I2C slave communication method uses interrupt handling, where any operations and requests from the I2C master will be mapped to corresponding interrupts. Once the slave detects this, it will enter the interrupt to perform the corresponding processing, and the interrupt method also ensures normal and stable communication.

Currently, many MCUs on the market support multiple address modes for I2C slave mode, but the IP function designs vary among manufacturers: some directly set slave addresses via registers, limiting the number of supported slave addresses; others use address masking methods (similar to CAN communication ID filters), allowing for more flexible support of multiple slave addresses. The second method is more flexible than the first and supports a larger number of slave device addresses!

The MM32F032 does not support multi-address slave functions, but the MM32F0140 supports multiple slave address communications, allowing you to choose the corresponding chip model based on actual project needs. The multi-address function for the slave uses an address masking method to filter slave addresses, supporting more slave device addresses by setting the slave device address and address mask.The specific code implementation for hardware I2C slave communication is as follows:

void hI2C_SLAVE_Init(uint8_t SlaveAddress){    I2C_InitTypeDef  I2C_InitStructure;    GPIO_InitTypeDef GPIO_InitStructure;    NVIC_InitTypeDef NVIC_InitStructure;
    QUEUE_INIT(QUEUE_HI2C_SLAVE_IDX);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);
    I2C_StructInit(&I2C_InitStructure);    I2C_InitStructure.Mode       = I2C_Mode_SLAVE;    I2C_InitStructure.OwnAddress = 0;    I2C_InitStructure.Speed      = I2C_Speed_FAST;    I2C_InitStructure.ClockSpeed = 400000;    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_ITConfig(I2C1, I2C_IT_RD_REQ,  ENABLE);    I2C_ITConfig(I2C1, I2C_IT_RX_FULL, ENABLE);
    I2C_Cmd(I2C1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1);    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_FLOATING;    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_7;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;    GPIO_Init(GPIOB, &GPIO_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    NVIC_Init(&NVIC_InitStructure);
    I2C_SendSlaveAddress(I2C1, SlaveAddress);}
void I2C1_IRQHandler(void){    static uint8_t Data = 0;
    if(I2C_GetITStatus(I2C1, I2C_IT_RD_REQ)  != RESET)    {        I2C_ClearITPendingBit(I2C1, I2C_IT_RD_REQ);
        while(1)        {            I2C_SendData(I2C1, Data++);            while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TX_EMPTY) == RESET);
            if((Data % 10) == 0)            {                I2C_GenerateSTOP(I2C1, ENABLE); break;            }        }    }
    if(I2C_GetITStatus(I2C1, I2C_IT_RX_FULL) != RESET)    {        QUEUE_WRITE(QUEUE_HI2C_SLAVE_IDX, I2C_ReceiveData(I2C1));    }}
The actual test results are as follows:
Understanding I2C Communication Methods

4. Software Simulated I2C Slave Communication

Software simulated I2C slave communication is the reverse implementation process of I2C communication timing. It requires capturing the signal timing of the I2C master to parse the events, requests, and data sent from the master, while correctly responding to the I2C master. Thus, its implementation method is completely different from that of simulated I2C master. This requires developers to be very familiar with I2C timing, so it is recommended to analyze the following software simulated I2C slave communication program against the I2C timing (note: this part is somewhat challenging).

The implementation of software simulated I2C slave communication is achieved through two GPIO port pins connected to the SCL and SDA of the I2C master. In the program, these two GPIO port pins are configured to work in external interrupt EXTI mode, capturing the rising and falling edges and the high/low states of the GPIO port pins, combined with the state management of the software simulated I2C slave, to realize communication functions with the I2C master. Detailed comments and explanations have been added in the following program for easier reading and understanding. The specific code implementation is as follows:

typedef struct{    uint32_t      SCL_RCC;    GPIO_TypeDef *SCL_GPIO;    uint16_t      SCL_PIN;
    uint8_t       SCL_EXTI_PortSource;    uint8_t       SCL_EXTI_PinSource;    uint32_t      SCL_EXTI_Line;
    uint32_t      SDA_RCC;    GPIO_TypeDef *SDA_GPIO;    uint16_t      SDA_PIN;
    uint8_t       SDA_EXTI_PortSource;    uint8_t       SDA_EXTI_PinSource;    uint32_t      SDA_EXTI_Line;
    uint8_t       SlaveAddress;} sI2C_SLAVE_TypeDef;

sI2C_SLAVE_TypeDef sI2C_SLAVE = {    RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_6, EXTI_PortSourceGPIOB, EXTI_PinSource6, EXTI_Line6,    RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_7, EXTI_PortSourceGPIOB, EXTI_PinSource7, EXTI_Line7,    0xA0,};

#define sI2C_SLAVE_STATE_NA        0#define sI2C_SLAVE_STATE_STA       1#define sI2C_SLAVE_STATE_ADD       2#define sI2C_SLAVE_STATE_ADD_ACK   3#define sI2C_SLAVE_STATE_DAT       4#define sI2C_SLAVE_STATE_DAT_ACK   5#define sI2C_SLAVE_STATE_STO       6
uint8_t sI2C_SLAVE_State = sI2C_SLAVE_STATE_NA;
uint8_t sI2C_SLAVE_ShiftCounter = 0;uint8_t sI2C_SLAVE_SlaveAddress = 0;uint8_t sI2C_SLAVE_ReceivedData = 0;uint8_t sI2C_SLAVE_TransmitData = 0x50;
uint8_t sI2C_SLAVE_TransmitBuffer[16] = {    0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78,    0x89, 0x9A, 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0xF0,};uint8_t sI2C_SLAVE_TransmitIndex = 0;

bool sI2C_SLAVE_READ_SCL(sI2C_SLAVE_TypeDef *sI2C){    return GPIO_ReadInputDataBit(sI2C->SCL_GPIO, sI2C->SCL_PIN);}
bool sI2C_SLAVE_READ_SDA(sI2C_SLAVE_TypeDef *sI2C){    return GPIO_ReadInputDataBit(sI2C->SDA_GPIO, sI2C->SDA_PIN);}

/******************************************************************************* * @brief       Configure the GPIO ports for simulating I2C, default set to input mode, and enable the corresponding external trigger interrupt function (rising and falling edges) * @param        * @retval       * @attention   *******************************************************************************/void sI2C_SLAVE_Init(sI2C_SLAVE_TypeDef *sI2C){    GPIO_InitTypeDef GPIO_InitStructure;    EXTI_InitTypeDef EXTI_InitStructure;    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_AHBPeriphClockCmd(sI2C->SCL_RCC, ENABLE);    RCC_AHBPeriphClockCmd(sI2C->SDA_RCC, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = sI2C->SCL_PIN;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;    GPIO_Init(sI2C->SCL_GPIO, &GPIO_InitStructure);
    GPIO_StructInit(&GPIO_InitStructure);    GPIO_InitStructure.GPIO_Pin   = sI2C->SDA_PIN;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;    GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);
    SYSCFG_EXTILineConfig(sI2C->SCL_EXTI_PortSource, sI2C->SCL_EXTI_PinSource);
    EXTI_StructInit(&EXTI_InitStructure);    EXTI_InitStructure.EXTI_Line    = sI2C->SCL_EXTI_Line;    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;    EXTI_InitStructure.EXTI_LineCmd = ENABLE;    EXTI_Init(&EXTI_InitStructure);
    SYSCFG_EXTILineConfig(sI2C->SDA_EXTI_PortSource, sI2C->SDA_EXTI_PinSource);
    EXTI_StructInit(&EXTI_InitStructure);    EXTI_InitStructure.EXTI_Line    = sI2C->SDA_EXTI_Line;    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;    EXTI_InitStructure.EXTI_LineCmd = ENABLE;    EXTI_Init(&EXTI_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_15_IRQn;    NVIC_InitStructure.NVIC_IRQChannelPriority  = 0x00;    NVIC_InitStructure.NVIC_IRQChannelCmd     = ENABLE;    NVIC_Init(&NVIC_InitStructure);}

/******************************************************************************* * @brief       Set the input/output direction of the SDA signal line, 0 represents Output, 1 represents Input * @param        * @retval       * @attention   *******************************************************************************/void sI2C_SLAVE_SDA_SetDirection(sI2C_SLAVE_TypeDef *sI2C, uint8_t Direction){    GPIO_InitTypeDef GPIO_InitStructure;
    if(Direction)   /* Input */    {        GPIO_StructInit(&GPIO_InitStructure);        GPIO_InitStructure.GPIO_Pin   = sI2C->SDA_PIN;        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;        GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);    }    else            /* Output */    {        GPIO_StructInit(&GPIO_InitStructure);        GPIO_InitStructure.GPIO_Pin   = sI2C->SDA_PIN;        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_OD;        GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);    }}

/****************************************************************************** * @brief       Set the output level of the SDA signal line (high level / low level) * @param        * @retval       * @attention   ******************************************************************************/void sI2C_SLAVE_SDA_SetLevel(sI2C_SLAVE_TypeDef *sI2C, uint8_t Level){    sI2C_SLAVE_SDA_SetDirection(sI2C, 0);
    if(Level)    {        GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_SET);    }    else    {        GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_RESET);    }}

/****************************************************************************** * @brief       Handle the rising edge external interrupt when SCL triggers * @param        * @retval       * @attention   ******************************************************************************/void sI2C_SLAVE_SCL_RiseHandler(sI2C_SLAVE_TypeDef *sI2C){    /* SCL rising edge, data is locked, the master and slave get data bits from the SDA bus */    switch(sI2C_SLAVE_State)    {        case sI2C_SLAVE_STATE_ADD:
            /* I2C sends the MSB, first sends the high bit, then the low bit, so when receiving, the data shifts left */            sI2C_SLAVE_SlaveAddress <<= 1;            sI2C_SLAVE_ShiftCounter  += 1;
            if(sI2C_SLAVE_READ_SDA(sI2C) ==  Bit_SET)            {                sI2C_SLAVE_SlaveAddress |= 0x01;            }
            /* After receiving 8 bits of address, the slave needs to give an ACK response on the 9th clock */            if(sI2C_SLAVE_ShiftCounter == 8)            {                sI2C_SLAVE_State = sI2C_SLAVE_STATE_ADD_ACK;            }
            break;
        case sI2C_SLAVE_STATE_ADD_ACK:            /* After ACK response to the slave address, switch to the data transmission state */            sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;
            sI2C_SLAVE_ShiftCounter = 0;    /* Clear the data shift counter */            sI2C_SLAVE_ReceivedData = 0;    /* Clear the received data of sI2C_SLAVE */            break;
        case sI2C_SLAVE_STATE_DAT:            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* Master write operation: at this time, the slave should get the SDA signal line level sent by the master to store bits */                sI2C_SLAVE_ReceivedData <<= 1;                sI2C_SLAVE_ShiftCounter  += 1;
                if(sI2C_SLAVE_READ_SDA(sI2C) == Bit_SET)                {                    sI2C_SLAVE_ReceivedData |= 0x01;                }
                /* When receiving a complete 8-bit data, store the received data in the I2C receive message queue, switch state to send ACK response to the master */                if(sI2C_SLAVE_ShiftCounter == 8)                {                    QUEUE_WRITE(QUEUE_SI2C_SLAVE_IDX, sI2C_SLAVE_ReceivedData);
                    sI2C_SLAVE_ShiftCounter = 0;    /* Clear the data shift counter */                    sI2C_SLAVE_ReceivedData = 0;    /* Clear the received data of sI2C_SLAVE */
                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT_ACK;                }            }            else            {                /* Master read operation: during the rising edge of SCL, the master gets the current SDA state bit; if it is at the rising edge of the 8th bit, the next will be the master responding to the slave with ACK or NACK, so switch to the waiting ACK state, and prepare the next data to be sent */                if(sI2C_SLAVE_ShiftCounter == 8)                {                    sI2C_SLAVE_ShiftCounter = 0;    /* Clear the received data of sI2C_SLAVE */                    sI2C_SLAVE_TransmitData = sI2C_SLAVE_TransmitBuffer[sI2C_SLAVE_TransmitIndex++];
                    sI2C_SLAVE_TransmitIndex %= 16;
                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT_ACK;                }
            }            break;
        case sI2C_SLAVE_STATE_DAT_ACK:            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* Master write operation: the slave sends ACK after receiving data, waiting for the master to read the slave's ACK signal */                sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;  /* Switch state to data receiving state */            }            else            {                /* Master read operation: the master sends ACK, the slave can read the ACK signal sent by the master */                uint8_t ack = sI2C_SLAVE_READ_SDA(sI2C);
                if(ack == Bit_RESET)                {                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;    /* Received ACK, continue sending data */                }                else                {                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_STO;    /* Received NACK, stop sending data */                }
            }            break;
        default:            break;    }}

/****************************************************************************** * @brief       Handle the falling edge external interrupt when SCL triggers * @param        * @retval       * @attention   ******************************************************************************/void sI2C_SLAVE_SCL_FallHandler(sI2C_SLAVE_TypeDef *sI2C){    /* SCL falling edge, data can change */    switch(sI2C_SLAVE_State)    {        case sI2C_SLAVE_STATE_STA:            /*             * After detecting the START signal, the first falling edge of SCL indicates the start of transmitting Slave Address,             * According to the validity rules of data, the first bit of the address must be read when SCL is high             * Switch to the state of obtaining Slave Address, waiting for the rising edge of SCL             */            sI2C_SLAVE_State = sI2C_SLAVE_STATE_ADD;
            sI2C_SLAVE_ShiftCounter = 0;    /* Clear the data shift counter */            sI2C_SLAVE_SlaveAddress = 0;    /* Clear the slave address of sI2C_SLAVE */            sI2C_SLAVE_ReceivedData = 0;    /* Clear the received data of sI2C_SLAVE */            break;
        case sI2C_SLAVE_STATE_ADD:            /*             * When the master sends Slave Address, the slave only reads the SDA state for address parsing, so no processing is done here             */            break;
        case sI2C_SLAVE_STATE_ADD_ACK:            /* When SCL is low, send the address response signal to the I2C bus, state remains unchanged, waiting for the next rising edge to send ACK */            sI2C_SLAVE_SDA_SetLevel(sI2C, 0);   /* Pull SDA low to send ACK signal to the master */            break;
        case sI2C_SLAVE_STATE_DAT:            /* On the falling edge of the SCL clock signal, the SDA signal line can change state */            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* Master write operation: set the SDA signal line to the obtaining state, waiting for the next rising edge of SCL to obtain the data bit */                sI2C_SLAVE_SDA_SetDirection(sI2C, 1);            }            else            {                /* Master read operation: set the SDA signal line output level based on the data sent, waiting for the next rising edge of SCL to send the data bit */                if(sI2C_SLAVE_TransmitData & 0x80)                {                    sI2C_SLAVE_SDA_SetLevel(sI2C, 1);                }                else                {                    sI2C_SLAVE_SDA_SetLevel(sI2C, 0);                }
                sI2C_SLAVE_TransmitData <<= 1;                sI2C_SLAVE_ShiftCounter  += 1;            }
            break;
        case sI2C_SLAVE_STATE_DAT_ACK:            /* On the 8th SCL clock signal falling edge */            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* Master write operation: the slave needs to give the master an ACK response after receiving data, state remains unchanged, waiting for the next rising edge to send ACK */                sI2C_SLAVE_SDA_SetLevel(sI2C, 0);   /* Pull SDA low to send ACK signal to the master */            }            else            {                /* Master read operation: the slave needs to release the current SDA signal line so that the master can send ACK or NACK to the slave, state remains unchanged, waiting for the next rising edge to read the ACK signal */                sI2C_SLAVE_SDA_SetDirection(sI2C, 1);            }
            break;
        default:            break;    }}

/**  * @brief  Handle the rising edge external interrupt when SDA triggers  * @param  None  * @retval None  */void sI2C_SLAVE_SDA_RiseHandler(sI2C_SLAVE_TypeDef *sI2C){    if(sI2C_SLAVE_READ_SCL(sI2C) == Bit_SET)    /* SCL is high, SDA rising edge: STOP */    {        sI2C_SLAVE_State = sI2C_SLAVE_STATE_STO;    }    else                                        /* SCL is low, SDA rising edge: data changes */    {    }}

/**  * @brief  Handle the falling edge external interrupt when SDA triggers  * @param  None  * @retval None  */void sI2C_SLAVE_SDA_FallHandler(sI2C_SLAVE_TypeDef *sI2C){    if(sI2C_SLAVE_READ_SCL(sI2C) == Bit_SET)    /* SCL is high, SDA falling edge: START */    {        sI2C_SLAVE_State = sI2C_SLAVE_STATE_STA;    }    else                                        /* SCL is low, SDA falling edge: data changes */    {    }}

/******************************************************************************* * @brief        * @param        * @retval       * @attention   *******************************************************************************/void EXTI4_15_IRQHandler(void){    /* I2C SCL */    if(EXTI_GetITStatus(sI2C_SLAVE.SCL_EXTI_Line) != RESET)    {        if(sI2C_SLAVE_READ_SCL(&sI2C_SLAVE) == Bit_SET)        {            sI2C_SLAVE_SCL_RiseHandler(&sI2C_SLAVE);        }        else        {            sI2C_SLAVE_SCL_FallHandler(&sI2C_SLAVE);        }
        EXTI_ClearITPendingBit(sI2C_SLAVE.SCL_EXTI_Line);    }
    /* I2C SDA */    if(EXTI_GetITStatus(sI2C_SLAVE.SDA_EXTI_Line) != RESET)    {        if(sI2C_SLAVE_READ_SDA(&sI2C_SLAVE) == Bit_SET)        {            sI2C_SLAVE_SDA_RiseHandler(&sI2C_SLAVE);        }        else        {            sI2C_SLAVE_SDA_FallHandler(&sI2C_SLAVE);        }
        EXTI_ClearITPendingBit(sI2C_SLAVE.SDA_EXTI_Line);    }}
The actual test results are as follows:

Understanding I2C Communication Methods

These are the various ways to implement I2C interface communication based on the MM32 ecosystem. If you need to view the original images, source code of the software engineering, or I2C-related materials, please click the bottom “Read the Original” to download.

Author: xld0932
Source: 21ic Forum
Friendly Reminder:

Due to recent changes in the WeChat public platform push rules, many readers have reported not seeing updated articles in time. According to the latest rules, it is recommended to click “recommended reading, sharing, collecting”, etc., to become a regular reader.

Recommended Reading:

  • Foreign Media: Russia’s Keyboards Are Running Out

  • Unveiling the Story Behind the Development of the “Fat Five” Rocket Engine

  • A Man Stole 8 Baseband Processing Modules, Causing Signal Paralysis in the Entire District
Understanding I2C Communication Methods
Understanding I2C Communication Methods

Add WeChat Administrator

What is the difference between you and top engineers?
Join the technical exchange group and face-to-face with experts

Please click 【View】 to give the editor a thumbs up~

Understanding I2C Communication Methods

Leave a Comment