本章由我编写,iic从实战开始,希望你已经把上一章和上一章的视频看完了,如果没有。我建议你看完 这个视频 补完理论,然后跟我从实操开始。
并行通信与串行通信
iic本身指的是一种通信协议,由于数字电子线路只存在0与1,所以机器的沟通也只是0与1的传递,对于简单的沟通来说(例如按钮控制灯泡亮灭),他就是io口接收0与1来实现开与关的简单效果。但对于较为复杂的情况,例如你想要按照asiic字符发送一个大写字母A(2进制0100 0001 16进制0x41)到另外一个 机器/元件 当中这个情况就比较麻烦了,对于这种问题来说业界有两种解决方案,就是串行通信与并行通信。下面是最简单的并行通信示意图。

而串行通信我们以iic来举例,他的传递方式靠两根线只需要靠两根线来实现。相比并行通信有效的减少了并行线路的干扰以及节约了io口。

iic通信方式
在iic的通信当中,默认SDA(数据线)与SCL(时钟线)处于电平拉高的状态(也就是高电平),之后的数据传输按照以下方式传输。
SDA首次被拉低,代表着进入传输模式(图中S区)
SCL(时钟线)拉低后,将SCL重新拉高的那一刻 将此刻SDA(数据线)的高低电平记录 (图中绿色区域)
在发送完1个字节(8帧数据 / 8个高低电平)会发送一帧校验位(每一次通信视作一帧)。如果对应元件收到信号,会进行 应答。在 应答 结束后,才会发送下一个字节的数据。
发送完后将SDA、SCL重新拉高,作为终止信号广播给所有节点
应答(ACK/NACK) 是一个非常重要的机制,用于确保数据在发送方和接收方之间正确传输。
ACK(Acknowledge,应答):表示接收方成功接收到了数据。
NACK(Not Acknowledge,非应答):表示接收方未能成功接收数据,或者通信需要终止。ACK用SDA低电平表示:
表示接收方成功接收到了数据,并准备好接收下一个字节。
通常由接收方(Slave)在接收到数据后主动拉低 SDA 线。
NACK用SDA高电平表示:
表示接收方未能成功接收数据,或者通信需要终止。
可能的原因包括:
接收方未正确接收到数据。
接收方不希望继续接收数据(例如,通信结束)。
总线上没有设备响应发送方的地址。
他的发送标志代码和终止代码如下:
// 微秒级延时函数
void Delay_us(unsigned int n) {
while(n--) {
_nop_(); // nop函数就是让机器空转一个周期
_nop_();
_nop_();
_nop_();
}
}
// I2C起始信号
void I2C_Start() {
SDA = 1;
SCL = 1;
Delay_us(5);
SDA = 0;
Delay_us(5);
SCL = 0;
}
// I2C停止信号
void I2C_Stop() {
SDA = 0;
SCL = 1;
Delay_us(5);
SDA = 1;
Delay_us(5);
}
// 检测应答信号
bit I2C_CheckAck() {
bit ack;
SDA = 1;
SCL = 1;
Delay_us(2);
ack = SDA;
SCL = 0;
return ack;
}
I2C核心代码 发送一个字节(8bit)数据:
void I2C_SendByte(unsigned char dat) {
unsigned char i;
for(i=0; i<8; i++) { // 循环8次,逐位发送一个字节
/*
SDA = (dat & 0x80) ? 1 : 0; 会将dat的最高位赋值给SDA
将dat的数据与0x80 (0b1000 0000)相与而后进行判断为真则SDA = 1,为假SDA = 0
0b1xxx xxxx(x为任意数据) 就会得到1,0b0xxx xxxx会得到0
*/
SDA = (dat & 0x80) ? 1 : 0;
dat <<= 1; // 左移dat,准备发送下一位
//例如 0bx1xx xx1x 左移后会变成 0b1xxx x1x0 (八位数据向左移动一位,空出来的数据用0填充) 例如 0b1010 1111 左移后会变成 0b0101 1110
SCL = 1; // 将SCL线拉高,表示数据位有效
Delay_us(5); // 延时5微秒,确保数据稳定
SCL = 0; // 将SCL线拉低,为下一位数据做准备
Delay_us(5); // 延时5微秒,确保时序正确
}
I2C_CheckAck(); // 发送完一个字节后,检查从设备的应答(ACK/NACK)
}
iic的数据包
iic可以进行双向传输,但主机发送与从机发送的数据包并不完全相同。可参考下方图片


iic总线可以承载多个设备,我们以主机发送为例,按照图片从左到右进行介绍。
- S:IIC开始信号
- 地址:每个iic总线上的设备都有一个8位地址作为自己的"门牌号",每次进行通信会先确认“与谁通信”来确保数据能够正确传输。
- A: 第一次应答成功(证明你发送的这个地址有设备进行回应)
- 发送一字节数据
- A: 第二次应答成功(第一字节数据被接收)
- 发送亿字节数据
- A:第三次应答成功(第一字节数据被接收) / N(~A): 从机想结束通讯
- P: Pause 停止通讯
这一整个流程发送的数据,我们称之为一个 数据包 ,所以如果我们要通过iic发送数据,最好将其进行封包操作,这是一个封包函数(要注意的是,应答函数在I2C_SendByte()里,请查看上文的函数内容)。
// 封包函数
void Write_I2C_Command(unsigned char addr,unsigned char cmd, unsigned char dat) {
I2C_Start(); // 启动I2C
I2C_SendByte(addr); // 发送器件地址
I2C_SendByte(cmd); // 发送指令
I2C_SendByte(dat); // 发送数据
I2C_Stop(); // 停止I2C
}
电路图如下

我们运行函数
Write_I2C_Command(0x7C,0xAA,0xBB);
通过debuger看到数据包,如果没有点击工具栏 Debug -> 最下方 I2C Debugger 就会有窗口

S(开始) -> 0x7C地址 -> 无应答 -> 数据1 0xAA -> 无应答 -> 数据2 0xBB -> 无应答 -> 结束
在我们接入了一个地址为0x7C的iic显示器后,电路图如(可以点击图片放大)

S(开始) -> 0x7C地址 -> 应答 -> 数据1 0xAA -> 应答 -> 数据2 0xBB -> 应答 -> 结束
作业
在2月18号之前,最好不要复制本文的iic代码,自己编写iic驱动然后自行查找此显示器的资料,在显示器上写下 700LAB 和自己名字的拼音 发送到实验室邮箱.(示例如下)