lswood 发表于 2019-5-7 16:04:57

一行比较两个整数大小的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几年电工,第一遇到这么诡异的问题。

xiaomu 发表于 2019-5-7 16:18:41

变量有在中断中使用吗,如果有,要加上volatile,修饰,否则有可能判读的时候被中断,数值被改变而不知道!

vuo50z 发表于 2019-5-7 16:19:35

stc的单片机不熟悉,如果是8位机,而usWaterCounter和usDestLiters是16或32位的,则加载他们的时候不是原子操作,需要关中断保护。

lvfv 发表于 2019-5-7 16:20:25

对变量进行强制类型转换。

vuo50z 发表于 2019-5-7 16:21:13

xiaomu 发表于 2019-5-7 16:18
变量有在中断中使用吗,如果有,要加上volatile,修饰,否则有可能判读的时候被中断,数值被改变而不知道! ...

如果中断中有修改,加volatile也没用,需要关中断保护。
volatile只是让程序每次都从内存取数,而不是用寄存器中暂存的。

huike 发表于 2019-5-7 16:26:13

34063这颗料很LOW的,我现在还有一管LC买的料,质量不怎么滴!
我也不知道这个是买了哪个牌子的,反正输入12V这个料就会挂,换LC买的HTC的,就稳稳的!

kingaaa 发表于 2019-5-7 16:27:04

变量定义加volatile试试?

lswood 发表于 2019-5-7 16:34:19

vuo50z 发表于 2019-5-7 16:19
stc的单片机不熟悉,如果是8位机,而usWaterCounter和usDestLiters是16或32位的,则加载他们的时候不是原子 ...

关中断也不起作用,而且中断里只是++操作,即便不关中断也不改出问题啊。大学专业是计科,写过操作系统,对计算机的底层还是颇为了解的,这回把我弄懵了。

lswood 发表于 2019-5-7 16:38:43

huike 发表于 2019-5-7 16:26
34063这颗料很LOW的,我现在还有一管LC买的料,质量不怎么滴!
我也不知道这个是买了哪个牌子的,反正输入1 ...

34063没有问题,便宜好用,用了很多的还没出过问题。一直用st的。

prince2010 发表于 2019-5-7 16:41:45

会不会是数据越界?

dadatou 发表于 2019-5-7 16:44:24

本帖最后由 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语言中的一句话,是由若干指令完成的,在这中间的任何位置都有可能被中断,所以必须使用原子操作,简单的方法主是在主程序中使用这些变量时,关闭中断,使用完再开启中断。

liyang121316 发表于 2019-5-7 16:47:12

感觉楼主没有真正找到问题的原因啊。

albert_w 发表于 2019-5-7 16:48:30

这估计要调试器看栈数据才好推测了. 走进去就中断执行

lswood 发表于 2019-5-7 16:53:22

albert_w 发表于 2019-5-7 16:48
这估计要调试器看栈数据才好推测了. 走进去就中断执行

设备上没办法调试了

lswood 发表于 2019-5-7 16:56:44

lianglee 发表于 2019-5-7 16:53
usWaterCounter.n >= usDestLiters.n

这两个变量如果是16位或32位,且其中一个在中断里自加1.


你说的这个我能理解,上午关中断试过没疗效啊。
这个固件用了几年没问题,突然就出现了问题,而且有点过猛。

lswood 发表于 2019-5-7 17:02:33

lianglee 发表于 2019-5-7 16:53
usWaterCounter.n >= usDestLiters.n

这两个变量如果是16位或32位,且其中一个在中断里自加1.


我再试试,看是不是上午中断关的不对,比较前关全局中断试试。
多谢

lswood 发表于 2019-5-7 17:03:56

dadatou 发表于 2019-5-7 16:44
如果两个变量在中断中使用了,那么首先肯定是要用volatile修饰,让程序每次都到物理地址去读取,但楼主使用 ...

多谢回复,打了这么多字辛苦了

modbus 发表于 2019-5-7 17:09:12

有启动电机等这么重要的动作,应该加上延时滤波啊,即加水量达到目标体积并保持100ms以上则执行

vuo50z 发表于 2019-5-7 17:11:32

lswood 发表于 2019-5-7 16:34
关中断也不起作用,而且中断里只是++操作,即便不关中断也不改出问题啊。大学专业是计科,写过操作系统, ...

如果cpu宽度比数据宽度窄,++操作也不是原子的。
如果你的关中断方法正确,那还真是见鬼了。
你可以把判断异常的值记录下来,看看能不能看出什么规律。

shiva_shiva 发表于 2019-5-7 17:14:03

除了关中断,还可以监视一下执行if(usWaterCounter.n >= usDestLiters.n) 过程中有没有进中断触发++操作,理论上这个概率比出错的概率还要大一个数量级

ddplys 发表于 2019-5-7 17:15:02

不知道程序是不是带操作系统的

t3486784401 发表于 2019-5-7 17:21:19

对于全局变量,从来是“关中断->缓冲至局部变量->开中断”来实现读取的,
写入的话需要更谨慎

zf12862177 发表于 2019-5-7 17:31:14

可以判定100% 是中断其他地方读取这个变量地址引起引起的问题。 因为写两遍就没有问题,说明确实是非原子操作造成了影响

lswood 发表于 2019-5-7 17:47:21

zf12862177 发表于 2019-5-7 17:31
可以判定100% 是中断其他地方读取这个变量地址引起引起的问题。 因为写两遍就没有问题,说明确实是非原子操 ...

现在可以100%判定不是中断引起的,因为这个变量比较小,最大400多,而且出错的时候变量的值只有不超过100,高位字节一直是0。
现在大概找到问题了,是电源烦扰导致的跑飞应该,之前从来还没遇到过mcu跑飞的情况,最多是变频器干扰到485通信不正常。
现在是把电磁阀线去掉,怎么都正常,一接电磁阀就出现这个故障。我看下这批电磁阀电流是否过大、还有pwm的频率是否过低(防止电磁阀过热)

lswood 发表于 2019-5-7 17:51:43

lianglee 发表于 2019-5-7 17:04
这个能理解,我之前也是同样的情况,分析了好久才得出结论。

同一程序,之前好好的,但是加入了某段函数 ...

大概率是mcu的pc寄存器跑飞了,把电磁阀线断开就好了,一接电磁阀就出问题。而且刚才突然想起中断访问的变量值出错时从没超过255,高位字节一直是0。
现在极度怀疑电磁阀pwm驱动的频率有点低,这批电磁阀可能电流太大了,导致干扰严重。

arm 发表于 2019-5-7 17:55:20

应该是中断里有改变usWaterCounter.n和usDestLiters.n的值,你应该在if(usWaterCounter.n >= usDestLiters.n)前加条EA=0这条语句,执行完再开总中断

kebaojun305 发表于 2019-5-7 18:44:15

lswood 发表于 2019-5-7 17:51
大概率是mcu的pc寄存器跑飞了,把电磁阀线断开就好了,一接电磁阀就出问题。而且刚才突然想起中断访问的 ...

电磁阀 2端 有没有吸收电容或者电阻电容?

lswood 发表于 2019-5-7 18:49:13

kebaojun305 发表于 2019-5-7 18:44
电磁阀 2端 有没有吸收电容或者电阻电容?

有续流回路,明天上午看看到底是哪块问题。

a136498491 发表于 2019-5-7 18:53:28

哈哈哈,看到单片机型号,我就确信我遇到的问题原因了。

modbus 发表于 2019-5-7 19:05:10

lswood 发表于 2019-5-7 17:51
大概率是mcu的pc寄存器跑飞了,把电磁阀线断开就好了,一接电磁阀就出问题。而且刚才突然想起中断访问的 ...

软件上对付电磁阀干扰最有效的措施就是延时滤波

lswood 发表于 2019-5-7 19:18:08

a136498491 发表于 2019-5-7 18:53
哈哈哈,看到单片机型号,我就确信我遇到的问题原因了。

你遇到什么问题了?
别总让stc背锅,stc抗干扰还是可以的,这次我要看看到底是谁的锅。

xiaojian 发表于 2019-5-7 19:26:18

很大可能是电磁阀的锅,国内这些做电磁阀的,乱七八糟乱做

gyzzg2030 发表于 2019-5-7 19:36:31

看来楼主对软件比较拿手,对硬件有欠缺,电源变化会跟故障关联明显是硬件问题,可能是LAYOUT问题比较大,电感啥的吸收不良,这类问题的典型特征就是一阶段产品OK,稍有变化,比如电源或某个器件变化就出现问题

su33691 发表于 2019-5-7 19:43:39

楼主还是要好好查查硬件电路和器件。

lb0857 发表于 2019-5-7 20:38:26

gyzzg2030 发表于 2019-5-7 19:36
看来楼主对软件比较拿手,对硬件有欠缺,电源变化会跟故障关联明显是硬件问题,可能是LAYOUT问题比较大,电 ...

赞同大伙从软件分析故障都非常精辟   但是忽略了楼主描述的一个故障现象:电压调低之后故障明显发生率下降   这个是关键点    34063是哪家公司的   有些芯片最高电压是30v请重新看看芯片手册或者按照先简单后复杂的排除思路,重新换一颗34063(满足36v工作的),或者直接宽电压类似7805的DCDC电源芯片换上去,看看是不是硬件引起的故障

a136498491 发表于 2019-5-7 22:30:01

lswood 发表于 2019-5-7 19:18
你遇到什么问题了?
别总让stc背锅,stc抗干扰还是可以的,这次我要看看到底是谁的锅。 ...

不能说完全是STC的锅,我这里问题不好描述,因为在办公室里从来没有重现过,类似跑飞又不是完全跑飞。而且换了单片机就好。或者有问题的机器,重新烧程序也行。
我估计是我电源没有做好,我是AC-DC12V然后 DC-DC到 5V,可能现场干扰大或者因为用的ACDC质量差吧。反正不管了,已经不做了

cu_ice 发表于 2019-5-7 22:39:40

在上家公司有一个产品,用的也是STC单片机,也会碰到跟楼主一样的问题,也是偶尔出现的,有时判断就出错,没查出什么原因,程序是别人写的,只是我发现的,后来做了些处理,判断错了也做某些补救动作就算了

qwe2231695 发表于 2019-5-8 00:45:55

电源脉冲会导致数据改变,是真的。听说是一种破解技术

lswood 发表于 2019-5-8 07:29:04

a136498491 发表于 2019-5-7 22:30
不能说完全是STC的锅,我这里问题不好描述,因为在办公室里从来没有重现过,类似跑飞又不是完全跑飞。而 ...

有问题重新烧程序就好了,这种问题我也经常遇到,看来stc要背一点点锅了

wye11083 发表于 2019-5-8 07:35:19

qwe2231695 发表于 2019-5-8 00:45
电源脉冲会导致数据改变,是真的。听说是一种破解技术

电压跌落会造成半导体性能波动。估计lz跑的是最高频率。8位机用32位数确实很头疼,之前用avr兼容机跑程序,当程序达到10kb及以上时,各种莫名其妙的毛病全来了。最后搞了2天还搞不定,换riscv兼容机了。

lswood 发表于 2019-5-8 08:16:37

这要是mcu用在宇宙辐射环境中,各种问题得多棘手啊

yuyu87 发表于 2019-5-8 08:57:23

3楼正解,不是原子操作,所有单片机处理不当都会有这个问题

EMC菜鸟 发表于 2019-5-8 08:59:51

lswood 发表于 2019-5-8 07:29
有问题重新烧程序就好了,这种问题我也经常遇到,看来stc要背一点点锅了 ...

就算是 STC的锅,老姚也不会陪你损失的吧 ,,,只能说你的电路原来虽然没问题、但离问题很近了

norman33 发表于 2019-5-8 09:03:43

变量莫名其妙被改,楼主查查整个程序有没有数组下标或者指针范围溢出,特别是长时间运行后偶尔出现这种

ackyee 发表于 2019-5-8 09:14:07

程序不太严谨   我都是每次中断里 刷新数据后 某个FLAG置为1主循环里检测到1之后进行处理然后置0   

dellric 发表于 2019-5-8 10:03:08

判断前后要加上若干个NOP,否则CISC单片机出现跑飞后会出现指令不同步情况。同样在RET指令后面也要加上3条以上NOP指令。

lswood 发表于 2019-5-8 10:08:07

norman33 发表于 2019-5-8 09:03
变量莫名其妙被改,楼主查查整个程序有没有数组下标或者指针范围溢出,特别是长时间运行后偶尔出现这种 ...

变量没有被改,一直是正确的,就是判断出错。

lswood 发表于 2019-5-8 10:09:04

ackyee 发表于 2019-5-8 09:14
程序不太严谨   我都是每次中断里 刷新数据后 某个FLAG置为1主循环里检测到1之后进行处理然后置0      ...

关中断了,不是中断的锅

lswood 发表于 2019-5-8 10:16:33

结题,不再深究了,没时间。问题总结:1.不是原语问题,判断前后关开中断没有效果,并且出措施变量高位字节没有改变一直是0;2.变量没有被篡改过,就是判断出错,应该是电磁阀干扰到了cpu寄存器的条件位,导致跳转错误;3.电磁阀是12v的,36vpwm驱动,并联的ss36作为续流二极管,之前没有问题现在有问题,可能是这一批电磁阀有问题,没有上批存货没法对比了;4.通过多加一条if解决问题,暂时先这样吧。

EMC菜鸟 发表于 2019-5-8 10:35:43

lswood 发表于 2019-5-8 10:16
结题,不再深究了,没时间。问题总结:1.不是原语问题,判断前后关开中断没有效果,并且出措施变量高位字节 ...

如果是干扰导致 CPU 出错,那你这么改只是治标不治本吧 ? 干扰可不止只干扰这一个语句吧?

lswood 发表于 2019-5-8 12:28:54

EMC菜鸟 发表于 2019-5-8 10:35
如果是干扰导致 CPU 出错,那你这么改只是治标不治本吧 ? 干扰可不止只干扰这一个语句吧? ...

确实不治本,又在找电源部分的问题了,真不省心

modbus 发表于 2019-5-8 13:19:29

最简单的测试办法就是在“//停止加水”注释上面把usWaterCounter.n和usDestLiters.n的值存到另外两个变量中并监测之

lswood 发表于 2019-5-8 17:33:28

这回真的结题。真是见鬼了,确实是电源出的问题,生产这批板子时,工人把34063上的电容给贴错了,贴成了104,频率低成狗了,波纹也大成狗了,导致cpu工作不正常。纳闷的是其它地方都不跑飞,就这里跑飞。还纳闷贴片机的料栈没动,怎么就会错呢?

lswood 发表于 2019-5-8 17:34:12

看来要对生产流程严加管控了

lswood 发表于 2019-5-8 17:37:28

谢谢大家的参与,一个人的失误害的几个人折腾了两天。

nibia 发表于 2019-5-9 08:48:32

34063后面用的和104一样封装的陶瓷电容??应该用大个铝电解电容吧?

zhugean 发表于 2019-5-9 09:18:42

8位单片机,短整型需要分两次读取,当读取出低字节时产生了中断,你去++,这个时候正好发生了进位,结果就产生了错误
不管是不是这个个问题引起的你这个现象,这都是需要注意的问题。
多余的话就不说了

kentxiong 发表于 2019-5-9 11:08:14

确实是教训啊!

lswood 发表于 2019-5-9 12:15:14

nibia 发表于 2019-5-9 08:48
34063后面用的和104一样封装的陶瓷电容??应该用大个铝电解电容吧?

是控制34063工作频率的电容,贴片0805电容

nibia 发表于 2019-5-9 16:44:14

dadatou 发表于 2019-5-7 16:44
如果两个变量在中断中使用了,那么首先肯定是要用volatile修饰,让程序每次都到物理地址去读取,但楼主使用 ...

我有一个疑问,之前在8051中,有大量的 关中断的操作。
在现在都是arm的时代,是不是在stm32上,如果是32位的数据,已经不需要这种关闭中断的操作了

modbus 发表于 2019-5-9 17:20:52

nibia 发表于 2019-5-9 16:44
我有一个疑问,之前在8051中,有大量的 关中断的操作。
在现在都是arm的时代,是不是在stm32上,如果是32 ...

32位有时也需要,要不就不会有要求原子操作一说了

sf49ers 发表于 2019-5-10 08:59:04

lswood 发表于 2019-5-8 17:33
这回真的结题。真是见鬼了,确实是电源出的问题,生产这批板子时,工人把34063上的电容给贴错了,贴 ...

其实你的案例挺有特点,感觉是电压低到MCU工作不正常区域了,复位电压怎么设的?
页: [1]
查看完整版本: 一行比较两个整数大小的C语句运行结果竟然会偶尔出错!