当前位置: 首页 > news >正文

AT24C02 EEPROM I2C驱动移植与读写实战:基于TI C2000 TMS320F28P550开发板

AT24C02 EEPROM I2C驱动移植与读写实战:基于TI C2000 TMS320F28P550开发板

最近在做一个基于TI C2000 DSP的项目,需要保存一些校准参数和运行状态,断电后还不能丢失。用芯片内部的Flash吧,操作起来有点麻烦,而且擦写次数有限。这时候,外挂一颗EEPROM就成了一个非常靠谱的选择。AT24C02这个小芯片,价格便宜、容量够用(256字节)、接口简单(I2C),是很多嵌入式项目的“老朋友”了。

今天,我就手把手带你,把AT24C02的驱动移植到TI的TMS320F28P550这款DSP开发板上。咱们不光是调通代码,更重要的是理解I2C通信的底层时序,以及如何根据芯片手册来编写可靠的读写函数。整个过程从硬件连接到软件配置,再到代码编写和验证,我都会详细拆解。如果你也在用C2000系列芯片,或者对I2C设备驱动感兴趣,这篇教程应该能帮到你。

1. 认识我们的“存储小助手”:AT24C02

在动手接线写代码之前,咱们先花几分钟了解一下AT24C02到底是个啥,以及它怎么工作。这能帮你后面理解代码时,知道每一步是在干什么。

EEPROM,中文叫“电可擦可编程只读存储器”。这个名字听起来有点绕,其实核心就两点:一是掉电数据不丢失,像U盘一样;二是可以用电信号反复擦写,不像以前的ROM只能写一次。它经常用来保存一些需要修改但又不能丢的数据,比如设备的配置参数、用户的设定值或者运行日志。

我们用的AT24C02,是EEPROM家族里很常用的一款。它的“02”代表容量是2K位(bit),换算成字节就是256字节。别看容量不大,存几十个参数绰绰有余。它通过I2C总线和我们的主控芯片(比如TMS320F28P550)通信。I2C总线特别省引脚,只需要两根线(SCL时钟线和SDA数据线)就能挂一堆设备,非常适合连接各种传感器、存储器。

这里有几个关键参数,你接线和编程时得心里有数:

  • 工作电压:1.8V到5.5V都行,兼容性很好,咱们开发板的3.3V供电完全没问题。
  • 通信接口:I2C。
  • 内存大小:256字节。
  • 页大小:16字节。这是它一次连续写入的最大单位,超过这个长度地址会“翻卷”,覆盖之前写的数据,这个坑后面会重点讲。
  • 设备地址:这是I2C设备寻址的关键。AT24C02的7位地址固定是1010开头,后面三位(A2, A1, A0)可以通过芯片上的硬件引脚电平来设置(高或低)。这样,一条I2C总线上最多能挂8个(2的3次方)AT24C02。我们通常把模块的这三个引脚都接地,所以地址就是1010000。加上最后一位读写位(0写,1读),完整的8位写地址就是0xA0,读地址是0xA1

2. 硬件连接与引脚配置

理论清楚了,咱们就来动手接线。这一步很简单,但一定要接对,不然后面调试全是白费功夫。

2.1 模块与开发板连线

我用的AT24C02模块和TI TMS320F28P550开发板(立创开发板)。你需要准备杜邦线,按照下面的对应关系连接:

AT24C02模块引脚开发板引脚说明
VCC3V3接3.3V电源
GNDGND接地
SCLGPIO51I2C时钟线
SDAGPIO50I2C数据线

注意:AT24C02模块上的A0, A1, A2地址选择引脚,我建议你都用跳线帽接到GND(低电平),这样设备地址就是0xA0/0xA1,和我们的代码一致。

2.2 使用SysConfig工具配置GPIO

TI的C2000芯片现在推荐用SysConfig图形化工具来配置引脚和外围设备,比直接啃寄存器手册方便太多了。咱们就用它来把GPIO50和GPIO51配置成普通的数字输出/输入引脚,用来模拟I2C时序(这种方式叫“软件I2C”或“GPIO模拟I2C”)。

  1. 打开工程:在你的CCS(Code Composer Studio)工程里,找到并双击c2000.syscfg这个文件。
  2. 添加GPIO配置:在SysConfig界面,找到GPIO相关的配置部分,点击“ADD”或“+”号,添加两个GPIO配置项。
  3. 配置引脚参数:分别配置这两个GPIO。
    • 第一个,我们用来做SCL(时钟线):
      • Pin: 选择GPIO51
      • Direction: 选择Output(输出)。虽然I2C的SCL只能是主机输出,但我们先统一配成输出。
      • Initial Value: 设为Low(低电平初始状态)。
      • Name (optional): 可以起个易懂的名字,比如Module_SCL
    • 第二个,用来做SDA(数据线):
      • Pin: 选择GPIO50
      • Direction: 选择Output(输出)。注意,SDA线是双向的,我们会在代码里动态切换输入输出模式。
      • Initial Value: 设为High(高电平初始状态,I2C总线空闲时为高)。
      • Name (optional): 比如Module_SDA
  4. 保存并生成代码:按Ctrl + S保存配置文件。然后按Ctrl + B编译一次工程。这时SysConfig会自动根据你的配置,在board.h等文件中生成对应的引脚宏定义。比如,你刚才起的Module_SCLModule_SDA名字,现在就成了可以在代码里直接使用的标识符。

提示:编译时可能会有些警告,只要不是错误,通常可以忽略。关键是要确认board.h文件里已经生成了我们定义的引脚宏。

3. 手把手编写驱动代码

硬件准备好了,接下来就是重头戏——写代码。我们会创建两个文件:bsp_at24c02.c(源文件)和bsp_at24c02.h(头文件)。我把它们放在工程里新建的module_driver文件夹里。

3.1 头文件定义 (bsp_at24c02.h)

头文件主要是宏定义和函数声明,干净利落。

#ifndef __BSP_AT24C02_H__ #define __BSP_AT24C02_H__ #include "tjx_init.h" // 这个头文件包含了board.h,所以我们的引脚宏在这里可用 // 定义SDA引脚的方向控制宏(非常关键!) #define SDA_IN() GPIO_setDirectionMode(Module_SDA, GPIO_DIR_MODE_IN) // 设置为输入模式 #define SDA_OUT() GPIO_setDirectionMode(Module_SDA, GPIO_DIR_MODE_OUT) // 设置为输出模式 // 定义SCL和SDA引脚的读写操作宏 #define SCL(BIT) GPIO_writePin(Module_SCL, BIT) // 控制SCL电平,BIT=0/1 #define SDA(BIT) GPIO_writePin(Module_SDA, BIT) // 控制SDA电平(输出模式时用) #define SDA_GET() GPIO_readPin(Module_SDA) // 读取SDA电平(输入模式时用) // 根据硬件连接定义的器件地址(A2=A1=A0=0) #define AT24C02_ADDRESS_READ 0xA1 // 读操作地址 #define AT24C02_ADDRESS_WRITE 0xA0 // 写操作地址 // 高层应用函数声明 void AT24C02_WriteByte(unsigned char WordAddress, unsigned char Data); unsigned char AT24C02_ReadByte(unsigned char WordAddress); #endif

代码解读

  • SDA_IN()SDA_OUT()软件模拟I2C的精髓。因为SDA线既要输出数据(主机发送),又要输入数据(主机接收从机应答和数据),所以必须在发送和接收的瞬间动态切换GPIO的方向。
  • 引脚操作宏(SCL,SDA,SDA_GET)让代码更简洁,底层调用了TI的驱动库函数。

3.2 底层I2C时序模拟 (bsp_at24c02.c)

这部分是驱动的基础,我们通过控制GPIO电平的高低和延时,来“画”出I2C通信的时序图。一定要理解每个时序的意义。

起始信号 (IIC_Start) 和停止信号 (IIC_Stop)

I2C通信总是以起始信号开始,以停止信号结束。

  • 起始信号:当SCL为高电平时,SDA出现一个下降沿。
  • 停止信号:当SCL为高电平时,SDA出现一个上升沿。
void IIC_Start(void) { SDA_OUT(); // 确保SDA为输出模式 SDA(1); // SDA拉高 delay_us(5); // 短暂延时 SCL(1); // SCL拉高 delay_us(5); SDA(0); // 在SCL高期间,SDA拉低,产生起始条件 delay_us(5); SCL(0); // 拉低SCL,准备后续数据传输 delay_us(5); } void IIC_Stop(void) { SDA_OUT(); SCL(0); // 确保SCL先拉低 SDA(0); // SDA拉低 delay_us(5); SCL(1); // SCL拉高 delay_us(5); SDA(1); // 在SCL高期间,SDA拉高,产生停止条件 delay_us(5); }
发送一个字节 (Send_Byte) 和接收一个字节 (Read_Byte)

I2C的数据传输在SCL为低电平时变化,在SCL为高电平时保持稳定并被读取。

  • 发送:从最高位(MSB)开始,依次将数据的每一位放到SDA线上,然后制造一个SCL脉冲(低->高->低),对方就在SCL高的时候读取这一位。
  • 接收:主机先释放SDA线(设置为输入),然后在每个SCL高电平期间去读取SDA线的状态,拼接到接收变量里。
void Send_Byte(uint8_t dat) { int i = 0; SDA_OUT(); // 设置为输出模式 SCL(0); // 拉低时钟线开始 for(i = 0; i < 8; i++) // 循环8次,发送8个bit { // 取出最高位,右移7位得到0或1,然后设置到SDA引脚 SDA( (dat & 0x80) >> 7 ); delay_us(1); SCL(1); // 拉高SCL,从机在此刻采样数据位 delay_us(5); SCL(0); // 拉低SCL,为下一个数据位做准备 delay_us(5); dat <<= 1; // 数据左移,准备发送下一位 } } unsigned char Read_Byte(void) { unsigned char i, receive = 0; SDA_IN(); // 关键!设置为输入模式,释放SDA线,让从机控制 for(i = 0; i < 8; i++) { SCL(0); delay_us(5); SCL(1); // 拉高SCL,此时从机会将数据位放到SDA上 delay_us(5); receive <<= 1; // 左移,为接收新位腾出空间 if( SDA_GET() ) // 读取SDA引脚电平 { receive |= 1; // 如果为高,最低位置1 } delay_us(5); } SCL(0); return receive; }
应答机制 (IIC_Send_Ack 与 I2C_WaitAck)

I2C每传输完一个字节(8位),接收方必须发送一个应答信号(ACK)。

  • 主机等待从机应答:主机发送完地址或数据后,需要释放SDA线(设为输入),然后产生一个SCL脉冲。在这个脉冲的高电平期间,主机去检查SDA线是否被从机拉低(ACK=0)。如果从机无应答(SDA仍为高),可能地址错误或设备忙。
  • 主机发送应答:主机接收完从机的一个字节数据后,需要在下一个时钟周期通过拉低SDA线来发送一个应答信号(ACK),告诉从机“我收到了,请发下一个”。如果不想再接收了,就发送非应答(NACK),然后发停止信号。
void IIC_Send_Ack(unsigned char ack) { SDA_OUT(); SCL(0); SDA(0); // 先拉低,准备发送ACK delay_us(5); if(!ack) SDA(0); // 发送ACK (0) else SDA(1); // 发送NACK (1) SCL(1); // 产生一个时钟脉冲,从机在此刻读取应答位 delay_us(5); SCL(0); SDA(1); // 释放SDA线 } unsigned char I2C_WaitAck(void) { char ack = 0; unsigned char ack_flag = 10; // 超时计数 SCL(0); SDA(1); // 主机释放SDA线 SDA_IN(); // 设置为输入,等待从机拉低 delay_us(5); SCL(1); // 产生时钟脉冲 delay_us(5); // 等待SDA被拉低(ACK),同时做超时判断 while( (SDA_GET()==1) && ( ack_flag ) ) { ack_flag--; delay_us(5); } if( ack_flag <= 0 ) // 超时,无应答 { IIC_Stop(); // 发送停止信号 return 1; // 返回错误 } else { SCL(0); SDA_OUT(); // 恢复输出模式 } return 0; // 返回成功 }

3.3 AT24C02应用层读写函数

底层时序函数搭好了,现在来写真正操作EEPROM的函数。根据AT24C02的数据手册,它的读写操作有固定的帧格式。

字节写函数 (AT24C02_WriteByte)

向指定地址写入一个字节。

  1. 发送起始信号。
  2. 发送器件写地址 (0xA0)。
  3. 等待应答。
  4. 发送要写入的内存地址(0-255)。
  5. 等待应答。
  6. 发送要写入的数据字节
  7. 等待应答。
  8. 发送停止信号。
void AT24C02_WriteByte(unsigned char WordAddress, unsigned char Data) { IIC_Start(); Send_Byte(AT24C02_ADDRESS_WRITE); // 发送写地址 I2C_WaitAck(); Send_Byte(WordAddress); // 发送内存地址 I2C_WaitAck(); Send_Byte(Data); // 发送要存储的数据 I2C_WaitAck(); IIC_Stop(); // 停止信号触发芯片内部写周期 delay_ms(5); // 重要!等待内部写周期完成(典型值5ms) }

注意IIC_Stop()后,AT24C02开始内部擦写操作,这段时间(最多5ms)它不会响应I2C总线。所以写操作后必须加延时,或者用查询方式等待它写完,否则紧接着的读写操作会失败。这是最容易忽略的坑!

随机读函数 (AT24C02_ReadByte)

从指定地址读取一个字节。这个过程稍微复杂点,叫做“随机读”,需要先“假装”写一下来告诉芯片我们要读哪个地址。

  1. 发送起始信号。
  2. 发送器件写地址 (0xA0)。
  3. 等待应答。
  4. 发送要读取的内存地址
  5. 等待应答。
  6. 再次发送起始信号(称为“重复起始条件”)。
  7. 发送器件读地址 (0xA1)。
  8. 等待应答。
  9. 读取一个字节数据(此时主机不再发送ACK,而是发送NACK)。
  10. 发送停止信号。
unsigned char AT24C02_ReadByte(unsigned char WordAddress) { unsigned char Data; IIC_Start(); Send_Byte(AT24C02_ADDRESS_WRITE); // 第一步:发送写地址 I2C_WaitAck(); Send_Byte(WordAddress); // 第二步:发送要读的地址 I2C_WaitAck(); IIC_Start(); // 第三步:重新起始 Send_Byte(AT24C02_ADDRESS_READ); // 第四步:发送读地址 I2C_WaitAck(); Data = Read_Byte(); // 第五步:读取数据 IIC_Send_Ack(1); // 第六步:发送非应答(NACK),表示只读一个字节 IIC_Stop(); return Data; }

4. 上机验证:读写测试

代码都写好了,最后一步就是烧录到板子上跑起来看看。我们在主函数里写个简单的测试程序。

void main(void) { // ... 芯片初始化代码(CCS自动生成)... Device_init(); Device_initGPIO(); // ... 其他初始化 ... lc_printf("\nAT24C02 Demo Start.....\r\n"); uint8_t read_back_data_1 = 0; uint8_t read_back_data_2 = 0; // 测试1:向地址0写入数据48 AT24C02_WriteByte(0, 48); delay_ms(10); // 等待写入完成 // 测试2:向地址8写入数据66 AT24C02_WriteByte(8, 66); delay_ms(10); // 测试3:从地址0读出数据 read_back_data_1 = AT24C02_ReadByte(0); // 测试4:从地址8读出数据 read_back_data_2 = AT24C02_ReadByte(8); // 通过串口打印结果 lc_printf("Read from Address 0: %d (should be 48)\r\n", read_back_data_1); lc_printf("Read from Address 8: %d (should be 66)\r\n", read_back_data_2); while(1) { // 主循环,可以加个LED闪烁表示程序在跑 // ... } }

验证结果: 如果一切顺利,你通过串口助手应该能看到:

Read from Address 0: 48 (should be 48) Read from Address 8: 66 (should be 66)

这就说明你的AT24C02驱动移植成功了!你可以尝试修改写入的地址和数据,甚至写一个循环去读写整个256字节的空间,看看是否都正常。

几个调试心得

  1. 时序是关键:如果读写失败,首先检查delay_us的延时是否足够。不同主频的MCU需要调整。可以用逻辑分析仪或者示波器抓一下SCL和SDA的波形,对照I2C时序图看起始、停止、数据位、应答位对不对。
  2. 地址别搞错:确认你的AT24C02模块A0,A1,A2的接法,和代码里的地址定义是否匹配。
  3. 写后等待AT24C02_WriteByte函数后一定要有足够延时(delay_ms(5)),这是芯片硬件要求的。
  4. 上拉电阻:I2C总线需要上拉电阻(通常4.7kΩ到10kΩ)。如果你的模块上没有集成,需要在SCL和SDA线上各接一个上拉到3.3V。很多开发板的I2C接口已经内置了上拉,用GPIO模拟时则需要自己外接。

好了,关于AT24C02在TI C2000上的驱动移植和基本读写操作,到这里就全部讲完了。掌握了这些,你就能在项目里用它来可靠地存储数据了。如果需要存储超过一个字节的数据块,你可以基于WriteByteReadByte函数,很容易地封装出页写和连续读的函数,原理都是一样的,注意页边界不要跨页写入就行。

http://www.jsqmd.com/news/492748/

相关文章:

  • 便携式锂电焊台与60W双向PD快充融合设计
  • 突破数字封锁:baidu-wangpan-parse的技术突围战
  • VS Code 通义灵码实战:从安装到智能编码全流程解析
  • Hunyuan-MT-7B保姆级部署指南:单卡RTX 4080也能跑的高质量翻译
  • 从SQL到向量搜索:用pgvector改造现有PostgreSQL业务的避坑指南
  • 2026年去AI味提示词Kimi豆包元宝通用?不如直接用降AI工具 - 还在做实验的师兄
  • NVIDIA Profile Inspector显卡驱动深度配置指南:从问题诊断到性能优化
  • Qwen Pixel Art应用场景:独立开发者打造像素风APP图标与启动页素材
  • 2026年利津羊粪厂家哪家好?利津发酵鸡粪、干鸡粪、稻壳鸡粪、有机肥、纯鸡粪、风干颗粒小鸡粪、牛粪、猪粪、鸭粪厂家选择指南,利津县旺田肥业品类齐全+服务贴心 - 海棠依旧大
  • Qwen2.5-VL-7B效果实测:多模态视觉任务处理,RTX 4090推理速度惊艳
  • MySQL列转行避坑指南:为什么你的UNION ALL结果不对?
  • 为什么你的Docker 27集群启动慢10倍?揭秘storage-driver配置中被忽略的4个内核级陷阱
  • Phi-3-vision-128k-instruct精彩案例:同一张建筑图纸多轮追问——结构/材料/造价逐层解析
  • 图表替代文字降AI率不会?看完这篇5分钟学会 - 还在做实验的师兄
  • 基于立创开发板(R7FA6E2BB3CNE)的MS5611气压传感器I2C驱动移植与数据读取实战
  • Youtu-VL-4B-Instruct WebUI稳定性压测:100并发持续2小时无内存泄漏报告
  • 基于TI MSPM0G3507的土壤湿度传感器模块移植与自动浇花应用实战
  • Face3D.ai Pro开源大模型:基于ModelScope的cv_resnet50_face-reconstruction可商用方案
  • 动态卷积避坑指南:从原理到实现的5个关键问题解析
  • GD32VW553开发板I2C驱动SHT20温湿度传感器移植实战
  • AFSim 2.9任务处理器实战:从零配置有限状态机到任务分配
  • 22潘长栋
  • 解决 VS2026 使用卡顿的问题
  • Cascade R-CNN实战:如何用PyTorch一步步实现高精度目标检测(附完整代码)
  • 亲测好用 9个降AIGC平台全场景通用测评,哪款最能帮你降AI率?
  • Phi-3-vision-128k-instruct开源可部署:轻量多模态模型本地化实践指南
  • League Akari:基于LCU API的英雄联盟智能辅助工具全攻略
  • Dify评估系统插件安装终极 checklist,含OpenSSL版本冲突修复、CUDA驱动适配表及Judge模型权重校验密钥
  • 2026年AI率从88%降到1.6%是怎么做到的?分享我的降AI经验 - 还在做实验的师兄
  • Phi-3-vision-128k-instruct智能助手:基于Chainlit的私有图文问答平台