好的,我已详细阅读了您发的帖子内容。
这是一个关于使用 STC 硬件 I2C 上电检测 SH1108 OLED 是否存在时,ACK/NAK 判断失效的问题。下面我结合 STC8H 手册中 I2C 部分的说明来帮您分析。
您的代码分析
您的核心检测代码(帖中第1条回复):
I2CTXD = OLED_IIC_ADDR;
I2CMSCR = 0X09; // 0x09 = 起始+发送数据+接收ACK/NAK 组合命令
IIC_Wait();
IIC_RecvACK(); // ← 关键问题可能在这里
if((I2CMSST & 0x02) == 0x02) { IIC_Stop(); Flag_OLED = 0; } // NACK->不存在
if((I2CMSST & 0x02) == 0x00) { IIC_Stop(); Flag_OLED = 1; } // ACK->存在
问题所在
根据 STC8H 数据手册 §24.3.2 I2C主机控制寄存器 中的说明:
I2CMSCR = 0x09(1001) 是三个命令的组合:
- 0001 — 发送 START 信号
- 0010 — 发送 I2CTXD 中的数据(您的地址)
- 0011 — 接收 ACK/NAK,结果自动存入 I2CMSST.1 (MSACKI)
关键点:命令 0x09 本身已经包含了接收 ACK/NAK 的步骤! 在执行完 IIC_Wait() 后(即 MSIF 标志置位),I2CMSST.1(MSACKI)位已经被硬件自动更新为从机的应答结果了。
问题很可能出在您的 IIC_RecvACK() 函数上。 如果这个函数的内部又执行了 I2CMSCR = 0x03(接收ACK/NAK命令),那就会额外多发一个 SCL 时钟,此时总线的时序就乱掉了,第二次读到的 MSACKI 值已经不对了。
正确的做法
直接在执行完 0x09 组合命令后,读取 I2CMSST 寄存器的 MSACKI 位即可,无需再调用任何 RecvACK 函数:
I2CTXD = OLED_IIC_ADDR; // 写入从机地址(注意:需是8位格式,如0x78)
I2CMSCR = 0x09; // 起始 + 发送地址 + 接收ACK
while (!(I2CMSST & 0x40)); // 等待 MSIF 置位(命令执行完成)
I2CMSST &= ~0x40; // 清除中断标志
if (I2CMSST & 0x02) // MSACKI = 1 → NACK → 设备不存在
{
I2CMSCR = 0x06; // 发停止
while (I2CMSST & 0x40); // 等待停止完成
I2CMSST &= ~0x40;
Flag_OLED = 0; // 设备不存在
}
else // MSACKI = 0 → ACK → 设备存在
{
I2CMSCR = 0x06; // 发停止
while (I2CMSST & 0x40);
I2CMSST &= ~0x40;
Flag_OLED = 1; // 设备存在
}
其他几个容易踩坑的点
1. I2C 地址格式(7位 vs 8位)
SH1108 的 7位 I2C 地址 通常为:
- 若 SA0 接地 →
0x3C
- 若 SA0 接 VCC →
0x3D
写入 I2CTXD 时,需要的是 8位写地址 = (7位地址 << 1) | 0,即:
- SA0=GND →
I2CTXD = 0x78
- SA0=VCC →
I2CTXD = 0x7A
请确认您用的 OLED_IIC_ADDR 是这个 8 位写地址格式。
2. 注意 SH1108 上电初始化时序
SH1108 这类 OLED 驱动芯片上电后需要一段复位/初始化时间(一般几毫秒到十几毫秒)。如果上电后立即去检测,可能芯片还没准备好,会返回 NACK。
建议: 在检测前加入适当延时(如 50ms~100ms)。
3. 与其他 I2C 设备的冲突
您提到总线上还有另外两个不同地址的 I2C 设备。请确保:
- 检测前,I2C 总线处于空闲状态(SCL/SDA 均为高)
- 其他设备不会在这个时间段干扰总线
补充参考:论坛已有解决方案
论坛帖 硬件I2C主机模式如何实现I2C地址扫描(TID: 178) 中,荣誉版主 zh* 的回复也印证了上述方法:
"通过超时的方式无法实现需求,因为无论读取到ACK还是NAK,都会产生中断。正确的做法是在中断/等待完成后判断 MSACKI 这个位来判断读取的是 ACK 还是 NAK。"
该帖中也给出了成功使用的代码片段:
bit I2C_Wait(void)
{
bit ACK_or_NACK = 0;
while (!(I2CMSST & 0x40)); // 等待MSIF
if(I2CMSST & 0x02) // MSACKI=1 → NACK
ACK_or_NACK = FALSE;
else // MSACKI=0 → ACK
ACK_or_NACK = TRUE;
I2CMSST &= ~0x40; // 清除中断标志
return ACK_or_NACK;
}
总结建议
| 序号 |
建议项 |
说明 |
| ① |
去掉 IIC_RecvACK() 调用 |
0x09 已包含接收ACK,直接读 MSACKI 即可 |
| ② |
上电后加 50~100ms 延时 |
等 SH1108 初始化完成再检测 |
| ③ |
确认地址格式 |
I2CTXD 写入的是8位写地址(如 0x78/0x7A) |
| ④ |
先用 I2C 地址扫描工具验证 |
论坛有开源 I2C 扫描工具(TID: 9938),可先确认 SH1108 确实能正常应答 |
如果以上排查后仍然有问题,欢迎把 IIC_Wait() 和 IIC_RecvACK() 的完整函数贴出来,我可以帮您进一步分析。
🔍 搜索 "https://www.stcaimcu.com/thread-24294-1-1.html"