把Delay和Nixie这两个变量模块化之后单片机上的数字就一闪一闪,如何解决?

编程达人挑战赛·第5期 10w+人浏览 394人参与

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。
  
📌 特别说明:
文中问题案例来源于真实生产环境与公开技术社区,并结合多位一线资深工程师与架构师的长期实践经验,经过筛选与系统化整理后输出。文中的解决方案并非唯一“标准答案”,而是兼顾可行性、可复现性与思路启发性的实践参考,供你在实际项目中灵活运用与演进。
  
欢迎你 关注、收藏并订阅本专栏,与持续更新的技术干货同行,一起让问题变资产,让经验可复制,技术跃迁,稳步向上。

📢 问题描述

问题描述:如下是相关代码:这样写能让单片机上的数码管正常显示数字,但是把Delay和Nixie这两个变量模块化之后单片机上的数字就一闪一闪了,这是什么问题?还有就是我把Delay(1)改为Delay(0.8)程序居然不会报错,这是为什么?

#include <STC89C5xRC.H>
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
 
void Delay(unsigned char xms)    
{
    while(xms)
    {
        unsigned char data i, j;
 
        i = 2;
        j = 239;
        do
        {
            while (--j);
        } while (--i);
        xms--;
    }
}
 
 
void Nixie(unsigned char location,Number)
{
    switch(location)
    {
        case 1:P24=1;P23=1;P22=1;break;
        case 2:P24=1;P23=1;P22=0;break;
        case 3:P24=1;P23=0;P22=1;break;
        case 4:P24=1;P23=0;P22=0;break;
        case 5:P24=0;P23=1;P22=1;break;
        case 6:P24=0;P23=1;P22=0;break;
        case 7:P24=0;P23=0;P22=1;break;
        case 8:P24=0;P23=0;P22=0;break;
    }
    
    P0=NixieTable[Number];
    Delay(1);
    P0=0x00;
}
void main()
{
    while(1)
    {
        Nixie(1,1);
        Nixie(2,1);
        Nixie(3,4);
        Nixie(4,5);
        Nixie(5,1);
        Nixie(6,4);
    }
}

📣 请知悉:如下方案不保证一定适配你的问题!

  如下是针对上述问题进行专业角度剖析答疑,不喜勿喷,仅供参考:

✅️ 问题理解

我们先来深度剖析一下这两个“灵异现象”背后的逻辑。

1. 为什么 Delay(0.8) 不报错?
  • 现象: 将参数从整数 1 改为浮点数 0.8,编译器居然通过了。

  • 本质: 这是 C 语言的隐式类型转换(Implicit Type Conversion)

    • 你的函数定义是 void Delay(unsigned char xms)
    • 形参 xms 的类型是 unsigned char(无符号字符型,范围 0-255,整数)。
    • 当你传入 0.8(双精度浮点数 double)时,C 语言编译器会自动尝试把它“塞进” unsigned char 里。
    • 转换规则: 浮点数转整数是直接截断小数部分(Truncation),而不是四舍五入。
    • 结果: 0.8 被截断为 0。所以你实际上调用的是 Delay(0)
2. 为什么模块化后数码管“一闪一闪”?
  • 现象: 代码逻辑没变,只是搬到了不同文件(.c/.h)里,显示就异常了。

  • 深度推测:

    • 可能性一(最常见):编译器优化(Compiler Optimization)。 在 Keil 中,当 Delay 函数被独立到一个文件时,编译器可能发现里面的 while(--j) 只是在空转,没干任何“有意义”的事(没修改全局变量,没操作 IO 口),于是它自作聪明把这些循环删掉了!导致延时几乎为 0。
    • 可能性二:变量定义冲突或可见性。 NixieTable 如果在头文件里定义而没有用 extern,会导致重复定义;或者在 .c 里定义了但 main 函数看不到(虽然这通常报编译错,但有时表现为逻辑错乱)。
    • 可能性三:延时变成了 0(关联第一个问题)。 如果因为某种原因延时失效,数码管点亮后立刻执行 P0=0x00(消隐),LED 导通时间极短,肉眼看到的就是亮度极低且闪烁

✅️ 问题解决方案

针对以上分析,我为你提供几套切实可行的方案。请根据你的实际情况选择!

🟢 方案 A:修正隐式类型转换与逻辑错误(针对 Delay(0.8))

这个问题必须首先解决,因为它直接导致延时失效。

  • 原理: Delay 函数依然需要整数。如果你想要更短的延时(比如 0.8ms),不能通过传小数实现,而是需要修改 Delay 函数内部的基准,或者传入更小的单位(比如微秒)。

  • 操作步骤:

    1. 不要传小数: 永远不要给 unsigned char0.8,因为它等于 0
    2. 修改 Delay 函数: 如果觉得 1ms 太长导致闪烁,我们需要重写一个微秒级的延时函数。
// 建议添加一个新的微秒延时函数
void Delay10us(unsigned char us) // 这里的 us 代表 10微秒 的倍数
{
    while(us--)
    {
        // 这里的循环次数需要根据你的晶振频率(如11.0592MHz或12MHz)进行微调
        // 下面是大致针对 11.0592MHz 的经验值
        _nop_(); _nop_(); // 需要 #include <intrins.h>
    }
}
  • 修正原调用: 恢复为 Delay(1)Delay(2)。数码管动态扫描通常每个位停留 1ms - 2ms 是最佳的。
🟡 方案 B:解决模块化后的“优化消失”问题(针对闪烁)

如果你的代码搬运正确,但延时失效了,大概率是编译器把你的 while 循环优化掉了。

  • 解决方案: 使用 volatile 关键字。
  • 代码修改:Delay 模块中,将循环变量声明为 volatile。这告诉编译器:“别动这几个变量,老老实实执行循环,不要自作聪明优化掉!”

文件:Delay.c

#include <STC89C5xRC.H> // 确保包含头文件

void Delay(unsigned char xms)    
{
    while(xms)
    {
        // 🟢 重点:加上 volatile 关键字
        volatile unsigned char i, j; 
 
        i = 2;
        j = 239;
        do
        {
            while (--j);
        } while (--i);
        xms--;
    }
}

如果不加 volatile,Keil 的高等级优化(Level 8+)极有可能把这个纯软件延时当作无用代码删除。

🔴 方案 C:标准的工程模块化规范(终极解法)

为了彻底解决“一闪一闪”并保证代码健壮性,你需要按照标准的 C 语言工程规范来拆分文件。

1. 创建 Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

// 声明函数,让外部可以调用
void Delay(unsigned char xms);

#endif

2. 创建 Delay.c

#include "Delay.h"

void Delay(unsigned char xms) {
    while(xms) {
        // 使用 volatile 防止优化
        volatile unsigned char i, j;
        i = 2; j = 239;
        do {
            while (--j);
        } while (--i);
        xms--;
    }
}

3. 创建 Nixie.h

#ifndef __NIXIE_H__
#define __NIXIE_H__

void Nixie(unsigned char location, unsigned char Number);

#endif

4. 创建 Nixie.c

#include <STC89C5xRC.H>
#include "Delay.h"  // 🟢 必须包含 Delay.h 才能认识 Delay 函数
#include "Nixie.h"

// 数组定义放在 .c 文件中
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

void Nixie(unsigned char location, unsigned char Number)
{
    // ... (你的位选代码 switch case) ...
    // 这里省略 switch 代码以节省空间,保持原样即可

    P0 = NixieTable[Number];
    Delay(1); // 这里的 1 必须是整数
    P0 = 0x00; // 消隐
}

5. main.c

#include <STC89C5xRC.H>
#include "Nixie.h" // 只包含头文件

void main()
{
    while(1)
    {
        // 循环扫描
        Nixie(1,1);
        Nixie(2,1);
        Nixie(3,4);
        Nixie(4,5);
        Nixie(5,1);
        Nixie(6,4);
    }
}

✅️ 问题延伸:为什么动态扫描需要消隐?

你的代码中 P0=0x00 这一步做得非常好,这叫 “消隐”(Blanking)

如果不加这一句,或者像你遇到的情况那样延时时间不对,就会出现 “鬼影”

  • 过程:

    1. 位选 1 打开。
    2. 段选送入数字 1。
    3. 延时(人眼看到数字)。
    4. (如果不消隐):位选 1 关闭,位选 2 还没来得及打开的瞬间,或者位选 2 刚打开但段选还是上一个数字的数据,导致下一位隐约显示上一个数字。
    5. 你遇到的闪烁如果是“Delay(0.8) -> Delay(0)”,那么流程变成了:位选->段选->立即消隐。灯亮的时间是微秒级的,所以看起来像没亮或者狂闪。

为了更直观地理解,请看下面的流程图:

你的问题核心在于:Delay(0.8) 导致中间粉色的 “Delay 1ms” 变成了 “Delay 0ms”,于是灯还没来得及让眼睛感觉到亮,就被关掉了。

✅️ 问题预测

根据你的学习进度,我大胆预测你接下来会遇到以下问题,可以提前准备哦:

  1. 数码管显示不均匀: 随着代码增加,main 函数里的逻辑变多,会导致扫描周期变长,数码管会闪烁。

    • 解法: 学习 定时器中断(Timer Interrupt)。把 Nixie 扫描放到定时器中断里,每 1ms 自动刷新一位,主循环爱干嘛干嘛,数码管永远稳如老狗。🐶
  2. 按键消抖影响显示: 如果你在 main 里加了 Delay(20) 来给按键消抖,数码管会瞬间熄灭 20ms,产生明显的闪烁。

    • 解法: 同样需要定时器,或者使用状态机非阻塞消抖。

✅️ 小结

  1. 关于报错: C 语言中 Delay(0.8) 发生了隐式截断,变成了 Delay(0),导致数码管点亮时间极短,看起来像闪烁或熄灭,且编译器认为这是合法的,所以不报错。

  2. 关于模块化闪烁: 很大原因是编译器优化掉了外部文件中的空循环,或者模块化结构有误导致逻辑异常。

  3. 解决办法:

    • 使用 Delay(1)(整数)。
    • 在延时循环变量前加 volatile
    • 严格按照 .h.c 规范进行模块化。

🌹 结语 & 互动说明

希望以上分析与解决思路,能为你当前的问题提供一些有效线索或直接可用的操作路径

若你按文中步骤执行后仍未解决:

  • 不必焦虑或抱怨,这很常见——复杂问题往往由多重因素叠加引起;
  • 欢迎你将最新报错信息、关键代码片段、环境说明等补充到评论区;
  • 我会在力所能及的范围内,结合大家的反馈一起帮你继续定位 👀

💡 如果你有更优或更通用的解法:

  • 非常欢迎在评论区分享你的实践经验或改进方案;
  • 你的这份补充,可能正好帮到更多正在被类似问题困扰的同学;
  • 正所谓「赠人玫瑰,手有余香」,也算是为技术社区持续注入正向循环

🧧 文末福利:技术成长加速包 🧧

  文中部分问题来自本人项目实践,部分来自读者反馈与公开社区案例,也有少量经由全网社区与智能问答平台整理而来。

  若你尝试后仍没完全解决问题,还请多一点理解、少一点苛责——技术问题本就复杂多变,没有任何人能给出对所有场景都 100% 套用的方案。

  如果你已经找到更适合自己项目现场的做法,非常建议你沉淀成文档或教程,这不仅是对他人的帮助,更是对自己认知的再升级。

  如果你还在持续查 Bug、找方案,可以顺便逛逛我专门整理的 Bug 专栏:《全栈 Bug 调优(实战版)》
这里收录的都是在真实场景中踩过的坑,希望能帮你少走弯路,节省更多宝贵时间。

✍️ 如果这篇文章对你有一点点帮助:

  • 欢迎给 bug菌 来个一键三连:关注 + 点赞 + 收藏
  • 你的支持,是我持续输出高质量实战内容的最大动力。

同时也欢迎关注我的硬核公众号 「猿圈奇妙屋」

获取第一时间更新的技术干货、BAT 等互联网公司最新面试真题、4000G+ 技术 PDF 电子书、简历 / PPT 模板、技术文章 Markdown 模板等资料,统统免费领取
你能想到的绝大部分学习资料,我都尽量帮你准备齐全,剩下的只需要你愿意迈出那一步来拿。

🫵 Who am I?

我是 bug菌:

  • 活跃于 CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等技术社区;
  • CSDN 博客之星 Top30、华为云多年度十佳博主、掘金多年度人气作者 Top40;
  • 掘金、InfoQ、51CTO 等平台签约及优质作者;
  • 全网粉丝累计 30w+

更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看
硬核技术公众号 「猿圈奇妙屋」 期待你的加入,一起进阶、一起打怪升级。

- End -

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bug菌¹

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值