如何上电时检测IIC上是否存在SH1108的OLED
项目中使用了SH1108的OLED显示屏,想上电时检测是否存在SH1108(区分带屏和不带屏),目前显示等功能都正常,但这个检测功能一直有问题,存在SH1108时无法检测,系统中还有另外2个不同地址和功能的IIC设备运行时也都没有问题。我的检测思路是上电时初始化IIC(用的硬件IIC)后,给SH1108发“地址+写”,如果收到ACK就认为存在SH1108,如果收到NACK就认为不存在。这个思路有没有问题?需要注意什么问题?有什么更好的思路?补充一下主要代码: I2CTXD = OLED_IIC_ADDR;
I2CMSCR = 0X09;
IIC_Wait();
IIC_RecvACK();
if((I2CMSST & 0x02) == 0x02)
{
IIC_Stop();
Flag_OLED = 0;
}
if((I2CMSST & 0x02) == 0x00)
{
IIC_Stop();
Flag_OLED = 1;
}
用I2CMSCR = 0X02; 发送数据也不行 I2C扫描程序供参考:
https://www.stcaimcu.com/thread-9938-1-1.html
<p>好的,我已详细阅读了您发的帖子内容。</p>
<p>这是一个关于使用 <strong>STC 硬件 I2C</strong> 上电检测 SH1108 OLED 是否存在时,ACK/NAK 判断失效的问题。下面我结合 STC8H 手册中 I2C 部分的说明来帮您分析。</p>
<hr />
<h2>您的代码分析</h2>
<blockquote>
<p>您的核心检测代码(帖中第1条回复):</p>
</blockquote>
<pre><code class="language-c">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->存在
</code></pre>
<h3>问题所在</h3>
<p>根据 <strong>STC8H 数据手册 §24.3.2 I2C主机控制寄存器</strong> 中的说明:</p>
<blockquote>
<p><strong><code>I2CMSCR = 0x09</code>(1001)</strong> 是三个命令的组合:</p>
<ol>
<li><strong>0001</strong> — 发送 START 信号</li>
<li><strong>0010</strong> — 发送 I2CTXD 中的数据(您的地址)</li>
<li><strong>0011</strong> — <strong>接收 ACK/NAK</strong>,结果自动存入 <strong>I2CMSST.1 (MSACKI)</strong></li>
</ol>
</blockquote>
<p><strong>关键点:命令 <code>0x09</code> 本身已经包含了接收 ACK/NAK 的步骤!</strong> 在执行完 <code>IIC_Wait()</code> 后(即 MSIF 标志置位),<code>I2CMSST.1</code>(MSACKI)位已经被硬件自动更新为从机的应答结果了。</p>
<p><strong>问题很可能出在您的 <code>IIC_RecvACK()</code> 函数上。</strong> 如果这个函数的内部又执行了 <code>I2CMSCR = 0x03</code>(接收ACK/NAK命令),那就会<strong>额外多发一个 SCL 时钟</strong>,此时总线的时序就乱掉了,第二次读到的 MSACKI 值已经不对了。</p>
<h3>正确的做法</h3>
<p><strong>直接在执行完 <code>0x09</code> 组合命令后,读取 <code>I2CMSST</code> 寄存器的 MSACKI 位即可,无需再调用任何 <code>RecvACK</code> 函数:</strong></p>
<pre><code class="language-c">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; // 设备存在
}
</code></pre>
<hr />
<h2>其他几个容易踩坑的点</h2>
<h3>1. I2C 地址格式(7位 vs 8位)</h3>
<p>SH1108 的 <strong>7位 I2C 地址</strong> 通常为:</p>
<ul>
<li>若 SA0 接地 → <code>0x3C</code></li>
<li>若 SA0 接 VCC → <code>0x3D</code></li>
</ul>
<p>写入 I2CTXD 时,需要的是 <strong>8位写地址</strong> = <code>(7位地址 << 1) | 0</code>,即:</p>
<ul>
<li>SA0=GND → <code>I2CTXD = 0x78</code></li>
<li>SA0=VCC → <code>I2CTXD = 0x7A</code></li>
</ul>
<p><strong>请确认您用的 <code>OLED_IIC_ADDR</code> 是这个 8 位写地址格式。</strong></p>
<h3>2. 注意 SH1108 上电初始化时序</h3>
<p>SH1108 这类 OLED 驱动芯片上电后需要一段<strong>复位/初始化时间</strong>(一般几毫秒到十几毫秒)。如果上电后<strong>立即</strong>去检测,可能芯片还没准备好,会返回 NACK。</p>
<p><strong>建议:</strong> 在检测前加入适当延时(如 50ms~100ms)。</p>
<h3>3. 与其他 I2C 设备的冲突</h3>
<p>您提到总线上还有另外两个不同地址的 I2C 设备。请确保:</p>
<ul>
<li>检测前,<strong>I2C 总线处于空闲状态</strong>(SCL/SDA 均为高)</li>
<li>其他设备不会在这个时间段干扰总线</li>
</ul>
<hr />
<h2>补充参考:论坛已有解决方案</h2>
<p>论坛帖 <strong><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=178">硬件I2C主机模式如何实现I2C地址扫描(TID: 178)</a></strong> 中,荣誉版主 <strong>zh</strong>* 的回复也印证了上述方法:</p>
<blockquote>
<p><em>"通过超时的方式无法实现需求,因为无论读取到ACK还是NAK,都会产生中断。正确的做法是在中断/等待完成后判断 <strong>MSACKI</strong> 这个位来判断读取的是 ACK 还是 NAK。"</em></p>
</blockquote>
<p>该帖中也给出了成功使用的代码片段:</p>
<pre><code class="language-c">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;
}
</code></pre>
<hr />
<h2>总结建议</h2>
<table>
<thead>
<tr>
<th align="center">序号</th>
<th>建议项</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">①</td>
<td><strong>去掉 <code>IIC_RecvACK()</code> 调用</strong></td>
<td><code>0x09</code> 已包含接收ACK,直接读 MSACKI 即可</td>
</tr>
<tr>
<td align="center">②</td>
<td><strong>上电后加 50~100ms 延时</strong></td>
<td>等 SH1108 初始化完成再检测</td>
</tr>
<tr>
<td align="center">③</td>
<td><strong>确认地址格式</strong></td>
<td>I2CTXD 写入的是<strong>8位写地址</strong>(如 0x78/0x7A)</td>
</tr>
<tr>
<td align="center">④</td>
<td><strong>先用 I2C 地址扫描工具验证</strong></td>
<td>论坛有开源 I2C 扫描工具(TID: 9938),可先确认 SH1108 确实能正常应答</td>
</tr>
</tbody>
</table>
<p>如果以上排查后仍然有问题,欢迎把 <code>IIC_Wait()</code> 和 <code>IIC_RecvACK()</code> 的完整函数贴出来,我可以帮您进一步分析。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&mod=forum&q=https%3A%2F%2Fwww.stcaimcu.com%2Fthread-24294-1-1.html&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "https://www.stcaimcu.com/thread-24294-1-1.html"</a></p>
使用硬件I2C,必须是完整的I2C操作流程,不完整或顺序错误会导致硬件I2C卡死
另外一个思路:ADC引脚对地接个电阻(先试一下内部上拉电阻是否稳定,不行就还需外加上拉电阻),通过焊接不同阻值的电阻来区分不同的功能,或普通IO检测输入高低电平来区分。 不管带不带OLED,I2C访问没有应答就会退出。
当然也可以在初始化OLED时读取一些信息以确认OLED是否存在(依旧是每次访问没有应答就退出,认为不存在)。 我参考那个帖子修改了自己的程序,还是无法检测到,虽然不影响其它的但还是想把这个试验分析清楚。是不是跟硬件有关系?但假定存在OLED的话三个IIC设备的所有通信都是正常的。那个帖子中发地址后再发0x00是什么目的?我原来是没有这个,加了之后也不行,我试过另外发一个OLED的初始化命令,也不行。我没有使用中断方式,但这个应该没有差异吧?检测思路一样的
页:
[1]
2