不积跬步无以至千里,不积小流无以成江海。
睡落枕了,怪早上睡懒觉睡的太嗨了,一觉到中午也是够了,怕是对自己的惩罚,早睡早起身体好。革命的本钱不能丢!
代码下载可以到我的Github上<传送门>
开始器件之前,先来复习一波I2C。(只捡重点提…)
<1>、I2C通信主要靠两根线SCL和SDA
<2>、高位在先低位在后。(对比UART的低位在先)
<3>、有ACK和NAK一说。
<4>、起始信号和终止信号时在SCL在高电平器件变化。数据信号时在SCL为低电平期间变化,SCL高电平器件读取。
<5>、关于时序的掌握,感觉有一句话说的特别好!
“整体上来说器件都是有一个最快速度的限制,而没有最慢限制,所以当换用高速的单片机后通常都是靠在各步骤间插入软件延时来满足较慢的时序要求。”
EEPROM系列名字有24X01/02/04/08/16,名字就显而易见的告诉了我们它的大小,比如01表示容量1K bit,其余同理。
还有几个EEPROM的小知识
<1>、8-byte Page (1K, 2K), 16-byte Page (4K, 8K, 16K) Write Modes(1K和2K的每页都是8字节,其余几个每页16字节)
<2>、1K/2K硬件地址位有三个,言外之意可以一个总线挂8个1K/2K的I2C器件。
而4K两个地址位最多挂四个。8K一个地址位最多挂2个。16K只能一个总线挂一个!
❤️.0>、写字节(停止信号前ACK)
❤️.1>、写页(停止信号前ACK)
<4.0>读当前地址(停止信号前NAK)
<4.1>读随机地址(停止信号前NAK)
<4.2>读连续地址(停止信号前NAK)
<5>、写数据的时候需要注意,eeprom是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!如果在这个时候去让它应答是没有响应的!
<6>、24C02是2kbits也就是256字节,对应地址时0x00~0xFF.
好了,基础知识的提取差不多就是这些了,下面开始搞代码。
1、EEPROM初探
试读取一个存在的器件地址,然后串口打印响应的ACK。
关于代码有几点要说的,
Q1:器件地址如何确定呢?
根据这张图,
高四位是1010,接下来三位硬件地址000,然后最后一个是写对应0
合起来,0xA0。
但是细心的你一定发现了,我程序中的代码地址似乎并不是如上面所分析的那样。
而是0x50,,,对应 0101 0000。。。
别急,看我II2C里面的I2CAddressing
,有一个addr << 1
左移一位后对应的不就是0xA0了嘛!这样写的好处是,可以方便操作读和写,写就和上面一样,读的话(addr << 1) | 0x01
不就行了嘛!一箭双雕,一举两得,何乐不为呢?
Q2:延时为什么选择5us呢?
I 2 C 通信分为低速模式 100kbit/s、快速模式 400kbit/s 和高速模式3.4Mbit/s。因为所有的 I 2 C 器件都支持低速,但却未必支持另外两种速度,所以作为通用的I 2 C 程序这里选择了 100k 这个速率来实现,也就是说实际程序产生的时序必须小于等于 100k的时序参数。由datasheet,很明显也就是要求 SCL 的高低电平持续时间都不短于 5us,
个人认为,这样才更符合我们的逻辑!嘻嘻。。。
2、单字节读写
读出0x02地址对应的数据,然后+1后再写回去!
这个有一点需要知道,24C02是2kbits也就是256字节,对应地址时0x00~0xFF…
这个程序中我们虽然有去响应ACK,但是并未处理。因为我们仅仅是写一个字节,再次上电时间远远大于5ms,所以不必处理ACK。但是如果我们连续写字节,就必须考虑这个应答位的问题了,下面也将介绍到!
梳理一下几个要点:
A、在本例中单片机是主机,24C02 是从机;
B、无论是读是写,SCL 始终都是由主机控制的;
C、写的时候应答信号由从机给出,表示从机是否正确接收了数据;
D、读的时候应答信号则由主机给出,表示是否继续读下去。
3、多字节读写
多字节读写模式访问EEPROM,然后依次+1、+2、+3…返回。
注意程序是一步步进化的,较之上面的摒弃了,I2CAddressing
、I2CWriteByte
、I2CReadByte
而加入了E2Read
和E2Write
…
注意比较新加入的两者的区别,len的位置,读是可以连续地址读的,不需要再进行一遍 起始信号 -> 控制字节 -> …
读的整个流程 起始信号 -> 控制字节(写) -> 地址字节 -> 起始信号 -> 控制字节(读) -> …ACK - (len- 1)个字节…->NAK 第len个字节 -> 停止信号
写就不一样了,每写一个字节就得再进行一遍 起始信号 -> 控制字节 -> …
写的整个流程 起始信号 -> 控制字节(写) -> 地址字节 -> 写入数据 -> 停止信号 ->起始信号 -> 控制字节(写) … -> 停止信号
还有一点注意的是,每次写操作之前,我们都要进行查询判断当前 EEPROM 是否响应,正常响应后才可以写数据。
4、连续读和分页写
连续读分页写,同上访问eeprom,然后依次+1+2+3返回。
上面第三个我们也能感受到一次写一个字节的慢,然后等待ACK后才能写入下一个字节。效率太低!所以就诞生了分页写的模式。
这一次我们专门成立了一个eeprom的模块。
24C02,一共是 256 个字节,8 个字节一页,那么就一共有 32 页。
分配好页之后,如果我们在同一个页内连续写入几个字节后,最后再发送停止位的时序。EEPROM 检测到这个停止位后,就会一次性把这一页的数据写到非易失区域,就不需要像上节课那样写一个字节检测一次了,并且页写入的时间也不会超过 5ms。
如果我们写入的数据跨页了,那么写完了一页之后,我们要发送一个停止位,然后等待并且检测 EEPROM 的空闲模式,一直等到把上一页数据完全写到非易失区域后,再进行下一页的写入,这样就可以在很大程度上提高数据的写入效率。
手册当然也明确说了,如果强行跨页就会从该页开头覆盖写!
贴出E2PROM的代码
void E2Read(u8 *buf, u8 addr, u8 len)
{
do{
I2CStart();
if(I2CWrite(0x50<<1))
{
break;
}
I2CStop();
}while(1);
I2CWrite(addr);
I2CStart();
I2CWrite((0x50<<1)|0x01);
while(len > 1)
{
*buf++ = I2CReadACK();
len--;
}
*buf = I2CReadNAK();
I2CStop();
}
void E2Write(u8 *buf, u8 addr, u8 len)
{
while(len > 0)
{
do{
I2CStart();
if(I2CWrite(0x50<<1))
{
break;
}
I2CStop();
}while(1);
I2CWrite(addr);
while(len > 0)
{
I2CWrite(*buf++);
len--;
addr++;
if((addr & 0x07) == 0)
{
break;
}
}
I2CStop();
}
}