写点什么

STM32 入门开发 介绍 IIC 总线、读写 AT24C02(EEPROM)(采用模拟时序)

作者:DS小龙哥
  • 2022 年 8 月 15 日
    重庆
  • 本文字数:7521 字

    阅读完需:约 25 分钟

一、环境介绍

编程软件: keil5


操作系统: win10


MCU 型号: STM32F103ZET6


STM32 编程方式: 寄存器开发 (方便程序移植到其他单片机)


IIC 总线: STM32 本身支持 IIC 硬件时序的,本文采用的是模拟时序,下篇文章就介绍配置 STM32 的 IIC 硬件时序读写 AT24C02 和 AT24C08。


模拟时序更加方便移植到其他单片机,通用性更高,不分 MCU;硬件时序效率更高,单每个 MCU 配置方法不同,依赖硬件本身支持。


目前器件: 采用 AT24C02 EEPROM 存储芯片

二、AT24C02 存储芯片介绍

2.1 芯片功能特性介绍

AT24C02 是串行 CMOS 类型的 EEPROM 存储芯片,AT24C0x 这个系列包含了 AT24C01、AT24C02、AT24C04、AT24C08、AT24C16 这些具体的芯片型号。


他们容量分别是:1K (128 x 8)、2K (256 x 8)、4K (512 x 8)、8K (1024 x 8)、16K (2048 x 8) , 其中的 8 表示 8 位(bit)


它们的管脚功能、封装特点如下:




芯片功能描述:


AT24C02 系列支持 I2C,总线数据传送协议 I2C,总线协议规定任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器;数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件和从器件都可以作为发送器或接收器,但由主器件控制传送数据(发送或接收)的模式,由于 A0、A1 和 A2 可以组成 000~111 八种情况,即通过器件地址输入端 A0、A1 和 A2 可以实现将最多 8 个 AT24C02 器件连接到总线上,通过进行不同的配置进行选择器件。


芯片特性介绍:


\1. 低压和标准电压运行 –2.7(VCC=2.7 伏至 5.5 伏) –1.8(VCC=1.8 伏至 5.5 伏)


\2. 两线串行接口(SDA、SCL)


\3. 有用于硬件数据保护的写保护引脚


\4. 自定时写入周期(5 毫秒~10 毫秒),因为内部有页缓冲区,向 AT24C0x 写入数据之后,还需要等待 AT24C0x 将缓冲区数据写入到内部 EEPROM 区域.


\5. 数据保存可达 100 年


\6. 100 万次擦写周期


\7. 高数据传送速率为 400KHz、低速 100KHZ 和 IIC 总线兼容。 100 kHz(1.8V)和 400 kHz(2.7V、5V)


\8. 8 字节页写缓冲区 这个缓冲区大小与芯片具体型号有关: 8 字节页(1K、2K)、16 字节页(4K、8K、16K)

2.2 芯片设备地址介绍


IIC 设备的标准地址位是 7 位。上面这个图里 AT24C02 的 1010 是芯片内部固定值,A2 、A1、 A0 是硬件引脚、由硬件决定电平;最后一位是读/写位(1 是读,0 是写),读写位不算在地址位里,但是根据 IIC 的时序顺序,在操作设备前,都需要先发送 7 位地址,再发送 1 位读写位,才能启动对芯片的操作,我们在写模拟时序为了方便统一写 for 循环,按字节发送,所以一般都是将 7 地址位与 1 位读写位拼在一起,组合成 1 个字节,方便按字节传输数据。


我现在使用的开发板上 AT24C02 的原理图是这样的:



那么这个 AT24C02 的标准设备地址就是: 0x50(十六进制),对应的二进制就是: 1010000


如果将读写位组合在一起,读权限的设备地址: 0xA1 (10100001) 、写权限的设备地址: 0xA0 (10100000)

2.3 对 AT24C02 按字节写数据的指令流程(时序)


详细解释:


\1. 先发送起始信号


\2. 发送设备地址(写权限)


\3. 等待 AT24C02 应答、低电平有效


\4. 发送存储地址、AT24C02 内部一共有 256 个字节空间,寻址是从 0 开始的,范围是(0~255);发送这个存储器地址就是告诉 AT24C02 接下来的数据改存储到哪个地方。


\5. 等待 AT24C02 应答、低电平有效


\6. 发送一个字节的数据,这个数据就是想存储到 AT24C02 里保存的数据。


\7. 等待 AT24C02 应答、低电平有效


\8. 发送停止信号

2.3 对 AT24C02 按页写数据的指令流程(时序)


详细解释:


\1. 先发送起始信号


\2. 发送设备地址(写权限)


\3. 等待 AT24C02 应答、低电平有效


\4. 发送存储地址、AT24C02 内部一共有 256 个字节空间,寻址是从 0 开始的,范围是(0~255);发送这个存储器地址就是告诉 AT24C02 接下来的数据改存储到哪个地方。


\5. 等待 AT24C02 应答、低电平有效


\6. 可以循环发送 8 个字节的数据,这些数据就是想存储到 AT24C02 里保存的数据。


AT24C02 的页缓冲区是 8 个字节,所有这里的循环最多也只能发送 8 个字节,多发送的字节会将前面的覆盖掉。


需要注意的地方: 这个页缓冲区的寻址也是从 0 开始,比如: 0~7 算第 1 页,8~15 算第 2 页......依次类推。 如果现在写数据的起始地址是 3,那么这一页只剩下 5 个字节可以写;并不是说从哪里都可以循环写 8 个字节。


详细流程: 这里程序里一般使用 for 循环实现


(1). 发送字节 1


(2). 等待 AT24C02 应答,低电平有效


(3). 发送字节 2


(4). 等待 AT24C02 应答,低电平有效


.........


最多 8 次.


\7. 等待 AT24C02 应答、低电平有效


\8. 发送停止信号

2.4 从 AT24C02 任意地址读任意字节数据(时序)


AT24C02 支持当前地址读、任意地址读,最常用的还是任意地址读,因为可以指定读取数据的地址,比较灵活,上面这个指定时序图就是任意地址读。


详细解释:


\1. 先发送起始信号


\2. 发送设备地址(写权限)


\3. 等待 AT24C02 应答、低电平有效


\4. 发送存储地址、AT24C02 内部一共有 256 个字节空间,寻址是从 0 开始的,范围是(0~255);发送这个存储器地址就是告诉 AT24C02 接下来应该返回那个地址的数据给单片机。


\5. 等待 AT24C02 应答、低电平有效


\6. 重新发送起始信号(切换读写模式)


\7. 发送设备地址(读权限)


\8. 等待 AT24C02 应答、低电平有效


\9. 循环读取数据: 接收 AT24C02 返回的数据.


读数据没有字节限制,可以第 1 个字节、也可以连续将整个芯片读完。


\10. 发送非应答(高电平有效)


\11. 发送停止信号

三、IIC 总线介绍

2.1 IIC 总线简介

I2C(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备,是微电子通信控制领域广泛采用的一种总线标准。具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。


I2C 规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。


I2C 总线通过串行数据(SDA)线和串行时钟(SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别,而且都可以作为一个发送器或接收器(由器件的功能决定)。


I2C 有四种工作模式: 1.主机发送 2.主机接收 3.从机发送 4.从机接收


I2C 总线只用两根线:串行数据 SDA(Serial Data)、串行时钟 SCL(Serial Clock)。


总线必须由主机(通常为微控制器)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。


SDA 线上的数据状态仅在 SCL 为低电平的期间才能改变。

2.2 IIC 总线上的设备连接图


I2C 总线在物理连接上非常简单,分别由 SDA(串行数据线)和 SCL(串行时钟线)及上拉电阻组成。通信原理是通过对 SCL 和 SDA 线高低电平时序的控制,来产生 I2C 总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。


其中上拉电阻范围是 4.7K~100K。

2.3 I2C 总线特征

I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个从设备都会对应一个唯一的地址(可以从 I2C 器件的数据手册得知)。主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把 CPU 带 I2C 总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。


1. 总线上能挂接的器件数量 I2C 总线上可挂接的设备数量受总线的最大电容 400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址的限制。 一般 I2C 设备地址是 7 位地址(也有 10 位),地址分成两部分:芯片固化地址(生产芯片时候哪些接地,哪些接电源,已经固定),可编程地址(引出 IO 口,由硬件设备决定)。 例如: 某一个器件是 7 位地址,其中 10101 xxx 高 4 位出厂时候固定了,低 3 位可以由设计者决定。 则一条 I2C 总线上只能挂该种器件最少 8 个。 如果 7 位地址都可以编程,那理论上就可以达到 128 个器件,但实际中不会挂载这么多。


2. 总线速度传输速度: I2C 总线数据传输速率在标准模式下可达 100kbit/s,快速模式下可达 400kbit/s,高速模式下可达 3.4Mbit/s。一般通过 I2C 总线接口可编程时钟来实现传输速率的调整。


3. 总线数据长度 I2C 总线上的主设备与从设备之间以字节(8 位)为单位进行双向的数据传输。

2.4 I2C 总线协议基本时序信号

空闲状态: SCL 和 SDA 都保持着高电平。


起始条件: 总线在空闲状态时,SCL 和 SDA 都保持着高电平,当 SCL 为高电平期间而 SDA 由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他 I2C 器件无法访问总线。


停止条件: 当 SCL 为高而 SDA 由低到高的跳变,表示产生一个停止条件。


答应信号: 每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为低,则表示一个应答信号。


非答应信号: 每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为高,则表示一个应答信号。应答信号或非应答信号是由接收器发出的,发送器则是检测这个信号(发送器,接收器可以从设备也可以主设备)。


注意:起始和结束信号总是由主设备产生。

2.5 起始信号与停止信号

起始信号就是: 时钟线 SCL 处于高电平的时候,数据线 SDA 由高电平变为低电平的过程。SCL=1;SDA=1;SDA=0;


停止信号就是: 时钟线 SCL 处于低电平的时候, 数据线 SDA 由低电平变为高电平的过程。SCL=1;SDA=0;SDA=1;


2.6 应答信号

数据位的第 9 位就时应答位。 读取应答位的流程和读取数据位是一样的。示例: SCL=0;SCL=1;ACK=SDA; 这个 ACK 就是读取的应答状态。


2.7 数据位传输时序

通过时序图了解到,SCL 处于高电平的时候数据稳定,SCL 处于低电平的时候数据不稳定。


那么对于写一位数据(STM32--->AT24C02): SCL=0;SDA=data; SCL=1;


那么对于读一位数据(STM32<-----AT24C02): SCL=0;SCL=1;data=SDA;


2.8 总线时序

四、IIC 总线时序代码、AT24C02 读写代码

在调试 IIC 模拟时序的时候,可以在淘宝上买一个 24M 的 USB 逻辑分析仪,时序出现问题,使用逻辑分析仪一分析就可以快速找到问题。


4.1 iic.c 这是 IIC 模拟时序完整代码

 #include "iic.h" /* 函数功能:IIC接口初始化 硬件连接: SDA:PB7 SCL:PB6 */ void IIC_Init(void) {     RCC->APB2ENR|=1<<3;//PB     GPIOB->CRL&=0x00FFFFFF;     GPIOB->CRL|=0x33000000;     GPIOB->ODR|=0x3<<6; } /* 函数功能:IIC总线起始信号 */ void IIC_Start(void) {     IIC_SDA_OUTMODE(); //初始化SDA为输出模式     IIC_SDA_OUT=1;       //数据线拉高     IIC_SCL=1;           //时钟线拉高     DelayUs(4);        //电平保持时间     IIC_SDA_OUT=0;       //数据线拉低     DelayUs(4);        //电平保持时间     IIC_SCL=0;           //时钟线拉低 } /* 函数功能:IIC总线停止信号 */ void IIC_Stop(void) {     IIC_SDA_OUTMODE();    //初始化SDA为输出模式     IIC_SDA_OUT=0;       //数据线拉低     IIC_SCL=0;           //时钟线拉低     DelayUs(4);           //电平保持时间     IIC_SCL=1;           //时钟线拉高     DelayUs(4);           //电平保持时间     IIC_SDA_OUT=1;       //数据线拉高 } /* 函数功能:获取应答信号 返 回 值:1表示失败,0表示成功 */ u8 IIC_GetACK(void) {     u8 cnt=0;     IIC_SDA_INPUTMODE();//初始化SDA为输入模式     IIC_SDA_OUT=1;        //数据线上拉     DelayUs(2);         //电平保持时间     IIC_SCL=0;            //时钟线拉低,告诉从机,主机需要数据     DelayUs(2);         //电平保持时间,等待从机发送数据     IIC_SCL=1;            //时钟线拉高,告诉从机,主机现在开始读取数据     while(IIC_SDA_IN)   //等待从机应答信号     {         cnt++;         if(cnt>250)return 1;     }     IIC_SCL=0;            //时钟线拉低,告诉从机,主机需要数据     return 0; } /* 函数功能:主机向从机发送应答信号 函数形参:0表示应答,1表示非应答 */ void IIC_SendACK(u8 stat) {     IIC_SDA_OUTMODE(); //初始化SDA为输出模式     IIC_SCL=0;           //时钟线拉低,告诉从机,主机需要发送数据     if(stat)IIC_SDA_OUT=1; //数据线拉高,发送非应答信号     else IIC_SDA_OUT=0;      //数据线拉低,发送应答信号     DelayUs(2);            //电平保持时间,等待时钟线稳定     IIC_SCL=1;               //时钟线拉高,告诉从机,主机数据发送完毕     DelayUs(2);            //电平保持时间,等待从机接收数据     IIC_SCL=0;               //时钟线拉低,告诉从机,主机需要数据 } /* 函数功能:IIC发送1个字节数据 函数形参:将要发送的数据 */ void IIC_WriteOneByteData(u8 data) {     u8 i;     IIC_SDA_OUTMODE(); //初始化SDA为输出模式     IIC_SCL=0;           //时钟线拉低,告诉从机,主机需要发送数据     for(i=0;i<8;i++)     {         if(data&0x80)IIC_SDA_OUT=1; //数据线拉高,发送1         else IIC_SDA_OUT=0;      //数据线拉低,发送0         IIC_SCL=1;               //时钟线拉高,告诉从机,主机数据发送完毕         DelayUs(2);            //电平保持时间,等待从机接收数据         IIC_SCL=0;                   //时钟线拉低,告诉从机,主机需要发送数据         DelayUs(2);            //电平保持时间,等待时钟线稳定         data<<=1;              //先发高位     } } /* 函数功能:IIC接收1个字节数据 返 回 值:收到的数据 */ u8 IIC_ReadOneByteData(void) {     u8 i,data;     IIC_SDA_INPUTMODE();//初始化SDA为输入模式     for(i=0;i<8;i++)     {         IIC_SCL=0;            //时钟线拉低,告诉从机,主机需要数据         DelayUs(2);         //电平保持时间,等待从机发送数据         IIC_SCL=1;            //时钟线拉高,告诉从机,主机现在正在读取数据         data<<=1;                    if(IIC_SDA_IN)data|=0x01;         DelayUs(2);         //电平保持时间,等待时钟线稳定     }     IIC_SCL=0;                  //时钟线拉低,告诉从机,主机需要数据 (必须拉低,否则将会识别为停止信号)     return data; }
复制代码

4.2 AT24C02.c 这是 AT24C02 完整的读写代码

 #include "at24c02.h" /* 函数功能:检查AT24C02是否存在 返 回 值:1表示失败,0表示成功 */ u8 At24c02Check(void) {     u8 data;     At24c02WriteOneByteData(255,0xAA);     data=At24c02ReadOneByteData(255);     if(data==0xAA)return 0;     else return 1; } /* 函数功能:AT24C02随机读数据 函数形参:读取的地址(0~255) 返 回 值:读出一个数据 */ u8 At24c02ReadOneByteData(u32 addr) {     u8 data;     IIC_Start(); //发送起始信号        IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式     IIC_GetACK();//获取应答     IIC_WriteOneByteData(addr); //设置读取数据的位置     IIC_GetACK();//获取应答     IIC_Start(); //发送起始信号        IIC_WriteOneByteData(AT24C02_READ_ADDR); //设置读模式     IIC_GetACK();//获取应答     data=IIC_ReadOneByteData(); //接收数据     IIC_SendACK(1); //发送非应答信号     IIC_Stop(); //停止信号     return data; } /* 函数功能:AT24C02写一个字节的数据 函数形参:         addr:写入的地址(0~255)         data:写入的数据 */ void At24c02WriteOneByteData(u32 addr,u8 data) {     IIC_Start(); //发送起始信号     IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式     IIC_GetACK();//获取应答     IIC_WriteOneByteData(addr); //设置写入数据的位置     IIC_GetACK();//获取应答     IIC_WriteOneByteData(data); //设置写入的数据     IIC_GetACK();//获取应答     IIC_Stop();  //停止信号     DelayMs(10); //等待写入完毕 } /* 函数 功 能:AT24C02当前位置读一个字节数据 函数返回值:读出的数据 */ u8 At24c02CurrentAddrReadOneByteData(void) {     u8 data;     IIC_Start(); //发送起始信号     IIC_WriteOneByteData(AT24C02_READ_ADDR); //设置读模式     IIC_GetACK();//获取应答     data=IIC_ReadOneByteData(); //接收数据     IIC_SendACK(1); //发送非应答信号     IIC_Stop(); //停止信号     return data; } /* 函数功能:AT24C02连续读数据 函数形参: u8 addr   //读取的地址(0~255) u8 len    //读取的长度 u8 *buff  //读出的数据存放缓冲区 */ void At24c02ReadByteData(u32 addr,u8 len,u8 *buff) {     u8 i;     IIC_Start(); //发送起始信号        IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式     IIC_GetACK();//获取应答     IIC_WriteOneByteData(addr); //设置读取数据的位置     IIC_GetACK();//获取应答      IIC_Start(); //发送起始信号        IIC_WriteOneByteData(AT24C02_READ_ADDR); //设置读模式     IIC_GetACK();//获取应答     for(i=0;i<len;i++)     {          buff[i]=IIC_ReadOneByteData(); //接收数据          IIC_SendACK(0); //发送应答信号     }     IIC_SendACK(1); //发送非应答信号     IIC_Stop(); //停止信号 } /* 函数功能:AT24C02页写 函数形参:         addr:写入的地址(0~255)         *data:写入的数据缓冲区         len :写入的长度 1. 页写的缓冲区大小是8个字节,一次最多写8个字节进去。 2. 页写的地址是固定的。 0~7 是第一页 8~15是第二页             */ void At24c02PageWrite(u32 addr,u8 *data,u8 len) {     u8 i;     IIC_Start(); //发送起始信号     IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式     IIC_GetACK();//获取应答     IIC_WriteOneByteData(addr); //设置写入数据的位置     IIC_GetACK();//获取应答     for(i=0;i<len;i++)     {         IIC_WriteOneByteData(data[i]); //设置写入的数据         IIC_GetACK();//获取应答     }     IIC_Stop();  //停止信号     DelayMs(10); //等待写入完毕 } void AT24C02_WriteData(u32 addr,u8 *data,u8 len) {     u32 page_remain=8-addr%8; //一页剩余的字节数量     if(page_remain>=len)     {         page_remain=len;     }     while(1)     {         At24c02PageWrite(addr,data,page_remain);         if(page_remain==len)         {             break;         }         addr+=page_remain;         data+=page_remain;         len-=page_remain;         if(len>=8)page_remain=8;         else page_remain=len;     } }
复制代码


发布于: 刚刚阅读数: 3
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
STM32入门开发 介绍IIC总线、读写AT24C02(EEPROM)(采用模拟时序)_8月月更_DS小龙哥_InfoQ写作社区