【待续】Arduino踩坑手册-《中断故障排查指南》-中断、串口、定时器等片内硬件资源之间的冲突

有个老师说:Arduino这玩意 更多的时候是儿童玩具 这话说的不错
我还挺愿意把这话推广一下 :过度封装(而不读文档)是万坑之源
各位可以发现:很多稍微高级一些的操作与arduino库函数共用就会出现奇怪的问题
编者本着刨根问底的精神 看了些官方库源码 现将这几天的研究结果总结如下
时间所限,如有疏漏之处还请各位看官指正


一、定时器相关

arduino uno(atmega328p)带有三个定时器T0,T1,T2。T0和T2是8位定时器,T1是16位定时器,当noInterrputs函数执行之后,这三个计时器都会罢工。

1.不靠谱的tone,delay,millis函数

tone函数:
是利用T2定时器实现异步定时蜂鸣的,使用MsTimer2时要注意避免与tone发生冲突(症状:tone会影响MsTimer2的时钟周期)。
这里可以考虑使用外国友人制作的TimerFreeTone库,本质上是软件实现的蜂鸣输出,没直接用定时器,但是用了millis(软蜂鸣音与定时器产生的蜂鸣信号可能不是一个波形,用耳朵仔细听会发现两者音色不同,有强迫症的朋友要小心了)
delay函数与millis函数:
是利用T0定时器实现延时,计时的,只要你不瞎调T0有关的寄存器,正常使用是没问题的。
什么时候T0有关的寄存器会被更改呢?请看“PWM相关”一节。

2.题外话:VisualStudio相关

VisualStudio有个良心插件:VisualMicro,可以为用户提供对Arduino调试的功能,然而Atmega xx8系列(Uno)统统是不支持JTAG调试的(儿童玩具实锤),剩下的方案就只有利用定时器实现软调试,这个良心插件做到了软调试,然而:

已知软调试的不能做的:

  • 在不经任何处理的情况下,在attachInterrupt等中断服务函数里跟踪变量值
  • 在一切比定时器优先级高的中断面前仍能守住控制权
  • 在noInterrupts之后与上位机保持联系
  • 从儿童玩具变成实用工具
    除非你能保证本文里提到的除adc、串口外的一切资源,你都不会使用,否则用它很难让你开心的调试。

二、attachInterrupt、Serial.event、Wire.begin等中断相关

这个是用来设置外部中断的(相似的有IIC从机模式中断设置函数、串口中断设置函数(MsTimer2的中断函数设置暂时没发现有下述问题)),通常会引发一些很迷的问题(“万物皆失灵,中断行不行。“),比如IIC设备输出失灵,计时函数失灵

1.异常原因

进入使用attachInterrupt设置过的中断函数前,arduino 会执行noInterrupts函数,关闭自己和比自己优先级低的中断(优先级更低的中断是否被关闭了有待考证)

2.异常名单

已测试可用的:

  • Serial.println()
  • pwm输出

已测试有异常的:

  • IIC OLED输出失灵
  • delay,tone瞬间完成
  • millis不计时

3.解决方法

在需要恢复定时器中断的地方使用sei();恢复中断
例子:

#inlcude "avr/io.h"//avr官方库,可以引入高级底层函数
void isr()
{
	Serial.println(millis());//可以正常输出,但是在中断开关打开前始终都是同一个值
	_delay_ms(100);//来自avr官方的软延时,在这里不会被影响,用途是去抖
	sei();/*恢复定时器中断,从这里开始millis和delay等就恢复正常了,
	同时中断开关会再次被打开,有可能发生中断还未结束,下一个中断就又来了的情况
	(因此这个sei不应该被放在中断函数的顶部,否则用户的一次中断操作可能会瞬间引发相当多次的中断响应)*/
	delay(100);//正常
	Serial.println(millis());//跟上一次不一样了
}
void setup()
{
	Serial.begin(9600);
	attachInterrupt(1,isr,LOW);
	pinMode(3,INPUT_PULLUP);
}
void loop()
{
}

三、PWM相关

arduino uno一共有6个PWM输出脚,他们是基于定时器做输出的
不同的引脚工作时占用的定时器不同(这里就可能有时钟周期无意间被改动了的问题)
列表如下:

引脚 定时器
5\6 T0
9\10 T1
11\3 T2

如果你发现你设置的定时器本来应该每一秒做某事,结果这个周期突然就变了,可以考虑从这个方向排查一下,更换一组不与目前正在用的定时中断冲突的PWM引脚

已经发现的异常

  • MsTimer2与tone()共用,结果MsTimer2突然飞快计时
  • (猜测)TimerOne与9/10脚pwm输出同时使用,可能导致TimerOne周期异常

四、ADC相关

stc15系列做adc查询推荐了一种中断式查询方案,在这里我想说,幸亏arduino没用这个方案,所以目前看来analogRead()应该是你做过的最安全的事情之一,这个函数是阻塞式查询的(这几乎是废话,因为异步查询的函数一定会让开发者提供个回调函数,或是个存返回值的地址,而analogRead显然没有)——在adc采完样之前,cpu会一直等着,卡在while循环里等着adc忙位回零
但是仍然要小心,adc查询过程可能会被中断打断,这就可能影响你采集数据的时效性,怎么说呢,analogRead还是挺耗时的,这就容易被中断拦腰斩,所以,在时效性采集项目里,请管好你的中断!

(未完待续 欢迎各位指正)

你可能感兴趣的:(Arduino)