【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议R Read 为 1 W Write 为 0

大家好,欢迎来到IT知识分享网。

AT24C02芯片(又叫E2PROM存储器、EEPROM存储器),是一种通过I2C(IIC)协议通信的掉电保存存储器芯片,其内部含有256个8位字节在介绍这款芯片之前,我们先来粗略了解一下I2C协议。

I2C总线是一种双向二线制的同步串行总线,它只需要两根线即可在连接于总线上的器件之间传送信息(分别为SDA和SCL)。在I2C总线上,可以有若干个从机(如AT24C02芯片),但只能有一个主机(如单片机)。像不同的通信协议一样,I2C协议规定了一些SDA、SCL的行为(如什么时候谁置高、什么时候谁置低,用以代表什么含义),连在I2C总线上的器件则依靠这个规则来传输数据与接收数据。

(想要深入了解I2C协议,可以参考嵌入式硬件入门——EEPROM(AT24C02+I2C协议))

通常,单片机上内置了硬件I2C。开启硬件I2C,并使用相应的HAL库函数,单片机中的硬件I2C就会按照其已经设定好的I2C协议与别的I2C器件进行通信。与硬件I2C相对的,还有软件I2C。软件I2C是指按照I2C协议,自行用两个GPIO端口置高或低模拟SDA和SCL的行为。在比赛方提供的资源数据包——底层驱动代码参考中,有用HAL库函数实现的软件I2C的库,我们就基于这个库来使用AT24C02芯片(E2PROM存储器)进行读写操作。

打开电路原理图(CT117E-M4产品手册),可以看到STM32G431RBT6的24C02芯片被挂到了PB6、PB7上(相当于I2C总线的SDA和SCL):

【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

因此,我们需要在Cube中将PB6和PB7设置为GPIO输出模式。

设置完成后,需要调用比赛方提供的软件I2C库:i2c_hal.c和i2c_hal.h,我们需要用到的函数如下:

/ * @brief I2C起始信号 * @param None * @retval None */ void I2CStart(void); / * @brief I2C结束信号 * @param None * @retval None */ void I2CStop(void); / * @brief I2C等待确认信号 * @param None * @retval None */ unsigned char I2CWaitAck(void); / * @brief I2C发送非确认信号 * @param None * @retval None */ void I2CSendNotAck(void) / * @brief I2C发送一个字节 * @param cSendByte 需要发送的字节 * @retval None */ void I2CSendByte(unsigned char cSendByte); / * @brief I2C接收一个字节 * @param None * @retval 接收到的字节 */ unsigned char I2CReceiveByte(void);

下面,我们来看看如何利用这些函数来进行E2PROM的读写操作。

打开AT24C02芯片手册(在选手资源数据包——芯片资料中),可以找到向E2PROM写入数据与读取数据(读取内存)的流程图:

【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

 【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

对应流程图,我们就可以编写E2PROM的读写操作函数如下:

/* e2prom.c */ #include "e2prom.h" /* E2PROM写操作,对应Figure 8. Btye Write */ void e2prom_write(unsigned char address, unsigned char info) { I2CStart(); //1.I2C起始信号(START) I2CSendByte(0xa0); //2.发送设备地址与“写”信号(DEVICE ADDRESS+WRITE),将在下文解释 I2CWaitAck(); //3.IC2等待确认信号(ACK) I2CSendByte(address); //4.发送数据存储地址(WORD ADDRESS)(可以为0~255,对应256个) I2CWaitAck(); //5.I2C等待确认信号(ACK) I2CSendByte(info); //6.发送数据(DATA) I2CWaitAck(); //7.I2C等待确认信号(ACK) I2CStop(); //8.I2C结束信号(STOP) } /* E2PROM读操作,对应Figure 11. Random Read */ unsigned char e2prom_read(unsigned char address) { unsigned char val; I2CStart(); //1.I2C起始信号(START) I2CSendByte(0xa0); //2.发送设备地址与“写”信号(DEVICE ADDRESS+WR-TE),将在下文解释 I2CWaitAck(); //3.IC2等待确认信号(ACK) I2CSendByte(address); //4.发送数据存储地址(WORD ADDRESS)(可以为0~255,对应256个) I2CWaitAck(); //5.I2C等待确认信号(ACK) I2CStop(); //下一次发送起始信号前先停止 I2CStart(); //6.I2C起始信号(START) I2CSendByte(0xa1); //7.发送设备地址与“读”信号(DEVICE ADDRESS+READ),将在下文解释 I2CWaitAck(); //8.I2C等待确认信号(ACK) val = I2CReceiveByte(); //9.接收数据(DATA) I2CSendNotAck(); //10.I2C发送非确认信号(NO ACK) I2CStop(); //11.I2C结束信号(STOP) return (val); } 
/* e2prom.h */ #ifndef __E2PROM_H #define __E2PROM_H #include "main.h" #include "i2c_hal.h" void e2prom_write(unsigned char address, unsigned char info); unsigned char e2prom_read(unsigned char address); #endif /* __E2PROM_H */ 

下面来解释一下为什么第二步和第七步发送的是0xa0和0xa1。同样在芯片手册中,可以找到AT24C02芯片的地址:

【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

AT24C01/02/04/08/16分别对应1K/2K/4K/8K/16K,A2、A1、A0分别对应电路原理图的E3、E2、E1。在下图中可以看到,E1、E2、E2均接地,为0。最后一位的R(Read)为1,W(Write)为0。因此,在STM32G431RBT6中,AT24C02的地址为_(即0xa_),最后一位视读或写操作为1或0。

【速成】蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

通过调用我们自己编写的e2prom的库,就可以使用E2PROM存储器进行简单的8位数据(unsigned char或uint8_t类型)的存储操作了。

附录

i2c_hal.c

/* 程序说明: CT117E-M4嵌入式竞赛板GPIO模拟I2C总线驱动程序 软件环境: MDK-ARM HAL库 硬件环境: CT117E-M4嵌入式竞赛板 日 期: 2020-3-1 */ #include "i2c_hal.h" #define DELAY_TIME 20 / * @brief SDA线输入模式配置 * @param None * @retval None */ void SDA_Input_Mode() { GPIO_InitTypeDef GPIO_InitStructure = {0}; GPIO_InitStructure.Pin = GPIO_PIN_7; GPIO_InitStructure.Mode = GPIO_MODE_INPUT; GPIO_InitStructure.Pull = GPIO_PULLUP; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); } / * @brief SDA线输出模式配置 * @param None * @retval None */ void SDA_Output_Mode() { GPIO_InitTypeDef GPIO_InitStructure = {0}; GPIO_InitStructure.Pin = GPIO_PIN_7; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); } / * @brief SDA线输出一个位 * @param val 输出的数据 * @retval None */ void SDA_Output( uint16_t val ) { if ( val ) { GPIOB->BSRR |= GPIO_PIN_7; } else { GPIOB->BRR |= GPIO_PIN_7; } } / * @brief SCL线输出一个位 * @param val 输出的数据 * @retval None */ void SCL_Output( uint16_t val ) { if ( val ) { GPIOB->BSRR |= GPIO_PIN_6; } else { GPIOB->BRR |= GPIO_PIN_6; } } / * @brief SDA输入一位 * @param None * @retval GPIO读入一位 */ uint8_t SDA_Input(void) { if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET){ return 1; }else{ return 0; } } / * @brief I2C的短暂延时 * @param None * @retval None */ static void delay1(unsigned int n) { uint32_t i; for ( i = 0; i < n; ++i); } / * @brief I2C起始信号 * @param None * @retval None */ void I2CStart(void) { SDA_Output(1); delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); SDA_Output(0); delay1(DELAY_TIME); SCL_Output(0); delay1(DELAY_TIME); } / * @brief I2C结束信号 * @param None * @retval None */ void I2CStop(void) { SCL_Output(0); delay1(DELAY_TIME); SDA_Output(0); delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); SDA_Output(1); delay1(DELAY_TIME); } / * @brief I2C等待确认信号 * @param None * @retval None */ unsigned char I2CWaitAck(void) { unsigned short cErrTime = 5; SDA_Input_Mode(); delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); while(SDA_Input()) { cErrTime--; delay1(DELAY_TIME); if (0 == cErrTime) { SDA_Output_Mode(); I2CStop(); return ERROR; } } SDA_Output_Mode(); SCL_Output(0); delay1(DELAY_TIME); return SUCCESS; } / * @brief I2C发送确认信号 * @param None * @retval None */ void I2CSendAck(void) { SDA_Output(0); delay1(DELAY_TIME); delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); SCL_Output(0); delay1(DELAY_TIME); } / * @brief I2C发送非确认信号 * @param None * @retval None */ void I2CSendNotAck(void) { SDA_Output(1); delay1(DELAY_TIME); delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); SCL_Output(0); delay1(DELAY_TIME); } / * @brief I2C发送一个字节 * @param cSendByte 需要发送的字节 * @retval None */ void I2CSendByte(unsigned char cSendByte) { unsigned char i = 8; while (i--) { SCL_Output(0); delay1(DELAY_TIME); SDA_Output(cSendByte & 0x80); delay1(DELAY_TIME); cSendByte += cSendByte; delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); } SCL_Output(0); delay1(DELAY_TIME); } / * @brief I2C接收一个字节 * @param None * @retval 接收到的字节 */ unsigned char I2CReceiveByte(void) { unsigned char i = 8; unsigned char cR_Byte = 0; SDA_Input_Mode(); while (i--) { cR_Byte += cR_Byte; SCL_Output(0); delay1(DELAY_TIME); delay1(DELAY_TIME); SCL_Output(1); delay1(DELAY_TIME); cR_Byte |= SDA_Input(); } SCL_Output(0); delay1(DELAY_TIME); SDA_Output_Mode(); return cR_Byte; } // void I2CInit(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_6; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); } 

i2c_hal.h

#ifndef __I2C_HAL_H #define __I2C_HAL_H #include "stm32g4xx_hal.h" void I2CStart(void); void I2CStop(void); unsigned char I2CWaitAck(void); void I2CSendAck(void); void I2CSendNotAck(void); void I2CSendByte(unsigned char cSendByte); unsigned char I2CReceiveByte(void); void I2CInit(void); #endif 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/131847.html

(0)
上一篇 2025-08-04 21:26
下一篇 2025-08-04 21:33

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信