Arduino/AVR ATmega微控制器,随机复位、跳跃或可变/数据损坏



我认为许多Arduino/AVR MCU的程序员可以分享一些知识。

我的具体问题是:
在我的案例中,Atmel Atmega128 AVR
基于ADC数据,我运行了一个循环,在串行控制台中进行一些计算,它还驱动了一个中断。

发生的事情是,当我确实使用了一定数量的串行输出时,程序突然变得非常不稳定
代码中的随机跳跃、随机未知中断、随机变量损坏、无MCU寄存器位设置的随机重置
在特定点添加缓冲区解决了不稳定性
改变gcc的优化参数也改变了行为,如果没有优化,代码相当稳定。

检查我的答案,找出可能的原因和真正的原因。

有很多陷阱,这些都是一些卑鄙的陷阱:

1)突然复位可能有多种原因您的AVR可能在过低的电压下运行,降低频率或增加电压。查看数据表,他们有一张图表,他们对此很认真:)

2)另一个潜在问题是未处理的中断,您必须处理每个中断,因为未知的IRQ将导致立即重置
我喜欢将此代码添加到一个特殊的"全部捕获"ISR中,该ISR捕获所有未处理的IRQ。

ISR(BADISR_vect)
{
    for (;;) UDR0='!';
}

这个片段将写大量的!输入UART。或者,你可以让LED闪烁,等等。只需确保不要从那里返回,因为这只会隐藏问题,而且当程序继续运行时,你可能找不到错误。

3)在main()或init代码中,应尽快检查MCU状态寄存器并将其设置为零
在大多数重置情况下,此寄存器将保存重置原因。

if(MCUCSR & (1<<PORF )) myprintf0P(PSTR("Power-on reset.n"));
if(MCUCSR & (1<<EXTRF)) myprintf0P(PSTR("External reset!n"));
if(MCUCSR & (1<<BORF )) myprintf0P(PSTR("Brownout reset!n"));
if(MCUCSR & (1<<WDRF )) myprintf0P(PSTR("Watchdog reset!n"));
if(MCUCSR & (1<<JTRF )) myprintf0P(PSTR("JTAG reset!n"));
MCUCSR = 0;

4)意外行为的另一个很好的原因是编译器优化
你可以从很多选项中进行选择,你优化得越多,你的代码就会压缩得越多(至少在一般情况下)。无用的数据和函数被删除,代码被压缩并变为更快或更小的指令
程序员通常在编写代码时禁用或减少优化,这有助于调试过程不会随机换行,并根据自己的代码非常准确地显示正在发生的事情
然而,如果你有一个小的内存问题(比如一个错误),那么一个未优化的代码可能会在没有可见问题的情况下运行,但一旦优化提高,变量的位置可能会改变,或者突然两个变量在堆栈或堆中相邻,所以一次写入可能会突然影响以前没有受到影响的代码
像valgrind这样的调试工具不适用于AVR,所以我最好的建议是用激活的大脑进行编写
如果你用三分球打球,那么仔细检查你是否从未出界。

5)编译器优化可能会"破坏"您的轮询代码例如,您正在ISR(uart、ADC、TWI等)中编写一个原子(8位)变量/寄存器。在主循环中,您现在可以查看这个变量在用作新数据的指示符/标志时是否发生了变化
这是一种正确的代码编写方式,但编译器不知道您正在更改ISR中的此变量
因此,优化例程很可能只是把这个变量当作静态变量,毕竟你运行了一个无休止的循环,而你在这个循环中只从中读取
解决方案是将变量设置为volatile
这里有一个FIFO环形缓冲区的例子,它有两个从正常代码和ISR代码读取和写入的索引:

struct fifo 
{
    uint8_t size;            /* size of buffer in bytes */
    volatile uint8_t read;           /* read pointer */
    volatile uint8_t write;        
    unsigned char *buffer;       /* fifo ring buffer */
};

6)这是我的具体问题,它引起了以上所有问题在我的情况下,整个问题源于我在3.3Volt和16MHZ下使用AVR的愚蠢。在这个频率下,它需要大约4.5V才能稳定运行
早些时候,我做了一些测试,MCU似乎运行得非常稳定,但随着代码大小的增加,稳定性降低了
它表现得好像我有一个非常严重的内存损坏,可能是由ISR触发的
或者就好像一些libc函数(与progmem相关的函数)有故障一样
把设备调到5V就解决了这个问题。这种智慧花了我无数个小时的软件分析,我从文字上深入搜索软件方面的每一个可能的原因
教训:如果你对微控制器进行编程,千万不要把它当作纯粹的软件:)

7)对于高级内存损坏分析,您可以将堆栈设置为特定的预定义状态。这可以极大地帮助您进行调试,因为您可以观察变量在数据中的增长情况
此外,null终止的丢失会使指针运行到已知数据而不是未知数据
只需在你的项目中添加一个C文件,代码如下:

extern void *_end, *__stack;
#define __ALD(x) ((uintptr_t)(x) - ((uintptr_t)(x) & 0x03))
#define __ALU(x)   ((uintptr_t)(x) + ((uintptr_t)(x) & 0x03))
void _stackfill(void) __attribute__((naked)) __attribute__((optimize("O3"))) __attribute__((section (".init1")));
void _stackfill(void)
{
    uint32_t* start = (uint32_t*)__ALU(&_end);
    uint32_t* end   = (uint32_t*)__ALD(&__stack);
    for (uint32_t *pos = start; pos < end; pos++)
        *pos = 0x41424142; // ends up as endless ascii BABA
}

此代码将自动挂接到代码的init部分,并在sram中写入模式BABABABABA
这对你的程序没有坏影响,它只是用一个已知的模式初始化sram
如果您在调试期间查看它,您将看到变量在哪里分配,在哪里不分配
它运行良好,也可以写入init3。

现在就这样。我希望这个简短的综述能帮助一些程序员解决AVR的奇怪/令人沮丧的行为。

代码部分是为ATMEGA 128编写的,但将在任何8位AVR上运行,只是一些寄存器名称可能需要进行小的更改。

8)使用最新的gcc编译器以降低错误编译器优化的可能性(Arduino gcc 4.3.2问题)。

9)使用JTAG调试器以提高随机错误的机会捕获

TLDR:约翰肯定列举了一大堆可能的原因。然而,另一个是缺失的,它是由断电引起的重置。

长版本:我遇到了一个类似的问题,我的Atmega328在进入设置、初始化MicroSD并打开wifi(esp8266)模块后一直重置。每次打开wifi模块时,它都会重置。此外,只有当设备由电池供电而不是由FTDI232 USB电缆供电时,才会重置。

我用的是3节AA电池,但当时电压降到了3.6伏。因此,当MicroSD和wifi都打开时,我的DMM读数显示为3.2伏。我相信瞬时最小电压远低于这个电压。

我使用了Arduino Pro Mini 8M 3.3v的默认引导程序,efuse设置为0x5。根据Atmega328数据表,表29-12,相应的BOD水平为2.7v。因此,发生的情况是:当wifi和MicroSD打开时,电压降至2.7v以下,并且棕色功能重置程序。

那么解决方案呢?将BODLEVEL降低到1.8或像我一样禁用它。只需修改boards.txt文件并在Arduino Pro Mini:下更改以下设置

atmega328_384_8.bootloader.extended_fuses=0x05

atmega328_384_8.bootloader.extended_fuses=0x07

然后重新启动Arduino IDE并重新刻录引导程序。然后这个问题就消失了。

论坛上有一些关于禁用它或选择正确级别是否明智的讨论。但对于我的项目来说,让程序不断重置并耗尽电池是没有意义的。但它会对你的设备造成伤害,尤其是在低电压操作下的MicroSD卡。因此,我使用了一个电池电压测量电路,以确保一旦电压下降到某个水平以下,该程序使用更少的功率。

我在论坛上读了很多帖子,得到了约翰的回复,受益匪浅。我希望我的2美分能给有同样问题的人带来一点帮助。

最新更新