1、24C02简介
24C02是一个2Kbit的串行EEPROM存储芯片,可存储256个字节数据。工作电压范围为1.8V到6.0V,具有低功耗CMOS技术,自定时擦写周期,1000000次编程/擦除周期,可保存数据100年。24C02有一个16字节的页写缓冲器和一个写保护功能。通过I2C总线通讯读写芯片数据,通讯时钟频率可达400KHz。
可以通过存储IC的型号来计算芯片的存储容量是多大,比如24C02后面的02表示的是可存储2Kbit的数据,转换为字节的存储量为2*1024/8 = 256byte;有比如24C04后面的04表示的是可存储4Kbit的数据,转换为字节的储存量为2*1024/8 = 512byte;以此来类推其它型号的存储空间。
24C02的管脚图如下:
VCC和VSS是芯片的电源和地,电压的工作范围为:+1.8V~+6.0V。
A0、A1、A2是IC的地址选择脚。
WP是写保护使能脚。
SCL是I2C通讯时钟引脚。
SDA是I2C通讯数据引脚。
2、24C02的设备地址和写写保护功能
I2C主机在与24C02通讯时,需要发送一个设备地址进行寻址,在I2C总线上,每一个从机设备的地址都是唯一的。
24C02的设备地址包含两部分,第一部分是bit7~bit4是固定的“1010”,第二部分bit3~bit1位由A2、A1、A0组成。主机在与24C02进行通讯时,除了发送设备地址还需要发送数据的读写方向位R/W,24C02的是设备地址与R/W位组成了一个字节的数据。如下图:
上图列出了几个存储IC的设备地址与R/W位组成的字节。由图中可以看到,存储IC地址的bit7~bit4位固定为“1010”;bit3~bit1位由A2、A1、A0引脚的电平状态决定,如果Ax接的是电源(高电平),那么Ax=1,如果Ax接的是地,那么Ax=0,即由A2、A1、A0可以组合成8种设备地址,也就是说在同一个I2C总线上可以同时挂载8个24C02芯片。一般如果I2C总线上只有一片24C02芯片的话,A2、A1、A0引脚都接到地。
由于24C02只有256个字节的存储空间,所以只需要1个字节就可以寻址完24C02的存储空间,但是无法寻址完更大容量的存储IC,比如24C04的存储容量是512字节,需要9个bit的地址位才能寻址完。由上图可以看到,24C04的设备地址内是没有A0参数的,被a8代替了,这个a8就是24C04的第9个bit的地址位,也就是说24C04的A0引脚是不起作用的,这样也就造成了在I2C总线上只能同时挂载4个24C04芯片。其它存储器如24C08、24C16也可以这么类推。
24C02的WP引脚是写保护引脚,当WP引脚接高电平的时,24C02只能进行读取操作,不能进行写操作。只有当WP引脚悬空或接低电平时,24C02才能进行写操作。
3、24C02数据读取操作
在这里只是对24C02的读写进行一些说明和一些注意的实现,不会涉及具体的程序代码,只是进行代码概述,工程代码已经上传到个人GitHub中,感兴趣的可以去GitHub中下载查看,GitHub代码地址如下:
https://github.com/h1019384803/STM32F103ZET6_I2C.git。这是一个使用STM32F103ZET6的IO模拟I2C操作24C02的工程。
MCU通过使用I2C读取24C02任意存储空间地址内的数据,代码如下:
1 uint8_t AT24CXX_READ_ONE_BYTE(uint16_t address)
2 {
3 uint8_t dat;
4
5 I2C_START();
6 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD);
7 if(AT24CXX_ERR != 0)//没有响应直接退出
8 {
9 AT24CXX_ERR = I2C_WRITE_BYTE(address & 0xFF);
10 if(AT24CXX_ERR != 0)
11 {
12 I2C_START();
13
14 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_READ_CMD);
15 if(AT24CXX_ERR != 0)
16 {
17 dat = I2C_READ_BYTE(0);
18 I2C_STOP();
19 }
20 }
21 }
22
23 return dat;
24 }
第5行主机产生一个I2C起始信号,第6行发送设备地址和写数据位给24C02,第9行是发送需要读取的地址给24C02,第12行主机产生一个重复起始信号,第14行设备地址和读数据位给24C02,第17行是读取24C02相应地址存储的数据。第18行是主机产生I2C结束信号。
在上面的程序代码中,AT24CXX_ERR是用来获取24C02的应答信号,如果主机与24C02的通讯正常,主机每发送一个字节给24C02,24C02都会反馈一个应答信号给主机,如果24C02没有反馈应答信号,那么说明24C02正在进行其它操作或者通讯异常导致无法通讯,主机会产生一个结束信号来结束操作。在I2C_WRITE_BYTE()函数内部有一个等待应答信号的操作,如果没有收到应答信号,在I2C_WRITE_BYTE()函数内会产生一个停止信号来结束当前操作。AT24CXX_ERR用来判断接下来的操作是否执行,如果AT24CXX_ERR=0说明没有收到应答信号,直接退本次读取操作;如果AT24CXX_ERR!=0说明有收到应答信号,继续读取操作。
24C02内部有一个地址计数器,主机发送要读写的存储空间地址给24C02,就相当于改变24C02的内部地址计数器的值,主机每读写一个字节24C02之后,它内部地址计数器的值就会自动加1。也就是说如果当前地址是N,那么主机读取完一个字节的数据之后,再次读的话就变为了读取N+1地址的数据。
这里需要注意的一点是,24C02的内部地址计数器的地址只能从0~255之间递增,这是因为24C02的存储控制只有256个字节,地址计数器只能在0~255(共256个地址)内变化。如果连续读取使得地址计数器超过255,那么地址计数器就会从0地址开始循环。比如说当前内部计数器地址为255,主机在读取一个字节数据之后会导致内部计数器地址变为0,那么主机再次读取数据的时候读取得到的是24C02地址0的数据。
MCU使用I2C连续读取24C02内多个存储空间地址数数的代码如下:
1 void AT24CXX_READ_BUFF(uint16_t address,uint8_t *buffer,uint16_t Len) 2 { 3 uint16_t i; 4 5 I2C_START(); 6 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD); 7 if(AT24CXX_ERR != 0)//没有响应直接退出 8 { 9 AT24CXX_ERR = I2C_WRITE_BYTE(address & 0xFF); 10 if(AT24CXX_ERR != 0) 11 { 12 I2C_START(); 13 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_READ_CMD); 14 if(AT24CXX_ERR != 0) 15 { 16 for(i=0;i) 17 { 18 buffer[i] = I2C_READ_BYTE(0); 19 } 20 } 21 22 if(AT24CXX_ERR != 0) 23 { 24 I2C_STOP(); 25 } 26 } 27 } 28 }
上面的代码,大部分跟读取一个字节的程序代码是一样的,不一样的是第16~19行,这里用一个for循环来连续读取24C02内的数据,这里并没有对超范围读取数据进行限制,所以在使用的时候需要注意不要连续读取超过24C02的存储空间,就算超过也不会有问题,只是会重新开始从0地址读取。
4、24C02数据写入操作
MCU使用I2C写入一个字节数据到24C02任意存储空间地址内的代码如下:
1 void AT24CXX_WRITE_ONE_BYTE(uint16_t address,uint8_t dat)
2 {
3 I2C_START();
4 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD);
5 if(AT24CXX_ERR != 0)//没有响应直接退出
6 {
7 AT24CXX_ERR = I2C_WRITE_BYTE(address & 0xFF);
8 if(AT24CXX_ERR != 0)
9 {
10 AT24CXX_ERR = I2C_WRITE_BYTE(dat);
11 if(AT24CXX_ERR != 0)
12 {
13 I2C_STOP();
14 }
15 }
16 }
17 }
第3行主机产生一个I2C起始信号,第4行发送设备地址和写数据位给24C02,第7行是发送需要写入数据的地址给24C02,第10行是将要写入的数据发送给24C02。第18行是主机产生I2C结束信号。上面大部分操作跟读取是一样的,不一样的只是最后将读取操作改为了写入操作。
如果需要连续写入数据,可以如下:
1 for(i = 0;i < 256;i ++)
2 {
3 AT24C02_BUFF[i] = i;
4 AT24CXX_WRITE_ONE_BYTE(i,AT24C02_BUFF[i]);
5 }
但是在实际使用的过程中,发现只有一部分AT24C02_BUFF[]数组里面的数据被写入到了24C02当中,有一些数据没有写进24C02。这是因为24C02擦写数据没有那么快,需要一定的时间,在24C02正在擦写数据的过程中,是不会应答主机的通讯的,所以如果主机在写入一个数据之后又立马写入另一个数据,就会导致24C02跟不上主机的通讯速度从而导致无法写入数据。
需要注意的是24C02并不是在主机发送数据给24C02之后就立马擦写数据的,24C02是在主机产停止信号之后才开始擦写数据的,并且在擦写数据完成之前不会响应主机的其它操作。
可以通过一定的延时函数来等待24C02擦写完成,代码如下:
for(i = 0;i < 256;i ++)
{
AT24C02_BUFF[i] = i;
AT24CXX_WRITE_ONE_BYTE(i,AT24C02_BUFF[i]);
HAL_Delay(1);
}
通过调用HAL_Delay()函数进行延时,具体的延时时间可以通过调试来决定,这里使用1ms的延时时间,具体24C02擦写数据需要多久并不清楚。
除了通过延时函数进行等待24C02擦写完成,也可以通过发送设备地址给24C02,然后查询是否有应答信号返回来判断24C02是否擦写完成。24C02在擦写数据时是反馈应答信号给主机的,这样就可以通过不断的发送数据给24C02,然后查询应答信号来判断24C02是否擦写完成,一旦擦写完成就可以进行下一个数据的写入。代码如下:
1 void Wait_AT24CXX_WRITE_OK(void)
2 {
3 uint8_t Wait_Cnt;
4
5 Wait_Cnt = 50;
6 do
7 {
8 I2C_START();
9 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD);
10 if(AT24CXX_ERR != 0)
11 {
12 I2C_STOP();//接收到响应信号退出
13 break;
14 }
15
16 }while(Wait_Cnt--);
17
18 }
Wait_Cnt是一个次数限制变量,不能无限的在里面等待,不然遇到异常就有可能造成程序卡死。
程序通过发送起始信号、发送设备地址和写数据方向给24C02,如果24C02反馈了一个应答信号给主机,主机就产生一个停止信号,然后退出当前循环。应用代码如下:
1 for(i = 0;i < 256;i ++)
2 {
3 AT24C02_BUFF[i] = i;
4 AT24CXX_WRITE_ONE_BYTE(i,AT24C02_BUFF[i]);
5 Wait_AT24CXX_WRITE_OK();//可以通过发送设备地址给从机,通过从机反馈的响应信号来判断从机是否可以正常通讯
6 }
5、24C02页写入
24C02有一个页写入功能,可以连续写入16个字节的数据。
24C02可以以页来划分存储空间,每16个字节组成一个页,24C02的存储空间大小为256个字节,所以24C02总共有16个页。如:
页0:地址从0x00~0x0F
页1:地址从0x10~0x1F
......
页15:地址从0xF0~0xFF
24C02可以在一个页内连续的写入数据,但是需要注意的是如果写入的数据超过页大小,那么就会覆盖页初始地址的值,比如说连续写入3个数据,第1个数据写入到地址0x0F当中,第2个数据由于溢出页的限制,会被写入到地址0x00当中,第3个数据会被写入到地址0x01当中。
以个人的理解,24C02内部有一个16byte的数据缓存器,在上面的介绍中知道,主机在发送数据给24C02的时候,24C02是不会擦写数据的,只有当主机发送停止信号之后24C02才会擦写数据。那么当主机发送数据给24C02时,只是将数据写入到了24C02内部的缓存器中,只有当主机发送结束信号之后,24C02才将缓存器内的数据写入到内部存储空间。
由24C02的数据缓存器只有16个byte(每个型号的存储IC的页大小是不一样的也就是缓存器大小是不不一样的)。所以如果写入的数据超过缓存器的大小就会覆盖之前写入的数据。
使用页写入连续将数据写入24C02的代码如下:
1 void AT24CXX_WRITE_BUFF(uint16_t address,uint8_t *Buffer,uint16_t Len)
2 {
3 uint8_t i;
4 uint16_t re_main;
5
6 if(address >= 256)//对输入的地址进行限制,24C02只有256个字节的存储空间,其它型号的存储器IC可以通过查资料
7 {
8 return;
9 }
10
11 re_main = 256 - address;//计算出还有多少存储空间
12
13 if(Len > re_main)//如果要写入的数据量超过剩余存储空间,则只写入剩余存储空间数量的数据
14 {
15 Len = re_main;
16 }
17
18 re_main = 16 - address%16;//计算当前页还可以写入多少个数据
19
20 if(Len <= re_main)//如果要写入的数据小于等于当前页剩余的存储空间,则只写入Len个字节数据就好,不需要跨页操作
21 {
22 re_main = Len;
23 }
24
25 do
26 {
27 I2C_START();
28 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD);
29 if(AT24CXX_ERR == 0)//没有响应直接退出
30 {
31 break;
32 }
33
34 I2C_WRITE_BYTE(address & 0xFF);
35 for(i = 0;i < re_main;i ++)//最多连续写入一个页数据的大小
36 {
37 AT24CXX_ERR = I2C_WRITE_BYTE(Buffer[i]);
38 }
39
40 I2C_STOP();
41 Wait_AT24CXX_WRITE_OK();//等待24C02完成擦写数据动作
42
43 if(re_main != Len)
44 {
45 address += re_main;//已经写入re_main个数据,
46 Buffer += re_main;
47 Len -= re_main;
48
49 re_main = 16;//写一个页的的大小也是16个字节
50
51 if(Len <= re_main)
52 {
53 re_main = Len;
54 }
55 }
56 else
57 {
58 break;//数据写入完成退出
59 }
60 }
61 while(1);
62 }
AT24CXX_WRITE_BUFF()函数的思路如下:
首先判断输入的地址是否超过存储IC的存储空间,如果超过则退出,如第6~9行。
计算出输入的地址到存储器存储的结束地址剩余多少空间,如果要写入的数据比剩余空间还多,那么剩余多少空间就写入多少空间,需要写入的多余部分就去掉,如第11~16行。
计算当前页还可以写入多少数据,如果当前页剩余的空间比Len要写入的数据长度还大,那么只要写入当前页就足够了,不需要再跨页写入数据。如第18~23行。
通过一个while(1)循环连续写入数据到24C02中。
第27~41行是将re_main个数据写入到24C02当中,由于是在一个页内操作,所以可以连续写入,写完之后在产生一个结束信号然后等待24C02擦写完成。
如果不需要跨页写入就已经将数据全部写完,那么就可以直接break退出while循环,如第58行。
如果需要跨页写入数据,还需要将地址、buffer、Len减去已经写入的数据量,然后下一个页可以写入整个页的数据量,也就是16个字节,通过比较判断一个页的剩余空间是否能够写完剩余的Len数据,如果不行就重复循环操作,如果可以写完,在写完之后就退出while循环。