https://blog.csdn.net/u010249597/article/details/135607546
您的代码是实现软件模拟IIC通信的基本操作,这些函数的实现是基于IIC协议的通信时序标准,包括起始条件、数据传输、应答信号和停止条件。我们来详细分析每个函数的逻辑和其与IIC时序的关系,并给出发送两个字节数据的例子。
IIC通信时序基础
IIC协议的通信时序包括以下几个关键点:
起始条件(START Condition):
- 主设备将 SDA 从高电平拉低,同时保持 SCL 为高电平。
- 起始条件标志通信的开始。
数据传输(Data Transfer):
- 每个字节(8位)按位从高位到低位发送。
- 在每一位传输时,SDA 数据线在 SCL 时钟线为低电平时稳定,SCL 上升沿采样数据。
应答信号(ACK/NACK):
- 接收方在收到完整的一个字节后,在第9个时钟周期拉低 SDA,表示收到数据(ACK)。
- 如果接收方不拉低 SDA,则表示 NACK(通常用于结束通信)。
停止条件(STOP Condition):
- 主设备将 SDA 从低电平拉高,同时保持 SCL 为高电平。
- 停止条件标志通信结束。
函数逻辑分析
1. IIC_Start()
c
void IIC_Start(void) {
IIC_SDA_H(); // 确保 SDA 处于高电平
IIC_SCL_H(); // 确保 SCL 处于高电平
HAL_Delay(1); // 短暂延时,确保稳定
IIC_SDA_L(); // SDA 从高电平拉低,产生起始条件
HAL_Delay(1); // 短暂延时,确保起始信号被识别
IIC_SCL_L(); // 将 SCL 拉低,准备发送数据
}
逻辑分析:
- 起始条件:当 SCL 为高电平时,SDA 从高电平拉低,表示通信开始。
- 确保了 SCL 和 SDA 的状态符合 IIC 时序要求。
2. IIC_Stop()
c
void IIC_Stop(void) {
IIC_SDA_L(); // 确保 SDA 处于低电平
IIC_SCL_H(); // 将 SCL 拉高
HAL_Delay(1); // 短暂延时,确保信号稳定
IIC_SDA_H(); // SDA 从低电平拉高,产生停止条件
HAL_Delay(1); // 短暂延时,确保停止信号被识别
}
逻辑分析:
- 停止条件:当 SCL 为高电平时,SDA 从低电平拉高,表示通信结束。
3. IIC_SendByte(uint8_t data)
c
void IIC_SendByte(uint8_t data) {
for (int i = 0; i < 8; i++) { // 循环发送8位数据
if (data & 0x80) { // 判断最高位是1还是0
IIC_SDA_H(); // 如果是1,设置 SDA 为高电平
} else {
IIC_SDA_L(); // 如果是0,设置 SDA 为低电平
}
data <<= 1; // 左移1位,准备发送下一位
HAL_Delay(1); // 短暂延时,确保 SDA 稳定
IIC_SCL_H(); // 拉高 SCL,产生上升沿(数据被接收方采样)
HAL_Delay(1); // 短暂延时,确保接收方完成采样
IIC_SCL_L(); // 拉低 SCL,准备发送下一位
}
}
逻辑分析:
- 每一位数据在 SCL 为低电平时设置 SDA。
- SCL 上升沿时,接收方采样 SDA 上的数据。
4. IIC_WaitAck()
c
uint8_t IIC_WaitAck(void) {
uint8_t ack;
IIC_SDA_H(); // 释放 SDA,接收方控制 SDA
HAL_Delay(1);
IIC_SCL_H(); // 拉高 SCL,接收方在此时钟周期返回 ACK
HAL_Delay(1);
ack = IIC_SDA_READ(); // 读取 SDA 电平
IIC_SCL_L(); // 拉低 SCL,准备下一步
return ack == 0 ? 1 : 0; // 返回1表示收到 ACK,0表示 NACK
}
逻辑分析:
- 主机释放 SDA(设置为高阻态),等待从机控制 SDA。
- 在第9个时钟周期读取从机返回的 SDA 状态,判断是否收到 ACK。
发送两个字节数据的例子
假设需要发送两个字节数据 0xA5
和 0x5A
,目标从机地址为 0x3C(7位地址)
。
代码实现
c
void IIC_SendData(uint8_t deviceAddr, uint8_t data1, uint8_t data2) {
IIC_Start(); // 1. 发送起始信号
IIC_SendByte(deviceAddr << 1); // 2. 发送从机地址(左移1位 + 写模式)
if (!IIC_WaitAck()) { // 3. 等待从机返回 ACK
IIC_Stop(); // 如果没有收到 ACK,则停止通信
return;
}
IIC_SendByte(data1); // 4. 发送第一个字节数据
if (!IIC_WaitAck()) { // 5. 等待从机返回 ACK
IIC_Stop(); // 如果没有收到 ACK,则停止通信
return;
}
IIC_SendByte(data2); // 6. 发送第二个字节数据
if (!IIC_WaitAck()) { // 7. 等待从机返回 ACK
IIC_Stop(); // 如果没有收到 ACK,则停止通信
return;
}
IIC_Stop(); // 8. 发送停止信号
}
执行流程
- 起始信号:调用
IIC_Start()
,在总线上产生起始条件。 - 发送从机地址:通过
IIC_SendByte()
发送从机地址(如0x3C << 1
表示写操作)。 - 等待ACK:从机返回ACK信号表示地址匹配。
- 发送第一个字节数据:调用
IIC_SendByte()
发送0xA5
。 - 等待ACK:从机返回ACK信号,表示数据接收成功。
- 发送第二个字节数据:调用
IIC_SendByte()
发送0x5A
。 - 等待ACK:从机返回ACK信号。
- 停止信号:调用
IIC_Stop()
,在总线上产生停止条件。
总结
与时序的关系:
- 起始信号、数据传输、ACK、停止信号均严格按照IIC时序标准实现。
- SCL 上升沿用于数据采样,SCL 低电平时设置 SDA。
逻辑清晰性:
IIC_Start
和IIC_Stop
控制通信的开始和结束。IIC_SendByte
负责逐位发送数据。IIC_WaitAck
确保接收方正常应答。
发送两个字节的流程:
- 起始信号 -> 发送设备地址 -> 等待ACK -> 发送数据 -> 等待ACK -> 停止信号。
如果还有任何疑问,欢迎继续交流!