0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于MM32生态实现I2C接口通讯的几种方式

硬件攻城狮 来源:21ic论坛 作者:xld0932 2022-09-26 14:19 次阅读
概述:I2C通讯接口,是我们日常应用中使用最多的MCU外设。最早在MCU没有硬件I2C之前,都是通过GPIO口模拟I2C的时序来完成I2C通讯的。后来MCU带有了I2C外设接口,其硬件I2C的使用也变成了日常。更主要的是,在节省MCU资源的同时,其操作也变得更加简单和易用。再后来经过市场需求的变化,开始有了支持I2C多从机地址通讯功能的MCU,让I2C的应用紧跟市场需求。

虽然从I2C特性上知晓具有不同I2C地址的器件是可以挂载在同一个I2C总线上进行通讯的,但是,如果需要操作的I2C器件地址冲突呢?MCU的硬件I2C接口数量不够呢?或者说MCU的I2C不支持从机多地址通讯功能呢?这时候,我们还是需要通过GPIO口来模拟I2C时序完成I2C主机/从机的功能。所以,并不是有了硬件I2C,软件I2C就没有发挥的空间了,恰恰是软件和硬件这两种实现方式共存互相补充。

1f006296-3d61-11ed-9e49-dac502259ad0.jpg

对于I2C的基本概念及时序等知识点,本文不再详细描述,大家可以下载附件中的《I2C总线概要》和《I2C总线规范》进行研究。本文将通过如下四个方面,讲述I2C在MM32F032/MM32F0140系列MCU上的实现,以及使用I2C工具(图莫斯USB2XXX总线适配器)进行实际测试:
  1. 硬件I2C主机通讯
  2. 软件模拟I2C主机通讯
  3. 硬件I2C从机通讯
  4. 软件模拟I2C从机通讯(有难度)
MM32F032系列MCU带有1路硬件I2C接口,支持标准模式(数据传输速率为0~100kbps)和快速模式(数据最大传输速率为400kbps)两种工作速率模式,其主要特征如下所示:
  1. I2C总线协议转换器/并行总线;
  2. 半双工同步操作;
  3. 支持主从模式;
  4. 支持7位地址和10位地址;
  5. 支持标准模式100kbps、快速模式400kbps;
  6. 产生Start、Stop、Repeated Start,以及Acknowledge信号检测
  7. 在主机模式下只支持一个主机;
  8. 分别有2个字节的发送和接收缓冲;
  9. 在SCL和SDA上增加了无毛刺电路;
  10. 支持DAM、中断和查询操作方式;
  11. MM32F0140系列MCU在MM32F032的基础上I2C做了更丰富的功能,支持多从机地址通讯的功能、支持时钟延展等等……具体的可以参考官方的数据手册。

一、硬件I2C主机通讯

MM32的硬件I2C是我使用到现在,在代码程序段操作最为简洁的了;不需要再去考虑START信号、ACK信号,以及各种EVENT事件等……这些复杂的操作、或者是可以省略的操作都由官方的底层库程序和芯片IP去实现了,让我们在设计驱动程序时变量简单了。对于硬件I2C主机的配置,我们只需要复用的GPIO端口引脚、I2C通讯参数,以及从机地址即可;然后就可以编程去读写I2C从机设备了,初始化配置及对I2C从机设备的读写操作的实现代码如下:
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("
hI2C Master Read : 
");
        for(uint8_t i = 0; i < sizeof(Buffer); i++)        {            printf("0x%02x ", Buffer[i]);        }
        printf("
");    }}SHELL_EXPORT_CMD(HI2C_MASTER, hI2C_MASTER_SHELL_Handler, Hardware I2C Master Read And Write);
实测结果如下所示:1f135e3c-3d61-11ed-9e49-dac502259ad0.png

二、软件模拟I2C主机通讯

对于软件模拟I2C主机通讯的实现方式,主要是通过操作GPIO端口引脚的高低电平,在满足I2C通讯时序的要求上完成对I2C从机设备的读写操作;在实现软件模拟I2C主机时,需要正确的产生Start起始条件、Stop停止条件,以及Restart重启条件;需要在适当的位置对GPIO端口引脚的输入输出状态进行配置,以便能够正确的判断出ACK和NACK的应答信号;需要正确操作发送的字节格式,使地址内容、数据内容能够被正确识别……

如下的软件模拟I2C主机的实现方式通过定义了一个操作结构体,通过传递操作实例的方式,让软件模拟I2C主机的程序实现了面向对象的编程,借住同一段实现代码,可以同时实现多个软件模拟I2C主机通讯接口,在代码实现上大大的节省了空间,同时也让代码的可移植性变得更加通用,具体的代码实现如下所示:


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("
sI2C Master Read : 
");
        for(uint8_t i = 0; i < sizeof(Buffer); i++)        {            printf("0x%02x ", Buffer[i]);        }
        printf("
");    }}SHELL_EXPORT_CMD(SI2C_MASTER, sI2C_MASTER_SHELL_Handler, Software I2C Master Read And Write);
实测结果如下所示:1f22af2c-3d61-11ed-9e49-dac502259ad0.png

三、硬件I2C从机通讯

对于硬件I2C从机通讯来说,更多的是采用中断的响应方式来避免程序在某一处一直等待I2C主机的操作;而轮询的方式很容易捕捉不到I2C的请求或者事件;所以如下硬件I2C从机通讯的方式使用的就是中断处理方式,I2C主机任何操作和请求都会映射成对应的中断,待从机检测到了之后,进入中断进行相应的处理,同时中断的方式也保证了通讯的正常和稳定性。

现在市面上很多MCU的I2C从机模式都支持多地址模式,但每家的IP功能设计都不一样:有些是直接通过寄存器设置从机地址方式,这种方式限制了所支持从机地址的个数;有些是通过地址掩码的方式(类似于CAN通讯的ID滤波器),通过逐位比较的方式来判别所支持的I2C从机地址,这种方式可以支持很多个从机地址;第二种方式相比于第一种实现方式更灵活,支持的从机设备地址也更多!

MM32F032不支持多地址从机功能,但MM32F0140支持从机多地址通讯,可以根据实际项目需求选择对应的芯片型号;从机多地址功能采用的是地址掩码方式来过滤从机地址的,这样可以支持更多的从机设备地址;通过设置从机设备地址和从机地址掩码来实现从机多地址通讯功能;硬件I2C从机通讯具体的代码实现如下:


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));    }}
实测结果如下所示:1f3209e0-3d61-11ed-9e49-dac502259ad0.png

四、软件模拟I2C从机通讯

软件模拟I2C从机通讯是I2C通讯时序逆向的实现过程,它需要通过捕捉I2C主机的信号时序对主机的事件、请求,以及发送过来的数据进行解析,又要正确的回复I2C主机,所以它的实现方式比I2C模拟主机完全不同。这需要开发者对I2C时序十分熟悉,所以在研读下面软件模拟I2C从机通讯程序时,建议对照I2C时序一点点分析(提示:这部分内容有点难度)。

对于软件模拟I2C从机通讯的实现是通过两个GPIO端口引脚分别与I2C主机的SCL和SDA进行连接,程序中将这两个GPIO端口引脚配置成外部中断EXTI工作模式,通过捕获GPIO端口引脚的上升沿、下降沿,以及高低电平状态,配合软件模拟I2C从机的状态管理,实现与I2C主机之间的通讯功能,在如下的程序中添加了详细的注释和说明,方便大家阅读和理解,具体的代码实现如下:


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);}

/******************************************************************************* * [url=home.php?mod=space&uid=247401]@brief[/url]       配置模拟I2C的GPIO端口, 默认设置成输入模式, 并使能相应的外部触发 *              中断功能(上升沿和下降沿) * @param        * @retval       * [url=home.php?mod=space&uid=93590]@Attention[/url]   *******************************************************************************/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);}

/******************************************************************************* * [url=home.php?mod=space&uid=247401]@brief[/url]       设置SDA信号线的输入输出方便, 0代表Output输出, 1代表Input输入 * @param        * @retval       * [url=home.php?mod=space&uid=93590]@Attention[/url]   *******************************************************************************/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       设置SDA信号线的输出电平(高电平 / 低电平) * @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       当SCL触发上升沿外部中断时的处理 * @param        * @retval       * @attention   ******************************************************************************/void sI2C_SLAVE_SCL_RiseHandler(sI2C_SLAVE_TypeDef *sI2C){    /* SCL为上升沿, 数据锁定, 主从机从SDA总线上获取数据位 */    switch(sI2C_SLAVE_State)    {        case sI2C_SLAVE_STATE_ADD:
            /* I2C发送遵义MSB, 先发送高位, 再发送低位, 所以在接收的时候, 数据进行左移 */
            sI2C_SLAVE_SlaveAddress <<= 1;            sI2C_SLAVE_ShiftCounter  += 1;
            if(sI2C_SLAVE_READ_SDA(sI2C) ==  Bit_SET)            {                sI2C_SLAVE_SlaveAddress |= 0x01;            }
            /* 当接收到8位地址位后, 从机需要在第9个时钟给出ACK应答, 等待SCL下降沿的时候给出ACK信号 */            if(sI2C_SLAVE_ShiftCounter == 8)            {                sI2C_SLAVE_State = sI2C_SLAVE_STATE_ADD_ACK;            }            break;
        case sI2C_SLAVE_STATE_ADD_ACK:            /* 从机地址的ACK回复后, 切换到收发数据状态 */            sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;
            sI2C_SLAVE_ShiftCounter = 0;    /* 数据移位计数器清零 */            sI2C_SLAVE_ReceivedData = 0;    /* sI2C_SLAVE的接收数据清零 */            break;
        case sI2C_SLAVE_STATE_DAT:            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* 主机写操作:此时从机应该获取主机发送的SDA信号线电平状态, 进行位存储 */                sI2C_SLAVE_ReceivedData <<= 1;                sI2C_SLAVE_ShiftCounter  += 1;
                if(sI2C_SLAVE_READ_SDA(sI2C) == Bit_SET)                {                    sI2C_SLAVE_ReceivedData |= 0x01;                }
                /* 当收到一个完整的8位数据时, 将收到的数据存放到I2C接收消息队列中, 状态转换到给主机发送ACK应答 */                if(sI2C_SLAVE_ShiftCounter == 8)                {                    QUEUE_WRITE(QUEUE_SI2C_SLAVE_IDX, sI2C_SLAVE_ReceivedData);
                    sI2C_SLAVE_ShiftCounter = 0;    /* 数据移位计数器清零 */                    sI2C_SLAVE_ReceivedData = 0;    /* sI2C_SLAVE的接收数据清零 */
                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT_ACK;                }            }            else            {                /* 主机读操作:在SCL上升沿的时候, 主机获取当前SDA的状态位, 如果到了第8个数位的上升沿,                 * 那接下来就是主机回复从机的应答或非应答信号了, 所以将状态切换到等待ACK的状态, 同时准备下一个需要发送的数据                 */                if(sI2C_SLAVE_ShiftCounter == 8)                {                    sI2C_SLAVE_ShiftCounter = 0;    /* 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)            {                /* 主机写操作:从机发送ACK, 等待主机读取从机发送的ACK信号 */
                sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;  /* 状态切换到数据接收状态 */            }            else            {                /* 主机读操作:主机发送ACK, 从机可以读取主机发送的ACK信号 */
                uint8_t ack = sI2C_SLAVE_READ_SDA(sI2C);
                if(ack == Bit_RESET)                {                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;    /* 接收到 ACK, 继续发送数据 */                }                else                {                    sI2C_SLAVE_State = sI2C_SLAVE_STATE_STO;    /* 接收到NACK, 停止发送数据 */                }            }            break;
        default:            break;    }}

/****************************************************************************** * @brief       当SCL触发下降沿外部中断时的处理 * @param        * @retval       * @attention   ******************************************************************************/void sI2C_SLAVE_SCL_FallHandler(sI2C_SLAVE_TypeDef *sI2C){    /* SCL为下降沿, 数据可变 */    switch(sI2C_SLAVE_State)    {        case sI2C_SLAVE_STATE_STA:            /*             * 检测到START信号后, SCL第一个下降沿表示开始传输Slave Address,             * 根据数据有效性的规则, 地址的第一位需要等到SCL变为高电平时才可以读取             * 切换到获取Slave Address的状态, 等待SCL的上升沿触发             */            sI2C_SLAVE_State = sI2C_SLAVE_STATE_ADD;
            sI2C_SLAVE_ShiftCounter = 0;    /* 数据移位计数器清零 */            sI2C_SLAVE_SlaveAddress = 0;    /* sI2C_SLAVE的从机地址清零 */            sI2C_SLAVE_ReceivedData = 0;    /* sI2C_SLAVE的接收数据清零 */            break;
        case sI2C_SLAVE_STATE_ADD:            /*             * 在主机发送Slave Address的时候, 从机只是读取SDA状态, 进行地址解析, 所以这边没有处理             */            break;
        case sI2C_SLAVE_STATE_ADD_ACK:
            /* SCL低电平的时候, 给I2C总线发送地址的应答信号, 状态不发生改变, 等待下一个上升沿将ACK发送出去 */
            sI2C_SLAVE_SDA_SetLevel(sI2C, 0);   /* 将SDA信号拉低, 向主机发送ACK信号 */            break;
        case sI2C_SLAVE_STATE_DAT:
            /* 在SCL时钟信号的下降沿, SDA信号线处理可变的状态 */
            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* 主机写操作:将SDA信号线设置成获取状态, 等待下一个SCL上升沿时获取数据位 */                sI2C_SLAVE_SDA_SetDirection(sI2C, 1);            }            else            {                /* 主机读操作:根据发送的数据位设置SDA信号线的输出电平, 等待下一个SCL上升沿时发送数据位 */                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:
            /* 在第8个SCL时钟信号下降沿的处理 */
            if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00)            {                /* 主机写操作:从机在接收到数据后, 需要给主机一个ACK应答信号, 状态不发生改变, 等待下一个上升沿将ACK发送出去 */
                sI2C_SLAVE_SDA_SetLevel(sI2C, 0);   /* 将SDA信号拉低, 向主机发送ACK信号 */            }            else            {                /* 主机读操作:从机需要释放当前的SDA信号线, 以便主机发送ACK或NACK给从机, 状态不发生改变, 等待下一个上升沿读取ACK信号 */                sI2C_SLAVE_SDA_SetDirection(sI2C, 1);            }            break;
        default:            break;    }}

/**  * @brief  当SDA触发上升沿外部中断时的处理  * @param  None  * @retval None  */void sI2C_SLAVE_SDA_RiseHandler(sI2C_SLAVE_TypeDef *sI2C){    if(sI2C_SLAVE_READ_SCL(sI2C) == Bit_SET)    /* SCL为高时,SDA为上升沿:STOP */    {        sI2C_SLAVE_State = sI2C_SLAVE_STATE_STO;    }    else                                        /* SCL为低时,SDA为上升沿:数据的变化 */    {    }}

/**  * @brief  当SDA触发下降沿外部中断时的处理  * @param  None  * @retval None  */void sI2C_SLAVE_SDA_FallHandler(sI2C_SLAVE_TypeDef *sI2C){    if(sI2C_SLAVE_READ_SCL(sI2C) == Bit_SET)    /* SCL为高时,SDA为下降沿:START */    {        sI2C_SLAVE_State = sI2C_SLAVE_STATE_STA;    }    else                                        /* SCL为低时,SDA为下降沿:数据的变化 */    {    }}

/******************************************************************************* * @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);    }}
实测结果如下所示:

1f3209e0-3d61-11ed-9e49-dac502259ad0.png

以上就是基于MM32生态实现I2C接口通讯的几种方式了,如果有需要查看原图、软件工程源代码、I2C相关资料的小伙伴,请点击底部“阅读原文”进行下载。
审核编辑 :李倩
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 通讯
    +关注

    关注

    9

    文章

    891

    浏览量

    34820
  • I2C
    I2C
    +关注

    关注

    28

    文章

    1478

    浏览量

    123172
  • GPIO
    +关注

    关注

    16

    文章

    1191

    浏览量

    51869
  • MM32
    +关注

    关注

    1

    文章

    106

    浏览量

    740
收藏 人收藏

    评论

    相关推荐

    RISC V的I2C操作

     双向IO,需设置为弱上拉。I2C对应的三门的OE信号:需要赋值为I2C SDA/SCL的write信号;rtl中的表述如下:主要原因在于:i2c的通信过程中,当总线空闲时,两根线均
    的头像 发表于 11-01 11:06 104次阅读

    物联网嵌入式软件中的I2C总线设计详解

    基本概念、特点、通信协议,以及在不同场景下的高级应用和最佳实践。I2C接口只有2根信号线,总线上可以连接多个设备,硬件实现简单,可扩展性强。I2C
    的头像 发表于 09-29 16:21 250次阅读
    物联网嵌入式软件中的<b class='flag-5'>I2C</b>总线设计详解

    I2C基本指南

    电子发烧友网站提供《I2C基本指南.pdf》资料免费下载
    发表于 09-10 09:40 0次下载
    <b class='flag-5'>I2C</b>基本指南

    I2C通讯为什么要用开漏输出和上拉电阻?

    每个设备都可以将线拉低(Ground),但不能将线拉高(Vcc)。这种设计使得多个设备可以共享同一条总线,以进行通信。二、I2C接口接外部上拉电阻的原因I2CI
    的头像 发表于 05-16 08:10 5704次阅读
    <b class='flag-5'>I2C</b><b class='flag-5'>通讯</b>为什么要用开漏输出和上拉电阻?

    什么是I2C协议 I2C总线的控制逻辑

    在实际使用过程中,I2C比较容易出现的一个问题就是死锁 ,死锁在I2C中主要表现为:I2C死锁时表现为SCL为高,SDA一直为低。
    发表于 03-12 09:17 914次阅读
    什么是<b class='flag-5'>I2C</b>协议 <b class='flag-5'>I2C</b>总线的控制逻辑

    i2c接口由哪几根线组成 i2c接口可以接哪些器件

    I2C接口的线路可以适应不同的系统需求和器件规格,因此具体的线路配置可能会有所不同。但通常来说,时钟线(SCL)和数据线(SDA)是I2C接口的核心线路。
    的头像 发表于 03-05 16:55 3613次阅读
    <b class='flag-5'>i2c</b><b class='flag-5'>接口</b>由哪几根线组成 <b class='flag-5'>i2c</b><b class='flag-5'>接口</b>可以接哪些器件

    I2C接口称重采集单元

    本周带来的是一款称重采集变送器单元WeightI2CUnit。该传感器内部采用”STM32+HX711芯片”的方案,通过I2C通讯方式实现2
    的头像 发表于 02-24 08:28 368次阅读
    <b class='flag-5'>I2C</b><b class='flag-5'>接口</b>称重采集单元

    什么是I2C接口?什么是I3C接口I2CI3C的区别

    I2C接口是一种串行通信协议,全称为Inter-Integrated Circuit,即集成电路间总线。它是一种双向、两线制(SCL和SDA)的串行通信接口,主要用于连接微控制器和其他外围设备,如传感器、存储器、显示器等。
    的头像 发表于 02-17 14:39 5343次阅读
    什么是<b class='flag-5'>I2C</b><b class='flag-5'>接口</b>?什么是<b class='flag-5'>I3C</b><b class='flag-5'>接口</b>?<b class='flag-5'>I2C</b>与<b class='flag-5'>I3C</b>的区别

    蓝牙串口通讯总线——I2C/SPI/UART

    嵌入式工程师在做串口通信调试工作的时候,会经常用到I2C、SPI、UART这3条总线,I2C、SPI、UART这三种通信总线在嵌入式领域很常见,目前主流的SOC芯片都内置了这三种总线的控制器,各种
    的头像 发表于 01-22 09:41 1426次阅读
    蓝牙串口<b class='flag-5'>通讯</b>总线——<b class='flag-5'>I2C</b>/SPI/UART

    XV4001BC车载用SPI/I2C输出接口的数字输出

    XV4001BC车载用SPI/I2C输出接口的数字输出,可实现角速度输出和温度传感器输出,满足汽车导航和通讯系统的要求。SPI / I2C
    发表于 01-02 16:33 0次下载

    最简单的总线通讯I2C通讯总线的原理原来这么好懂

    通讯I2C
    安泰仪器维修
    发布于 :2024年01月02日 11:16:31

    ADXL345运行时间长了i2c通讯不上怎么解决?

    stm32单片机通过i2c接口实时读取adxl345的三轴加速度数据,发现刚上电的时候正常工作。运行一段时间,大概几小时,i2c通讯失败。 单片机手动复位后也不能
    发表于 01-01 08:20

    i2c接口和spi接口区别

    I2C(Inter-Integrated Circuit)和SPI(Serial Peripheral Interface)都是用于设备间通信的串行接口标准。它们在很多方面有着相似之处,但也有一些
    的头像 发表于 12-25 15:02 3713次阅读

    I2C地址跳变问题的调试案例

    引言:I2C作为使用最为广泛的通讯接口,调试各类I2C器件,大家应该都很轻车熟路。一般对于外挂电阻配置器件的I2C地址,例如电阻上拉之后,器
    的头像 发表于 11-22 10:51 1526次阅读
    <b class='flag-5'>I2C</b>地址跳变问题的调试案例

    I2C总线信号与测试案例(一)

    I2C bus是Inter-IC bus的缩写,意思是IC器件之间的通讯总线;I2C 总线的特点如下
    的头像 发表于 11-20 15:45 1845次阅读
    <b class='flag-5'>I2C</b>总线信号与测试案例(一)