[CW32F030系列] CW32F030C8T6: I2C功能详解与应用案例

[复制链接]
guanjiaer 发表于 2025-8-12 16:39 | 显示全部楼层 |阅读模式
一.CW32F030C8T6的I2C功能
CW32x030 内部集成 2 个 I2C 控制器,能按照设定的传输速率(标准,快速,高速)将需要发送的数据按照 I2C 规范串行发送到 I2C 总线上,并对通信过程中的状态进行检测,另外还支持多主机通信中的总线冲突和仲裁处理。
1.1主要特性

支持主机发送 / 接收,从机发送 / 接收四种工作模式
支持时钟延展 ( 时钟同步 ) 和多主机通信冲突仲裁
支持标准 (100Kbps)/ 快速 (400Kbps)/ 高速 (1Mbps) 三种工作速率
支持 7bit 寻址功能
支持 3 个从机地址
支持广播地址
支持输入信号噪声过滤功能
支持中断状态查询功能
1.2协议描述

I2C 总线使用两根信号线(数据线 SDA 和时钟线 SCL)在设备间传输数据。SCL 为单向时钟线,固定由主机驱动。 SDA 为双向数据线,在数据传输过程中由收发两端分时驱动。
I2C 总线上可以连接多个设备,所有设备在没有进行数据传输时都处于空闲状态(未寻址从机接收模式),任一 设备都可以作为主机发送 START 起始信号来开始数据传输,在 STOP 停止信号出现在总线上之前,总线一直处于 被占用状态。
I2C 通信采用主从结构,并由主机发起和结束通信。主机通过发送 START 起始信号来发起通信,之后发送 SLA+W/R 共 8bit 数据(其中,SLA 为 7bit 从机地址,W/R 为读写位),并在第 9 个 SCL 时钟释放 SDA 总线, 对应的从机在第 9 个 SCL 时钟占用 SDA 总线并输出 ACK 应答信号,完成从机寻址。此后根据主机发送的第 1 字 节的 W/R 位来决定数据通信的发端和收端,发端每发送 1 个字节数据,收端必须回应 1 个 ACK 应答信号。数据 传输完成后,主机发送 STOP 信号结束本次通信。
1.3协议帧格式

标准 I2C 传输协议帧包含四个部分:起始信号 (START) 或重复起始信号 (Repeated START) 信号,从机地址及读 写位,数据传输,停止信号 (STOP)。如下图所示。

1.png


1.4起始信号 (START)

当总线处于空闲状态时(SCL 和 SDA 线同时为高),SDA 线上出现由高到低的下降沿信号,则被定义为起始 信号。主机向总线发出起始信号后开始数据传输,并占用总线。
1.5重复起始信号 (Repeated START)

当一个起始信号后未出现停止信号之前,出现了新的起始信号,新的起始信号被定义为重复起始信号。在主 机发送停止信号前,SDA 总线一直处于占用状态,其它主机无法占用总线。
1.6停止信号 (STOP)

当 SCL 线为高时,SDA 线上出现由低到高的上升沿信号,则被定义为停止信号。主机向总线发出停止信号以 结束数据传输,并释放总线。

2.png


1.7从机地址及读写位

当起始信号产生后,主机开始传输第 1 字节数据:7 bit 从机地址 + 读写位。读写位(1:读;0:写)控制总 线上数据传输方向。被寻址的从机在第 9 个 SCL 时钟周期内占用 SDA 总线,并将 SDA 置为低电平作为 ACK 应答。
1.8数据传输

主机在 SCL 线上输出串行时钟信号,主从机通过 SDA 线进行数据传输。数据传输过程中,1 个 SCL 时钟脉 冲传输 1 个数据位(最高有效位 MSB 在前),且 SDA 线上的数据只在 SCL 为低时改变,每传输 1 个字节跟 随 1 个应答位。如下图所示:

3.png


1.9传输应答

在总线上传输数据时,发端每传输完 1 个字节数据,在第 9 个 SCL 时钟周期发端放弃对 SDA 的控制,收端须在 第 9 个 SCL 时钟周期回复 1 个应答位:接收成功,发送 ACK 应答,接收异常发送 NACK 应答。

4.png


1.10冲突检测与仲裁

在多主机通信系统中,总线上的每个节点都有从机地址。每个节点可以作为从节点被其它节点访问,也可以作为 主节点向其它的节点发送控制字节和传送数据。如果有两个或两个以上的节点同时向总线发出起始信号并开始传 输数据,就会造成总线冲突。I2C 控制器内置一个仲裁器,可对 I2C 总线冲突进行检测和仲裁,以保证数据通信 的可靠性和完整性。
1.11冲突检测原理

在物理实现上, SDA 和 SCL 引脚电路结构相同,引脚的输出驱动与输入缓冲连在一起。输出结构为漏极开 路的场效应管、输入结构为高输入阻抗的同相器。基于该结构: 1. 由于 SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑; 2. 设备向总线写数据的同时读取数据,可用来检测总线冲突,实现 “时钟同步”和“总线仲裁”。 根据“线与”逻辑,如果 2 个主机同时发送逻辑 1 或逻辑 0,则 2 个主机都检测不到冲突,需要等到下一位 数据发送再继续检测冲突;如果 2 个主机一个发送逻辑 1,一个发送逻辑 0,此时总线上为逻辑 0,发送逻 辑 1 的主机检测到冲突,发送逻辑 0 的主机没有检测到冲突。
1.12冲突仲裁原理

当主机检测到总线冲突后,该主机丢失仲裁,退出主机发送模式,进入未寻址从机模式,释放 SDA 数据线, 并回到地址侦测状态,之后根据接收到的 SLA+W/R 进入相应的从机模式 (SLA 地址匹配进入已寻址从机模式, SLA 地址不匹配则进入未寻址从机模式 )。仲裁失败的主机,仍会发送 SCL 串行时钟,直到当前字节传输结束。 当主机没有检测到总线冲突,该主机赢得仲裁,继续主导本次数据传输,直到通信完成。

5.png


1.13功能框图

6.png


7.png


1.14串行时钟发生器

8.png


1.15仲裁和同步逻辑

9.png


10.png


11.png

1.16工作模式


12.png


13.png


14.png


15.png


16.png


17.png


18.png


19.png


20.png


21.png


1.17多主机通讯

22.png


23.png


24.png


25.png


26.png


27.png


28.png


29.png


二.CW32F030C8T6的I2C案例——通过I2C中断方式读取eeprom
/* 定义测试使用的I2C模块,I2C1=1,I2C2=2 */
#define TESTI2C 2   

/* I2C1引脚配置 */
#define I2C1_SCL_GPIO_PORT CW_GPIOB  // I2C1 SCL引脚所在GPIO端口
#define I2C1_SCL_GPIO_PIN GPIO_PIN_10 // I2C1 SCL引脚编号
#define I2C1_SDA_GPIO_PORT CW_GPIOB  // I2C1 SDA引脚所在GPIO端口
#define I2C1_SDA_GPIO_PIN GPIO_PIN_11 // I2C1 SDA引脚编号

/* I2C2引脚配置 */  
#define I2C2_SCL_GPIO_PORT CW_GPIOB  // I2C2 SCL引脚所在GPIO端口
#define I2C2_SCL_GPIO_PIN GPIO_PIN_0  // I2C2 SCL引脚编号
#define I2C2_SDA_GPIO_PORT CW_GPIOB  // I2C2 SDA引脚所在GPIO端口
#define I2C2_SDA_GPIO_PIN GPIO_PIN_1  // I2C2 SDA引脚编号

/* EEPROM相关参数 */
uint8_t u8Addr = 0x00;        // EEPROM内部访问地址
#define WRITELEN 8           // 写入数据长度
#define READLEN 8            // 读取数据长度  
#define WriteReadCycle 35    // 写读循环次数

/* 数据缓冲区 */
uint8_t u8Senddata[8] = {0x66, 0x02, 0x03, 0x04, 0x05, 0x60, 0x70, 0x20}; // 写入数据1
uint8_t u8Senddata2[8] = {0x55, 0xAA, 0xAA, 0x55, 0x55, 0xAA, 0x55, 0xAA}; // 写入数据2
uint8_t u8Recdata[16] = {0x00}; // 接收数据缓冲区
uint8_t u8SendLen = 0;        // 已发送数据长度
uint8_t u8RecvLen = 0;        // 已接收数据长度

/* 状态标志 */
uint8_t Send** = 0;         // 发送标志:0=写,1=读
uint8_t Comm_** = 0;        // 通信完成标志
uint8_t u8recv** = 0;       // 接收完成标志
uint8_t u8State = 0;         // I2C状态机状态
uint8_t receivedflag = 0;    // 数据接收完成标志

/**
* @brief I2C1 EEPROM读写中断处理函数
* @NOTE 根据I2C状态机处理不同阶段的通信流程
*/
void I2c1EepromReadWriteInterruptFunction(void)
{
    u8State = I2C_GetState(CW_I2C1); // 获取当前I2C状态

    switch (u8State)
    {
        case 0x08:     // START信号已发送
            I2C_GenerateSTART(CW_I2C1, DISABLE); // 关闭START生成
            I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS, 0X00); // 发送从机地址+写命令
            break;

        case 0x10:     // 重复START信号已发送
            I2C_GenerateSTART(CW_I2C1, DISABLE); // 关闭START生成
            if (0 == Send**) // 写模式
                I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS, 0X00);
            else              // 读模式
                I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS, 0X01);
            break;

        case 0x18:    // SLA+W/R已发送
            I2C_GenerateSTART(CW_I2C1, DISABLE); // 关闭START生成
            I2C_SendData(CW_I2C1, u8Addr);      // 发送EEPROM内部地址
            break;

        case 0x20:    // SLA+W后从机返回NACK
        case 0x38:    // 丢失仲裁
        case 0x30:    // 发送数据后从机返回NACK  
        case 0x48:    // SLA+R后从机返回NACK
            I2C_GenerateSTOP(CW_I2C1, ENABLE);  // 生成STOP信号
            I2C_GenerateSTART(CW_I2C1, ENABLE); // 重新开始传输
            break;

        case 0x58:    // 接收完最后一个数据字节
            u8Recdata[u8RecvLen++] = I2C_ReceiveData(CW_I2C1); // 保存接收数据
            receivedflag = 1;                   // 设置接收完成标志
            I2C_GenerateSTOP(CW_I2C1, ENABLE);  // 生成STOP信号
            break;

        case 0x28:    // 数据字节已发送
            if (0 == Send**) // 写模式
            {
                if (u8SendLen < WRITELEN) // 还有数据要发送
                    I2C_SendData(CW_I2C1, u8Senddata[u8SendLen++]);
                else // 写操作完成
                {
                    u8SendLen = 0;
                    Comm_** = 1;       // 设置通信完成标志
                    Send** = 1;        // 切换为读模式
                    I2C_GenerateSTOP(CW_I2C1, ENABLE); // 生成STOP信号
                }
            }
            else // 读模式:发送完地址后需重新START
            {
                CW_I2C1->CR_f.STA = 1;  // 手动设置START位
                I2C_GenerateSTOP(CW_I2C1, DISABLE); // 禁用STOP
            }
            break;

        case 0x40:     // SLA+R已发送,开始接收数据
            u8RecvLen = 0; // 重置接收计数器
            if (READLEN > 1) // 多字节读取时启用ACK
                I2C_AcknowledgeConfig(CW_I2C1, ENABLE);
            break;

        case 0x50:     // 接收到数据字节
            u8Recdata[u8RecvLen++] = I2C_ReceiveData(CW_I2C1); // 保存数据
            if (u8RecvLen == READLEN - 1) // 倒数第二个字节
                I2C_AcknowledgeConfig(CW_I2C1, DISABLE); // 最后一个字节发送NACK
            break;
    }
    I2C_ClearIrq(CW_I2C1); // 清除中断标志
}



int32_t main(void)
{
    I2C_InitTypeDef I2C_InitStruct;  // I2C初始化结构体
    uint16_t tempcnt = 0 ;           // 循环计数器

    //硬件初始化部分
    RCC_Configuration();            // 初始化系统时钟
    GPIO_Configuration();           // 初始化GPIO引脚
    I2C_InitStruct.I2C_Baud = 0x01; // 设置I2C时钟频率(500KHz)
    I2C_InitStruct.I2C_BaudEn = ENABLE;
    I2C_InitStruct.I2C_FLT = DISABLE;
    I2C_InitStruct.I2C_AA =  DISABLE;

// 根据TESTI2C宏选择初始化I2C1或I2C2
#if(0x01 == TESTI2C)
    I2C1_DeInit();
    I2C_Master_Init(CW_I2C1, &I2C_InitStruct);
#elif(0x02 == TESTI2C)
    I2C2_DeInit();
    I2C_Master_Init(CW_I2C2, &I2C_InitStruct);
#endif

    NVIC_Configuration();           // 配置中断控制器

// 使能对应I2C模块
#if(0x01 == TESTI2C)
    I2C_Cmd(CW_I2C1, ENABLE);
#elif(0x02 == TESTI2C)
    I2C_Cmd(CW_I2C2, ENABLE);
#endif

    // 初始化发送数据缓冲区
    tempcnt = 0;
    for (uint8_t i = 0; i < 8; i++)
    {
        u8Senddata = i;
    }

    // 主循环实现I2C读写测试
    while (1)
    {
        // 发送START信号
#if(0x01 == TESTI2C)
        I2C_GenerateSTART(CW_I2C1, ENABLE);
#elif(0x02 == TESTI2C)
        I2C_GenerateSTART(CW_I2C2, ENABLE);
#endif

        while (1)
        {
            // 等待发送完成
            while (!Comm_**) ;
            FirmwareDelay(3000);     // 延时3ms

            // 准备读取数据
            Comm_** = 0;
            receivedflag = 0;
#if(0x01 == TESTI2C)
            I2C_GenerateSTART(CW_I2C1, ENABLE);
#elif(0x02 == TESTI2C)
            I2C_GenerateSTART(CW_I2C2, ENABLE);
#endif

            // 等待接收完成
            while (!receivedflag) ;

            // 重置标志位和数据长度
            receivedflag = 0;
            Send** = 0;
            u8RecvLen = 0;

            // 更新发送数据内容
            tempcnt++;
            for (uint8_t i = 0; i < 8; i++)
            {
                u8Senddata = tempcnt + i;
            }
            break;
        }

        // 达到测试次数后退出
        if (tempcnt >= WriteReadCycle)
        {
            break;
        }
    }
    while (1);  // 测试完成后进入死循环
}

/**
* @brief  系统时钟配置
* @note   使能GPIOB和I2C外设时钟
*/
void RCC_Configuration(void)
{
    CW_SYSCTRL->AHBEN_f.GPIOB  = 1;
#if(0x01 == TESTI2C)
    CW_SYSCTRL->APBEN1_f.I2C1 = 1U;
#elif(0x02 == TESTI2C)
    CW_SYSCTRL->APBEN1_f.I2C2 = 1U;
#endif
}

/**
* @brief  GPIO配置
* @note   配置I2C引脚为开漏输出模式
*/
void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
#if(0x01 == TESTI2C)
    PB10_AFx_I2C1SCL();  // 配置PB10为I2C1 SCL
    PB11_AFx_I2C1SDA();  // 配置PB11为I2C1 SDA
    GPIO_InitStructure.Pins = I2C1_SCL_GPIO_PIN | I2C1_SDA_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;  // 开漏输出模式
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStructure);
#elif(0x02 == TESTI2C)
    PB00_AFx_I2C2SCL();  // 配置PB00为I2C2 SCL
    PB01_AFx_I2C2SDA();  // 配置PB01为I2C2 SDA
    GPIO_InitStructure.Pins = I2C2_SCL_GPIO_PIN | I2C2_SDA_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(I2C2_SCL_GPIO_PORT, &GPIO_InitStructure);
#endif
}

/**
* @brief  中断控制器配置
* @note   使能I2C中断
*/
void NVIC_Configuration(void)
{
    __disable_irq();  // 全局中断禁用
#if(0x01 == TESTI2C)
    NVIC_EnableIRQ(I2C1_IRQn);  // 使能I2C1中断
#elif(0x02 == TESTI2C)
    NVIC_EnableIRQ(I2C2_IRQn);  // 使能I2C2中断
#endif
    __enable_irq();   // 全局中断使能
}



三.总结
I2C通信测试流程:

初始化阶段配置时钟、GPIO和I2C参数
主循环中执行写-读操作序列
每次循环更新发送数据内容
通过中断标志位同步通信状态
硬件配置特点:

支持I2C1和I2C2模块选择
GPIO配置为开漏输出模式
使用中断机制处理通信事件
时钟频率设置为500KHz
实现I2C EEPROM的读写操作,主要特点包括:

支持I2C1和I2C2两个接口的配置
使用状态机方式处理I2C通信流程
包含完整的写操作和读操作流程
处理了NACK、仲裁丢失等异常情况
通过中断方式实现非阻塞通信
代码中各种状态码对应I2C协议的不同阶段,注释中已详细说明每个状态的处理逻辑。实际使用时需要根据具体硬件平台调整GPIO和I2C外设的配置。
————————————————
版权声明:本文为CSDN博主「brave and determined」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43260261/article/details/149112287

OKAKAKO 发表于 2025-8-15 14:43 | 显示全部楼层
I2C 总线使用两根信号线(数据线 SDA 和时钟线 SCL)在设备间传输数据。
szt1993 发表于 2025-8-19 16:44 | 显示全部楼层
I2C功能详解与应用案例
小小蚂蚁举千斤 发表于 2025-8-21 23:27 | 显示全部楼层
非常详细的IIC资料
您需要登录后才可以回帖 登录 | 注册

本版积分规则

99

主题

4338

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部

99

主题

4338

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部