分享:
这两天将STM32的IIC按照原子哥的程序,大致走了一遍,多少对IIC不是那么地陌生了,也多少有了自己的一些感悟,在这里,将这两天的学习的一个大致步骤总结下,一来可以让自己形成一个清晰地思路,二来,希望能给一些新手一点启发。
首先IIC是一种通信协议,通信方式相对比较简单,主要有两条线,SDA,SCL。SDA是串行数据线,上面走命令和数据,而SCL只是一条时钟线,其保证数据是按照时钟节拍来进行传输。IIC上面可以外挂很多的IIC芯片,每一个芯片对应着不同的地址,通过地址来将不同的芯片进行分开,保证不同芯片之间的数据传输,由于每一个芯片都是可以独立地收发,故,每一个芯片都是主机/从机。接下来,就是数据的传输过程了,
大致的一个数据传输流程是:主机向SDA线上发送一个起始信号,表示有信号进行传输,此时所有连接到IIC总线上的芯片都处于接收状态,接下来,主机发送想要与其进行数据传输的从机地址信号,所有的从机都会接收到该地址信号并和自己固有的地址信号进行匹配,当配对成功时,接下来就在时钟信号的带动下进行数据传输,数据的传输是按照每8位一个单元进行数据的传输。每一位的传输过程中,在SCL高电平期间,一定要保证SDA数值的稳定,否则会出现出错的情况,SDA数值的改变发生在SCL的低电平期间。最终8位全部传输完毕,从机产生一个应答信号给主机,主机在接收到该应答信号后决定接下来是发送一组新的数据还是终止发送。
大致按照传输的一个流程和模拟IIC的源码来对数据传输过程进行分析:
分析之前,将一些定义告诉大家,免得有疑问:
//I/O方向设置
#define SDA_OUT() {GPIOB->CRL &= 0X0FFFFFFF;GPIOB->CRL |= 3<<28;} //0011 通用推挽输出,50M
#define SDA_IN() {GPIOB->CRL &= 0X0FFFFFFF;GPIOB->CRL |= 8<<28;} //1000 上拉/下拉输入
//I/O操作函数
#define IIC_SCL PBout(6)
#define IIC_SDA PBout(7)
#define READ_SDA PBin(7)
其中SDA_IN()、 SDA_OUT()设置了I/O口的方向,也就是SDA是输入那?还是输出那?
IIC_SCL 是SCL输出管脚
IIC_SDA 是SDA输出管脚
READ_SDA 读取SDA输入引脚,等待应答信号时要用。
OK!接下来就磨刀霍霍,开始分析数据传输过程
1、起始信号
首先,SDA、SCL线默认是高,表示总线处于空闲状态,接着SDA线被主机拉低,表示主机有信号进行传输,要么是发数据,或者是要进行读数据,当SDA线拉低之后,SCL线也同样被拉低,准备接下来的数据传输。用了原子哥的模拟IIC,此开始信号的产生如下:
声明:此时将CPU当做了主机,而AT24C02当做了从机。
//起始信号
void IIC_Start(void)
{
SDA_OUT(); //设为主机输出
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 0; //scl为高电平时,SDA出现低电平跳变,表示传输开始
delay_us(5);
IIC_SCL = 0; //钳位SCL,方便接下来进行数据传输
}
此程序产生的波形就是上面起始信号所对应的波形。
2、终止信号
终止信号:就是在SCL为高电平时,SDC出现一个上升沿的跳变,即表示终止信号
源程序如下:
//终止信号
void IIC_Stop(void)
{
SDA_OUT(); //设为主机输出
IIC_SDA = 0;
IIC_SCL = 1;
delay_us(2); //scl为高电平,SDA出现高电平跳变,表示传输结束
IIC_SDA = 1;
}
3、等待应答信号
主机将一组数据发送完毕,接下来就进入等待从机发送応答信号的到来,从机会在第9个时钟信号时,发送该应答信号,此应答信号就是将SDA信号线拉低,而又由于SDA、SCL为开漏输出,故支持线与的形式,当从机那边将SDA线拉低,而主机这边依旧是将SDA线置高,主机通过判断SDA线的高低电平就可以知道是否收到了该应答信号,当然,接收到该应答信号的前提是,使能了从机的应答功能,否则接受不到,之能通过每组发送完毕的一个长延时来保证从机接收数据完毕。
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u16 Errtime=0;
SDA_IN(); //此时设置为输入
IIC_SCL = 1;
IIC_SDA = 1; //线与的关系
if(READ_SDA)
{
Errtime++;
if(Errtime > 250)
{
IIC_Stop();
return 1;
}
}
else
IIC_SCL = 0; //钳位,方便下次传输
return 0;
}
程序中需要注意的地方是,虽然设置了GPIO的方向为输入,但是GPIO的电平还是可以设置的,没有矛盾,GPIO方向由CRL CRH来决定,输出电平由ODR寄存器来决定
4、产生应答信号
如上所说,从机可以选择产生应答信号,同样也可以选择不产生应答信号。
产生应答辛号就是从机将SDA线拉低,不产生应答辛号就是SDA线一直保持高电平
//产生应答信号
void IIC_Ack(void)
{
SDA_OUT(); //此时,相当于主机在接收数据,是被动方
IIC_SCL = 0;
IIC_SDA = 0;
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
}
需要注意的是,此时,CUP相当于是从机,故I/O方向为输出
5、不产生应答信号
//不产生应答信号
void IIC_NAck(void)
{
SDA_OUT(); //此时,相当于主机在接收数据,是被动方
IIC_SCL = 0;
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
}
道理同上
6、写一个字节
按照该时序图就可以写数据了,流程如下:
主机:起始信号----->第一位------>第二位---------> ........ -------->第八位---------> 等待应答信号------->停止信号
SDA数据的有效性,在上面已经分析过,就是在SCL高电平期间,SDA数据不能改变,这样才是一个有效数据,否则无效
SDA只能在SCL低电平期间改变
//发送一个字节数据
void IIC_Send_Byte(u8 txd)
{
u8 i;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(i=0;i<8;i++)
{
if(txd & 0x80)
IIC_SDA = 1;
else
IIC_SDA = 0;
txd <<= 1;
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
delay_us(2);
}
}
高位先发,程序中严格保证了SDA的有效性
7、读一个字节
此时CPU相当于是从机,而芯片才是主机,主机在发数据,从机在接受
//读一个字节数据 ack = 1 时,产生应答 ack = 0 ,不产生应答
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();
for(i=0;i<8;i++)
{
IIC_SCL = 0;
delay_us(2);
IIC_SCL = 1; //高电平时数据已经稳定,故进行数据的读取
receive <<= 1;
if(READ_SDA)
receive++;
}
if(ack)
IIC_Ack();
else
IIC_NAck();
return receive;
}
以上就是模拟IIC的函数
接下来就要针对AT24C02来进行程序的编写
1、在AT24C02指定地址读出一个数据
先来看AT24C02的随机读数据的时序图
此时序图上需要注意两个地方,箭头所标记的两次开始信号,以及椭圆圈起来的写信号和读信号,接下来就分析该过程
首先,发送一个开始信号,接下来发送器件的地址,注意最后一位为Write标志位(0),等待应答,然后发送要读的地址,等待应答,又要发送一个起始信号,发送器件的地址,注意此时,最后一位为Read标志位(1),等待应答,接下来数据就可以读回来了。
器件的地址:
地址按照这个来进行设置,AT24CXX,这个XX即表示存储量XX(K),很显然AT24C02为2K,有256Byte,A2,A1,A0定义看芯片,如下
故,AT24C02的写地址为0XA0,读地址为0XA1,到这里,任督二脉已打通,接下来,上源码
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址 0-255
//返回值 :读到的数据
u8 AT24C02_ReadOneByte(u8 ReadAddr)
{
u8 temp;
IIC_Start();
IIC_Send_Byte(0XA0); //发送器件地址
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr); //发送首地址
IIC_Wait_Ack();
IIC_Start(); //注意,这个需要一个新的起始信号
IIC_Send_Byte(0XA1);
IIC_Wait_Ack();
temp = IIC_Read_Byte(0); //不产生应答
IIC_Stop();
return temp;
}
2、在AT24C02指定地址写入一个数据
有了上面读的分析,这里应该可以看懂,注意箭头那位就好,严格按照这个流程来就好了
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24C02_WriteOneByte(u8 WriteAddr,u8 DataToWrite)
{
IIC_Start();
IIC_Send_Byte(0XA0); //发送器件地址
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr); //发送首地址
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite);
IIC_Wait_Ack();
IIC_Stop();
delay_ms(10); //这个延时是必须的,保证下次发送之前,上次的数据已经被接收
}
需要注意的地方是这个延时,红色标记,因为后面要用到这个进行连续的写操作,故这里加一个延时,保证下次写之前,上次的数据已经被完全接收,否则有可能会出错
3、在AT24C02里面的指定地址开始写入长度为Len的数据
直接调用上面的在指定位置写数据的函数即可
//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr :开始写入的地址
//DataToWrite:数据数组首地址
//Len :要写入数据的长度2,4
void AT24C02_WriteLenByte(u16 WriteAddr,u32 DataToWtie,u8 Len)
{
u8 i;
for(i=0;i<Len;i++)
{
AT24C02_WriteOneByte(WriteAddr + i,(DataToWtie >> (8*i)) & 0XFF);
}
}
需要注意的是,此时将数据的高位写到了后面地址处,所以接下来读数据的时候,要从后面开始读,免得出错
4、在AT24C02里面的指定地址开始读取长度为Len的数据
同上,直接调用上面在指定位置读取数据的函数即可
//在AT24CXX里面的指定地址开始读取长度为Len的数据
//该函数用于读取16bit或者32bit的数据.
//WriteAddr :开始写入的地址
//Len :要写入数据的长度2,4
u32 AT24C02_ReadLenByte(u16 WriteAddr,u8 Len)
{
u8 i;
u32 temp=0;
for(i=0;i<Len;i++)
{
temp <<= 8;
temp += AT24C02_ReadOneByte(WriteAddr + Len - i - 1);
}
return temp;
}
注意,先读的是后面的数据
5、校验IIC芯片是否在,是否正常
通过IIC芯片的最后一个地址255,写入数据0X55,当能读回这个数据时,说明芯片工作正常,否则不正常
源码如下
//检查AT24C02是否正常
//这里用了2402的最后一个地址(255)来存储标志字.0x55
//返回1:检测失败
//返回0:检测成功
u8 AT24C02_Check(void)
{
u8 temp=0;
temp = AT24C02_ReadOneByte(255);
if(temp == 0X55)
{
return 0;
}
else
{
AT24C02_WriteOneByte(255,0X55);
temp = AT24C02_ReadOneByte(255);
if(temp == 0X55)
return 0;
}
return 1;
}
上来直接读,可以免得每次都要写,写一次就好了,其他时间都是读她就行
6、在AT24C02里面的指定地址开始读出指定个数的数据
依旧是调用上面的,不多说了,要饿死了zzzzzzzzz
源码:
//在AT24C02里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24C02_Read(u8 ReadAddr,u8 *pBuffer,u8 NumToRead)
{
while(NumToRead)
{
*pBuffer++ = AT24C02_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
7、在AT24C02里面的指定地址开始写指定个数的数据
源码:
//在AT24C02里面的指定地址开始写指定个数的数据
//WriteAddr :开始写数据的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToWrite:要写数据的个数
void AT24C02_Write(u8 WriteAddr,u8 *pBuffer,u8 NumToWrite)
{
while(NumToWrite)
{
AT24C02_WriteOneByte(WriteAddr++,*pBuffer++);
NumToWrite--;
}
}
看好DATASHEET是关键,我所获得的有很多都是来自于网络,所以要记得回馈,羊有跪乳之恩,只是当今时代发展太快,都忘记了感恩,心怀感恩,世界是这么美好,希望也能给你一点帮助吧!