学习《8051U深度入门到32位51大型实战视频》遇到问题,请大佬指点。
学到第八集,定时器调度任务。其中有一个是检测按键,然后串口打印信息的代码。程序执行完P05 = !P05,然后执行printf就卡住了。但是如果把printf写到主函数里面,可以正常执行。有遇到同样问题的吗?请指点一下,谢谢。
<p>好的,帖子内容已获取。这是一个典型的 <strong>在定时器中断/任务调度函数中使用 <code>printf</code> 导致卡死</strong> 的问题。我来分析原因并给出解决方案。</p>
<hr />
<h2>问题分析</h2>
<p>楼主遇到的这个现象非常典型:<strong>在定时器调度任务函数中调用 <code>printf</code> 导致程序卡死,但同样的 <code>printf</code> 放在主函数中却能正常工作。</strong></p>
<h3>根本原因</h3>
<p><code>printf</code> 函数内部默认会调用 <code>putchar</code> 来逐个发送字符,而 <code>putchar</code> 的实现通常采用 <strong>查询方式</strong> 等待串口发送完成:</p>
<pre><code class="language-c">// putchar 典型实现
char putchar(char c)
{
SBUF = c;
while (!TI); // ← 如果TI已经被中断服务函数清零,这里会死等!
TI = 0;
return c;
}
</code></pre>
<p>当 <code>printf</code> 在 <strong>定时器中断服务函数</strong> 或 <strong>被定时器中断抢占的上下文</strong> 中调用时,可能出现以下情况:</p>
<ol>
<li>
<p><strong>串口中断(ES)与定时器中断的优先级问题</strong>:如果定时器中断优先级高于串口中断,在定时器中断里调用 <code>printf</code> 发送数据时,串口发送完成中断(TI置位)无法及时响应,<code>putchar</code> 中的 <code>while(!TI)</code> 会一直等待。</p>
</li>
<li>
<p><strong>更常见的情况</strong>:如果用户代码中使能了串口中断(<code>ES = 1</code>),并且在串口中断服务函数中清除了 <code>TI</code> 标志,那么:</p>
<ul>
<li><code>printf</code> 发送第一个字节 → 硬件置位 <code>TI</code></li>
<li>进入串口中断 → 中断服务函数把 <code>TI</code> 清零了</li>
<li>回到 <code>putchar</code> 的 <code>while(!TI)</code> → TI已经被清,永远等不到 → <strong>卡死</strong></li>
</ul>
</li>
</ol>
<h3>解决方案</h3>
<p>有以下几种解决方式,推荐使用 <strong>方案一</strong>:</p>
<hr />
<h4>✅ 方案一(推荐):自定义 <code>putchar</code>,使用查询方式发送</h4>
<p>在工程中添加以下函数,覆盖标准库的 <code>putchar</code>,确保发送过程不被中断干扰:</p>
<pre><code class="language-c">/* 自定义 putchar - 查询方式发送,不依赖串口中断 */
char putchar(char c)
{
SBUF = c;
while (!TI); // 等待发送完成
TI = 0; // 手动清除标志
return c;
}
</code></pre>
<blockquote>
<p><strong>注意</strong>:如果使能了串口中断(<code>ES = 1</code>),需要在串口中断服务函数中 <strong>不要清除 TI</strong>,或者干脆 <strong>关闭串口中断</strong>(<code>ES = 0</code>),因为 <code>putchar</code> 已经用查询方式处理了 TI。</p>
</blockquote>
<hr />
<h4>✅ 方案二:关闭串口中断,完全使用查询方式</h4>
<p>在初始化串口时,<strong>不使能串口中断</strong>:</p>
<pre><code class="language-c">void UART1_Init(void)
{
// ... 串口配置代码 ...
SCON = 0x50; // 模式1,允许接收
// ES = 1; // ← 不要使能串口中断!
// EA = 1; // 总中断根据需要开启
}
</code></pre>
<p>这样 <code>printf</code> 内部的 <code>putchar</code> 查询 <code>TI</code> 就不会被中断服务函数干扰。</p>
<hr />
<h4>✅ 方案三:在定时器任务中改用自定义发送函数,不用 <code>printf</code></h4>
<p>如果只是打印简单信息,直接用自定义的串口发送函数替代 <code>printf</code>:</p>
<pre><code class="language-c">void UART_SendString(unsigned char *str)
{
while (*str)
{
SBUF = *str++;
while (!TI); // 等待发送完成
TI = 0;
}
}
// 在 KEY_Task 中调用
void KEY_Task(void)
{
if(P32 == 0)
{
Key_Vol++;
if(Key_Vol == 5)
{
P05 = !P05;
UART_SendString("按键按下\r\n");// 不用 printf
}
}
else
{
Key_Vol = 0;
}
}
</code></pre>
<hr />
<h3>总结</h3>
<table>
<thead>
<tr>
<th>问题</th>
<th>原因</th>
<th>解决</th>
</tr>
</thead>
<tbody>
<tr>
<td>定时器任务中 <code>printf</code> 卡死</td>
<td><code>printf</code> 内部查询 <code>TI</code>,但 <code>TI</code> 被串口中断服务函数意外清零,导致死等</td>
<td>自定义 <code>putchar</code> 或关闭串口中断,或改用自定义发送函数</td>
</tr>
</tbody>
</table>
<p><strong>建议做法</strong>:在工程中统一添加自定义的 <code>putchar</code> 函数(方案一),这样无论在何处调用 <code>printf</code> 都不会卡死,同时保持代码简洁。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&mod=forum&q=STC8H8K64U%20%E5%8D%95%E7%89%87%E6%9C%BA%20%E4%BE%8B%E7%A8%8B%20%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC8H8K64U 单片机 例程 使用说明"</a></p>
<p>⏱️ 查找用时 2.9s | 总回答用时 12.9s</p>
页:
[1]