IIC通信协议代码分享及调试过程中的一些小误区分享
1.前奏
很久没有更新博客了,hiahia,之前忙着考研,现在却忙着找工作,哎咦呦~,但也没事,研没考上也可以好好学学我感兴趣的东西,用这些来找工作也挺好,做自己喜欢的事情,研究生还是想考,等快到暑假再看吧。这段时间重新把以前学的东西又巩固了一遍,和我想的一样,以前的基础果然不牢。这次感觉花的时间挺久的就是IIC协议了,经过这次自己敲代码写,对协议的理解更加深了。所以想分享给大家,主要是代码,虽然网上有很多,但是就是想把自己的代码分享出来,当然,其中也有一些走过的小误区,可以给大家做一个参考,也可以让自己以后回写协议的时候有一个回忆参考。
2.IIC协议简单说明
开始信号:SCL为高电平时,SDA为下降沿;
停止信号:SCL为高电平时,SDA为上升沿;
应答信号:发送完一个字节后,当SCL为高电平时,读出的SDA为低电平;
发送数据:把SDA数据一位一位准备好,SCL为上升沿时一位一位地写入数据,方向为高位到低位;
接收数据:SCL为下降沿时一位一位地读出数据,读取数据的方向为高位到低位;
3.代码分享与说明及小误区分享
我这里的代码因为是我自己写的,有的地方想尽量写简单,所以才会犯了我调试过程中的一些错误,这里面大家当做参考就好,如有和大家一样的错误,那就真是英雄所见略同啦,这里用的主控芯片是STM32F103ZET6。
开始信号:
//下降沿为开始信号void IIC_Start(void){ SDA_OUT(); SDA=1; SCL=1; delay_us(3); SDA=0; delay_us(3); SCL=0;}
小说明:这里的第二第三句SDA=1;SCL=1;最好不要变。
误区解读:若是SCL=1;SDA=1;我们不能确定在SCL=1之前SDA是高电平还是低电平,假如是低电平,那么这两条语句之后,SDA就会产生一个上升沿。
停止信号:
//上升沿为停止信号void IIC_End(void){ SDA_OUT(); SDA=0; SCL=1; delay_us(3); SDA=1; delay_us(3); SCL=0; delay_us(1);}
小说明:同理,这里的第二第三句SDA=0;SCL=1;不能变。
误区解读:若是SCL=1;SDA=0;我们不能确定在SCL=1之前SDA是高电平还是低电平,假如是高电平,那么这两条语句之后,SDA就会产生一个下降沿。这里我就是因为把这两句调换了,所以才一直接受不到数据,调试了很久,不知道是哪的问题,后来还是走在路上买东西一直想才终于搞明白了,然后才有了想法写一篇博客,hiahia~,开心开心。
应答信号:
//返回值为0表示应答成功,1表示非应答uint8_t IIC_Ack(void){ uint8_t count; SDA_IN(); SCL=0; delay_us(3); SCL=1; delay_us(2); while(Read_SDA==1) { count++; if(count>250) { IIC_End(); return 1; } } SCL=0; //释放时钟线 return 0;}
小说明:这里定义了一个count,为的就是防止SDA接收不到低电平程序卡炸在这里。第三句SCL=0; delay_us(3);是先释放总线,等待SDA的低电平信号,但是亲测过,不加这两句也可以。
发送数据:
//上升沿写入数据,从高位到低位写数据void IIC_Send_One_Byte(uint8_t data){ uint8_t i; SCL=0; SDA_OUT(); for(i=0;i<8;i++) { if((data&0x80)>>7)SDA=1;elseSDA=0;data<<=1;delay_us(3);SCL=1;delay_us(3);SCL=0;delay_us(2); }}
小说明:先把SDA上的数据准备好,然后利用SCL上升沿发送,注意是先高位再低位发送就可以,逐位数据代码发送的算法也可以自己再想想。
接收数据:
//下降沿读出数据,从高位到低位读数据uint8_t IIC_Read_One_Byte(void){ uint8_t data=0; uint8_t i; SDA_IN(); for(i=0;i<7;i++) { SCL=1; delay_us(3); data|=Read_SDA; data<<=1; SCL=0; delay_us(3); } return data;}
误区解读:这里注意i<7,之前想当然地以为接收8位数据就是i<8了,后来在自己的脑海中模拟了一下这段程序才想明白。
向AT24C02指定地址写一个字节:
extern uint8_t num;void AT24C02_Write_One_Byte(uint8_t addr,uint8_t data){ IIC_Start(); IIC_Send_One_Byte(0xA0); if(IIC_Ack()==0) { IIC_Send_One_Byte(addr); if(IIC_Ack()==0){ IIC_Send_One_Byte(data); if(IIC_Ack()==0) { IIC_End(); USART1_Send(num); }} } delay_ms(10); //可以注释掉}
小说明:这里按照AT24C02的写字节操作来就好了
向AT24C02指定地址读一个字节:
uint8_t AT24C02_Read_One_Byte(uint8_t addr){ uint8_t data; IIC_Start(); IIC_Send_One_Byte(0xA0); if(IIC_Ack()==0) { IIC_Send_One_Byte(addr); if(IIC_Ack()==0){ IIC_Start(); IIC_Send_One_Byte(0xA1); if(IIC_Ack()==0) { data=IIC_Read_One_Byte(); IIC_End(); }} } delay_ms(10); //可以注释掉 return data;}
小说明:这里按照AT24C02的读指定地址操作来就好了
其他函数:
#define SCL PBout(6)#define SDA PBout(7)#define Read_SDA PBin(7)//转变SDA的输入输出模式#define SDA_IN() {GPIOB->CRL&=0x0fffffff;GPIOB->CRL|=0x80000000;}#define SDA_OUT() {GPIOB->CRL&=0x0fffffff;GPIOB->CRL|=0x30000000;}void AT24C02_Init(void){ GPIO_InitTypeDef GPIO_Struct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_Struct.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_Struct.GPIO_Pin=GPIO_Pin_6; //IIC SCL GPIO_Struct.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_Struct); GPIO_Struct.GPIO_Pin=GPIO_Pin_7; //IIC SDA GPIO_Init(GPIOB,&GPIO_Struct); GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); }int main(void){ AT24C02_Init(); delay_init(); key_init(); uart_init(115200); num=AT24C02_Read_One_Byte(0); USART1_Send(num); while(1) { if(key0==0) //°´¼ü°´Ï { delay_ms(10); if(key0==0) { num+=2; AT24C02_Write_One_Byte(0,num); }while(!key0);} }}
4.实验现象:
小说明:按一次key0,num的值加2,然后显示在串口上位机上,一直连续重复出现的AC、AE、B2、B6我按过复位键之后显示的,由此可以检验AT24C02可以断电之后存储原来的数据,代码磨问题啦~
5.写完收工!
若是有错误,欢迎大家指正!有想法,欢迎大家一起探讨!