IIC总线通信——集成电路总线
1》物理层
1,IIC是一种简单的双向二线制串行通信的通信方式,SCL(时钟线),SDA(数据线)(可双向传输数据),注意:IIC是半双工同步通信协议。
2,IIC是飞利浦公司开发总线协议。IIC是高位先行的通信方式(先发送高位)
3,IIC可以实现多主机多从机通信,但是IIC没有片选信号。每一个设备都有唯一的ID,主机通过SDA数据线发送设备的地址来查找从机
4,在IIC总线上必须接两个上拉电阻,用于将总线拉高
5,IIC的SCL线的作用:用于控制SDA线上的数据的有效性----SCL为高电平时,SDA线上的数据有效。SCL为低电平时,SDA线上的数据无效
6,当有多个设备同时发起IIC通信时,总线仲裁决定谁掌握总线的控制权,低电平优先原则
7,IIC的通信速率,标准100kbit/s 快速 400kbit/s 高速3.4Mbit/s
8,IIC的特点:具有应答机制-----相较于其他方式,更加稳定
补充:接收方应答,发送放等待应答
9,IIC通信规定,一个通信过程,必须以起始条件开始,以停止条件结束
10,能够发起IIC通信的都是主机,时钟线在32中由主机控制
2》协议层
起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
应答信号:SCL为高电平期间,SDA输出一个低电平
非应答信号:SCL为高电平期间,SDA输出一个高电平
等待应答信号:拉高SCL线,等待SDA线上的高低电平
发送数据信号:当SCL线为高电平时,SDA线上的数据是稳定的,当SCL为低电平时,允许SDA线上的数据发生改变
接收数据信号:拉高SCL线,读取SDA线上的信号,拉低SCL线,使从机准备下一个数据
IIC与SPI的比较:
1,IIC的线数比SPI的少,布线方便,更适合多设备间的通信。
2,IIC的通信速率没有SPI的快。主要是因为SPI有片选信号,而 IIC没有。每当 IIC需要找到对应的从机时都需要SDA数据线发送从机的地址才能够找到从机设备。所以,SPI更适合高速通信,而IIC则是用于低速设备通信。
为什么IIC的SDA线与SCL线上面需要上拉电阻?
我们可以看到,在上图中数据线与时钟线都上拉了一个电阻。为什么要怎么做呢? 因为,IIC内部的SCL与SDA都被设计成开漏的,此时IIC如果想要输出高电平,那就必须外接一个上拉电阻,否则无法输出高电平。
那么为什么要设计成开漏呢? 在IIC内部直接上拉电阻不好吗?
设计成开漏的原因是,如果IIC内部直接已经设计好上拉电阻了,那么当多个IIC器件在一起通信时,由于此时IIC是并联的,所以多个IIC器件在器件与器件之间内部的上拉电阻也就变成并联的了,而电阻的阻值是越并联越小。那么此时上拉电阻阻值变小,电源到IIC的电流就会变大,从而导致IIC器件功耗变大,甚至烧毁。所以,我们就将其设计为内部开漏,直接在外部接上拉电阻,这样的话就不会造成电阻由于并联导致电流变大了。外部的上拉电阻一般取值为1K-10K之间(一般是4.7k),上拉电阻阻值不能取太大也不能取太小。电阻阻值取太大,由于RC会导致通信的上升沿和下降沿变缓很多,影响通信速率。电阻阻值取太小,电阻阻值太小,导致电流太大,增加了IIC的功耗。
而且上拉电阻不宜过小,还有一个原因,一般IO 端口的驱动能力在2mA~4mA量级。如果上拉电阻阻值过小,VDD灌入端口的电流将较大,这样会导致MOS管不完全导通,有饱和状态变成放大状态,这样端口输出的低电平值增大(I2C协议规定,端口输出低电平的最高允许值为0.4V),这样的话,当下拉的时候就无法到低电平了;如果灌入端口的电流过大,还可能损坏端口。故通常上拉电阻应选取不低于1KΩ的电阻(当VDD=3V时,灌入电流不超过3mA)。
所以,如果有一个引脚是默认输出高电平,那么说明该引脚为上拉输出,即:该引脚内部很可能默认接了上拉电阻,如果此时再在该引脚外部接一个上拉电阻,那么外面的上拉电阻就会与内部的上拉电阻并联,由于电阻越并越小,那么电流就会变大,此时该引脚接的外设内部的mos管就会不饱和,导致无法下拉到低电平。此时,我们只需要撤除外部的上拉电阻即可。
所以,具体还是要我们去实际通信速率来测试波形。IIC的通信速率,标准100kbit/s 快速 400kbit/s 高速3.4Mbit/s。
上拉电阻大小对i2c总线的影响_weixin_33963594的博客-CSDN博客
IIC总线最多可以挂多少个设备?
由IIC地址决定,8位地址,减去1位广播地址,是7位地址,2^7=128,但是地址0x00不用,那就是127个地址, 所以理论上可以挂127个从器件。
但是IIC协议没有规定总线上device最大数目,但是规定了总线电容不能超过400pF。管脚都是有输入电容的,PCB上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8个器件。
规定电容大小的原因:
IIC的OD(漏极开路)要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。
传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。
原文链接:https://blog.csdn.net/qq_38769551/article/details/105108062
IIC仲裁机制:
低电平优先原则:谁(SDA)先发出低电平,谁掌握总线,谁(SDA)的低电平长谁掌握总线。注意:当有多个主机存在的时候,才会出现仲裁机制,一般我们都是一个主机。
IIC时序图
IIC的实现:
1》使用片上外设自动生成IIC(即:由硬件外设自动生成IIC时序,不占用CPU的资源)
2》使用GPIO来模拟IIC时序(用GPIO模拟,占用CPU的资源)
起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
注意:在SCL为低电平的时候,才允许SDA发生改变。这是因为,当SCL为低电平的时候,SDA的一切电平状态都会被视为无效电平,此时改变SDA不会对其他的任何设备产生影响。然后我们等SDA的电平稳定了以后,再拉高SCL,那么此时SDA的这个稳定后的电平状态就会被视为有效的电平。
代码:
// 起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
void IIC_START(void) //用I/O口模拟IIC的通信
{
SDA_HIGH; //拉高SDA
SCL_HIGH; //拉高SCL
SDA_LOW; //拉低SCL
}
根据上图,起始条件:SCL为高电平期间,SDA由高电平跳变到低电平。一开始我们并不知道SDA和SCL是高电平还是低电平,所以我们先拉高SDA和SCL,然后再将SDA拉低就行了。
乍一看,这没什么问题。但是,第一步先执行了SDA_HIGH,那么如果要是一开始,SCL是高电平而SDA是低电平呢?此时SDA由低拉高,SCL又是高电平。此时产生的就是一个有效的停止信号,而不是一个起始信号了。而且,我们还可以发现,这个函数里面没有对GPIO进行模式配置,此时我们也不知道GPIO是输入模式还是输出模式。
所以,我们需要先将SCL拉低,让所有的操作信号都变为无效信号,那么后面的操作就无法影响到其他的任何设备了。然后我们再配置GPIO的模式,然后再将SDA拉高(此时由于SCL是低电平,所有这里拉高SDA后,它不会被认为是一个有效的停止信号)。然后我们再将SCL拉高,由于SCL是高电平,那么此后的操作都会被认为是有效信号。然后我们再拉低SDA,此时SCL是高电平,SDA由高变为低,此时就会被认为是有效的起始信号了。修改如下:
void IIC_START(void) //用I/O口模拟IIC的通信
{
SCL_LOW; //拉低SCL,让后面的操作无效
IIC_SDA_OUT(); /*配置GPIO为开漏输出模式。注意: IIC_SDA_OUT(); 一定要写在 SCL_LOW的后面,否则如果一开始SCL是高电平,SDA是低电平,那么此时设置GPIO为输出模式,而此时恰好GPIO输出寄存器内是一个1,那么此时GPIO就会输出一个高电平,那么此时SDA就会变为高电平了,由于此时SCL是高电平,这里就会产生一个有效的停止信号,这是我们不希望的。*/
SDA_HIGH; //拉高SDA
SCL_HIGH; //拉高SCL,让后面的操作有效
Systick_NusDelay(5); //拉高SDA后,需要等待SDA上面的电平稳定后,再拉低SDA来产生一个有效的起始信号
SDA_LOW; //拉低SDA,此时产生有效的起始信号
Systick_NusDelay(5); //拉低SDA后,还需要等待SDA电平稳定
SCL_LOW; //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
}
疑问:
等待SDA上面的电平稳定后,再拉低SDA来产生一个有效的起始信号,需要等待多久长时间,才能再拉低SDA?
芯片手册上面有标识,我们拉高SDA后需要等待4.7uS以上,才能再拉低SDA。然后,拉低SDA后,还需要再等待4uS,才能再进行其他的操作。 这里实际上就是在设置IIC中SDA与SCL的最大频率了。
// 停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
void IIC_STOP(void)
{
SCL_LOW;
IIC_SDA_OUT();
SDA_LOW;
SCL_HIGH;
Systick_NusDelay(5);
SDA_HIGH;
Systick_NusDelay(5);
}
疑问:
为什么停止信号函数的最后没有加上,SCL_LOW来掌控IIC的主控权?
因为停止信号本身表达的就是放弃IIC的主控权,所以不需要去掌控IIC的主控权。
应答信号:SCL为高电平期间,SDA输出一个低电平(接收方接收到数据的时候,反馈给发送方一个应答信号,表示接收到数据)
注意:主机即可以作为发送方也可以做为接收方,从机亦是如此。
非应答信号:SCL为高电平期间,SDA输出一个高电平。有时。主机会向从机发送一个非应答信号。
// 应答信号:SCL为高电平期间,SDA输出一个低电平。给发送方发送一个应答信号。
void IIC_SendACK(void)
{
SCL_LOW; //拉低SCL,让后面的操作无效
IIC_SDA_OUT();
SDA_LOW; //拉低SDA,产生应答信号
SCL_HIGH; //拉高SCL,上面的SDA的电平变为有效电平
Systick_NusDelay(5);
SCL_LOW; //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
}
// 非应答信号:SCL为高电平期间,SDA输出一个高电平
void IIC_SendNoACK(void)
{
SCL_LOW; //拉低SCL,让后面的操作无效
IIC_SDA_OUT();
SDA_HIGH;
SCL_HIGH; //拉高SCL,上面的SDA的电平变为有效电平
Systick_NusDelay(5);
SCL_LOW; //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
}
// 等待应答信号:拉高SCL线,等待SDA线上的高低电平。等待接收方反馈一个应答信号过来。
uint8_t IIC_WaitACK(void)
{
uint8_t temp=0;
SCL_LOW; //拉低SCL,让后面的操作无效
IIC_SDA_IN(); //配置GPIO为输入模式。
SCL_HIGH; //拉高SCL,让后面读取到的SDA的数据有效
Systick_NusDelay(4);
while(SDA_READ){ /*等待读取SDA的数据,如果读到的是非应答信号1,那么这里就进入循环内部,如果循环读取250次,还没有读到应答信号0,那么就返回NOACK(非应答)。如果读到应答信号0,那么就不再进入while循环内,然后拉低SCL,并返回ACK(应答信号)*/
temp++;
if(temp>250){ //超时等待,读取SDA数据线250次
return NOACK;
}
}
SCL_LOW;
return ACK; //返回应答信号
}
// 发送数据信号:当SCL线为高电平时,SDA线上的数据是稳定的,当SCL为低电平时,允许SDA线上的数据发生改变
void IIC_SendByteData(uint8_t data) //将要发送的数据作为参数填入
{
uint8_t i;
SCL_LOW;
IIC_SDA_OUT();
for(i=0;i<8;i++){ //循环8次
if(data&0x80){ //IIC通讯是高位先行,所以先发送的是最高位。所以用data & 1000 0000来判断data的最高位是什么
SDA_HIGH; //如果data最高位是1,那么SDA就为高电平
}else{
SDA_LOW; //如果data最高位是0,那么SDA就为低电平
}
SCL_HIGH; //拉高SCL,发送上面的SDA电平
Systick_NusDelay(5);
SCL_LOW; //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
Systick_NusDelay(5);
data=data<<1; //将data左移一位,来判断data的次高位
}
SCL_LOW;
}
// 接收数据信号:拉高SCL线,读取SDA线上的信号,拉低SCL线,使从机准备下一个数据
uint8_t IIC_RxByteData(uint8_t ackflag) /*ackflag可以填应答信号,也可以填非应答信号。如果这里填应答信号,那么表示接收完数据后,返回一个应答信号回去。如果这里填非应答信号,那么表示接收完数据后,返回一个非应答信号回去*/
{
uint8_t i;
uint8_t Rxdata=0; // Rxdata是一个8bit的数值。
SCL_LOW;
IIC_SDA_IN();
for(i=0;i<8;i++){ //循环8次
SCL_HIGH; //拉高SCL,让后面接收到的SDA都是有效数据
Systick_NusDelay(5);
Rxdata=Rxdata<<1; /*注意: Rxdata=Rxdata<<1; 不能放在下面,只能放在这里,因为for循环是循环8次,放在下面,最后一次左移,会将读到的最高位给移除掉*/
if(SDA_READ){ /*读取SDA数据,如果读到的是1,那么使用Rxdata|0x01将Rxdata的最低位置1。如果读到的是0,那么Rxdata本身就是0。所以不需要清0。*/
Rxdata=Rxdata|0x01;
}
SCL_LOW;
Systick_NusDelay(5);
}
if(ACK==ackflag){ //如果ackflag为应答信号,那么表示接收完数据后,发送一个应答信号回去。如果ackflag为非应答信号,那么表示接收完数据后,发送一个非应答信号回去
IIC_SendACK();
}else{
IIC_SendNoACK();
}
SCL_LOW;
return Rxdata;
}
IIC完整代码
头文件代码:
#ifndef __DRV_IIC_H__
#define __DRV_IIC_H__
#include "stm32f10x.h"
#include "drv_systick.h"
#define SCL_HIGH GPIO_SetBits(GPIOB,GPIO_Pin_6)
#define SCL_LOW GPIO_ResetBits(GPIOB,GPIO_Pin_6)
#define SDA_HIGH GPIO_SetBits(GPIOB,GPIO_Pin_7)
#define SDA_LOW GPIO_ResetBits(GPIOB,GPIO_Pin_7)
#define SDA_READ GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)
#define ACK 0
#define NOACK 1
void IIC_Config(void);
void IIC_SDA_OUT(void);
void IIC_SDA_IN(void);
void IIC_START(void);
void IIC_STOP(void);
void IIC_SendACK(void);
void IIC_SendNoACK(void);
uint8_t IIC_WaitACK(void);
void IIC_SendByteData(uint8_t data);
uint8_t IIC_RxByteData(uint8_t ackflag);
#endif //__DRV_IIC_H__
IIC驱动代码
#include "drv_iic.h"
void IIC_Config(void) //初始化SCL
{
GPIO_InitTypeDef GPIO_InitStruct;
// 1,打开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
// 2,初始化GPIO
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_10MHz; //这里实际上就是在设置SCL的上升沿和下降沿的时间
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
void IIC_SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_7;//SDA
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_10MHz; //这里实际上就是在设置SDA的上升沿和下降沿时间
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
void IIC_SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_7; //SDA
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
// 起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
void IIC_START(void)
{
SCL_LOW;
IIC_SDA_OUT();
SDA_HIGH;
SCL_HIGH;
Systick_NusDelay(5);
SDA_LOW;
Systick_NusDelay(5);
SCL_LOW;
}
// 停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
void IIC_STOP(void)
{
SCL_LOW;
IIC_SDA_OUT();
SDA_LOW;
SCL_HIGH;
Systick_NusDelay(5);
SDA_HIGH;
Systick_NusDelay(5);
}
// 应答信号:SCL为高电平期间,SDA输出一个低电平
void IIC_SendACK(void)
{
SCL_LOW;
IIC_SDA_OUT();
SDA_LOW;
SCL_HIGH;
Systick_NusDelay(5);
SCL_LOW;
}
// 非应答信号:SCL为高电平期间,SDA输出一个高电平
void IIC_SendNoACK(void)
{
SCL_LOW;
IIC_SDA_OUT();
SDA_HIGH;
SCL_HIGH;
Systick_NusDelay(5);
SCL_LOW;
}
// 等待应答信号:拉高SCL线,等待SDA线上的高低电平
uint8_t IIC_WaitACK(void)
{
uint8_t temp=0;
SCL_LOW;
IIC_SDA_IN();
SCL_HIGH;
Systick_NusDelay(4);
while(SDA_READ){
temp++;
if(temp>250){
return NOACK;
}
}
SCL_LOW;
return ACK;
}
// 发送数据信号:当SCL线为高电平时,SDA线上的数据是稳定的,当SCL为低电平时,允许SDA线上的数据发生改变
void IIC_SendByteData(uint8_t data)
{
uint8_t i;
SCL_LOW;
IIC_SDA_OUT();
for(i=0;i<8;i++){
if(data&0x80){
SDA_HIGH;
}else{
SDA_LOW;
}
SCL_HIGH;
Systick_NusDelay(5);
SCL_LOW;
Systick_NusDelay(5);
data=data<<1;
}
SCL_LOW;
}
// 接收数据信号:拉高SCL线,读取SDA线上的信号,拉低SCL线,使从机准备下一个数据
uint8_t IIC_RxByteData(uint8_t ackflag)
{
uint8_t i;
uint8_t Rxdata=0;
SCL_LOW;
IIC_SDA_IN();
for(i=0;i<8;i++){
SCL_HIGH;
Systick_NusDelay(5);
Rxdata=Rxdata<<1;
if(SDA_READ){
Rxdata=Rxdata|0x01;
}
SCL_LOW;
Systick_NusDelay(5);
}
if(ACK==ackflag){
IIC_SendACK();
}else{
IIC_SendNoACK();
}
SCL_LOW;
return Rxdata;
}
IIC驱动EEPROM
EEPROM 电可擦除可改写存储器
特点:具有掉电保护的功能
我们经常将设置好的参数、密码保存在EEPROM中,当单片机再次上电时,我们的再将我们之前设置的参数、密码从EEPROM中取出,那么这样的话,我们就不需要再次去花费时间去再设置一遍参数和密码了。
那么,我们怎么将数据存储到EEPROM中去呢?我们可以使用IIC总线。
图中的AT24C02就是EEPROM器件,单片机运行程序,通过IIC总线将数据存储到EEPROM中去。
我们来看看EEPROM的规格书:
TWO-wire Serial EEPROM:两线串行的EEPROM。其中,两线:即是IIC的两线,串行:串行通信。
描述:
AT24C01A/02/04/08A/16A提供1024/2048/4096/8192/16384位串口电可擦除和可编程只读存储器(EEPROM)组织为128/256/512/1024/2048个8位一字节。该设备经过优化,适用于许多工业和商业应用,其中低功率和低电压操作是必不可少的。AT24C01A/02/04/08A/16A可提供节省空间的8引脚PDIP, 8引脚JEDEC SOIC, 8引脚MAP, 5引脚SOT23(AT24C01A/AT24C02/AT24C04), 8引脚TSSOP和8球dBGA2封装,通过两线串行接口访问。此外,整个系列还有2.7V (2.7V至5.5V)和1.8V (1.8V至5.5V)版本。
A0-A2引脚是地址的输入引脚,也是设备ID的输入
SDA:串行的数据线
SCL:串行的时钟线
WP:写保护引脚。一般是低电平有效,当写保护有效的时候,就不能往里面写数据了,就只能读取数据了。
这个是设备的ID(即:从设备地址),我们可以看到不同容量的存储设备,它们的ID的高4个位都是1010开头,只有A2、A1、A0是设备的ID,最后一位R/W是读/写位。所以,表达地址的就只有7位,即:1010 A2 A1 A0
我们可以看见A0、A1、A2(NC)都是接的地,所以它们都是0。所以,该从设备地址是101 0000,即0101 0000(0x50)。再加上一个读/写位,如下:
读数据操作:1010 0001,即0xa1
写数据操作:1010 0000,即0xa0
注意:从设备地址依然是0101 0000(0x50),只不过在读写数据的时候,会将从设备地址和读写位一起发送出去。
主芯片向EEPROM写一个字节数据
word address:字地址。表示EEPROM中的内存地址。发送给EEPROM的数据,需要存储在EEPROM中的哪个地址上去。
注意:读/写位:读操作为高电平,写操作为低电平。
注意:图中的写数据操作是主芯片向EEPROM写数据,然后主芯片等待EEPROM返回一个应答信号。
写数据代码
void AT24Cxx_WriteByteData(uint8_t ADDR,uint8_t DATA) //ADDR表示EEPROM中存储数据的地址,DATA表示要写的数据
{
IIC_START(); //发送起始信号
IIC_SendByteData(0xa0); //发送从设备ID,并设置为写操作
if(ACK!=IIC_WaitACK()){ //等待从设备返回应答信号
IIC_STOP(); //如果没有返回应答信号,则停止传输数据
return;
}
IIC_SendByteData(ADDR); //发送字地址
if(ACK!=IIC_WaitACK()){ //等待从设备返回应答信号
IIC_STOP(); //如果没有返回应答信号,则停止传输数据
return;
}
IIC_SendByteData(DATA); //开始发送数据
if(ACK!=IIC_WaitACK()){ //等待从设备返回应答信号
IIC_STOP(); //如果没有返回应答信号,则停止传输数据
return;
}
IIC_STOP(); //停止信号
}
主芯片从EEPROM中读取一个字节数据
读数据操作与写数据操作不同。首先,主芯片需要进行写操作,向EEPROM写入需要读的是EEPROM中哪个地址上面的数据,然后主芯片再进行读操作,读取EEPROM中该地址上面的数据。读完数据后,主芯片再向EEPROM发送一个非应答信号,表示读完数据了,此时EEPROM不用再发送数据给主芯片了。
读数据代码
uint8_t AT24Cxx_ReadByteData(uint8_t ADDR) // ADDR表示EEPROM中存储数据的地址
{
uint8_t Rxdata=0;
IIC_START(); //起始信号
IIC_SendByteData(0xa0); //先进行写操作
if(ACK!=IIC_WaitACK()){ //等待从设备返回应答信号
IIC_STOP();
return 0;
}
IIC_SendByteData(ADDR); //需要读取的是EEPROM中哪个地址上面的数据
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return 0;
}
IIC_START(); //起始信号
IIC_SendByteData(0xa1); //进行读操作
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return 0;
}
Rxdata=IIC_RxByteData(NOACK); //开始读数据。并且在读完数据后,主芯片向EEPROM发送一个非应答信号,表示读完数据了
IIC_STOP(); //停止信号
return Rxdata; //返回读到的数据
}
完整代码
#include "drv_at24cxx.h"
void AT24Cxx_WriteByteData(uint8_t ADDR,uint8_t DATA)
{
IIC_START();
IIC_SendByteData(0xa0);
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return;
}
IIC_SendByteData(ADDR);
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return;
}
IIC_SendByteData(DATA);
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return;
}
IIC_STOP();
}
uint8_t AT24Cxx_ReadByteData(uint8_t ADDR)
{
uint8_t Rxdata=0;
IIC_START();
IIC_SendByteData(0xa0);
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return 0;
}
IIC_SendByteData(ADDR);
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return 0;
}
IIC_START();
IIC_SendByteData(0xa1);
if(ACK!=IIC_WaitACK()){
IIC_STOP();
return 0;
}
Rxdata=IIC_RxByteData(NOACK);
IIC_STOP();
return Rxdata;
}
#include "drv_systick.h"
#include "stdio.h"
#include "drv_exti.h"
#include "drv_basetim.h"
#include "drv_iic.h"
#include "drv_at24cxx.h"
int main(void)
{
RCC_DeInit();
RCC_ConfigTo72M();
Systick_Config(72);
IIC_Config();
AT24Cxx_WriteByteData(0x55,0x80); //向EEPROM中的0x55地址,写入数据0x80
Systick_NmsDelay(2000);
printf("AT24Cxx(0x55)=%x\n",AT24Cxx_ReadByteData(0x55)); //读取EEPROM中0x55地址中存储的数据,并将其用串口打印出来
while(1){}
}