找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4433|回复: 1
收起左侧

[原创]STM32硬件I2C和软件模拟I2C实现对比

[复制链接]
ID:946578 发表于 2021-6-29 12:09 | 显示全部楼层 |阅读模式
一、  概述
    I2C是一种占用管脚少、传送可靠和传输速率较好的低速总线,在嵌入式设备中被广泛使用到。I2C 接口接收和发送数据,并将数据从串行转换成并,或并转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I2C总线。允许连接到标准(高至100 kHz)或快速(高至400 kHz) I2C总线。
    STM32一般有2-3组硬件I2C,以STM32F103CB为例,其硬件I2C情况如下——
  
I2C编号
  
I2C名称
对应管脚
1
I2C1
PB6, PB7
2
I2C2
PB10, PB11
所谓硬件I2C,即STM32芯片设计了SDA/SCL两个管脚内部已符合触发I2C的时序、协议、仲裁和定时等功能块,软件开发人员只需调用简单的API即可方便使用I2C功能了。
    此外,由于硬件设计的原因,需要使用很多个I2C或不巧硬件I2C管脚被占用,此时就需要通过GPIO软件模拟I2C,它是通过软件来实现I2C的时序、协议、仲裁和定时等完整的机制。
    原理和概念部分不再赘述,大家很容易在网上找到各类相关资料进行了解和学习,今天我们详细陈述具体实现过程和比较两者的一些差异,主要以代码作为表述形式。
二、   硬件I2C的实现
       硬件I2C调用的api接口均在stm32f10x_i2c.c文件中,通过这些API,我们需要实现
(1)  I2C的初始化;
(2)  I2C读和写的接口函数,供外部调用。
初始化代码如下——
  
#define  CPS1848_I2C                    I2C2
  
  
#define  CHECK_BUSY(checkflag,busyendflag,dwCnt, dwTimeOut) \
  
    for (dwCnt =0; dwCnt< dwTimeOut;  dwCnt++)  \
  
    {   \
  
        if((checkflag) == (busyendflag))\
  
        {\
  
            break;\
  
        }\
  
    }
  
  
#define  CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut)    \
  
    if (dwBusyCount >= dwTimeOut)   \
  
    {    \
  
        return ERR_WAIT_BUSY;   \
  
}
  
  
  
//  I2C 外设(CPS1848)初始化
  
void  I2C_CPS1848_Init(BYTE ucBoardAddr)
  
{
  
    //GPIO初始化
  
    GPIO_InitTypeDef  GPIO_InitStructure;
  
    I2C_InitTypeDef  I2C_InitStructure;
  
  
    /* 使能与 I2Cx 引脚的GPIO的时钟 */
  
     RCC_APB2PeriphClockCmd(CPS1848_I2C_SCL_GPIO_CLK |  CPS1848_I2C_SDA_GPIO_CLK, ENABLE);
  
    /* 使能 I2Cx 外设时钟 */
  
    RCC_APB1PeriphClockCmd(CPS1848_I2C_CLK,  ENABLE);
  
  
    /* Reset I2Cx IP */
  
    RCC_APB1PeriphResetCmd(CPS1848_I2C_CLK,  ENABLE);
  
  
    /* Release reset signal of I2Cx IP */
  
    RCC_APB1PeriphResetCmd(CPS1848_I2C_CLK,  DISABLE);
  
  
    /* I2Cx_SCL */
  
    GPIO_InitStructure.GPIO_Pin   = CPS1848_I2C_SCL_PIN;
  
    GPIO_InitStructure.GPIO_Speed =  GPIO_Speed_50MHz;
  
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;
  
    GPIO_Init(CPS1848_I2C_SCL_PORT,  &GPIO_InitStructure);
  
  
    /* I2Cx_SDA*/
  
    GPIO_InitStructure.GPIO_Pin   = CPS1848_I2C_SDA_PIN;
  
    GPIO_InitStructure.GPIO_Speed =  GPIO_Speed_50MHz;
  
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;
  
    GPIO_Init(CPS1848_I2C_SDA_PORT,  &GPIO_InitStructure);
  
  
    //模式初始化
  
    /* I2Cx 配置 */
  
    I2C_InitStructure.I2C_Mode =  I2C_Mode_SMBusHost/*I2C_Mode_I2C */;
  
    /* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
  
    I2C_InitStructure.I2C_DutyCycle =  I2C_DutyCycle_2;
  
    I2C_InitStructure.I2C_OwnAddress1 =  (ucBoardAddr << 1);
  
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable  ;
  
    /* I2Cx的寻址模式 */
  
    I2C_InitStructure.I2C_AcknowledgedAddress  = I2C_AcknowledgedAddress_7bit;
  
    /* 通信速率 */
  
    I2C_InitStructure.I2C_ClockSpeed =  100000;  //Standard-mode devices at 100  Kbps
  
  
    /* I2Cx 初始化 */
  
    I2C_Init(CPS1848_I2C, &I2C_InitStructure);
  
  
    /* 使能 I2Cx 外设*/
  
    I2C_Cmd(CPS1848_I2C, ENABLE);
  
  
    /*使能或者失能指定的I2C应答功能*/
  
    I2C_AcknowledgeConfig(CPS1848_I2C,  ENABLE);
  
}
  
       I2C的读操作实现如下——
  
BYTE  I2C_CPS1848_Read(uint8_t cps1848_index, uint32_t RegAddr, uint32_t *pdwData)
  
{
  
    volatile uint32_t i2cdata = 0;
  
    uint16_t NumByteToRead = 4;
  
  
    DWORD dwBusyCount = 0;
  
    const DWORD dwTimeOut = 1000;
  
  
    CHECK_POINTER_RET_ERR(pdwData);
  
  
    /* 等待busy */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_GetFlagStatus(CPS1848_I2C,  I2C_FLAG_BUSY), RESET, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* Enable Acknowledgement to be ready for  another reception
  
       necessary...*/
  
    I2C_AcknowledgeConfig(CPS1848_I2C,  ENABLE);
  
  
    /* 发送开始信号 */
  
    I2C_GenerateSTART(CPS1848_I2C, ENABLE);
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_MODE_SELECT), SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* 发送目标地址 : Reset the address bit0 for  write*/
  
    I2C_Send7bitAddress(CPS1848_I2C,  ((CPS1848_I2C_ADDR+cps1848_index) << 1), I2C_Direction_Transmitter);
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
  
    /* 发送寄存器地址--8bit */
  
    I2C_SendData(CPS1848_I2C, ((RegAddr>>18)  & 0xff));
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED), SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
    /* 发送寄存器地址--8bit */
  
    I2C_SendData(CPS1848_I2C,  ((RegAddr>>10) & 0xff));
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED), SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
    /* 发送寄存器地址--8bit */
  
     I2C_SendData(CPS1848_I2C, ((RegAddr>>2) & 0xff));
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED), SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
  
    /* 开始读取数据 */
  
    I2C_GenerateSTART(CPS1848_I2C, ENABLE);
  
  
    /* Test on EV5 and clear it */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_MODE_SELECT), SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* 发送读取的地址: Set the address bit0 for read*/
  
    I2C_Send7bitAddress(CPS1848_I2C,  ((CPS1848_I2C_ADDR+cps1848_index) << 1) | 0x01,  I2C_Direction_Receiver);
  
  
    /* Test on EV6 and clear it */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED), SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    while(NumByteToRead--)
  
    {
  
        if (0 == NumByteToRead)
  
        {
  
            /* 最后一个数据了,发送非应答信号(Disable Acknowledgement),结束传输;*/
  
             I2C_AcknowledgeConfig(CPS1848_I2C, DISABLE);
  
            /* 发送停止位 */
  
            I2C_GenerateSTOP(CPS1848_I2C,  ENABLE);
  
        }
  
  
        /* Test on EV7 and clear it */
  
        dwBusyCount = 0;
  
         CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_RECEIVED), SUCCESS, dwBusyCount, dwTimeOut);
  
        CHECK_WAIT_BUSY(dwBusyCount,  dwTimeOut);
  
        i2cdata = (i2cdata<<8) |  (I2C_ReceiveData(CPS1848_I2C));
  
    }
  
  
    *pdwData = i2cdata;
  
  
    /* Enable Acknowledgement to be ready for  another reception */
  
    I2C_AcknowledgeConfig(CPS1848_I2C,  ENABLE);
  
  
    return ERR_SUCCESS;
  
}
  
       I2C的写操作实现如下——
  
BYTE  I2C_CPS1848_Write(uint8_t cps1848_index, uint32_t RegAddr, uint32_t RegData)
  
{
  
    DWORD dwBusyCount = 0;
  
    const DWORD dwTimeOut = 10000;
  
  
    /* 等待busy */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_GetFlagStatus(CPS1848_I2C,  I2C_FLAG_BUSY),
  
                         RESET, dwBusyCount,  dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
  
    /* 发送开始信号 */
  
    I2C_GenerateSTART(CPS1848_I2C, ENABLE);
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_MODE_SELECT),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
  
    /* 发送目标地址 */
  
    I2C_Send7bitAddress(CPS1848_I2C,  ((CPS1848_I2C_ADDR+cps1848_index) << 1), I2C_Direction_Transmitter);
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
  
    /* 发送寄存器地址--8bit */
  
    I2C_SendData(CPS1848_I2C,  ((RegAddr>>18) & 0xff));
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
    /* 发送寄存器地址--8bit */
  
    I2C_SendData(CPS1848_I2C,  ((RegAddr>>10) & 0xff));
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
    /* 发送寄存器地址--8bit */
  
    I2C_SendData(CPS1848_I2C,  ((RegAddr>>2) & 0xff));
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* 发送数据 */
  
    I2C_SendData(CPS1848_I2C,  ((RegData>>24) & 0xff));
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* 发送数据 */
  
    I2C_SendData(CPS1848_I2C,  ((RegData>>16) & 0xff));
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS, dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* 发送数据 */
  
    I2C_SendData(CPS1848_I2C,  ((RegData>>8) & 0xff));
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
    /* 发送数据 */
  
    I2C_SendData(CPS1848_I2C, ((RegData)  & 0xff));
  
  
    /* 等待握手 */
  
    dwBusyCount = 0;
  
    CHECK_BUSY(I2C_CheckEvent(CPS1848_I2C,  I2C_EVENT_MASTER_BYTE_TRANSMITTED),
  
                         SUCCESS,  dwBusyCount, dwTimeOut);
  
    CHECK_WAIT_BUSY(dwBusyCount, dwTimeOut);
  
  
  
    /* 发送停止位 */
  
    I2C_GenerateSTOP(CPS1848_I2C, ENABLE);
  
  
    return ERR_SUCCESS;
  
}
  
  


三、  软件模拟I2C的实现
       GPIO软件模拟I2C的实现中,首先是初始化;其次,由于没有现存的I2C时序、协议、控制等API接口,这些就需要我们根据I2C协议进行严格实现,具体详见附件。

四、  两者的差异
       硬件I2C实现很简洁,且流程中延时、应答等机制都无需软件参与,只需做好正确配置和调用即可;软件模拟I2C的实现需要非常完备,且需要严格按照I2C协议进行编写,严格控制延时、应答、判定等。
       了解两者之间的差异,并且能够实现两种I2C的使用,会极大加深我们对于I2C的理解和熟练程度,非常有益。
51hei.png

C文件51hei下载: gpio_i2c_bsp.rar (1.31 KB, 下载次数: 45)

评分

参与人数 1黑币 +90 收起 理由
admin + 90 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:946578 发表于 2021-6-29 12:13 | 显示全部楼层
由于篇幅缘故,没有将所有代码贴在文中,而放在附件中了。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表