找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 878|回复: 11
打印 上一主题 下一主题
收起左侧

一个非常诡异的单片机程序调试问题

[复制链接]
跳转到指定楼层
楼主
ID:997026 发表于 2022-2-16 13:25 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 hxdby 于 2022-2-16 19:07 编辑

最近在调试中遇到一个非常诡异的问题!虽然我解决了,但是到现在我都不知道具体原因是啥, 特分享出来,与大家探讨。

做了一个简单的可以通过按键调整时间控制继电器打开关闭的单片机板子,带数码管显示,全部功能程序代码都已经完成,昨天板子我开了一夜做测试,今天早上起来突然发现一个键突然失灵了,不能切换分秒显示了,我以为是按键坏了,结果不是。

先解释板子的工作逻辑:1,板子带一路继电器,可以通过按键设置分和秒来设置继电器打开和关闭的时间
2,继电器打开时间设置函数是RELAY_SET_ON(), 继电器关闭时间设置函数是RELAY_SET_OFF(); 这两个函数实现功能和代码都一样,只是变量名不同
3,设置时间可以通过数码管显示,
4,总共三个按键,SW1, SW2, SW3。SW1是增加,SW2是减小。SW3是分和秒设置切换按键。简单说就是,如果设置分钟,按一下SW1,分钟增加1,按一下SW2,分钟减小1,如果要设置秒钟,则按一下SW3,切换到秒钟设置,然后在通过SW1/SW2来增减。

先上部分程序(不重要的单片机代码就不贴出来,只贴重要的):

//全局变量

uint8_t    relay_on_cnt =0; //打开时间设置中的分秒切换变量,通过SW3设置
uint8_t    relay_off_cnt =0; //关闭时间设置中的分秒切换变量,通过SW3设置
uint8_t    RELAY_min_set_ON =0 //继电器打开时间设置中的分钟变量
uint8_t    RELAY_sec_set_ON = 0 //继电器打开时间设置中的秒钟变量

uint8_t    RELAY_min_set_OFF =0  //继电器关闭时间设置中的分钟变量
uint8_t    RELAY_sec_set_OFF = 0 //继电器关闭时间设置中的分钟变量


uint8_t    key_number3 =0; //触发的按键编号,当SW3按下时,按键扫描KEY_SCAN()函数返回不同的key_number3键值
uint8_t    key_number =0; //触发的按键编号,当SW1 或SW2按下时,key_number返回不同的键值



//函数原型

void  RELAY_SET_ON(); //设置继电器打开时间
void  RELAY_SET_OFF(); //设置继电器关闭时间
void  KEY_SCAN() ;  //按键扫描
void  DISPLAY_MIN_SEC_SET();  //数码管显示设置的分秒参数

//主函数

int  main (void)
{
   while(1)
{
  RELAY_SET_ON();
  RELAY_SET_OFF();
  KEY_SCAN() ;
}


}




// 函数定义
void  RELAY_SET_ON();  //设置继电器打开时间

{
     switch (key_number3) //SW3按下
        {
        case 1:  //SW3短按,relay_on_cnt计数器在0和1之间循环自加
        relay_on_cnt ++;
        if (relay_on_cnt >1)
        {
                relay_on_cnt=0;
        }
        key_number3=0;
        break;
        
        case 2:
        relay_on_cnt --;  //SW3长按超过1s,relay_on_cnt计数器在0和1之间循环连续自减,实现连击功能
        if (relay_on_cnt>1)
        {
                relay_on_cnt=1;
        }
        key_number3=0;
        break;
        
        default:; break;
        }
/*以上的代码主要是通过按下SW3来切换分钟和秒钟的设置,如果 relay_on_cnt=0,则进入分钟设置界面。如果 relay_on_cnt=1,则进入秒钟设置界面,这段代码和RELAY_SET_OFF();中完全一样,但是这里没问题,RELAY_SET_OFF();会出现问题*/      


       if (relay_on_cnt==0)  //继电器打开分钟设置
        {
                switch (key_number)  //根据返回到键值进行分钟的加减操作
                {
                        case 1:  // SW1 触发,开始减操作
                        RELAY_min_set_ON --;
                        if (RELAY_min_set_ON >99)  
                        {
                                RELAY_min_set_ON =99;
                        }
                        
                        key_number=0;
                        
                        break;
                        
                        case 2: //SW2触发,开始加操作
                        RELAY_min_set_ON ++;
                        if (RELAY_min_set_ON >99)
                        {
                                RELAY_min_set_ON =0;
                        }
                        
                        key_number=0;
                        
                        break;
                        
                        default: ; break;
                }        
        }


         if (relay_on_cnt==1) //秒设置
        {
                        switch (key_number)
                        {
                                case 1:  // SW1 触发,开始减操作
                                RELAY_sec_set_ON --;
                                if (RELAY_sec_set_ON >99)  
                                {
                                        RELAY_sec_set_ON =99;
                                }
                                
                                key_number=0;
                                
                                break;
                                
                                case 2: //SW2触发,开始加操作
                                RELAY_sec_set_ON ++;
                                if (RELAY_sec_set_ON >99)
                                {
                                        RELAY_sec_set_ON =0;
                                }
                                
                                key_number=0;
                                
                                break;
                                
                                default: ; break;
                        }
               
        }
        
        DISPLAY_MIN_SEC_SET();  // 设置第参数在数码管即时显示        


}



//函数定义
void  RELAY_SET_OFF();  //设置继电器关闭时间,该函数代码与上述RELAY_SET_ON()中完全相同,只是变量名不同. 所以不再一一注释

{
     switch (key_number3)
        {
        case 1:
        relay_off_cnt ++;
        if (relay_off_cnt >1)
        {
                relay_off_cnt=0;
        }
        key_number3=0;
        break;
        
        case 2:
        relay_off_cnt --;
        if (relay_off_cnt>1)
        {
                relay_off_cnt=1;
        }
        key_number3=0;
        break;
        
        default:; break;
        }
        //上述红色部分代码出了问题!!出问题后,检查了这部分代码,和RELAY_SET_ON();中的完全一样,后来复制了RELAY_SET_ON();中的这部分代码到这里,改了变量名,问题就解决了!觉得很诡异,如果是这部分代码出问题,那么在一开始就应该有问题,而我的板子是跑了一夜之后出问题的,而且是100%必现,我仔细检查了代码,完全一样,手动修改某些变量,也不行。非得从RELAY_SET_ON();中复制才可以,why?????


       if (relay_off_cnt==0)
        {
                switch (key_number)
                {
                        case 1:  // SW1 触发,开始减操作
                        RELAY_min_set_OFF --;
                        if (RELAY_min_set_OFF >99)  
                        {
                                RELAY_min_set_OFF =99;
                        }
                        
                        key_number=0;
                        
                        break;
                        
                        case 2: //SW2触发,开始加操作
                        RELAY_min_set_OFF ++;
                        if (RELAY_min_set_OFF >99)
                        {
                                RELAY_min_set_OFF =0;
                        }
                        
                        key_number=0;
                        
                        break;
                        
                        default: ; break;
                }        
        }


         if (relay_off_cnt==1) //秒调整
        {
                        switch (key_number)
                        {
                                case 1:  // SW1 触发,开始减操作
                                RELAY_sec_set_OFF --;
                                if (RELAY_sec_set_OFF >99)  
                                {
                                        RELAY_sec_set_OFF =99;
                                }
                                
                                key_number=0;
                                
                                break;
                                
                                case 2: //SW2触发,开始加操作
                                RELAY_sec_set_OFF ++;
                                if (RELAY_sec_set_OFF >99)
                                {
                                        RELAY_sec_set_OFF =0;
                                }
                                
                                key_number=0;
                                
                                break;
                                
                                default: ; break;
                        }
               
        }
        
        DISPLAY_MIN_SEC_SET();
        


}

现在的问题是,板子跑了一夜之后,继电器关闭时间设置突然不显示了,数码管全黑。而继电器打开设置界面是正常显示的。我关闭板子电源,重新开机,继电器关闭时间设置界面可以显示,但是只要一按SW3按键切换分秒设置,就没显示了,而且这个问题是100%必现。

我怀疑是RELAY_SET_OFF(); 中的程序出了问题,所以和RELAY_SET_ON(); 对比了一下,发现完全一样,除了变量名不同,其他没有任何区别。而且这个问题不是一直出现的,是板子开始是好的,我开机一夜,跑了一夜后突然变成这样的。如果是程序问题,应该从一开就会有问题。

我百思不得其解,因为如果是RELAY_SET_OFF();程序问题,那RELAY_SET_ON();一定会出现问题,因为这两个函数中的代码逻辑完全相同,只是变量名不同,这套代码也用了好几月了,没出过问题。但现在的问题是,两个完全一样的函数,继电器打开时间设置显示没有任何问题,而且问题只出在按下SW3按键的时候。

于是我开始研究SW3这一段代码,也就是上述RELAY_SET_OFF();中红色部分,我对比了几百次,和RELAY_SET_ON();完全一样,因为本身也没有多少代码。我突发奇想,把RELAY_SET_ON();中对应部分代码复制过来,然后改掉变量名,然后烧录测试,板子居然好了!

我实在不理解为啥会这样?非得复制才行?我觉得不可理解,究竟是啥原因,有没有高人可以深入解释下,感谢!

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:213173 发表于 2022-2-16 17:32 | 只看该作者
代码有点长,粗略看了一下,无非就是个可调定时开关。程序够架不太合理,最关键的是main函数没有约束在while(1)中运行,随时都有可能跑飞。
回复

使用道具 举报

板凳
ID:997026 发表于 2022-2-16 19:08 | 只看该作者
wulin 发表于 2022-2-16 17:32
代码有点长,粗略看了一下,无非就是个可调定时开关。程序够架不太合理,最关键的是main函数没有约束在whil ...

不好意思,while(1)原程序中肯定有的,贴程序的时候漏掉了。
回复

使用道具 举报

地板
ID:311903 发表于 2022-2-16 19:30 | 只看该作者
代码太乱不想看,但是你可以试试编译的时候全部文件重新编译
回复

使用道具 举报

5#
ID:213173 发表于 2022-2-16 19:42 | 只看该作者
hxdby 发表于 2022-2-16 19:08
不好意思,while(1)原程序中肯定有的,贴程序的时候漏掉了。

把你的确切需求和硬件环境详细讲清楚,有空给你写一个示例。
回复

使用道具 举报

6#
ID:429003 发表于 2022-2-17 16:13 | 只看该作者
代码难得看,但从你描述来看,问题应该不是程序的问题。估计是单片机FLASH某个位置出了点故障了,不能置0又或者不能至1。如果这个板子烧写你发生故障前的代码每次必定出现,那么基本可以确定就是FLASH故障了。

你稍微调整一下代码,编译后烧写文件发生了变化,恰巧与故障点位的值相符,也就巧妙的避开这一问题。
回复

使用道具 举报

7#
ID:429003 发表于 2022-2-17 16:25 | 只看该作者
另外,你的设计是用单片机控制继电器,是否继电器控制线圈未设计续流二极管?
如果没有续流二极管,这样继电器线圈断开瞬间的感应电流无处泄放,线圈两端会产生很高的感应电压,这个感应电压串入你的单片机系统,那就很有可能烧坏一些器件,单片机FLASH被烧坏一些位置,也不是没有可能。
回复

使用道具 举报

8#
ID:584814 发表于 2022-2-17 17:19 | 只看该作者
有些问题看似莫名其妙的,可能是程序中有肉眼不可见的啥,也可能硬件里出现了个啥;
有些问题真的莫名其妙的,曾经调试程序时只在中间加了个空行(多个回车)就完美解决问题……
别以为单片机个个都是完美的,编译环境环境也全都是完美的......Bug无处不在
个人感觉,经验就是不断试错,物理学上的成功有时未必都能讲出道理。
先按书上写的办,毕竟是众人的经验集成,广告可以看但别全信;
不行后按自己的想法去干,成了有机会多验证再发展,又可能成长出一代宗师
回复

使用道具 举报

9#
ID:997026 发表于 2022-2-17 17:38 | 只看该作者
xstong 发表于 2022-2-17 16:25
另外,你的设计是用单片机控制继电器,是否继电器控制线圈未设计续流二极管?
如果没有续流二极管,这样继 ...

感谢回复。
继电器端加了足够的保护电路,包括续流二极管,压敏电阻,二极管用的是1N4007反向电压1000V,压敏电阻并接在继电器触点端消电弧。所以应该不是继电器的问题。
回复

使用道具 举报

10#
ID:997026 发表于 2022-2-17 17:40 | 只看该作者
man1234567 发表于 2022-2-17 17:19
有些问题看似莫名其妙的,可能是程序中有肉眼不可见的啥,也可能硬件里出现了个啥;
有些问题真的莫名其妙 ...

有道理,但这个问题确实想不通,平常都是遇到代码本身逻辑问题,但第一次遇见这样的问题,也是见鬼了。

回复

使用道具 举报

11#
ID:997026 发表于 2022-2-17 17:40 | 只看该作者
wulin 发表于 2022-2-16 19:42
把你的确切需求和硬件环境详细讲清楚,有空给你写一个示例。

感谢!

但应该不是代码本身问题,是一个莫名其妙的问题。我这个代码连续开机测试好几天了,挺稳定的。
回复

使用道具 举报

12#
ID:236035 发表于 2022-2-18 09:36 | 只看该作者
出现问题,能恢复到正常,则跟踪调试恢复过程中涉及到的代码就可以了。只要是能重现故障的,就一定可以找到故障点。至于说以前正常,突然不正常,任何项目都有健壮度的,不论电路还是程序。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表