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):

因此,我们需要在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写入数据与读取数据(读取内存)的流程图:

 

对应流程图,我们就可以编写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芯片的地址:

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的地址为1010000_(即0xa_),最后一位视读或写操作为1或0。

通过调用我们自己编写的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
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐