STM32 通过软件模拟 I2C 驱动 24Cxx 系列存储器
目录
- 一、AT24CXXX 系列存储器介绍
- 1、基本信息
- 2、寻址方式
- 3、页地址与页内单元地址
- 4、I2C 地址
- 5、AT24CXX 的数据读写
- 5.1 写操作
- 5.1.1 按字节写
- 5.1.2 按页写
- 5.2 读操作
- 5.2.1 当前地址读取
- 5.2.2 随机地址读取
- 5.2.3 顺序读取
- 二、代码实现
- 1、ctl_i2c
- 2、at24c
- 3、测试程序
I2C
相关知识可以参考 IIC 通信协议详解
一、AT24CXXX 系列存储器介绍
1、基本信息
下表是 AT24CXXX 的容量
:
AT24C01,AT24C02,AT24C04,AT24C08,AT24C16,AT24C32,AT24C64,AT24C128,AT24C256… 不同的 xxx 代表不同的容量。
AT24CXXX | bit容量 | Byte容量 |
---|---|---|
AT24C01 | 1Kbit | 128Byte |
AT24C02 | 2Kbit | 256Byte |
AT24C04 | 4Kbit | 512Byte |
AT24C08 | 8Kbit | 1024Byte |
AT24C16 | 16Kbit | 2048Byte |
AT24C32 | 32Kbit | 4096Byte |
AT24C64 | 64Kbit | 8192Byte |
AT24C128 | 128Kbit | 16384Byte |
AT24C256 | 256Kbit | 32768Byte |
AT24C512 | 512Kbit | 65536Byte |
下表是 AT24CXXX 的页内单元数
:
总容量(Byte容量) = 页数 × 页内字节单元数
AT24CXXX | Byte容量 | 页数 | 页内字节单元数 |
---|---|---|---|
AT24C01 | 128Byte | 16页 | 8Byte |
AT24C02 | 256Byte | 32页 | 8Byte |
AT24C04 | 512Byte | 32页 | 16Byte |
AT24C08 | 1024Byte | 64页 | 16Byte |
AT24C16 | 2048Byte | 128页 | 16Byte |
AT24C32 | 4096Byte | 128页 | 32Byte |
AT24C64 | 8192Byte | 256页 | 32Byte |
AT24C128 | 16384Byte | 256页 | 64Byte |
AT24C256 | 32768Byte | 512页 | 64Byte |
AT24C512 | 65536Byte | 512页 | 128Byte |
2、寻址方式
不是 I2C 地址,是存储器内部寻址
对 AT24CXXX
进行读写操作时,都得先访问存储地址、比如 AT24C04
写一个字节的 I2C 时序:
先发送设备地址,收到应答后再发送需要写数据的地址(WORD ADDRESS
)。AT24C04
容量为 512Byte 则 WORD ADDRESS 只需要 9bit 就可以覆盖 512Byte 的数据地址。通俗的讲就是 512Byte 就占用了 512 个地址,一个 9bit 的数据范围为( 0 − 511 0-511 0−511)刚好 512,所以 512Byte 的字节地址需要一个 9bit 的数据来表示。
3、页地址与页内单元地址
比如 AT24C04
有 32 页每页 16 个字节,9bit 的地址数据对其寻址,低 4bit(D3-D0)为页内字节单元地址,高 5bit(D8-D4)为页地址。
如从第 16 页开始写,则 WORD ADDRESS = 0x0100(0001 0000 0000)
,则:
- 000:地址无效位
- 1 0000:5 位页地址
- 0000:4 位页内单元地址
4、I2C 地址
I2C 通信需要先向从设备发送设备地址,AT24CXXX
芯片上有 A2、A1、A0 引脚,通过这三个引脚我们就可以自定义 AT24CXXX
芯片的通信地址。
下面以 24C04 和 24C08 的官方手册为例,说明其 I2C 地址,其它型号的芯片自行查阅手册。
可以看到,前 4 位是固定的为 1010,而后的 A2、A1、P0 三个引脚以及读写标志位有我们自己设置。如果将 A2、A1、P0 接地,则 I2C 写地址为 1010 0000
(0xA0),读地址为 1010 0001
(0xA1)。
5、AT24CXX 的数据读写
5.1 写操作
5.1.1 按字节写
5.1.2 按页写
和按字节写类似,不过在往 AT24CXXX
中写数据时,每写一个 Byte 的数据页内地址 +1,当前页写满后会重新覆盖掉这一页前面的数据,而不会自动跳转到下一页,但是读会自动翻页。
那要如何实现翻页写呢?
按页写其实就是执行一次上面的时序,也就是发送一次从机设备和字节地址最大就可以写入 16 字节(AT24C04)的数据,如果要连写多页,就重新按照上面的时序发送从机地址和字节地址等。
5.2 读操作
写操作和读操作类似,不过 R/W
标志位要设置为 1。
5.2.1 当前地址读取
5.2.2 随机地址读取
5.2.3 顺序读取
二、代码实现
说明:
// 实现i2c相关设置和初始化
ctl_i2c.h
ctl_i2c.c// 实现at24cx系列芯片的读写操作
at24c.h
at24c.c
1、ctl_i2c
下面是 ctl_i2c.h
文件,没什么可说的,实现了一些宏,以及相关函数的声明。
// ctl_i2c.h
#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H#include "stm32f4xx.h"#define I2C_WR 0 // 写控制bit
#define I2C_RD 1 // 读控制bit#define RCC_AT24CXX_I2C_PORT RCC_AHB1Periph_GPIOB // GPIO端口时钟
#define GPIO_AT24CXX_I2C_PORT GPIOB // GPIO端口
#define GPIO_AT24CXX_I2C_SCL_Pin GPIO_Pin_8 // 连接到SCL时钟线的GPIO
#define GPIO_AT24CXX_I2C_SDA_Pin GPIO_Pin_9 // 连接到SDA数据线的GPIO#define I2C_SCL_H() GPIO_SetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin) // SCL = 1
#define I2C_SCL_L() GPIO_ResetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin) // SCL = 0
#define I2C_SDA_H() GPIO_SetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin) // SDA = 1
#define I2C_SDA_L() GPIO_ResetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin) // SDA = 0
#define I2C_SDA_RD() GPIO_ReadInputDataBit(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin) // 读SDA口线状态
#define I2C_SCL_RD() GPIO_ReadInputDataBit(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin) // 读SCL口线状态void ctl_at24cxx_i2c_init(void);
void ctl_i2c_start(void);
void ctl_i2c_stop(void);
void ctl_i2c_sendbyte(uint8_t byte);
void ctl_i2c_ack(void);
void ctl_i2c_nack(void);
uint8_t ctl_i2c_waitack(void);
uint8_t ctl_i2c_readbyte(void);
uint8_t ctl_i2c_checkdevice(uint8_t address);#endif
接下来看 ctl_i2c.c
文件:
初始化 I2C 的 GPIO 端口:
/******************************************************************************* @brief 初始化I2C总线的GPIO* * @return none* * @note 采用模拟IO的方式实现*
******************************************************************************/
void ctl_at24cxx_i2c_init(void)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 设为输出口 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; // 设为开漏模式 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 上下拉电阻不使能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; // IO口最大速度GPIO_InitStructure.GPIO_Pin = GPIO_AT24CXX_I2C_SCL_Pin | GPIO_AT24CXX_I2C_SDA_Pin;GPIO_Init(GPIO_AT24CXX_I2C_PORT, &GPIO_InitStructure);// 给一个停止信号, 复位I2C总线上的所有设备到待机模式ctl_i2c_stop();
}
延时函数的实现:
/******************************************************************************* @brief I2C总线位延迟,最快400KHz* * @return none*
******************************************************************************/
static void i2c_delay(void)
{uint8_t i;/** * CPU主频168MHz时,在内部Flash运行, MDK工程不优化。用台式示波器观测波形。* 循环次数为5时,SCL频率 = 1.78MHz (读耗时: 92ms, 读写正常,但是用示波器探头碰上就读写失败。时序接近临界)* 循环次数为10时,SCL频率 = 1.1MHz (读耗时: 138ms, 读速度: 118724B/s)* 循环次数为30时,SCL频率 = 440KHz, SCL高电平时间1.0us,SCL低电平时间1.2us* 上拉电阻选择2.2K欧时,SCL上升沿时间约0.5us,如果选4.7K欧,则上升沿约1us* 实际应用选择400KHz左右的速率即可*/for (i = 0; i < 30; i++){__NOP();__NOP();}
}
I2C 开始信号:当 SCL 线在高电平期间 SDA 线从高电平向低电平切换
/******************************************************************************* @brief CPU发起I2C总线启动信号* * @return none* * @note 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号*
******************************************************************************/
void ctl_i2c_start(void)
{// 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号I2C_SDA_H();I2C_SCL_H();i2c_delay();I2C_SDA_L();i2c_delay();I2C_SCL_L();i2c_delay();
}
I2C 停止信号:当 SCL 线在高电平期间 SDA 线由低电平向高电平切换
/******************************************************************************* @brief CPU发起I2C总线停止信号* * @return none* * @note 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号*
******************************************************************************/
void ctl_i2c_stop(void)
{/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */I2C_SDA_L();I2C_SCL_H();i2c_delay();I2C_SDA_H();i2c_delay();
}
下面是应答信号和非应答信号的函数实现:
在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,给发送端传输应答或非应答信号
- SDA 为高电平:表示非应答信号(NACK)
- SDA为低电平:表示应答信号(ACK)
/******************************************************************************* @brief CPU产生一个ACK信号* * @return none*
******************************************************************************/
void ctl_i2c_ack(void)
{I2C_SDA_L(); // SCL低电平期间,SDA 为低电平,表示应答信号i2c_delay();I2C_SCL_H(); // CPU产生1个时钟i2c_delay();I2C_SCL_L();i2c_delay();I2C_SDA_H(); // 应答完成释放SDA总线,否则接收到的数据全是0
}/******************************************************************************* @brief CPU产生1个NACK信号* * @return none*
******************************************************************************/
void ctl_i2c_nack(void)
{I2C_SDA_H(); // CPU驱动SDA = 1i2c_delay();I2C_SCL_H(); // SCL 高电平期间,SDA 为高电平,表示非应答信号i2c_delay();I2C_SCL_L();i2c_delay();
}
为什么数据发送端要释放 SDA 的控制权(将SDA总线置为高电平)
数据有效性:IIC 总线进行数据传送时,SCL 信号为高电平期间,SDA 上的数据必须保持稳定,只有在 SCL 上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化(准备下一位数据)。数据在 SCL 的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。
数据传输:在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。数据位的传输是边沿触发
/******************************************************************************* @brief CPU向I2C总线设备发送8bit数据* * @param[in] byte : 等待发送的1个字节数据* * @return none* * @note SDA 上的数据变化只能在 SCL 低电平期间发生*
******************************************************************************/
void ctl_i2c_sendbyte(uint8_t byte)
{uint8_t i;/* 先发送字节的高位bit7 */for (i = 0; i < 8; i++){if (byte & 0x80){I2C_SDA_H();}else{I2C_SDA_L();}i2c_delay();I2C_SCL_H(); // SCL高电平有效,发送一位数据i2c_delay();I2C_SCL_L(); // SCL低电平,准备下一位数据// 若是最后一位数据,释放SDA总线,表示数据传输结束if (i == 7){I2C_SDA_H(); // 释放总线}// 数据左移,准备下一位数据(高位先到byte <<= 1;i2c_delay();}
} /******************************************************************************* @brief CPU从I2C总线设备读取8bit数据* * @return uint8_t *
******************************************************************************/
uint8_t ctl_i2c_readbyte(void)
{uint8_t i;uint8_t value = 0;/* 读到第1个bit为数据的bit7 */for (i = 0; i < 8; i++){value <<= 1;I2C_SCL_H(); // 将SCL拉高,准备接收数据i2c_delay();// 判断EEPROM发送过来的是1还是0if (I2C_SDA_RD()){value++;}I2C_SCL_L(); // 让EEPROM准备下一位数据i2c_delay();}return value;
}
最后是等待从机 EEPROM 应答和检查设备是否已连接:
/******************************************************************************* @brief CPU产生一个时钟,并读取器件的ACK应答信号* * @return uint8_t *
******************************************************************************/
uint8_t ctl_i2c_waitack(void)
{uint8_t re;I2C_SDA_H();// 自动释放SDA总线,将控制权交给EEPROMi2c_delay();I2C_SCL_H(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */i2c_delay();if (I2C_SDA_RD()) /* CPU读取SDA口线状态 */{re = 1;}else{re = 0;}I2C_SCL_L();i2c_delay();return re;
}/******************************************************************************* @brief 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在* * @param[in] address : 设备地址* * @return uint8_t : 0 表示成功检测到设备; 返回1表示未探测到*
******************************************************************************/
uint8_t ctl_i2c_checkdevice(uint8_t _Address)
{uint8_t ucAck;if (I2C_SDA_RD() && I2C_SCL_RD()){ctl_i2c_start(); // 发送启动信号// 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传ctl_i2c_sendbyte(_Address | I2C_WR);ucAck = ctl_i2c_waitack(); // 检测设备的ACK应答ctl_i2c_stop(); // 发送停止信号return ucAck;}return 1; // I2C总线异常
}
2、at24c
在 at24.h
文件中针对 AT24CX
系列的容量和页内单元数设置了不同的宏,可以针对自己使用的型号设置选择不同的宏使用,这里以 AT24C04 为例:#define AT24C04
// at24.h
#ifndef __AT24C_H
#define __AT24C_H#include "stm32f4xx.h"/* * AT24C02 2kb = 2048bit = 2048/8 B = 256 B* 32 pages of 8 bytes each** Device Address* 1 0 1 0 A2 A1 A0 R/W* 1 0 1 0 0 0 0 0 = 0xA0* 1 0 1 0 0 0 0 1 = 0xA1 *//* AT24C01/02每页有8个字节 * AT24C04/08A/16A每页有16个字节 、*/#define AT24C04#ifdef AT24C01#define AT24CX_MODEL_NAME "AT24C01"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 8 /* 页面大小(字节) */#define AT24CX_SIZE 128 /* 总容量(字节) */#define AT24CX_ADDR_BYTES 1 /* 地址字节个数 */#define AT24CX_ADDR_A8 0 /* 地址字节的高8bit不在首字节 */
#endif#ifdef AT24C02#define AT24CX_MODEL_NAME "AT24C02"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 8 /* 页面大小(字节) */#define AT24CX_SIZE 256 /* 总容量(字节) */#define AT24CX_ADDR_BYTES 1 /* 地址字节个数 */#define AT24CX_ADDR_A8 0 /* 地址字节的高8bit不在首字节 */
#endif#ifdef AT24C04#define AT24CX_MODEL_NAME "AT24C04"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 8 /* 页面大小(字节) */#define AT24CX_SIZE 512 /* 总容量(字节) */#define AT24CX_ADDR_BYTES 1 /* 地址字节个数 */#define AT24CX_ADDR_A8 1 /* 地址字节的高8bit在首字节 */
#endif #ifdef AT24C08#define AT24CX_MODEL_NAME "AT24C08"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 16 /* 页面大小(字节) */#define AT24CX_SIZE (16*64) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 1 /* 地址字节的高8bit在首字节 */
#endif#ifdef AT24C16#define AT24CX_MODEL_NAME "AT24C16"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 16 /* 页面大小(字节) */#define AT24CX_SIZE (128*16) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 1 /* 地址字节的高8bit在首字节 */
#endif#ifdef AT24C32#define AT24CX_MODEL_NAME "AT24C32"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 32 /* 页面大小(字节) */#define AT24CX_SIZE (128*32) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 1 /* 地址字节的高8bit在首字节 */
#endif#ifdef AT24C64#define AT24CX_MODEL_NAME "AT24C64"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 32 /* 页面大小(字节) */#define AT24CX_SIZE (256*32) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 1 /* 地址字节的高8bit在首字节 */
#endif#ifdef AT24C128#define AT24CX_MODEL_NAME "AT24C128"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 64 /* 页面大小(字节) */#define AT24CX_SIZE (256*64) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 0 /* 地址字节的高8bit不在首字节 */
#endif#ifdef AT24C256#define AT24CX_MODEL_NAME "AT24C256"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 64 /* 页面大小(字节) */#define AT24CX_SIZE (512*64) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 0 /* 地址字节的高8bit不在首字节 */
#endif #ifdef AT24C512#define AT24CX_MODEL_NAME "AT24C512"#define AT24CX_DEV_ADDR 0xA0 /* 设备地址 */#define AT24CX_PAGE_SIZE 128 /* 页面大小(字节) */#define AT24CX_SIZE (512*128) /* 总容量(字节) */#define AT24CX_ADDR_BYTES 2 /* 地址字节个数 */#define AT24CX_ADDR_A8 0 /* 地址字节的高8bit不在首字节 */
#endifuint8_t at24cx_checkok(void);
uint8_t at24cx_readbytes(uint8_t *readbuf, uint16_t address, uint16_t size);
uint8_t at24cx_writebytes(uint8_t *writebuf, uint16_t address, uint16_t size);#endif /* __AT24CH */
下面是 at24c.c
函数的实现:
首先检查设备是否连接成功:
/******************************************************************************* @brief 判断串行EERPOM是否正常* * @return uint8_t : 1 表示正常, 0 表示不正常*
******************************************************************************/
uint8_t at24cx_checkok(void)
{if (ctl_i2c_checkdevice(AT24CX_DEV_ADDR) == 0){return 1;}else{// 失败后,切记发送I2C总线停止信号ctl_i2c_stop();return 0;}
}
然后是读写函数:
/******************************************************************************* @brief 从串行EEPROM指定地址处开始读取若干数据* * @param[in] readuf : 起始地址 * @param[in] address : 数据长度,单位为字节* @param[in] size : 存放读到的数据的缓冲区指针* * @return uint8_t : 0 表示失败,1表示成功*
******************************************************************************/
uint8_t at24cx_readbytes(uint8_t *readbuf, uint16_t address, uint16_t size)
{uint16_t i;/*** 采用串行AT24CXPROM随即读取指令序列,连续读取若干字节*/ // 第1步:发起I2C总线启动信号ctl_i2c_start();// 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
#if AT24CX_ADDR_A8 == 1ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR | ((address >> 7) & 0x0E)); // 写指令
#elsectl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR); //写指令
#endif// 第3步:发送ACKif (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址if (AT24CX_ADDR_BYTES == 1){ctl_i2c_sendbyte((uint8_t)address);if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}}else{ctl_i2c_sendbyte(address >> 8);if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}ctl_i2c_sendbyte(address);if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}}// 第5步:重新启动I2C总线。下面开始读取数据ctl_i2c_start();// 第6步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
#if AT24CX_ADDR_A8 == 1ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_RD | ((address >> 7) & 0x0E)); // 写指令
#else ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_RD); // 此处是写指令
#endif // 第7步:发送ACK if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答 }// 第8步:循环读取数据 for (i = 0; i < size; i++){readbuf[i] = ctl_i2c_readbyte(); // 读1个字节// 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nackif (i != size - 1){ctl_i2c_ack(); // 中间字节读完后,CPU产生ACK信号(驱动SDA = 0)}else{ctl_i2c_nack(); // 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) }}// 发送I2C总线停止信号ctl_i2c_stop();return 1; // 执行成功// 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
cmd_fail: // 发送I2C总线停止信号ctl_i2c_stop();return 0;
}/******************************************************************************* @brief 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率* * @param[in] writeBuf : 起始地址 * @param[in] address : 数据长度,单位为字节* @param[in] size : 存放读到的数据的缓冲区指针* * @return uint8_t : 0 表示失败,1表示成功*
******************************************************************************/
uint8_t at24cx_writebytes(uint8_t *writebuf, uint16_t address, uint16_t size)
{uint16_t i, m;uint16_t addr;/*** 写串行AT24CXPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。* 对于24xx02,page size = 8* 简单的处理方法为:按字节写操作模式,每写1个字节,都发送地址* 为了提高连续写的效率: 本函数采用page wirte操作。*/addr = address;for (i = 0; i < size; i++){// 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址if ((i == 0) || (addr & (AT24CX_PAGE_SIZE - 1)) == 0){// 第0步:发停止信号,启动内部写操作ctl_i2c_stop();/*** 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms* CLK频率为200KHz时,查询次数为30次左右*/for (m = 0; m < 1000; m++){// 第1步:发起I2C总线启动信号ctl_i2c_start();// 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读#if AT24CX_ADDR_A8 == 1ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR | ((address >> 7) & 0x0E)); // 此处是写指令#else ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR);#endif// 第3步:发送一个时钟,判断器件是否正确应答if (ctl_i2c_waitack() == 0){break;}}if (m == 1000){goto cmd_fail; // AT24CXPROM器件写超时}// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址if (AT24CX_ADDR_BYTES == 1){ctl_i2c_sendbyte((uint8_t)addr);if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}}else{ctl_i2c_sendbyte(addr >> 8);if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}ctl_i2c_sendbyte(addr);if (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}}}// 第5步:开始写入数据 ctl_i2c_sendbyte(writebuf[i]);// 第6步:发送ACKif (ctl_i2c_waitack() != 0){goto cmd_fail; // AT24CXPROM器件无应答}addr++; // 地址增1}// 命令执行成功,发送I2C总线停止信号ctl_i2c_stop();/*** 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms* CLK频率为200KHz时,查询次数为30次左右*/for (m = 0; m < 1000; m++){// 第1步:发起I2C总线启动信号ctl_i2c_start();// 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读#if AT24CX_ADDR_A8 == 1ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR | ((address >> 7) & 0x0E)); // 此处是写指令#else ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR); // 此处是写指令#endif// 第3步:发送一个时钟,判断器件是否正确应答 if (ctl_i2c_waitack() == 0){break;}}if (m == 1000){goto cmd_fail; // AT24CXPROM器件写超时}// 命令执行成功,发送I2C总线停止信号ctl_i2c_stop(); return 1;// 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
cmd_fail: // 发送I2C总线停止信号ctl_i2c_stop();return 0;
}
3、测试程序
uint8_t test_array1[3 * AT24CX_PAGE_SIZE]; // 注:AT24C04时,AT24CX_PAGE_SIZE=8
uint8_t test_array2[3 * AT24CX_PAGE_SIZE]; // AT24C04时,一个页面有24个字节void at24c04_test_num(void)
{uint16_t i;uint16_t j;for (i = 0; i < 3 * AT24CX_PAGE_SIZE; i++){if (i >= 256)j = i - 256; // test_array1[256---383] 单元初始化数值 = 1---128else if (i >= 128)j = i - 128; // test_array1[128---255] 单元初始化数值 = 1---128elsej = i; // test_array1[0---127] 单元初始化数值 = 1---128test_array1[i] = j + 1;}memset(test_array2, 0x00, 3 * AT24CX_PAGE_SIZE);if (at24cx_checkok() == 1) // 如果检测到I2C器件存在{at24cx_writebytes(test_array1, 80, 3 * AT24CX_PAGE_SIZE); // 从I2C的地址80处开始写3页字节(测试跨页连续写)at24cx_readbytes(test_array2, 80, 3 * AT24CX_PAGE_SIZE); // 从I2C的地址80处开始读3页字节(测试跨页连续读)}printf("test begin\r\n");for (i = 0; i < sizeof(test_array2); ++i){printf("%d, ", test_array2[i]);}
}
结果如下:
相关文章:

STM32 通过软件模拟 I2C 驱动 24Cxx 系列存储器
目录 一、AT24CXXX 系列存储器介绍1、基本信息2、寻址方式3、页地址与页内单元地址4、I2C 地址5、AT24CXX 的数据读写5.1 写操作5.1.1 按字节写5.1.2 按页写 5.2 读操作5.2.1 当前地址读取5.2.2 随机地址读取5.2.3 顺序读取 二、代码实现1、ctl_i2c2、at24c3、测试程序 I2C 相关…...

Go语言匿名字段使用与注意事项
1. 定义 Go语言支持一种特殊的字段只需要提供类型而不需要写字段名的字段,称之为匿名字段或者嵌套字段。 所谓匿名字段实际上是一种结构体嵌套的方式,所以也可以称作嵌套字段。 这种方式可以实现组合复用,即通过匿名字段,结构体…...
2024最新!!Java后端面试题(2)看这一篇就够了
hello uu们 感谢收看!!!!我最近听了一首歌《21》,真的很感慨,马上步入20的我也感觉时间真的飞快...望大家都能过上理想的生活,不负内心的所托...现在口语化更新答案,让大家更加模拟的…...

超好用的10款视频剪辑软件,从入门到精通
视频剪辑软件哪款比较好呢?无论是专业制作团队、自媒体创作者,还是家庭用户,一款好用的视频剪辑软件都能极大地提升创作效率和作品质量。以下是十款备受推崇的视频剪辑软件,分别从适用人群、易用程度和功能特点进行介绍。 1.影忆…...
python股票因子,交易所服务器宕机,量化交易程序怎么应对
炒股自动化:申请官方API接口,散户也可以 python炒股自动化(0),申请券商API接口 python炒股自动化(1),量化交易接口区别 Python炒股自动化(2):获取…...

瑞芯微RK3566鸿蒙开发板Android11修改第三方输入法为默认输入法
本文适用于触觉智能所有支持Android11系统的开发板修改第三方输入法为默认输入法。本次使用的是触觉智能的Purple Pi OH鸿蒙开源主板,搭载了瑞芯微RK3566芯片,类树莓派设计,是Laval官方社区主荐的一款鸿蒙开发主板。 一、安装输入法并查看输入…...
使用nest+typeorm框架写数据库导致mysql的binlog暴增记录
这 两天用nesttypeorm写了一个商城,上线后mysql日志binlog两天就达到了10几个G,排查结果如下: 有个功能是定时遍历所有未签收的订单,看看是否到了自动签收时间,如果到了,就把订单状态设置成已签收。 代码…...

组合逻辑元件与时序逻辑元件
组合逻辑元件和时序逻辑元件都是数字电路中的基本构建块,但它们在功能和结构上存在显著差异。 1. 组合逻辑元件: 内容: 组合逻辑元件的输出仅取决于当前的输入,而与之前的输入无关。 它们没有记忆功能。 常见的组合逻辑元件包括: 与门 (AND…...

天龙八部怀旧单机微改人面桃花+安装教程+GM工具+虚拟机一键端
今天给大家带来一款单机游戏的架设:天龙八部怀旧单机微改人面桃花。 另外:本人承接各种游戏架设(单机联网) 本人为了学习和研究软件内含的设计思想和原理,带了架设教程仅供娱乐。 教程是本人亲自搭建成功的…...
docker管理
拉取容器镜像 docker pull 镜像名:镜像版本查看镜像 docker images查看容器列表 # 查看正在运行的容器 docker ps # 查看全部的容器(包括停止的容器) docker ps -a进入容器 docker exec -it 容器id /bin/bash停止容器 docker stop 容器id运行容器 docker start 容器id删除…...

electron教程(三)窗口设置
在main.js文件中,创建窗口时会设置窗口的大小,其实还有很多其他属性,可以根据实际需求选择设置,但部分属性存在局限性,官网也有明确告知:自定义窗口 | Electron (electronjs.org) 项目文件目录如下&#x…...

图像增强论文精读笔记-Deep Retinex Decomposition for Low-Light Enhancement(Retinex-Net)
1. 论文基本信息 论文标题:Deep Retinex Decomposition for Low-Light Enhancement 作者:Chen Wei等 发表时间和期刊:2018;BMVC 论文链接:https://arxiv.org/abs/1808.04560 2. 研究背景和动机 低光照条件下拍摄的…...

2024年配置YOLOX运行环境+windows+pycharm24.0.1+GPU
1.配置时间2024/9/25 2.Anaconda-python版本3.7,yolox版本0.2.0 YOLOX网址: https://github.com/Megvii-BaseDetection/YOLOX 本人下载的这个版本 1.创建虚拟环境 conda create -n yolox37 python37 激活 conda activate yolox37 2.安装Pytorch cuda等&…...

vue-i18n在使用$t时提示类型错误
1. 问题描述 Vue3项目中,使用vue-i18n,在模版中使用$t时,页面可以正常渲染,但是类型报错。 相关依赖版本如下: "dependencies": {"vue": "^3.4.29","vue-i18n": "^9.1…...
大厂面试真题-什么是CAS单点登录?什么原理
CAS(Central Authentication Service,中央认证服务)单点登录(SSO,Single Sign-On)的原理主要基于统一的认证机制和票据验证过程,使得用户只需在多个相互信任的应用系统中登录一次,即…...

用Java提取PDF表格到文本、CSV、Excel工作表
如何精准地提取PDF格式中嵌入的表格数据,并将其无缝转换为更加易于分析和操作的形式,如纯文本、CSV文件或Excel工作表,是一项重要的文档处理技巧。使用Java,我们可以简单地实现这一过程。本文将介绍如何利用Java从PDF文档提取表格…...
OpenCV视频I/O(10)视频采集类VideoCapture之从视频流中检索一帧图像函数 retrieve()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 解码并返回已抓取的视频帧。 cv::VideoCapture::retrieve() 是 VideoCapture 类的一个成员函数,用于从视频流中检索一帧图像。 retr…...

【RocketMQ】SpringBoot整合RocketMQ
🎯 导读:本文档详细介绍了如何在Spring Boot应用中集成Apache RocketMQ,并实现消息生产和消费功能。首先通过创建消息生产者项目,配置POM文件引入RocketMQ依赖,实现同步消息发送,并展示了如何发送普通字符串…...
mysql replace无法替换空格?如何解决
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互…...

Redis篇(环境搭建)
目录 一、安装包 1. Windows版下载地址 2. Linux版下载地址 二、安装Redis 1. 在Linux中安装Redis 2. 在Windows中安装Redis 3. 细节问题 三、Redis服务启动 1. 默认启动 2. 指定配置启动 3. 开机自启 四、Redis服务停止 1. Linux系统中启动和停止Redis 2. Window…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...