Today's the day

向软件大牛炫耀我会焊单片机,向硬件大牛炫耀我会写 Rails,向软硬件大牛炫耀我生物,向软硬件生物大牛炫耀我会折腾期货 -_-bbb

慎用焊锡膏
宅男必备 -- 脚踏键盘

单片机项目总结(一)-- 稳定性

galeki posted @ 2009年11月21日 12:46 in 单片机 , 5632 阅读

以前只写桌面软件和 Web 程序,感觉一个稍有规模的软件要想做到良好的稳定性是要下一番功夫的,更不要说系统软件、内核模块、操作系统了这些了,因为他们确实太复杂了。

与此相对的,初涉单片机的时候,就感觉这东西很简单,也很难出故障,这个感觉和平常的经验也相符,毕竟,你什么时候看到过电子表死机?遥控器死机?刮胡刀死机?电梯死机?因为他们的逻辑就那么简单,根本就不可能死嘛……

不过现在才明白,如果你在实验室里做个电子玩具,那么以上是成立的,当你准备把这个玩具做成产品的时候,那么诸多问题就来了:你的产品也许不会像电 脑主机一样放在桌角就不动了,它也许会被放在 -20 度的户外,也许会被阳光晒到 50 度以上,也许会在湿度经常达到 80% 以上的南方夏天使用,也许会在干燥得一碰就能打出静电火花的北方冬天使用,也许会正好放在冰箱的压缩机旁边,也许会从桌上摔到地上再弹起来……

最初没有认识到这点,以为原型搞定了就算完工,结果之后在稳定性上花了大量的时间。不过也算了解了不少东西,特此记录一下~

1. Watchdog

看门狗,一直以来都知道有这么个东西,但是从来没有用过。第一印象是觉得这个模块的作用怎么这么弱智,不就是个定时的复位器吗,还要在程序中不断” 喂狗“,为啥要这么麻烦……现在明白了,因为你不知道你的程序会在什么地方跑飞,或者进入死循环,即便是设计的 100% 完美的代码,也可能在外部干扰、电压不稳的情况下,到错误的地方执行错误的代码,看门狗给让你的程序至少不会失去响应,大多数还有标志位让你能分辨出看门 狗重启从而做特殊处理。

无奈的是看门狗是要消耗电量的,对电池设备不是那么合适,不过如果你的设备不是要求一节纽扣电池就要运行两三年的话,那么还是把看门狗打开吧~

如果追求更低的功耗,和更健壮的稳定性,那么用专用的外部看门狗芯片也是不错的。除此之外也有不少芯片有看门狗这个附加功能,比如 HT1621,不用白不用~ :)

2.BOD

BOD 给我的第一感觉,也跟看门狗一样,不就是个低电压复位器嘛,认为在电压很波动的环境中用用还情有可原,一般情况下用不到。其实不然,因为电压过低是造成程 序混乱的一个主要原因,最低电压 1.8V 的处理器,在 1.0V 电压下也可以运行,但是内部已经混乱了。这次就遇到了用电池的设备在低温下莫名其妙跑飞的情况,最后才发现是电池在电量快耗尽的时候,在低温下电压会降得 很厉害,加上 BOD 就 OK 了。

BOD 也有同样的问题,就是一般也是要耗电的,因为为了比较电压,至少要维护一个电压基准。不过比如 MSP430 就有 zero-power BOD 技术做到零功耗的 BOD,新版采用 picoPower 技术的 AVR 处理器也有 Sleeping BOD 功能,同样可以达到睡眠状态下 BOD 无功耗,所以,也是不用白不用啦~ :D

3.好用的 EPPROM

对于一般功能的设备来说,有 Watchdog 和 BOD 就足够了,但是对于一些要维护状态信息和长期运行的设备,比如数据记录器、监控器,那么程序跑飞,就不能简单的重启了事,更重要的是要恢复现场,接着跑飞前的地方继续运行。

这就需要非易失存储器的帮助了,而 EPPROM 又是其中最好用的一个,100 万次的写入寿命,并且大部分处理器都内置了 EPPROM,又是不用白不用,即便没有,添加一块外置的 EPPROM,比如 24c02,也只需要几毛钱。

虽然 EPPROM 只有几千个字节的存储空间,却足够可以保存运行时的所有变量,配合 Watchdog 的重启中断,可以在处理器被重启前,把重要的运行时参数保存到 EPPROM 里去,重启之后再装载回来。

现在的项目中,把运行时的变量保存在一个 configs 数组中,然后定期把这个数组保存到 EPPROM 中,这样即便掉电重启,也可以快速恢复到最近的一个备份上~

4.插拔和对接

这里主要指运行中设备的插拔,和两个运行中设备的对接,因为在这些情况下会有一些需要注意的问题。

曾经做过一个设备,需要在 3V 和 5V 电源之间无缝切换,当 5V 电源插入的时候,就自动用 5V 电源,5V 拔下就自动用内部的 3V 电池。做好之后却发现一个奇怪的问题,3V 切换到 5V 没有任何问题,当把 5V 拔下来的时候,处理器重启了。一直以为是电源的切换速度不够快,5V 掉电却没有来得及切换到 3V 上去,但是后来发现原因不在这里,原因竟然出在电容上。因为 5V 是外接电源,所以习惯性的在电源入口处放了个大电容(下图 C11),这样当 5V 拔下的时候,这个电容就会瞬间放电,这个瞬时的高压就将处理器复位了,去掉这个大电容,一切正常了~

双系统对接,电压不匹配是主要问题,比如 3V 和 5V 系统之间通过 I2C 总线互联。原来以为,只要 3V 的设备可以耐受 5V 的高压,双方的 VOH 什么的也可以保证逻辑正确的话就可以直接对接了,在实验室里中也经常这样干,但是在产品中用却发现了诸多的问题。比如做 3V 和 5V 设备直接的互联,3V 的设备要插入到 5V 设备上的端口上去,大部分时候插入没有问题,但是少数时候,一插入 3V 的设备就立刻死机了。

原因在于大部分处理器的 IO 口都有钳位二极管的保护(上图中红色框内),让 IO 口的输入电压不会高于 VCC 也不会低于 GND。这也就是为什么3V 和 5V 的系统互联后,即便双方 VCC 没有连接,但是还会发现 3V 系统的 VCC 变成将近 5V 了(其实就是 5V 减一个二极管的压降);这也是为什么有的芯片,不接电压,直接给它的 IO 口输入驱动也能跑起来。因为电流通过 IO 口的钳位二极管流到 VCC 去了。

但是通常钳位二极管有最大电流的限制,3V 与 5V 之间 2V 的电压差,在设备内阻的配合下,会造成将近 10mA 的电流流过钳位二极管,大部分情况下这个电流已经太大了,会造成设备闭锁失去响应,应该控制在 uA 的等级之内才对。

简易的解决办法就是将双方的电压拉近,比如将 5V 的电压用稳压器降到 3.3V,那么和 3V 之间只有 0.3V 的电压差,造成的影响就会减少很多。

除此之外一个效果不错又省事的办法就是加限流电阻(下图中 Rser),这样可以有效的限制住电流大小,因为双方只要 VCC 不互联,那么电压做到严格一致是不可能的,有了限流电阻,就稳妥得多,并且这个电阻还可以有防止双方高频互扰的效果。负面效果就是电阻会拖慢边沿速度,高 速系统中不是很适用,但对于 I2C 这类只有几百 K 的应用,串联个 100 欧左右的电阻,是非常合适的。

更多的不同电压转换技巧,网上可以搜索到一份 Microchip 的 3V 5V 电压转换技巧手册,非常有参考性。

5. Delay and Try

单片机的固件中,经常看到类似的代码:

...
enable_adc();

while((!(ADCSRA & (1<<ADIF)));

read_adc_data();
...

中间用 while 来等待标志位置位,即便不用轮询而用中断方式,也会遇到类似的判断,比如在 I2C 传输中判断 I2C 中断标志位是否已经被清除、循环等待 ACK 信号之类的。

这种无限期的等待就有死循环的风险,如果硬件错误使得标志位置位失败,或者是 I2C 设备意外掉电,ACK 信号丢失,处理器就卡死在 while 这里一直等下去了。

虽说有看门狗可以在这种情况下复位,但是更好的方法是 Delay and Try,让程序稍微有点容错性:

unsigned char i = 10;
do{
    _delay_ms(2);
    i--;
}while((!(ADCSRA & (1<<ADIF))) && (i != 0));

这样,重试 10 次,在足够长的 20ms 内要是还是没有置位,就不再等了,当然后面的程序要有相应的处理机制。

同样的机制,也可以用在桌面软件中。一些特殊的操作,比如检测硬件、从硬件中读取数据、和驱动交互之类的不是每次都能成功,加个 Delay and Try,会稳定不少:

bool succes = false;
int retry = 10;

do
{
     succes = dwObj.FindDeviceFromID(VendorID, ProductID, ref path);
     if (succes)
        break;
     
     Thread.Sleep(200);

     retry -= 1;
} while (retry != 0);

return succes;

虽然方法很迂腐,但是很有效~ :)

  • 无匹配
  • 无匹配

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter