每日更新教程,评论区答疑解惑,小白也能变大神!"
目录
二.CW32F030C8T6的I2C案例——通过I2C中断方式读取eeprom
一.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.4起始信号 (START)
- 当总线处于空闲状态时(SCL 和 SDA 线同时为高),SDA 线上出现由高到低的下降沿信号,则被定义为起始 信号。主机向总线发出起始信号后开始数据传输,并占用总线。
1.5重复起始信号 (Repeated START)
- 当一个起始信号后未出现停止信号之前,出现了新的起始信号,新的起始信号被定义为重复起始信号。在主 机发送停止信号前,SDA 总线一直处于占用状态,其它主机无法占用总线。
1.6停止信号 (STOP)
- 当 SCL 线为高时,SDA 线上出现由低到高的上升沿信号,则被定义为停止信号。主机向总线发出停止信号以 结束数据传输,并释放总线。
1.7从机地址及读写位
- 当起始信号产生后,主机开始传输第 1 字节数据:7 bit 从机地址 + 读写位。读写位(1:读;0:写)控制总 线上数据传输方向。被寻址的从机在第 9 个 SCL 时钟周期内占用 SDA 总线,并将 SDA 置为低电平作为 ACK 应答。
1.8数据传输
- 主机在 SCL 线上输出串行时钟信号,主从机通过 SDA 线进行数据传输。数据传输过程中,1 个 SCL 时钟脉 冲传输 1 个数据位(最高有效位 MSB 在前),且 SDA 线上的数据只在 SCL 为低时改变,每传输 1 个字节跟 随 1 个应答位。如下图所示:
1.9传输应答
- 在总线上传输数据时,发端每传输完 1 个字节数据,在第 9 个 SCL 时钟周期发端放弃对 SDA 的控制,收端须在 第 9 个 SCL 时钟周期回复 1 个应答位:接收成功,发送 ACK 应答,接收异常发送 NACK 应答。
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 串行时钟,直到当前字节传输结束。 当主机没有检测到总线冲突,该主机赢得仲裁,继续主导本次数据传输,直到通信完成。
1.13功能框图
1.14串行时钟发生器
1.15仲裁和同步逻辑
1.16工作模式
1.17多主机通讯
二.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 SendFlg = 0; // 发送标志:0=写,1=读
uint8_t Comm_flg = 0; // 通信完成标志
uint8_t u8recvflg = 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 == SendFlg) // 写模式
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 == SendFlg) // 写模式
{
if (u8SendLen < WRITELEN) // 还有数据要发送
I2C_SendData(CW_I2C1, u8Senddata[u8SendLen++]);
else // 写操作完成
{
u8SendLen = 0;
Comm_flg = 1; // 设置通信完成标志
SendFlg = 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] = 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_flg) ;
FirmwareDelay(3000); // 延时3ms
// 准备读取数据
Comm_flg = 0;
receivedflag = 0;
#if(0x01 == TESTI2C)
I2C_GenerateSTART(CW_I2C1, ENABLE);
#elif(0x02 == TESTI2C)
I2C_GenerateSTART(CW_I2C2, ENABLE);
#endif
// 等待接收完成
while (!receivedflag) ;
// 重置标志位和数据长度
receivedflag = 0;
SendFlg = 0;
u8RecvLen = 0;
// 更新发送数据内容
tempcnt++;
for (uint8_t i = 0; i < 8; i++)
{
u8Senddata[i] = 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外设的配置。