本帖最后由 口天土立口 于 2025-11-17 09:28 编辑
#申请原创# #技术资源#
@21小跑堂
1. 外设介绍
本驱动测试通过的AT24CXX型号如下: AT24C01,AT24C02,AT24C04,AT24C08,AT24C16,AT24C32,AT24C64,AT24C128,AT24C256,AT24C512
APM32F427拥有3个I2C外设,且最大支持到400Kbps的通讯速率。
I2C总线的几个信号: 起始信号->SCL稳定在高电平期间,SDA产生一个下降沿; 停止信号->SCL稳定在高电平期间,SDA产生一个上升沿; 应答信号->发送方在1个字节发送完成后,释放SDA线(输出变为输入),接收方在第9个时钟脉冲期间,拉低SDA线,表示接收到数据,否则为非应答信号;
I2C总线的SCL和SDA线需配置为开漏模式,同时外部需接上拉电阻,以实现输出高电平; 同时,SCL和SDA线的开漏模式配置,得以实现SCL的同步和SDA的仲裁。
SCL线的同步: 数据只在SCL的高电平期间有效,因此需要一个确定的时钟进行逐位仲裁;多主机同时发起通讯时,多个主机均会产生自己的时钟脉冲,因SCL开漏产生的线与关系,SCL的低电平时长由最长SCL低电平的主机决定,而先将SCL切换为高电平的主机并不能改变SCL的状态;当所有主机都将SCL拉为高电平后,SCL线状态才切为高电平,同时最开始将SCL线切为低电平的主机首先将SCL拉为低电平状态;如此往复,确定了SCL线的低电平和高电平时长切换;
SDA线的仲裁: 主机只能在总线空闲的时侯启动传输,多个主机可能在起始条件的最小持续时间内产生一个起始条件,结果在总线上产生一个规定的起始条件。SDA线的仲裁,发生在SCL的高电平期间。因SDA开漏产生线与的关系,先将SDA拉为高电平的主机,输掉仲裁,切为从机模式,丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾。仲裁可以持续多位,首先比较地址位,然后数据位,响应位。I2C总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。
用时钟同步机制作为握手: 在字节级的快速传输中,器件可以快速接收数据字节,但需要更多时间保存接收到的字节或准备另一个要发送的字节,然后从机以一种握手过程在接收和响应一个字节后使 SCL 线保持低电平,迫使主机进入等待状态,直到从机准备好下一个要传输的字节。在位级的快速传输中,器件可以通过延长每个时钟的低电平周期减慢总线时钟,从而任何主机的速度都可以适配这个器件的内部操作速率。
AT24CXX介绍: AT24CXX为EEPROM,使用I2C进行通讯,最高频率可达1MHz。各型号AT24CXX的信息对比如下:
AT24CXX的设备地址,高4bits固定为0xA,低3位为A0/A1/A2,硬件拉高为1,拉低为0,但AT24C04、AT24C08和AT24C16三个型号的情况不同,这三个型号的页写内部自增位数为4bits,但页数量对应的数据位数分别为5/6/7,总共需要的存储地址位数分别为9/10/11,但实际I2C通讯时,只能传8bits的存储地址,因此,AT24C04占用A0补充缺少的最高1位存储地址,AT24C08占用A0/A1补充缺少的最高2位存储地址,AT24C16占用A0/A1/A2补充缺少的最高3位存储地址,所以导致同一条I2C总线挂载的AT24C04数量最多为2^2个(即4个),AT24C08数量最多为2^1个(即2个),AT24C16数量最多为2^0个(即1个),其他型号的AT24CXX不占用A0/A1/A2作为存储地址,所以最多都能挂载2^3个(即8个)。
注意: 1. AT24CXX按页写时,内部自增位数到达最大值,会回环为0,即又回到当前页的起始地址处继续写入数据。所以每次开始写数据时,都需要计算当前开始写数据的地址,在当前页剩余可写入的数据量。 2. 每次写入完成当前页的数据,均需等待AT24CXX完成存储数据,否则过快的连续写入,将导致AT24CXX无法正确保存接收到的数据。具体需间隔多长时间才能发起新的写入时序,需查看对应厂商的芯片手册。
2. 硬件 APM32F427ZG TINY板
3. 驱动介绍 AT24CXX的I2C驱动,按照芯片手册实现读写时序即可。其中AT24C01~AT24C16的Word Address片内存储地址,只需发送1个字节,另外,AT24C04~AT24C16的Device Address包含片内的存储地址。
硬件I2C的初始化位于bsp_i2c_hardware.c文件内,注意需把硬件配置为开漏模式。 - /*
- * @brief 引脚初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- static void bsp_i2c_gpio_init(void)
- {
- GPIO_Config_T gpioConfig;
-
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
-
- /*
- * SCL -> PB6
- * SDA -> PB7
- */
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = GPIO_PIN_6;
- gpioConfig.mode = GPIO_MODE_AF;
- gpioConfig.otype = GPIO_OTYPE_OD;
- gpioConfig.speed = GPIO_SPEED_50MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(GPIOB, &gpioConfig);
- gpioConfig.pin = GPIO_PIN_7;
- GPIO_Config(GPIOB, &gpioConfig);
-
- GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_6, GPIO_AF_I2C1);
- GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_7, GPIO_AF_I2C1);
- }
- /*
- * @brief I2C初始化
- *
- * @param dev_id: 自身设备ID
- * speed: 速度
- *
- * @retval None
- *
- */
- void bsp_i2c_init(uint8_t dev_id, uint32_t speed)
- {
- I2C_Config_T i2cConfig;
-
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);
- bsp_i2c_gpio_init();
-
- I2C_Reset(I2C_INS);
- I2C_ConfigStructInit(&i2cConfig);
- i2cConfig.clockSpeed = speed;
- i2cConfig.mode = I2C_MODE_I2C;
- i2cConfig.dutyCycle = I2C_DUTYCYCLE_2;
- i2cConfig.ownAddress1 = dev_id;
- i2cConfig.ack = I2C_ACK_ENABLE;
- i2cConfig.ackAddress = I2C_ACK_ADDRESS_7BIT;
- I2C_Config(I2C_INS, &i2cConfig);
- I2C_Enable(I2C_INS);
- }
软件I2C只需把引脚按照IO输出配置即可,同时引脚模式需配置为开漏模式,而时钟频率的控制,更改bsp_i2c_timing_delay函数内的延时数量即可。后续各个I2C信号(起始信号、停止信号、应答信号)按照I2C的要求产生即可。 - /*
- * @brief 延时
- *
- * @param None
- *
- * @retval None
- *
- */
- static void bsp_i2c_timing_delay(void)
- {
- /* 调整此处的数值,以控制通讯频率 */
- volatile uint32_t d = 200;
- while (--d);
- }
- /*
- * @brief 引脚初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_i2c_timing_io_init(void)
- {
- GPIO_Config_T gpioConfig;
-
- GPIO_CLK_ENABLE;
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = SCL_PIN;
- gpioConfig.mode = GPIO_MODE_OUT;
- gpioConfig.otype = GPIO_OTYPE_OD;
- gpioConfig.speed = GPIO_SPEED_50MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(SCL_PORT, &gpioConfig);
-
- gpioConfig.pin = SDA_PIN;
- GPIO_Config(SDA_PORT, &gpioConfig);
-
- bsp_i2c_timing_stop();
- }
AT24CXX的读时序,无需特意处理页的问题,AT24CXX会持续在主机的SCL信号到来时,返回下一个地址的数据,直到主机发起停止信号。注意:读时序,需先发起一个写地址时序,然后通过停止信号结束,再重复发起读时序,此时可直接从AT24CXX内读出数据。 - /*
- * @brief 读数据通用接口
- *
- * @param dev_addr: 设备地址
- * m_addr: 存储地址
- * buf: 数据缓存
- * size: 数据大小
- * is_16bit_m: 16位存储
- *
- * @retval 读取数据量
- *
- */
- static uint32_t bsp_at24cxx_hardware_read_common(uint8_t dev_addr,
- uint8_t m_addr, uint8_t *buf, uint32_t size, uint8_t is_16bit_m)
- {
- uint32_t read_bytes = 0;
- uint32_t iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
-
- /* 等待IIC空闲 */
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BUSBSY) == SET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 起始信号 */
- I2C_EnableGenerateStart(I2C_INS);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_START) != SET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 设备地址 */
- I2C_Tx7BitAddress(I2C_INS, dev_addr, I2C_DIRECTION_TX);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_ADDR) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
- /* 确认设备是发送器模式(写) */
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TR) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 发送存储地址 */
- if (is_16bit_m == 0) {
- I2C_TxData(I2C_INS, m_addr);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
- } else {
- /* 高8bits */
- I2C_TxData(I2C_INS, ((m_addr >> 8) & 0xFF));
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TXBE) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 低8bits */
- I2C_TxData(I2C_INS, (m_addr & 0xFF));
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
- }
-
- /* 等待结束 */
- I2C_EnableGenerateStop(I2C_INS);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_STOP) != RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 重复起始信号 */
- I2C_EnableGenerateStart(I2C_INS);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_START) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 设备地址 */
- I2C_Tx7BitAddress(I2C_INS, dev_addr, I2C_DIRECTION_RX);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_ADDR) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
- /* 确认设备是接收器模式(读) */
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TR) != RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- /* 读数据 */
- while (read_bytes < size) {
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_RXBNE) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
- buf[read_bytes++] = I2C_RxData(I2C_INS);
- if ((read_bytes + 1) == size) {
- I2C_DisableAcknowledge(I2C_INS);
- }
- }
-
- /* 等待结束 */
- I2C_EnableGenerateStop(I2C_INS);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_STOP) != RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return read_bytes;
- }
- }
-
- return read_bytes;
- }
AT24CXX的写时序,写入存储地址后,可立刻传入数据。 - /*
- * @brief 写数据通用接口
- *
- * @param dev_addr: 设备地址
- * m_addr: 存储地址
- * buf: 数据缓存
- * size: 数据大小
- * is_16bit_m: 16位存储
- *
- * @retval 写入数据量
- *
- */
- static uint32_t bsp_at24cxx_hardware_write_common(uint8_t dev_addr,
- uint16_t m_addr, uint8_t *buf, uint32_t size, uint8_t is_16bit_m)
- {
- uint32_t write_bytes = 0;
- uint32_t iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
-
- /* 等待IIC空闲 */
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BUSBSY) == SET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
-
- /* 起始信号 */
- I2C_EnableGenerateStart(I2C_INS);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_START) != SET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
-
- /* 设备地址 */
- I2C_Tx7BitAddress(I2C_INS, dev_addr, I2C_DIRECTION_TX);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_ADDR) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
- /* 确认设备是发送器模式(写) */
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TR) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
-
- /* 发送存储地址 */
- if (is_16bit_m == 0) {
- I2C_TxData(I2C_INS, m_addr);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
- } else {
- /* 高8bits */
- I2C_TxData(I2C_INS, ((m_addr >> 8) & 0xFF));
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TXBE) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
-
- /* 低8bits */
- I2C_TxData(I2C_INS, (m_addr & 0xFF));
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
- }
-
- /* 写数据 */
- while (write_bytes < size) {
- I2C_TxData(I2C_INS, buf[write_bytes]);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TXBE) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
- write_bytes++;
- }
-
- /* 等待发送完成 */
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
-
- /* 等待结束 */
- I2C_EnableGenerateStop(I2C_INS);
- iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
- while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_MS) != RESET) {
- if ((iic_timeout--) == 0) {
- I2C_EnableGenerateStop(I2C_INS);
- return write_bytes;
- }
- }
-
- return write_bytes;
- }
AT24CXX的读数据操作,需特别注意AT24C04~AT24C16三个型号的设备地址和存储地址计算,然后直接调用读时序接口即可,同时无需等待AT24CXX的额外操作,能一次发起读时序操作读取出全部的数据。 - /*
- * @brief 读数据
- *
- * @param op: 操作接口
- * m_addr: 存储地址
- * buf: 数据缓存
- * size: 数据量
- *
- * @retval 读取数据量
- *
- */
- uint32_t at24cxx_read_data(struct at24cxx_op_f *op, uint16_t m_addr, uint8_t *buf, uint32_t size)
- {
- uint32_t read_bytes = size;
- uint8_t dev_addr = 0; /* 设备地址 */
- uint16_t mem_addr = m_addr; /* 存储地址 */
- uint16_t mem = 0;
-
- /* 入参检查 */
- if ((op == ((void *)0)) || (op->at24cxx >= AT24CXX_NUM)
- || (mem_addr >= at24cxx_info[op->at24cxx].capacity) || (size == 0)
- || ((((uint32_t)mem_addr) + size) > at24cxx_info[op->at24cxx].capacity)) {
- return read_bytes;
- }
-
- /* 计算设备地址和存储地址 */
- if ((op->at24cxx >= AT24C04) && (op->at24cxx <= AT24C16)) {
- dev_addr = op->dev_addr + (((mem_addr / at24cxx_info[op->at24cxx].page_size) >> 4) << 1);
- mem = mem_addr & 0xFF;
- } else {
- dev_addr = op->dev_addr;
- mem = mem_addr;
- }
-
- if (op->at24cxx <= AT24C16) {
- if (op->i2c_read(dev_addr, mem, buf, read_bytes) != read_bytes) {
- read_bytes = 0;
- }
- } else {
- if (op->i2c_read_16bit_m_addr(dev_addr, mem, buf, read_bytes) != read_bytes) {
- read_bytes = 0;
- }
- }
-
- return read_bytes;
- }
AT24CXX的写数据操作,也需特别注意AT24C04~AT24C16三个型号的设备地址和存储地址计算,同时写操作为按页写,需计算当前起始地址所处页可连续写的剩余数据数量,以避免页内地址位自增后回环的问题。AT24CXX写入一页数据后,保存数据需消耗一定的时间,具体时间可能不同厂商有不同的要求,因此,写入数据后,需等待AT24CXX保存完数据再发起下一次页写操作,不能连续写两页及以上。 - /*
- * @brief 写数据
- *
- * @param op: 操作接口
- * m_addr: 存储地址
- * buf: 数据缓存
- * size: 数据量
- *
- * @retval 写入数据量
- *
- */
- uint32_t at24cxx_write_data(struct at24cxx_op_f *op, uint16_t m_addr, uint8_t *buf, uint32_t size)
- {
- uint32_t write_bytes = 0;
- uint8_t dev_addr = 0; /* 设备地址 */
- uint16_t mem_addr = m_addr; /* 存储地址 */
- uint16_t mem = 0;
- uint8_t current_page_write_size = 0; /* 当前页写大小 */
- uint8_t *data = buf;
-
- /* 入参检查 */
- if ((op == ((void *)0)) || (op->at24cxx >= AT24CXX_NUM)
- || (mem_addr >= at24cxx_info[op->at24cxx].capacity) || (size == 0)
- || ((((uint32_t)mem_addr) + size) > at24cxx_info[op->at24cxx].capacity)) {
- return write_bytes;
- }
-
- do {
- /* 计算设备地址和存储地址 */
- if ((op->at24cxx >= AT24C04) && (op->at24cxx <= AT24C16)) {
- dev_addr = op->dev_addr + (((mem_addr / at24cxx_info[op->at24cxx].page_size) >> 4) << 1);
- mem = mem_addr & 0xFF;
- } else {
- dev_addr = op->dev_addr;
- mem = mem_addr;
- }
- /* 当前页可写数据量 */
- current_page_write_size = at24cxx_info[op->at24cxx].page_size - (mem_addr % at24cxx_info[op->at24cxx].page_size);
- /* 当前页需写数据量 */
- current_page_write_size = (current_page_write_size <= (size - write_bytes)) ? current_page_write_size : (size - write_bytes);
- if (op->at24cxx <= AT24C16) {
- if (op->i2c_write(dev_addr, mem, data, current_page_write_size) != current_page_write_size) {
- break;
- }
- } else {
- if (op->i2c_write_16bit_m_addr(dev_addr, mem, data, current_page_write_size) != current_page_write_size) {
- break;
- }
- }
-
- mem_addr += current_page_write_size;
- write_bytes += current_page_write_size;
- data += current_page_write_size;
- op->delay_5ms(); /* 等待存储完成操作,否则过快操作异常 */
- } while (write_bytes < size);
-
- return write_bytes;
- }
4. 测试 AT24CXX的测试代码如下,只需要定义struct at24cxx_op_f结构体,并传入对应的时序接口函数,然后调用at24cxx_write_data和at24cxx_read_data函数接口执行读写操作即可。如果只是使用AT24C01~AT24C16几个型号,则不需要填充16bit存储地址的时序函数,同理,只使用AT24C32~AT24C512几个型号,也无需填充默认的8bit存储地址的时序函数,但延时函数必须填充。 - /* 最大的EEPROM容量为 64KB */
- #define BUF_SIZE (1024 * 64)
- static struct at24cxx_op_f at24cxx_test = {
- AT24C02,
- 0xA0,
- bsp_at24cxx_hardware_write,
- bsp_at24cxx_hardware_read,
- bsp_at24cxx_hardware_write_16bit_m_addr,
- bsp_at24cxx_hardware_read_16bit_m_addr,
- bsp_at24cxx_hardware_delay5ms
- };
- uint8_t w_buf[BUF_SIZE];
- uint8_t r_buf[BUF_SIZE];
- volatile int8_t result = -1;
- // 应用初始化
- void app_init(void)
- {
- uint32_t at24cxx_capacity = 0;
-
- bsp_i2c_init(0xC0, 400000);
- at24cxx_capacity = at24cxx_get_capacity(at24cxx_test.at24cxx);
-
- /* 填充数据 */
- for (uint32_t i = 0; i < at24cxx_capacity; i++) {
- w_buf[i] = 40 + i;
- }
- /* 写入读取测试 */
- at24cxx_write_data(&at24cxx_test, 0, w_buf, at24cxx_capacity);
- at24cxx_read_data(&at24cxx_test, 0, r_buf, at24cxx_capacity);
- /* 测试结果 */
- result = memcmp(w_buf, r_buf, sizeof(w_buf));
- }
如下图,因写数据每次只能写1页,且写完1页需等5ms给AT24C02完成数据存储,所以写完整一片AT24C02耗时相对较长,而后部分的读数据能一次性将所有数据读回,所有耗时很短。
5. AT24CXX驱动代码移植说明 I2C的模拟时序移植,只需要更改实现如下的代码即可,bsp_at24cxx_simulate_timing和bsp_at24cxx的代码均无需修改。 - #define SCL_PORT (GPIOB)
- #define SCL_PIN (GPIO_PIN_6)
- #define SDA_PORT (GPIOB)
- #define SDA_PIN (GPIO_PIN_7)
- #define GPIO_CLK_ENABLE RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB)
- /* IO操作 */
- #define I2C_SCL(x) do{ x ? \
- GPIO_SetBit(SCL_PORT, SCL_PIN) : \
- GPIO_ResetBit(SCL_PORT, SCL_PIN); \
- } while(0) /* SCL */
- #define I2C_SDA(x) do{ x ? \
- GPIO_SetBit(SDA_PORT, SDA_PIN) : \
- GPIO_ResetBit(SDA_PORT, SDA_PIN); \
- } while(0) /* SDA */
- #define I2C_READ_SDA (GPIO_ReadInputBit(SDA_PORT, SDA_PIN) == BIT_SET) /* 读取SDA */
- /*
- * @brief 延时
- *
- * @param None
- *
- * @retval None
- *
- */
- static void bsp_i2c_timing_delay(void)
- {
- /* 调整此处的数值,以控制通讯频率 */
- volatile uint32_t d = 200;
- while (--d);
- }
- /*
- * @brief 引脚初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_i2c_timing_io_init(void)
- {
- GPIO_Config_T gpioConfig;
-
- GPIO_CLK_ENABLE;
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = SCL_PIN;
- gpioConfig.mode = GPIO_MODE_OUT;
- gpioConfig.otype = GPIO_OTYPE_OD;
- gpioConfig.speed = GPIO_SPEED_50MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(SCL_PORT, &gpioConfig);
-
- gpioConfig.pin = SDA_PIN;
- GPIO_Config(SDA_PORT, &gpioConfig);
-
- bsp_i2c_timing_stop();
- }
I2C的硬件时序,因不同的MCU外设操作有差异,需要实现bsp_i2c_hardware和bsp_at24cxx_hardware_timing文件的全部代码,而bsp_at24cxx文件无需修改。
不管是模拟I2C方式还是硬件I2C方式,在应用层调用的方式均一致。
6. 详细驱动代码 I2C硬件驱动代码和模拟驱动代码:
|