I2C基础归纳
两根信号线,一根数据一根时序,主从模式,一应一答。龙顺宇讲stm8时举的例子:衙门断案,非常形象。今天在书店偶然看到,仔细翻阅了一下,收获很大。
我觉得这个难点主要在于应答位的掌握,究竟是主机应答还是从机应答,因为有的时候即便应答位设置错误,也能正常写入。这就导致了没有示波器情况下,无法简单的通过应答位来判断读写是否成功。所幸Proteus有I2C debug功能,今天一天终于分析出了可以稳定使用的模拟方式。还有就是接收,写入,开始,停止等子函数运行后SCL和SDA高低电平影响下一个子函数的问题。
I2C时序
我按照书上的几个时序分解画了一下,datasheet没太看懂。。。
借此机会,发现了一个画时序图很好用的软件TimeGen。
需要注意的就是,主从应答信号前一个操作结束,应该在SCL = 0后,将SDA = 1,不然从机信号无法发送。不,主要是因为线与的关系,都是漏极开路,无法检测信号。
其余的没啥难度,下降沿触发开始,上升沿结束,有效数据要保持5us左右。然后就可以根据这些时序,将子函数依次写出来。
全部程序
#include #include sbit SDA = P2^0; sbit SCL = P2^1; unsigned char ACK_flag = 0; void delay_5us(); void I2C_init(); void I2C_Start(); void I2C_Stop(); void I2C_Send(unsigned char byte); unsigned char I2C_Get(); void I2C_ACK_Send(bit A); bit I2C_ACK_Get(); void I2C_Write(unsigned char add_7,unsigned char add,unsigned char byte); unsigned char I2C_Read(unsigned char add_7,unsigned char add); void delay(unsigned int z) { unsigned int i; unsigned char j; for(i = z;i>0;i--) for(j = 114;j>0;j--); } void main() { I2C_init(); //初始化 /* 写入段 */ I2C_Write(0xa0,0x20,0x55); delay(40); /* 读取段 */ P1 = I2C_Read(0xa0,0x20); if(ACK_flag) P1 = 0x00; //校验是否出现无应答 while(1); } /* 延时5微秒 */ void delay_5us() { _nop_(); } /* I2C初始化 */ void I2C_init() { SCL = 1; //拉高SDA和SCL _nop_(); SDA = 1; delay_5us(); } /* I2C开始信号 */ void I2C_Start() { SDA = 1; SCL = 1; //打开时钟 delay_5us(); SDA = 0; //产生SDA下降沿,触发开始信号 delay_5us(); } /* I2C结束信号 */ void I2C_Stop() { SCL = 0; SDA = 0; _nop_(); SCL = 1; //打开时钟 delay_5us(); SDA = 1; //产生SDA上升沿,触发结束信号 delay_5us(); } /* I2C数据发送 */ void I2C_Send(unsigned char byte) { unsigned char i,temp; temp = byte; for(i = 0;i<8;i++) { SCL = 0; //关闭时钟准备数据变化 if(temp & 0x80) //从最高位发送 1000 0000 { SDA = 1; } else { SDA = 0; } delay_5us(); SCL = 1; //打开时钟发送数据 delay_5us(); temp <<= 1; } } /* I2C数据接收 */ unsigned char I2C_Get() { unsigned char i,byte; for(i = 0;i<8;i++) { SCL = 0; //关闭时钟准备数据变化 _nop_(); SCL = 1; //打开时钟接收数据 delay_5us(); if(SDA) byte++; //从最高位接收 SCL = 0; //接收完毕关闭时钟 if(i == 7) return byte; byte <<= 1; } return 0x50; } /* I2C主机应答 */ void I2C_ACK_Send(bit A) { SCL = 0; _nop_(); if(A) //如果i = 1那么拉低数据总线,表示主机应答。 { SDA = 0; } //如果i = 0发送非应答 else { SDA = 1; } delay_5us(); SCL = 1; _nop_(); SCL = 0; _nop_(); SDA = 1; _nop_(); } /* I2C从机应答*/ bit I2C_ACK_Get() { bit flag; SCL = 0; SDA = 1; _nop_(); SCL = 1; _nop_(); flag = SDA; _nop_(); SCL = 0; return flag; } /* 写入段 */ void I2C_Write(unsigned char add_7,unsigned char add,unsigned char byte) { I2C_Start(); //开始 I2C_Send(add_7+0); //写eeprom if(I2C_ACK_Get()) ACK_flag = 1; //接收从机ACK I2C_Send(add); //选择内存地址 if(I2C_ACK_Get()) ACK_flag = 1; //接收从机ACK I2C_Send(byte); //写数据 if(I2C_ACK_Get()) ACK_flag = 1; //接收从机ACK I2C_Stop(); //主机停止 } /* 读取段 */ unsigned char I2C_Read(unsigned char add_7,unsigned char add) { unsigned char message; I2C_Start(); //开始 I2C_Send(add_7+0); //写eeprom if(I2C_ACK_Get()) ACK_flag = 1; //接收从机ACK I2C_Send(add); //选择内存地址 if(I2C_ACK_Get()) ACK_flag = 1; //接收从机ACK I2C_Start(); //重开始 I2C_Send(add_7+1); //读eeprom if(I2C_ACK_Get()) ACK_flag = 1; //接收从机ACK message = I2C_Get(); //接收从机数据 I2C_ACK_Send(0); //主机发送ACK I2C_Stop(); //主机停止 return message; } Proteus仿真 I2C debug还是很好用的,但是示波器一直搞不懂怎么用。还好有视频教程的例程,分析了半天发现问题主要在第二段上。然而把两部分开执行都可以正常运行,之前在开发板上运行的时候也是这样。用例程的读取EEPROM程序也能读出之前写入的值。 最后的原因是,EEPROM写入可擦除存储部分需要花10ms的时间,datasheet上有写,但是没看懂。
上一篇:单片机8位和16位是怎么区分的
下一篇:[51单片机] EEPROM 24c02 [I2C代码封装-保存实现流水灯]
推荐阅读最新更新时间:2024-11-13 08:39
设计资源 培训 开发板 精华推荐
- FEBFAN23SV56_LVA,基于 FAN23SV56 6A 同步降压稳压器的评估板,具有超声波模式、内部线性稳压器和宽输入范围
- 【已验证】莱曼耳放(纯放大板)
- [已验证]ESP32 PCB 加热台
- 使用 LTC3637EMSE 5.5V 至 76V 输入至 5V 输出、1A 降压型稳压器的典型应用
- LT1033CT 负 5V 线性稳压器的典型应用电路
- MSP430F149+1602数码显示和实时时钟
- 使用 Analog Devices 的 AD8033AKS 的参考设计
- 【训练营】ESP-c3-12F
- LTC3633AEFE-3 6A 1MHz 2 相降压稳压器的典型应用电路
- NCP300HSN30T1 3V 窗口电压检测器的典型应用