简介STC8G系列单片机的I2C从模式实现代码面向嵌入式开发者与单片机学习者解决主机与从机通信中的被动响应与中断处理问题。I2C协议使用数据线SDA与时钟线SCL两根信号线实现双向通信适用于连接EEPROM、传感器、显示屏等常见外围设备。方案基于中断模式编写详细演示了通过I2C控制、状态与数据寄存器完成从机地址配置与工作模式设置初始化时先关闭I2C模块、设置从机地址并开启中断使能中断服务程序通过读取状态寄存器区分起始条件、数据接收、数据发送等事件并正确处理应答与非应答信号同时覆盖总线冲突、数据溢出等异常的检测与恢复思路。压缩包为rar格式内仅含一个C语言源文件约1KB代码结构清晰按初始化配置、中断服务函数、数据传输逻辑及错误处理机制组织便于直接对照学习读者可借此掌握从机地址的匹配与应答以及数据收发过程中的时序控制等关键细节。目前已有2669人学习下载适合希望深入理解I2C从机通信机制并能快速上手STC8G硬件编程的读者。1. 单片机 I2C 从模式STC8G 怎么当好一颗被主机点名的从芯片大多数写 I2C 的人第一次都是当主机读 OLED、读写 EEPROM方向是「我主动发起」。STC8G 的 I2C 从模式正好反过来单片机被挂到总线上当从机等外部主机来读或写代码不是发起传输而是随时响应。这个项目就是 STC8G 硬件 I2C 做从模式的完整代码——初始化、地址匹配、收发中断、事件处理与主循环分离下载后改一个从机地址就能跑。适合两类人要把 STC8G 做成总线子模块、跟另一块开发板通信的开发者刚接触硬件 I2C、想抄一份干净代码再改成自己协议的新手。从模式对新手来说像个黑匣子读完这篇你能搞清楚状态机在哪触发、中断里该干什么以及第一次调从模式为什么会翻车。2. 技术选型与工程骨架硬件 I2C 从机为什么比模拟时序省心很多人写 I2C 第一反应是软件模拟两个 GPIO 拉来拉去自己产生时钟、自己读 ACK。当主机这么干没问题因为节奏掌握在你手里。做从机再这么玩就麻烦了这一章先把优缺点摆清楚再把资源包的文件结构拆开看。2.1 从机的活儿和主机完全反过来先想清楚事件再写代码软件模拟 I2C 当主机时START、STOP、ACK 都是你自己拉的电平SCL 的每个沿都是你主动给的时序完全可控。一旦角色换成从机SCL 变成外部主机给的信号你永远不知道下一个边沿什么时候来。如果靠外部中断抓边沿、再用延时去猜位宽总线一忙或者主机切了速率整套时序就废了。STC8G 的硬件 I2C 外设把这块全接管了总线时序由硬件产生从机状态机空闲、地址匹配、接收、发送也是硬件维护的软件要做的事情被压缩成一件——在中断里读状态寄存器然后根据事件类型把数据从接收寄存器搬走或者往发送寄存器里塞一个字节。对比维度软件模拟从机STC8G 硬件从机引脚占用任意两个 GPIO固定引脚可切换映射CPU 占用每个位都要中断伺候事件中断一字节一次时序稳定性依赖延时精度速率一变就废硬件处理100k/400k 都能扛调试难度逻辑分析仪都难定位读状态寄存器即可定位代码量状态机要自己维护初始化加一个中断函数从模式硬件和软件模拟的关键差别在于软件模拟从机要在「位」这一层跟外部时钟赛跑硬件从机只在「事件」这一层跟你打交道。所以这个项目选型直接锁死硬件 I2C代码里没有任何延时函数所有耗时逻辑全部丢给主循环。2.2 工程目录与文件职责一个最小从机工程该有哪些文件一个能跑的从机工程不需要花哨的分层四五个文件就够重点是职责别混。下面是这套资源的目录结构文件职责main.c主循环、事件轮询、协议处理入口i2c_slave.c从机初始化、中断服务函数i2c_slave.h从机地址、缓冲区大小、状态位掩码定义config.h引脚、地址、缓冲区等可调参数doc/protocol.md自定义通信协议说明main.c 里不放任何 I2C 寄存器操作它只负责「看到事件标志之后做什么」i2c_slave.c 里不放任何业务逻辑它只负责「把硬件事件转成软件标志」。这个划分是我实际调从模式吃了几次亏之后养成的习惯中断里一旦写了业务代码改协议就得连中断一起动后面想加寄存器功能、想换地址都要翻旧账。头文件里集中放地址和缓冲区大小换一颗芯片、换一条总线改一行重新编译就完事。3. 核心模块拆解从地址匹配到一帧数据收发的完整链路这一章是整个资源包的核心。从机代码的骨架其实就是三块初始化把外设配成从机模式中断服务函数按事件分叉处理收发主循环消费事件标志。下面一段一段过代码可以直接抄进 C51 工程。3.1 初始化开 XFR、配从机地址、开中断三件事缺一不可STC8G 的 I2C 寄存器在 XFR 扩展寄存器区地址从 0xFE80 开始。访问这个区域之前必须先置位 P_SW2 的最高位否则写寄存器会静默失败——你写了读回来还是原来的值。#include stc8g.h #include i2c_slave.h void i2c_slave_init(void) { P_SW2 | 0x80; // EAXFR 1允许访问 XFR 扩展寄存器区 I2CCFG 0x80; // ENS1 1先使能 I2C 外设 I2CCFG | 0x20; // 打开从机模式位对照头文件 I2CMS0 定义 I2CSLR 0x50; // 7 位从机地址 0x50不含 R/W 位 I2CSMCR I2C_SMCR_ERR | I2C_SMCR_END | I2C_SMCR_RX | I2C_SMCR_TX; I2CSST 0x00; // 清一次状态寄存器避免上电残留标志 EA 1; // 开总中断 }逐行说一下关键点。P_SW2 | 0x80 这一句放在最前面后面所有对 I2CCFG、I2CSLR 的写入才有意义这是从模式调不通的第一大元凶。I2CCFG 先写 0x80 使能外设再单独把从机模式位置 1比一次性写一个魔数可读性好不同批次 STC8G 头文件对模式位的命名有差异分两步写方便你对号入座。I2CSLR 写的是 7 位地址 0x50注意不是 0xA00xA0 是 8 位形式从机侧只认 7 位数字本身。I2CSMCR 的四个宏在头文件里定义分别打开错误中断、一帧结束中断、接收中断和发送中断。从机模式建议全开缺一个你都不知道总线上发生了什么。注意I2CSST 在多数 STC8G 批次上是写 0 清事件标志如果你的头文件注释写的是读后自动清除那这句多余写入也没有副作用留着是保险动作。3.2 中断服务函数读一次 I2CSST按事件位分叉处理硬件 I2C 从机的中断和串口中断不一样它不是一个中断只对应一种情况而是地址匹配、收到数据、需要发送、停止条件、总线错误都会进同一个中断向量。所以中断函数的第一件事永远是读 I2CSST看看这次到底是哪个事件。void i2c_isr(void) interrupt 18 { uint8_t sst I2CSST; // 一次只读一次状态后面都基于这个值判断 if (sst I2C_SST_ADDR) // 地址匹配主机选中了本机 { i2c_event | I2C_EVT_ADDR; tx_index 0; // 发送游标复位准备应答读操作 } if (sst I2C_SST_RX) // 接收事件主机写过来一个字节 { if (rx_len I2C_RX_SIZE) rx_buf[rx_len] I2CRXD; // 从接收寄存器取走数据 i2c_event | I2C_EVT_RX; } if (sst I2C_SST_TX) // 发送事件主机在读需要吐出下一个字节 { if (tx_index tx_len) I2CTXD tx_buf[tx_index]; else I2CTXD 0xFF; // 发送区越界就补 0xFF别让总线空着 i2c_event | I2C_EVT_TX; } if (sst I2C_SST_END) // 一帧结束停止条件已由硬件收到 { i2c_event | I2C_EVT_END; rx_len 0; // 一帧结束接收游标归零 } if (sst I2C_SST_ERR) // 总线错误干扰或时序异常 { i2c_event | I2C_EVT_ERR; } I2CSST 0x00; // 清事件标志释放硬件状态机 }这个函数体里没有任何业务判断它只做三件事认事件、搬数据、置标志。注意 18 这个中断号对应向量地址 0x0093是 STC8G 系列 I2C 中断的常规位置但不同型号的向量表有差异移植到其他系列时用向量表反推别直接抄数字。你要关心的另一个细节是发送分支里的 0xFF 兜底。主机发起读操作时从机的发送中断会一个字节一个字节地催你如果你 tx_len 根本是 0总线就会一直处于未应答状态主机那边表现为读超时。补一个 0xFF 至少让总线上有确定的数据主机不会卡死。真正要不要回 0xFF取决于你的协议约定但「别让发送中断空转」这条是通用的。3.3 缓冲区与主循环中断只搬数据业务全部留在 while(1) 里缓冲区设计是从模式最容易走歪的地方。我的做法是接收缓冲和发送缓冲分开都声明成 volatile因为中断和主循环会同时访问它们。volatile uint8_t i2c_event 0; // 事件标志位主循环轮询 volatile uint8_t rx_buf[I2C_RX_SIZE]; // 接收缓冲 volatile uint8_t rx_len 0; // 当前接收长度 static uint8_t tx_buf[I2C_TX_SIZE]; // 发送缓冲 static uint8_t tx_len 0; // 待发送长度 static uint8_t tx_index 0; // 发送游标主循环长这样while (1) { if (i2c_event I2C_EVT_END) // 主机完成了一轮访问 { i2c_event ~I2C_EVT_END; if (rx_len 1) { uint8_t cmd rx_buf[0]; // 第一个字节当命令 protocol_process(cmd, rx_buf[1], rx_len - 1); } rx_len 0; } }这里有个容易忽略的点rx_len 的清零动作同时出现在中断和主循环里。中断在检测到一帧结束时就先把 rx_len 归零是为了防止主循环处理太慢、下一帧数据又进来时新旧数据混在一起。主循环在处理完后再清一次纯粹是双保险。protocol_process 就是你的协议入口写命令、读状态、改参数都在这里展开不会碰任何 I2C 寄存器。volatile 之所以必须加是因为中断和主循环共享这些变量C51 编译器在开优化时可能把 rx_len 直接放进寄存器缓存不加 volatile 会出现「主循环读到的长度永远不更新」这种玄学问题。这属于典型的「代码看着没问题、跑起来就是不对」优先怀疑变量共享。4. 跑通最小系统硬件连线、C51 工程与主机验证三板斧代码读懂了不算完得在板子上跑起来。这一章从接线讲到烧录再讲怎么证明从机真的在干活。最小系统不需要什么昂贵设备一块 STC8G 板子、一个外部主机、两根杜邦线就够。4.1 硬件连接上拉电阻、共地与引脚映射硬件 I2C 是开漏总线SCL 和 SDA 必须有上拉电阻才能工作。常见的坑是有人把 SDA 当普通 GPIO 推挽输出用结果总线上两个设备抢电平数据全是乱的。接线要点如下主机 SCL 接 STC8G 的 SCL 引脚多数 STC8G 型号默认在 P1.4主机 SDA 接 STC8G 的 SDA 引脚多数默认在 P1.5两块板子必须共地GND 不接通信必然失败SCL、SDA 各接一颗 4.7kΩ 电阻上拉到 VCC如果主机端已经带了上拉可以只保留主机一侧的不要两头上 1kΩ 这种强上拉关于引脚映射要特别说一句STC8G 不同型号、不同封装I2C 默认引脚不完全一样。有的型号可以通过 P_SW2 里的切换位把 I2C 挪到另一组引脚。我见过有人拿着 P1.4/P1.5 的例程往一颗不同封装的芯片上烧怎么调都不应答最后发现那颗芯片的默认引脚是 P2.4/P2.5。上电之前先打开手册的引脚功能表把 SCL、SDA 确认清楚这一步省下来的时间是后面调 bug 时间的几十倍。4.2 C51 工程配置编译出 HEX 到烧录的必经步骤工程配置没什么玄学但有几步漏了就前功尽弃。建一个 C51 工程把 main.c、i2c_slave.c 加进去头文件路径指到 stc8g.h 所在目录。芯片型号选你手头那颗具体的 STC8G比如 STC8G1K08A 就选对应型号别图省事选一个系列通用型号——寄存器地址一样但中断向量和引脚映射可能有出入。输出配置里勾选生成 HEX 文件这一步忘了的话烧录工具没东西可烧。编出来的 HEX 用官方 ISP 烧录工具下载流程是选芯片型号、选串口、打开 HEX、点下载然后给板子断电再重新上电。很多 STC8G 型号是冷启动进入 ISP 模式的也就是必须断电再上电才能开始下载调试时每次都这样操作会非常烦但这就是芯片的启动逻辑省不掉。提示烧录失败先看两件事串口号有没有选对板子有没有断电重新上电。九成下载失败是这两个原因别急着换线。4.3 验证方法地址扫描、读写测试与抓波形从机烧进去之后第一件事不是抓波形而是让主机做一次地址扫描。地址扫描的逻辑很简单主机遍历 0x01 到 0x7F 的所有 7 位地址逐个发地址字节能收到 ACK 的那个就是存在于总线上的从机。如果扫到 0x50说明地址匹配链路已经通了中断里至少地址匹配分支是活的。地址扫描通过后做读写闭环测试。主机往 0x50 写两个字节 0x01 0x5A然后从 0x50 读两个字节看读回来的数据。这个测试能一次性验证 RX 分支、TX 分支和缓冲区搬运。读不到预期数据时大概率问题出在发送分支——主机读到的 0xFF 就是中断里兜底吐出去的说明 tx_buf 压根没准备好回看 6.2 节 protocol_process 里有没有在停止事件后往发送缓冲里写数据。最后用逻辑分析仪抓一帧波形这是唯一能让你「看到」从机行为的办法。抓到的一帧完整写操作应该是这样的[S][地址 0xA0(W)][ACK][数据 0x01][ACK][数据 0x5A][ACK][P]重点看三个位置地址字节是不是 0xA00x50 左移一位加上写标志、每个字节后面从机有没有拉低 SDA 回 ACK、帧尾有没有 STOP。AC K 缺失通常意味着从机没在中断里及时喂数据或者 I2CSST 没清导致状态机卡住。逻辑分析仪一抓这些问题全部现形比对着代码猜快得多。5. 避坑记录STC8G I2C 从模式最容易翻车的五个细节这一段是从模式踩坑的血泪经验汇总。每条都是真实调板子时遇到的问题按「现象、原因、解决」写你遇到类似情况直接对号入座。5.1 五个高频坑现象、原因、解决一条条说清坑一寄存器写不进读回来永远是 0现象初始化代码里给 I2CSLR 赋了 0x50调试时读回来还是 0从机完全不响应总线。原因没有先置位 P_SW2 的 EAXFR 位XFR 扩展寄存器区根本不可写。解决初始化第一行必须是 P_SW2 | 0x80之后所有 I2C 寄存器操作才生效。这个坑的特点是代码编译正常、运行也正常但外设就是不动排查时最容易怀疑硬件其实是软件没开访问权限。坑二主机读数据读回来全是 0xFF现象地址扫描能扫到从机写操作也正常但主机发起读时每个字节都是 0xFF。原因发送中断分支里去 I2CTXD 取数时tx_buf 里没有有效数据兜底逻辑把 0xFF 送出去了。更深层的原因是发送缓冲的准备时机不对——你在主循环里准备数据但主机的读操作可能发生在你准备之前。解决在地址匹配中断分支里就把首字节准备好或者确保协议流程里主机先写后读从机在停止事件后再填充发送缓冲。坑三地址明明配了 0x50主机就是扫描不到现象主机遍历地址扫不出任何从机但硬件连接没问题。原因主机按 8 位地址发送发的是 0x50 1 0xA0而你在从机侧把 I2CSLR 也写成了 0xA0两边对不上。解决统一按 7 位地址沟通。从机侧 I2CSLR 永远写 7 位数字本身主机侧发送时左移一位拼上 R/W 位。这个坑的隐蔽性在于两边看起来「都是 0x50 相关」但一个是 7 位、一个是 8 位。坑四速率调到 400k 之后偶发丢字节现象100k 模式下一切正常把 S1MODE 切到快速模式后主机读回来的数据偶尔中间少一个字节或者顺序错乱。原因中断里干了太多耗时操作比如在发送分支里临时计算 CRC、或者搬数据时用了循环等待快速模式下 SCL 不会等人硬件下一个事件已经来了你还在上一个事件里忙。解决中断服务函数只做寄存器读写和标志置位任何计算、查表、协议解析全部挪到主循环。这条属于从模式性能设计的底线不是调优选项。坑五换了板子之后从机完全不应答现象同一份代码在上一块板子上跑得好好的换了一块同型号核心板就没反应了。原因I2C 引脚被其他外设复用了。很多开发板默认初始化了其他功能比如把同一组引脚配成了普通 GPIO 输出引脚电平状态把 SCL、SDA 拉死了。解决先查引脚功能冲突把无关的外设初始化注释掉再用逻辑分析仪确认 SCL 上有没有正常时钟。这个坑提示一件事从机不应答时先看引脚电平别急着改代码。6. 二次开发把从机改造成寄存器式 I2C 设备跑通 Demo 之后最常见的改造方向是把它变成一个寄存器式的 I2C 从设备——主机像操作 EEPROM 一样先写一个寄存器地址再读或写对应数据。这个思路比自定义命令码方式通用得多上位机开发也简单。6.1 寄存器映射表像 EEPROM 一样给每个地址定含义设计一张寄存器映射表地址 0x00 到 0x10 分别放设备 ID、版本号、状态字和业务数据。主机写协议约定第一个字节是寄存器地址后续字节是写入数据读协议约定先写寄存器地址再发起读。static uint8_t reg_pointer 0; // 寄存器地址指针 static void protocol_reg_write(void) { if (rx_len 1) { reg_pointer rx_buf[0]; // 第一个字节是寄存器地址 for (uint8_t i 1; i rx_len; i) { reg_table[reg_pointer i - 1] rx_buf[i]; // 顺序写入 } } }寄存器表用数组定义偏移即地址读写逻辑天然统一。需要注意 reg_pointer 的越界检查主机可能写入超出表长度的偏移写之前判断 reg_pointer len 不超过表大小越界直接丢弃。6.2 扩展方向多地址响应、时钟延展与协议裁剪想再进一步可以在 I2C 上响应多个从机地址。硬件 I2C 通常只支持一个从机地址但你可以把 I2CSLR 匹配到的地址作为第一个地址另一个地址用软件判断——在地址匹配中断里硬编码区分。这个技巧在模拟「控制寄存器 数据寄存器」双地址设备时很实用。时钟延展是另一个值得研究的方向从机处理不过来时主动拉低 SCL 让主机等待具备时钟延展能力的主机都会配合。STC8G 硬件 I2C 在数据没准备好时会不会自动延展取决于具体型号的硬件行为试之前先看手册的时序图别把这个当通用功能。6.3 改完怎么验证回环测试和波形逐帧比对改完协议后我的验证习惯是写一段主机回环代码向某个寄存器写一组已知数据读回来逐字节比对。比对过了再去抓波形对着协议文档逐帧核对地址字节和数据顺序。从那以后我每次搭 STC8G 的 I2C 从机都会强制走一遍这个流程先开 XFR、再核头文件位定义、最后用逻辑分析仪抓一帧完整波形确认无误了才继续往上加功能。这套流程帮我挡掉了至少一半的返工希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?