关于iic协议和对AT24C02进行读写数据的理解和代码解读
认识IIC协议
本文是基于正点原子的第二十九章 IIC实验编写的。包括stm32f407和msp432e401y的代码展示,编写目的是方面自己理解,以下纯属自己的理解。
IIC协议是一种通过利用两条线,然后依次传输8位的数据的协议,所以说明他是传二进制数据的,我们可以利用接受到的二进制数据转化成我们需要的数字和字符,这里的意思就是比如MPU6050(陀螺仪)、vl53lxx(激光测距)这样我们从模块中获取传感器的数据,所以熟练使用这种协议是很有必要的。
IIC协议其实并不复杂,你就可以理解成它就是有两条线,我们通过规定这两条线的高低电平不同状态下的含义,然后就可以通过电平传输数据,类似这样的协议甚至你可以自己去定协议。
IIC协议主要分为8个识别信号组成,我们也称之为时序信号,他们分别是:开始信号、停止信号、结束信号、等待信号、ACK应答信号、no ACK应答信号、写信号、读信号。其中,结束信号我们几乎可以不用,因为我们在使用的时候每次只需要开启,结束的时候停止即可。
①开始信号:准备开始传输数据。 ②停止信号:停止传输数据。 ③等待信号:我们读写完每一次数据,都要等待反馈告诉我们从机接收到没有,其实接受的就是④⑤两个状态。 ④ACK应答信号⑤no ACK应答信号:给主机的信号,告诉他现在我的状态怎么样,有没有接收到你的数据。 这个个地方可以参考:https://blog.csdn.net/shenhuxi_yu/article/details/65935697(第五点),其实意思就是告诉我读取的设备我还要不要继续读取。 ⑥写信号:写入信号。此时单片机为发送端。 ⑦读信号:读取信号。此时单片机为接收端。
如果没有理解没关系,因为IIC是底层协议,我们在写底层的时候,只需要初始化好,写好时序就行,基本上可以完整移植代码,真正应用层是24cxx等这样的操作文件。
IIC协议软件模拟方法
上面我们也说了,IIC底层是固定的,包括两部分,一个是初始化,一个是时序。
管脚初始化
我们只有两根线,一根线是SCL(信号线),一根是SDA(数据线)。所以使能两个管脚,我们就要办使能就行了,输出上拉。(为什么要上拉,因为对于某些单片机,可能性能不过,由于IIC对于管脚电平电话很敏感,如果你电平转不过来,那根本用不了,所以设置为上拉,那何为上拉,建议百度一下,其实就是在管脚并路上面串一个电阻,电阻另一头接VCC,这样在需要VCC电压的时候可以快速跳转)
STM32的
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
msp432
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOM);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
GPIOPinTypeGPIOOutput(GPIO_PORTM_BASE, GPIO_PIN_0);
GPIOPinTypeGPIOOutput(GPIO_PORTG_BASE, GPIO_PIN_0);
GPIOM->PUR |= GPIO_PIN_0;GPIOG->PUR |= GPIO_PIN_0;
在完成管脚初始化之后,我们还需要方便代码的编写,那什么是方便代码的编写呢?其实就是,我们在时序里面,经常要对电平拉高拉低,我们不可能每次都写gpioset();或者GPIOPinWrite();这样去操作电平,因为看起来很麻烦,所以我们需要在IIC的h文件中,进行相关宏定义,方便编写,这也是很多移植代码里面,常见的操作(下次出一期讲讲这个MPU6050移植到msp432上面的文章)。
#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}
#define IIC_SCL PBout(8)
#define IIC_SDA PBout(9)
#define READ_SDA PBin(9)
可能很多金针菇就要问了,这都啥意思。
IIC_SCL和IIC_SDA其实设置scl和sda的输出电平情况,这要写,
我们只要比如IIC_SCL=0就是低电平,=1就是高电平,至于是怎么操作的,自己研究去。
金针菇们又要问了,为什么需要将SDA设为输入,因为你读的时候是读电平,当然是输入。READ_SDA相当于读SDA的电平
设置SDA是输入输出的在
那至于怎么操作的,就是寄存器的操作,自己去理解去吧。
大家可以看看在msp432里面我们是如何实现的:
#define SDA_IN() GPIOPinTypeGPIOInput(GPIO_PORTG_BASE, GPIO_PIN_0)
#define SDA_OUT() GPIOPinTypeGPIOOutput(GPIO_PORTG_BASE, GPIO_PIN_0)
#define IIC_SCL HWREG(GPIO_PORTM_BASE + (0x00000000 + (GPIO_PIN_0 << 2)))
#define IIC_SDA HWREG(GPIO_PORTG_BASE + (0x00000000 + (GPIO_PIN_0 << 2)))
#define READ_SDA GPIOPinRead(GPIO_PORTG_BASE, GPIO_PIN_0)
我们可以看到代码非常的简单,除了HWREG,其实HWREG还有另一个也是H开头为了叫啥了,长一点的,都是对寄存器的操作(在官方SDK里面的timers和pwm都有),这个函数其实就是对寄存器进行操作,实现拉高或者拉低,实现IIC_SCL=?就能操作。
那金针菇又问了,为什么你都是gpio0,这个其实由于不是高电平都是1,可以看看
GPIO_PIN_?的宏定义,我们在移植mpu6050的时候讲讲。
时序
好了,我们如果将前面都弄完,其实这个时序,你都可以直接移植到任何单片机。那我们是时序呢?其实时序就是我们这个信号是,电平要怎么变化,然后他就属于这个信号,比如起始信号,是SCL高电平时,SDA由高到底,就代表起始信号,ok,其他的就不说,因为都是固定的。 理解了上面,代码就不是问题,我们这里举一个例子来说明一下,就拿起始信号
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;
delay_us(4);
IIC_SCL=0;
}
这里msp432就不介绍了,一模一样,但是IIC_SDA和IIC_SCL高电平不一定是1,这个可以在NRF24L01移植的时候仔细说说。
至此,底层IIC就写完了。
可能金针菇们又要问了,那硬件呢?咋弄,其实你理解了软件模拟的思想,就可以了,关于硬件使用信手沾来,你可以问硬件IIC,好多接口函数,比如什么什么中断,FIFO啥的,这个你仔细看就明白了,中断什么其实就是某个地方的标志位,他单独弄出来。
最后就是建议还是用硬件IIC,因为避免占用MCU资源,速率也更快,接口函数也更多,使用也更多样化;软件模拟在移植的时候比较方便。
AT24C02
简介
其实AT24C02就是IIC通讯的一个存储单元硬件而已,往里面存东西的而已。下面简单介绍一下AT24C02。
首先AT24C02的意思是,是2k位,也就是21024位。
存储大小计算
可能金针菇们又问了,怎么算了,还有那些存储空间单位怎么回事,下面可以看一下存储空间的单位换算: 1kB=1024B(1k Byte = 1024Byte)(1千字节) 1B=8b(1Byte=8bit)(1字节为8个位)
那金针菇又问了,那为什么是2*k位,因为这里的k是kb,小b,也就是bit。
所以AT24C02大小是21024位,那我们当然是一个个字节存放啦,所以有21024/8=256个字节。
金针菇又问了,我知道这个干吗,因为IIC一次传输8位,也就是8个bit,所以每一次是读取一个字节,也就是我们有256个字节读取,可以看一下下面的图:
工作方式
接着就是我们AT24C02工作方式了,首先介绍一下AT24C02上面有三个管脚:A0、A1、A2,那是用来干什么的呢?是用来规定地址的,比如我们AT24C02这三个管脚都是用来固定地址,他们都接地了,所以我们访问的时候是传输1010 000x(1010是固定的,000就是A2、A1、A0,你也别问为什么是1010,厂家规定的,他总的有个位吧?,因为他是8位呀,总得传点什么),那什么是x,x就是你是读是写,1是读,0是写。(如果看不懂地址作用,那就一直看下去,下面讲工作流程有讲)
那我们每次只能读取一个字节的数据,所以,我们只要输入我们想要读写的地址就可以完成相关操作。
那聪明点的金针菇又问了,那他比256要多呢?ok,但是我们换个说法,是比2k多。你可能就猜到了,没错,都是2k、2k的存储,如果大于2k,我们就要利用地址(地址作用不明白往下看),2k的A0-A2是固定的,如果我们大于2k就要利用起来(2k一个区),比如4k,那我们只固定A2、A1(具体忘了哪个,就那个意思)A0可变,这样A0就可以决定你访问哪一个区(两个2 k的区,一个去256个字节)。(看不懂的金针菇多看几篇)
流程(代码)
那说完一些基础知识,那就开始实操了。有那么几个函数: ①u8 AT24CXX_Check(void)
②u8 AT24CXX_ReadOneByte(u16 ReadAddr); ③void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);
④void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead) ⑤void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
⑥void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) ⑦u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
那聪明的金针菇又要问了,怎么三个读写呀?下面来具体解释一下代码:
首先是AT24CXX_Check,这个函数其实就是检测AT24C02存不存在,那怎么检测的呢?其实就是,他在255字节地方写了个0X55进去,然后读取看看有没有,你其实也可以写在其他字节、其他符号(比如0xaa啥的),主要写了一个你不常用的地方,然后每次使用前读取看看对不对,读取到,ok,那就行了。
如何是②和③,是底层的读写函数,正如其名称,一个读写一个字节。这里解释一下IIC_Wait_Ack();这里每次发送指令都要IIC_Wait_Ack是因为确认发送的指令他收到没。另外就是,为什么有EE_TYPE>AT24C16,其实是为了判断你的存储大小是不是超过16k,所以所以正点的代码其实兼容了其他大小了,那究竟怎么做到的呢?我们想想,16k=8*2k,所以我们A0-A3已经用满了,需要其他方式,至于大于16k怎么用就没研究了,反正都差不多。
IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); 这句话兼容2-16k的,我们在读写第一步都是确认我们读写哪个区,对于AT24C02,后面都是0,只读写0XA0,因为他只有一个区,比如AT24C04,4个,两个256的区,那他的区就是0XA0和0XA1,后面的地址操作其实就是超过256,我们就是第一个去,就是1,就是0XA1,其他同理。(注意WriteAddr是16位)
④⑤和⑥⑦可能金针菇们就不理解了,怎么由两个读写函数。你可能发现④⑤在main没有用,其他这种函数你自己写就行,你想实现什么功能,就怎么写,注释也很清楚了,这里要注意一点,读写时从高位开始,因为比如我定义一个u32的temp,我读一次,是不是temp = (u8)data啊,然后temp<<=8,所以他第一次读的数据,最后会变成高8位。
基本上知识就那么多,总结一下。IIC传输速度慢,一次只能传8位数据,但是在现实生产中还是很常见的。
|