一行比较两个整数大小的C语句运行结果竟然会偶尔出错!
一个量产3年的产品,从来没有出过问题,代码没改过、板子没改过,最近进让出现了诡异的错误。if(usWaterCounter.n >= usDestLiters.n) { // 如果加水量达到目标体积
// 停止加水
if (VALVE_ON == ucValveState) {
StartStopValve(); // 关闭电磁阀
}
// 启动电机
if (!ucMotorState.Func.Run) {
RunMotor(2);
}
Beep(31);// 蜂鸣
ucStepN++; // 进入下一个工作状态
bUpdateData = 1; // 刷LCD显示
}
就是上面这段代码,每天循环运行,售出的数百台设备运行了3年没有故障。这几天发现新生产的设备运行到上述if语句时,明明usWaterCounter的值是小于usDestLiters的值(LCD实时显示这两个变量的值),然后就是能偶尔的执行到if语句之内开始停水启动电机进入下一个状态(加水量还没有达到目标数)。
作为一个严谨的电工,肯定先要怀疑自己,但是找了一天也没找到问题所在。程序内部没有数组越界访问、没有指针,变量值明明也是对的,就是判断大小会出错?真是遇见鬼了。用的stc15f2k32s2,真的想怀疑批次问题,但是没办法验证,之前的货都出完了。
这条语句是在while(1)循环内部的,循环运行十几秒就会出现误判情况。加水大概需要1分多钟,结果10几秒就停水了。出错概率大概就是1 / (每秒循环次数 X 10几秒),每秒循环次数估计有几千上万次。
于是我把if判断多写了一遍就工作正常了,如下:
if(usWaterCounter.n >= usDestLiters.n) if(usWaterCounter.n >= usDestLiters.n) { // 如果加水量达到目标体积
万分之1 X 万分之1就是数亿分之一的概率,我怕万一再出现错误,于是就又多谢了一遍if,测试一上午,没有再出错。现在的代码成了:
f(usWaterCounter.n >= usDestLiters.n) f(usWaterCounter.n >= usDestLiters.n) if(usWaterCounter.n >= usDestLiters.n) { // 如果加水量达到目标体积
这再要出错就特么都数以万亿分之一了,真可以去买彩票了。
变量没变,连续两次大小判断竟然会出现不一样的结果,真是见了鬼了。
差点忘了提到一个现象,系统DC 36V供电,使用34063DC-DC降压到5V,我如果把明纬36V电源的输出电压调低一点,那么出错的概率就会跟着降低一点。先出货,有时间再找找是不是36v降到5v占空比太低导致电源有问题,电源问题导致MCU跑错。
真他妈见鬼了,干了10几年电工,第一遇到这么诡异的问题。 变量有在中断中使用吗,如果有,要加上volatile,修饰,否则有可能判读的时候被中断,数值被改变而不知道! stc的单片机不熟悉,如果是8位机,而usWaterCounter和usDestLiters是16或32位的,则加载他们的时候不是原子操作,需要关中断保护。 对变量进行强制类型转换。 xiaomu 发表于 2019-5-7 16:18
变量有在中断中使用吗,如果有,要加上volatile,修饰,否则有可能判读的时候被中断,数值被改变而不知道! ...
如果中断中有修改,加volatile也没用,需要关中断保护。
volatile只是让程序每次都从内存取数,而不是用寄存器中暂存的。 34063这颗料很LOW的,我现在还有一管LC买的料,质量不怎么滴!
我也不知道这个是买了哪个牌子的,反正输入12V这个料就会挂,换LC买的HTC的,就稳稳的! 变量定义加volatile试试? vuo50z 发表于 2019-5-7 16:19
stc的单片机不熟悉,如果是8位机,而usWaterCounter和usDestLiters是16或32位的,则加载他们的时候不是原子 ...
关中断也不起作用,而且中断里只是++操作,即便不关中断也不改出问题啊。大学专业是计科,写过操作系统,对计算机的底层还是颇为了解的,这回把我弄懵了。 huike 发表于 2019-5-7 16:26
34063这颗料很LOW的,我现在还有一管LC买的料,质量不怎么滴!
我也不知道这个是买了哪个牌子的,反正输入1 ...
34063没有问题,便宜好用,用了很多的还没出过问题。一直用st的。 会不会是数据越界? 本帖最后由 dadatou 于 2019-5-7 16:49 编辑
如果两个变量在中断中使用了,那么首先肯定是要用volatile修饰,让程序每次都到物理地址去读取,但楼主使用多几次判断就能减少异常现象,则不是volatile修饰的原因。
那肯定是原子操作的问题,如果参与比较的两个变量不是8位,又在中断中使用了这个变量,那么就会出问题:
举个例子:
变量A为16位的数据,即两个字节,在中断中,A会执行加一操作,即A++;
那么在主程序中需要用到这个变量时,A正好加到255(0x00ff)。
第一步:主程序读出高字节为0。
第二步:主程序被中断,A执行加一操作,A的值变成256(0x0100)。
第三步:中断回来,主程序继续读低字节为0。
第四步:主程序此时使用的变量A的数值为0。而实际上,此时A的值为256。
这就是为什么要使用原子操作的原因。要明白一点,在8位机上操作大于8位的变量,在C语言中的一句话,是由若干指令完成的,在这中间的任何位置都有可能被中断,所以必须使用原子操作,简单的方法主是在主程序中使用这些变量时,关闭中断,使用完再开启中断。
感觉楼主没有真正找到问题的原因啊。 这估计要调试器看栈数据才好推测了. 走进去就中断执行 albert_w 发表于 2019-5-7 16:48
这估计要调试器看栈数据才好推测了. 走进去就中断执行
设备上没办法调试了 lianglee 发表于 2019-5-7 16:53
usWaterCounter.n >= usDestLiters.n
这两个变量如果是16位或32位,且其中一个在中断里自加1.
你说的这个我能理解,上午关中断试过没疗效啊。
这个固件用了几年没问题,突然就出现了问题,而且有点过猛。 lianglee 发表于 2019-5-7 16:53
usWaterCounter.n >= usDestLiters.n
这两个变量如果是16位或32位,且其中一个在中断里自加1.
我再试试,看是不是上午中断关的不对,比较前关全局中断试试。
多谢 dadatou 发表于 2019-5-7 16:44
如果两个变量在中断中使用了,那么首先肯定是要用volatile修饰,让程序每次都到物理地址去读取,但楼主使用 ...
多谢回复,打了这么多字辛苦了 有启动电机等这么重要的动作,应该加上延时滤波啊,即加水量达到目标体积并保持100ms以上则执行 lswood 发表于 2019-5-7 16:34
关中断也不起作用,而且中断里只是++操作,即便不关中断也不改出问题啊。大学专业是计科,写过操作系统, ...
如果cpu宽度比数据宽度窄,++操作也不是原子的。
如果你的关中断方法正确,那还真是见鬼了。
你可以把判断异常的值记录下来,看看能不能看出什么规律。 除了关中断,还可以监视一下执行if(usWaterCounter.n >= usDestLiters.n) 过程中有没有进中断触发++操作,理论上这个概率比出错的概率还要大一个数量级 不知道程序是不是带操作系统的 对于全局变量,从来是“关中断->缓冲至局部变量->开中断”来实现读取的,
写入的话需要更谨慎 可以判定100% 是中断其他地方读取这个变量地址引起引起的问题。 因为写两遍就没有问题,说明确实是非原子操作造成了影响 zf12862177 发表于 2019-5-7 17:31
可以判定100% 是中断其他地方读取这个变量地址引起引起的问题。 因为写两遍就没有问题,说明确实是非原子操 ...
现在可以100%判定不是中断引起的,因为这个变量比较小,最大400多,而且出错的时候变量的值只有不超过100,高位字节一直是0。
现在大概找到问题了,是电源烦扰导致的跑飞应该,之前从来还没遇到过mcu跑飞的情况,最多是变频器干扰到485通信不正常。
现在是把电磁阀线去掉,怎么都正常,一接电磁阀就出现这个故障。我看下这批电磁阀电流是否过大、还有pwm的频率是否过低(防止电磁阀过热) lianglee 发表于 2019-5-7 17:04
这个能理解,我之前也是同样的情况,分析了好久才得出结论。
同一程序,之前好好的,但是加入了某段函数 ...
大概率是mcu的pc寄存器跑飞了,把电磁阀线断开就好了,一接电磁阀就出问题。而且刚才突然想起中断访问的变量值出错时从没超过255,高位字节一直是0。
现在极度怀疑电磁阀pwm驱动的频率有点低,这批电磁阀可能电流太大了,导致干扰严重。 应该是中断里有改变usWaterCounter.n和usDestLiters.n的值,你应该在if(usWaterCounter.n >= usDestLiters.n)前加条EA=0这条语句,执行完再开总中断 lswood 发表于 2019-5-7 17:51
大概率是mcu的pc寄存器跑飞了,把电磁阀线断开就好了,一接电磁阀就出问题。而且刚才突然想起中断访问的 ...
电磁阀 2端 有没有吸收电容或者电阻电容? kebaojun305 发表于 2019-5-7 18:44
电磁阀 2端 有没有吸收电容或者电阻电容?
有续流回路,明天上午看看到底是哪块问题。 哈哈哈,看到单片机型号,我就确信我遇到的问题原因了。 lswood 发表于 2019-5-7 17:51
大概率是mcu的pc寄存器跑飞了,把电磁阀线断开就好了,一接电磁阀就出问题。而且刚才突然想起中断访问的 ...
软件上对付电磁阀干扰最有效的措施就是延时滤波 a136498491 发表于 2019-5-7 18:53
哈哈哈,看到单片机型号,我就确信我遇到的问题原因了。
你遇到什么问题了?
别总让stc背锅,stc抗干扰还是可以的,这次我要看看到底是谁的锅。 很大可能是电磁阀的锅,国内这些做电磁阀的,乱七八糟乱做 看来楼主对软件比较拿手,对硬件有欠缺,电源变化会跟故障关联明显是硬件问题,可能是LAYOUT问题比较大,电感啥的吸收不良,这类问题的典型特征就是一阶段产品OK,稍有变化,比如电源或某个器件变化就出现问题 楼主还是要好好查查硬件电路和器件。 gyzzg2030 发表于 2019-5-7 19:36
看来楼主对软件比较拿手,对硬件有欠缺,电源变化会跟故障关联明显是硬件问题,可能是LAYOUT问题比较大,电 ...
赞同大伙从软件分析故障都非常精辟 但是忽略了楼主描述的一个故障现象:电压调低之后故障明显发生率下降 这个是关键点 34063是哪家公司的 有些芯片最高电压是30v请重新看看芯片手册或者按照先简单后复杂的排除思路,重新换一颗34063(满足36v工作的),或者直接宽电压类似7805的DCDC电源芯片换上去,看看是不是硬件引起的故障 lswood 发表于 2019-5-7 19:18
你遇到什么问题了?
别总让stc背锅,stc抗干扰还是可以的,这次我要看看到底是谁的锅。 ...
不能说完全是STC的锅,我这里问题不好描述,因为在办公室里从来没有重现过,类似跑飞又不是完全跑飞。而且换了单片机就好。或者有问题的机器,重新烧程序也行。
我估计是我电源没有做好,我是AC-DC12V然后 DC-DC到 5V,可能现场干扰大或者因为用的ACDC质量差吧。反正不管了,已经不做了 在上家公司有一个产品,用的也是STC单片机,也会碰到跟楼主一样的问题,也是偶尔出现的,有时判断就出错,没查出什么原因,程序是别人写的,只是我发现的,后来做了些处理,判断错了也做某些补救动作就算了
电源脉冲会导致数据改变,是真的。听说是一种破解技术 a136498491 发表于 2019-5-7 22:30
不能说完全是STC的锅,我这里问题不好描述,因为在办公室里从来没有重现过,类似跑飞又不是完全跑飞。而 ...
有问题重新烧程序就好了,这种问题我也经常遇到,看来stc要背一点点锅了 qwe2231695 发表于 2019-5-8 00:45
电源脉冲会导致数据改变,是真的。听说是一种破解技术
电压跌落会造成半导体性能波动。估计lz跑的是最高频率。8位机用32位数确实很头疼,之前用avr兼容机跑程序,当程序达到10kb及以上时,各种莫名其妙的毛病全来了。最后搞了2天还搞不定,换riscv兼容机了。 这要是mcu用在宇宙辐射环境中,各种问题得多棘手啊 3楼正解,不是原子操作,所有单片机处理不当都会有这个问题 lswood 发表于 2019-5-8 07:29
有问题重新烧程序就好了,这种问题我也经常遇到,看来stc要背一点点锅了 ...
就算是 STC的锅,老姚也不会陪你损失的吧 ,,,只能说你的电路原来虽然没问题、但离问题很近了 变量莫名其妙被改,楼主查查整个程序有没有数组下标或者指针范围溢出,特别是长时间运行后偶尔出现这种 程序不太严谨 我都是每次中断里 刷新数据后 某个FLAG置为1主循环里检测到1之后进行处理然后置0 判断前后要加上若干个NOP,否则CISC单片机出现跑飞后会出现指令不同步情况。同样在RET指令后面也要加上3条以上NOP指令。 norman33 发表于 2019-5-8 09:03
变量莫名其妙被改,楼主查查整个程序有没有数组下标或者指针范围溢出,特别是长时间运行后偶尔出现这种 ...
变量没有被改,一直是正确的,就是判断出错。 ackyee 发表于 2019-5-8 09:14
程序不太严谨 我都是每次中断里 刷新数据后 某个FLAG置为1主循环里检测到1之后进行处理然后置0 ...
关中断了,不是中断的锅 结题,不再深究了,没时间。问题总结:1.不是原语问题,判断前后关开中断没有效果,并且出措施变量高位字节没有改变一直是0;2.变量没有被篡改过,就是判断出错,应该是电磁阀干扰到了cpu寄存器的条件位,导致跳转错误;3.电磁阀是12v的,36vpwm驱动,并联的ss36作为续流二极管,之前没有问题现在有问题,可能是这一批电磁阀有问题,没有上批存货没法对比了;4.通过多加一条if解决问题,暂时先这样吧。 lswood 发表于 2019-5-8 10:16
结题,不再深究了,没时间。问题总结:1.不是原语问题,判断前后关开中断没有效果,并且出措施变量高位字节 ...
如果是干扰导致 CPU 出错,那你这么改只是治标不治本吧 ? 干扰可不止只干扰这一个语句吧? EMC菜鸟 发表于 2019-5-8 10:35
如果是干扰导致 CPU 出错,那你这么改只是治标不治本吧 ? 干扰可不止只干扰这一个语句吧? ...
确实不治本,又在找电源部分的问题了,真不省心 最简单的测试办法就是在“//停止加水”注释上面把usWaterCounter.n和usDestLiters.n的值存到另外两个变量中并监测之 这回真的结题。真是见鬼了,确实是电源出的问题,生产这批板子时,工人把34063上的电容给贴错了,贴成了104,频率低成狗了,波纹也大成狗了,导致cpu工作不正常。纳闷的是其它地方都不跑飞,就这里跑飞。还纳闷贴片机的料栈没动,怎么就会错呢? 看来要对生产流程严加管控了 谢谢大家的参与,一个人的失误害的几个人折腾了两天。 34063后面用的和104一样封装的陶瓷电容??应该用大个铝电解电容吧? 8位单片机,短整型需要分两次读取,当读取出低字节时产生了中断,你去++,这个时候正好发生了进位,结果就产生了错误
不管是不是这个个问题引起的你这个现象,这都是需要注意的问题。
多余的话就不说了 确实是教训啊! nibia 发表于 2019-5-9 08:48
34063后面用的和104一样封装的陶瓷电容??应该用大个铝电解电容吧?
是控制34063工作频率的电容,贴片0805电容 dadatou 发表于 2019-5-7 16:44
如果两个变量在中断中使用了,那么首先肯定是要用volatile修饰,让程序每次都到物理地址去读取,但楼主使用 ...
我有一个疑问,之前在8051中,有大量的 关中断的操作。
在现在都是arm的时代,是不是在stm32上,如果是32位的数据,已经不需要这种关闭中断的操作了 nibia 发表于 2019-5-9 16:44
我有一个疑问,之前在8051中,有大量的 关中断的操作。
在现在都是arm的时代,是不是在stm32上,如果是32 ...
32位有时也需要,要不就不会有要求原子操作一说了 lswood 发表于 2019-5-8 17:33
这回真的结题。真是见鬼了,确实是电源出的问题,生产这批板子时,工人把34063上的电容给贴错了,贴 ...
其实你的案例挺有特点,感觉是电压低到MCU工作不正常区域了,复位电压怎么设的?
页:
[1]