AT24C02是一种失去电源供给后依旧能保持数据的储存器,常用来储存一些配置信息,在系统重新上电之后也可以加载。它的容量是2k bit的EEPROM存储器,采用I2C通信方式。
AT24C02支持两种写操作:字节写操作和页写操作。本实验中我们采用的是字节写操作,就是一个地址一个数据这样进行数据写入。页写模式就是连续写入数据,只需写入一个地址,连续写入数据的时候,地址会自动后移,但有页限制,超出一页的时候,超出数据会覆盖原先写入的数据。
AT24C02支持三种读操作:当前地址读操作,随机地址读操作和顺序读操作模式。
当前地址读模式是基于上一次读/写操作的最后位置继续读出数据。随机地址读模式是指定地址读出数据。顺序读操作模式就是连续读出数据,会自动翻页。
注意到我们采用的STM32F1系列里,SCL时钟线为PB6,SDA数据线为PB7。本次实验中,时钟线我们就正常的设置为推挽输入,数据线则设置为开漏模式(因为我们引入了外部上拉电阻,提供稳定的空闲高电平,而且我们的数据线既要作为输出,又要作为输入,用开漏输出模式,能很好地实现输入输出共用,避免频繁IO模式切换带来的麻烦)。编写代码时,注意完成发送的时候,主机要释放SDA。
在开漏模式下,MCU读取IDR状态寄存器,来获取引脚高低电平。
接下来编写我们的实验代码:
首先编写我们的函数头文件iic.c:
#include "./BSP/IIC/iic.h"
#include "./SYSTEM/delay/delay.h"
void iic_init(void){
GPIO_InitTypeDef gpio_init_struct = {0};
IIC_SCL_GPIO_CLK_ENABLE();
IIC_SDA_GPIO_CLK_ENABLE();
//SCL设置
gpio_init_strcut.Pin = IIC_SCL_GPIO_PIN;
gpio_init_strcut.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_strcut.Pull = GPIO_PULLUP;
gpio_init_strcut.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(IIC_GPIO_PORT, &gpio_init_strcut); //IIC_GPIO_PORT就是GPIOB
//SCL设置
gpio_init_strcut.Pin = IIC_SDA_GPIO_PIN;
gpio_init_strcut.Mode = GPIO_MODE_OUTPUT_OD;
HAL_GPIO_Init(IIC_GPIO_PORT, &gpio_init_strcut);
}
static void iic_delay(void){
delay_ms(2);
}
void iic_start(void){
//SCL为高电平时,SDA从高电平向低电平跳变,制造起始信号
IIC_SDA(1);
IIC_SCL(1);
delay_ms();
IIC_SDA(0);
delay_ms();
IIC_SCL(0);
delay_ms();//钳住总线,准备发送或者接受数据
}
void iic_stop(void){
//SCL为高电平的适合,SDA从低电平向高电平跳变,制造停止信号
IIC_SDA(0);
delay_ms();
IIC_SCL(1);
delay_ms();
IIC_SDA(1);
delay_ms();
}
uint8_t iic_wait_ack(void){ //主机检测应答信号
IIC_SDA(1); //主机释放SDA线
delay_ms();
IIC_SCL(1); //从机返回ACK
delay_ms();
if(IIC_READ_SDA){ //SDA高电平表示读取到高电平,从机未作出应答,由于上拉电阻有高电平阻塞效果,表示读取到非应答信号
iic_stop();
return 1;
}
IIC_SCL(0);
delay_ms();
return 0;
}
void iic_ack(void){ //从机发送低电平应答信号,将主机发送的高电平信号导入至此,使主机的数据线检测函数得到的是低电平,继续要求数据
IIC_SCL(0);
delay_ms();
IIC_SDA(0);
delay_ms();
IIC_SCL(1);
delay_ms();
}
void iic_nack(void){ //从机发送非应答信号,不再要求数据
IIC_SCL(0);
delay_ms();
IIC_SDA(1);
delay_ms();
IIC_SCL(1);
delay_ms();
}
void iic_send_byte(uint8_t data){
for(uint8_t t = 0; t < 8; t++){
//高位先发
IIC_SDA((data & 0x80) >> 7);
IIC_SCL(1);
delay_ms();
IIC_SCL(0);
data <<= 1; //左移一位,进行下一次发送
}
IIC_SDA(1); //完成后,释放数据线,进行应答检测
}
uint8_t iic_read_byte(uint8_t ack){
uint8_t receive = 0;
for(uint8_t i = 0; t < 8; t++){
//高位先被输入,左移以腾空右边的位置给下一低位数据
receive <<= 1;
IIC_SCL(1);
delay_ms();
if(IIC_READ_SDA) receive++;
IIC_SCL(0);
delay_ms();
}
if(!ack) iic_nack();
else iic_ack();
return receive;
}
接下来编写函数头文件iic.h:
#ifndef __IIC_H
#define __IIC_H
void iic_init(void);
static void iic_delay(void);
void iic_start(void);
void iic_stop(void);
uint8_t iic_wait_ack(void);
void iic_ack(void);
void iic_nack(void);
void iic_send_byte(uint8_t data);
uint8_t iic_read_byte(uint8_t ack);
#endif
接下来再编写存储器的函数文件24cxx.c:
#include "./BSP/IIC/iic.h"
#include "./BSP/24CXX/24cxx.h"
#include "./SYSTEM/delay/delay.h"
void at24c02_init(void){
iic_init();
}
void at24c02_write_byte(uint8_t addr, uint8_t data){
iic_start();
iic_send_byte(0xA0); //发送通讯地址(写地址操作)
iic_wait_ack();
iic_send_byte(addr);
iic_wait_ack();
iic_send_byte(data);
iic_wait_ack();
iic_stop();
delay_ms(10);
}
uint8_t at24c02_read_byte(uint8_t addr){
uint8_t rec = 0;
iic_start();
iic_send_byte(0xA0); //发送通讯地址(写地址操作)
iic_wait_ack();
iic_send_byte(addr);
iic_wait_ack();
iic_start();
iic_send_byte(0xA1); //发送通讯地址(写地址操作)
iic_wait_ack();
iic_send_byte(addr);
iic_wait_ack();
rec = iic_read_byte(0);
iic_stop();
return rec;
}
接下来编写存储器函数文件的头文件:24cxx.h:
#ifndef __24CXX_H
#define __24CXX_H
void at24c02_init(void);
void at24c02_write_byte(uint8_t addr, uint8_t data);
uint8_t at24c02_read_byte(uint8_t addr);
#endif
到这里我们的示例代码便编写完成了。