新闻详情

新闻详情

首页 / 资讯中心 / 详情

PN学堂GD32教程第11篇——I2C与EEPROM

发布时间:2026/10/2 15:17:40来源:尧图网络
PN学堂GD32教程第11篇——I2C与EEPROM
代码仓库git clone https://gitee.com/jumayusi/unit11-gd32-iic.git前言IIC的基本知识这篇笔记不会再详细介绍。11.2 EEPROM存储芯片AT24C02这个分类并不严谨FeRAM\ReRAM\MRAM都是已经在市场上量产的非易失性存储介质FeRAM是现在AI存算一体的主流解决方案这里不做过多展开了。EEPROM的主要特点Flash的主要特点24C02不仅支持按字节写入也支持按页写入。11.3 11.3 软件模拟IIC驱动24C02-起始、停止、应答引脚初始化GPIO口模拟I2C的基石是用拉高IO口作为SET信号线拉低IO口作为CLR信号线因此我们可以直接用宏定义声明#define GET_I2C_SDA() gpio_input_bit_get(GPIOB, GPIO_PIN_7) // 读取SDA端口 #define SET_I2C_SCL() gpio_bit_set(GPIOB, GPIO_PIN_6) // 时钟线SCL输出高电平 #define CLR_I2C_SCL() gpio_bit_reset(GPIOB, GPIO_PIN_6) // 时钟线SCL输出低电平 #define SET_I2C_SDA() gpio_bit_set(GPIOB, GPIO_PIN_7) // 数据线SDA输出高电平 #define CLR_I2C_SDA() gpio_bit_reset(GPIOB, GPIO_PIN_7) // 数据线SDA输出低电平由于我们只使用了GPIO这一个peripheral,因此I2C的初始化就是static void GpioInit(void) { rcu_periph_clock_enable(RCU_GPIOB); gpio_init(GPIOB, GPIO_MODE_OUT_OD, GPIO_OSPEED_10MHZ, GPIO_PIN_6 | GPIO_PIN_7); } void EepromDrvInit(void) { GpioInit(); }I2CStart下面实现I2C的起始信号代码示范/** ******************************************************************* * function 产生IIC起始时序准备发送或接收数据前必须由起始序列开始 * param * return * brief SCL为高电平时SDA由高电平向低电平跳变开始传输数据 * 生成下图所示的波形图即为起始时序 * 1 2 3 4 * __________ * SCL : __/ \_____ * ________ * SDA : \___________ ******************************************************************* */ static void I2CStart(void) { SET_I2C_SDA(); // 1#数据线SDA输出高电平 SET_I2C_SCL(); // 2#时钟线SCL输出高电平 DelayNus(4); // 延时4us CLR_I2C_SDA(); // 3#数据线SDA输出低电平 DelayNus(4); // 延时4us CLR_I2C_SCL(); // 4#时钟线SCL输出低电平保持I2C的时钟线SCL为低电平准备发送或接收数据 DelayNus(4); // 延时4us }1# 2#我们知道I2C在总线空闲时期SCL和SDA都需要保持高电平硬件实现上SCL/SDA两个引脚都接上了上拉电阻但为了避免IO口处于不确定状态我们需要先操作两个IO拉高。tHD;STA是I2C能够正常建立通信的一个重要指标它的中文全称叫做起始保持时间飞利浦在制定IIC规则时约束这个时间典型值为4us我们实际写代码时必须参考芯片的时序约束3V3的Vio下只需要0.25us我们这里规定4us是绰绰有余。3#同样的在完成SDA由高拉低的操作之后我们同样提供4us的相位冗余这是为了满足tHD;STA。4#最后拉低SCL电平因为正常通信时SDA只可以在SCL低电平时发生跳变这是为了数据传输做准备。I2CStop/** ******************************************************************* * function 产生IIC停止时序 * param * return * brief SCL为高电平时SDA由低电平向高电平跳变结束传输数据 * 生成下图所示的波形图即为停止时序 * 1 2 3 4 * _______________ * SCL : ______/ * __ ____________ * SDA: \______/ ******************************************************************* */ static void I2CStop(void) { CLR_I2C_SDA(); // 2#数据线SDA输出低电平 DelayNus(4); // 延时4us SET_I2C_SCL(); // 3#时钟线SCL输出高电平 DelayNus(4); SET_I2C_SDA(); // 4#数据线SDA输出高电平发送I2C总线结束信号 }就不介绍的那么细了非常推荐初学者用示波器实操一下加深印象。I2CSendByte代码示范/** ******************************************************************* * function 发送一字节数据从高位开始发送出去 * param byte * return * brief 下面是具体的时序图 * 1 2 3 4 * ______ * SCL: ________/ \______ * ______________________ * SDA: \\\___________________ ******************************************************************* */ static void I2CSendByte(uint8_t byte) { for (uint8_t i 0; i 8; i) // 循环8次从高到低取出字节的8个位 { if ((byte 0x80)) // 2#取出字节最高位并判断为‘0’还是‘1’从而做出相应的操作 { SET_I2C_SDA(); // 数据线SDA输出高电平数据位为‘1’ } else { CLR_I2C_SDA(); // 数据线SDA输出低电平数据位为‘0’ } byte 1; // 左移一位次高位移到最高位 DelayNus(4); // 延时4us SET_I2C_SCL(); // 3#时钟线SCL输出高电平 DelayNus(4); // 延时4us CLR_I2C_SCL(); // 4#时钟线SCL输出低电平 DelayNus(4); // 延时4us } }其实很好理解核心就是参考SCL的电平低电平改变数据高电平采样数据。在时钟信号实现跳变之前把SDA要输出的0/1塞到总线上。I2CReadByte/** ******************************************************************* * function 读取一字节数据 * param * return 读取的字节 * brief 下面是具体的时序图 * ______ * SCL: ______/ \___ * ____________________ * SDA: \\\\______________\\\ ******************************************************************* */ static uint8_t I2CReadByte(void) { uint8_t byte 0; // byte用来存放接收的数据 SET_I2C_SDA(); // 释放SDA for (uint8_t i 0; i 8; i) // 循环8次从高到低读取字节的8个位 { SET_I2C_SCL(); // 时钟线SCL输出高电平 DelayNus(4); // 延时4us byte 1; // 左移一位空出新的最低位 if (GET_I2C_SDA()) // 读取数据线SDA的数据位 { byte; // 在SCL的上升沿后数据已经稳定因此可以取该数据存入最低位 } CLR_I2C_SCL(); // 时钟线SCL输出低电平 DelayNus(4); // 延时4us } return byte; // 返回读取到的数据 }和上文加粗字体一样主机MCU先释放总线然后控制SCL切换到高电平之后读取SDA上从机发来的数据。循环内最后切换SCL到低电平并且钳住4us保证Tlow的时序完整性。I2CWaitAck/I2CSendAck/I2CSendNack/** ******************************************************************* * function 等待接收端的应答信号 * param * return 1接收应答失败0接收应答成功 * brief 当SDA拉低后表示接收到ACK信号然后拉低SCL * 此处表示发送端收到接收端的ACK * _______|____ * SCL: | \_________ * _______| * SDA: \_____________ ******************************************************************* */ static bool I2CWaitAck(void) { uint8_t errTimes 0; SET_I2C_SDA(); // 释放SDA总线,很重要 DelayNus(4); // 延时4us SET_I2C_SCL(); // 时钟线SCL输出高电平 DelayNus(4); // 延时4us while (GET_I2C_SDA()) // 读回来的数据如果是高电平即接收端没有应答 { errTimes; // 计数器加1 if (errTimes 250) // 如果超过250次则判断为接收端出现故障因此发送结束信号 { I2CStop(); // 产生一个停止信号 return false; // 返回值为1表示没有收到应答信号 } } CLR_I2C_SCL(); // 表示已收到应答信号时钟线SCL输出低电平 DelayNus(4); // 延时4us return true; // 返回值为0表示接收应答成功 } /** ******************************************************************* * function 发送应答信号 * param * return * brief 下面是具体的时序图 * 1 2 3 4 5 * ______ * SCL: ________/ \____________ * __ ______ * SDA: \___________________/ ******************************************************************* */ void I2CSendAck(void) { CLR_I2C_SDA(); // 2#数据线SDA输出低电平 DelayNus(4); // 延时4us SET_I2C_SCL(); // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉低为应答信号 DelayNus(4); // 延时4us CLR_I2C_SCL(); // 4#时钟线SCL输出低电平 DelayNus(4); // 延时4us SET_I2C_SDA(); // 5#数据线SDA输出高电平释放SDA总线,很重要 } /** ******************************************************************* * function 发送非应答信号 * param * return * brief 下面是具体的时序图 * 1 2 3 4 * ______ * SCL: ________/ \______ * __ ___________________ * SDA: __/ ******************************************************************* */ void I2CSendNack(void) { SET_I2C_SDA(); // 2#数据线SDA输出高电平 DelayNus(4); // 延时4us SET_I2C_SCL(); // 3#时钟线SCL输出高电平在SCL上升沿前就要把SDA拉高为非应答信号 DelayNus(4); // 延时4us CLR_I2C_SCL(); // 4#时钟线SCL输出低电平 DelayNus(4); // 延时4us }在 I2C 里第 9 个时钟每个字节 8 位之后的应答位是收/发双方唯一的握手机制。谁接收数据谁就在第 9 个时钟给出应答。所以主机MCU处于不同数据收发方向时用的是不同函数接口主机发数据写 EEPROM→ 从机应答 → 主机用 I2CWaitAck()主机收数据读 EEPROM→ 主机应答 → 主机用 I2CSendAck() / I2CSendNack()。11.4 软件模拟IIC驱动24C02-读写多字节数据
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

OmniAgent开发者指南:如何为Agent编写自定义工具与扩展,从Tool基类到Manifest插件 2026/10/2 21:39:07

OmniAgent开发者指南:如何为Agent编写自定义工具与扩展,从Tool基类到Manifest插件

OmniAgent开发者指南:如何为Agent编写自定义工具与扩展,从Tool基类到Manifest插件 【免费下载链接】OmniAgent An agent capable of self-evolving and dynamically hardening security 项目地址: https://gitcode.com/gh_mirrors/om/OmniAgent O…

阅读更多 →
远程app下载方法 远程操控软件推荐无界趣连2.0 2026/10/2 21:38:42

远程app下载方法 远程操控软件推荐无界趣连2.0

想要实现高效的跨设备远程操控,掌握正确的远程APP下载方法是第一步,正规靠谱的远程APP下载方法能帮大家避开捆绑软件、盗版工具,轻松解锁稳定流畅的远程操控体验。综合对比各类工具后,推荐大家下载使用无界趣连2.0,下载…

阅读更多 →
MacBook本地跑33B视频生成模型:h3.c封装ComfyUI完整工程实践 2026/10/2 21:38:26

MacBook本地跑33B视频生成模型:h3.c封装ComfyUI完整工程实践

上个月我干了一件有点疯狂的事:把 antirez 那份手写的 h3.c 推理代码,封装成了一个 ComfyUI 自定义节点,然后在 MacBook 上把一个 33B 参数级别的视频生成模型跑了起来。先说结论:这台机器没有大显存显卡,也没有任何云…

阅读更多 →
OpenAPI 自动生成 DeepSeek 工具,解决函数调用 JSON 手写难题 2026/10/2 21:38:25

OpenAPI 自动生成 DeepSeek 工具,解决函数调用 JSON 手写难题

这些年接过的后端服务越来越多,手头维护的 REST API 随便一数就是几十个,每次要接大模型 Function Calling 的时候,最头疼的就是“写 Tools”。一个接口一个接口地手写 JSON Schema,描述参数、写含义、想 example,写完…

阅读更多 →
为什么Amicro的动画如此丝滑?揭秘5组Spring物理预设(snappy/bouncy/smooth/gentle/stiff) 2026/10/2 21:38:24

为什么Amicro的动画如此丝滑?揭秘5组Spring物理预设(snappy/bouncy/smooth/gentle/stiff)

为什么Amicro的动画如此丝滑?揭秘5组Spring物理预设(snappy/bouncy/smooth/gentle/stiff) 【免费下载链接】Amicro--Micro-transitions- 项目地址: https://gitcode.com/gh_mirrors/am/Amicro--Micro-transitions- Amicro&#xff08…

阅读更多 →
Next.js与LangGraph.js实战:构建稳定可控的简历优化AI Agent 2026/10/2 21:38:23

Next.js与LangGraph.js实战:构建稳定可控的简历优化AI Agent

干过简历工具这类项目的人应该都有同感:需求看着简单,无非是"帮我看看简历"、"按这个JD优化一下"、"给一段经历润色",可一旦要做得像回事,背后就是一连串的脏活。解析格式、提取结构化信息、理解岗…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉