【问题标题】:Arduino/AVR ATmega microcontroller, random resets, jumps or variable/data corruptionArduino/AVR ATmega 微控制器、随机复位、跳转或变量/数据损坏
【发布时间】:2014-06-16 04:12:23
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: debugging arduino microcontroller avr atmega


    【解决方案1】:

    TLDR: 约翰当然列出了很多可能的原因。但是,缺少另一个是掉电导致的重置。

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

    我使用了 3 节 AA 电池,但那时它们的电压下降到 3.6v。因此,当 MicroSD 和 wifi 都打开时,我的 DMM 读数下降到 3.2v。而且我相信瞬时最小电压远低于此值。

    我使用了 Arduino Pro Mini 8M 3.3v 的默认引导加载程序,并且 efuse 设置为 0x5。根据Atmega328datasheet,表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 卡。所以我使用了一个电池电压测量电路来确保程序在电压下降到一定水平以下时使用更少的电量。

    我在论坛上阅读了很多帖子和类似 John 的回答,受益匪浅。我希望我的 2 美分能给遇到同样问题的人带来一点帮助。

    【讨论】:

      【解决方案2】:

      8) 使用最新的 gcc 编译器 降低错误编译器优化的机会(Arduino gcc 4.3.2 问题)。

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

      【讨论】:

        【解决方案3】:

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

        1) 突然重置可能有多种原因。 您的 AVR 可能在过低电压下运行,降低频率或提高电压。检查数据表,他们有一个图表,他们对此很认真:)

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

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

        这个sn-p会写一大堆!进入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 函数(与程序相关的函数)有问题。
        将设备置于 5V 即可解决问题。这种智慧花了我无数小时的软件分析,我从字面上深入搜索了软件方面的每一个可能的原因。
        教训:如果您对微控制器进行编程,切勿将其视为纯软件 :)

        7) 对于高级内存损坏分析,您可以将堆栈设置为特定的预定义状态。这可以极大地帮助您进行调试,因为您可以观察变量在数据中增长的位置。
        此外,空终止的丢失会使您的指针运行到已知数据而不是未知数据。
        只需将 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 中写入模式 BABABABABABABABA。
        这对您的程序没有不良影响,它只是用已知模式初始化 sram。
        如果您在调试期间查看它,您将看到变量分配在哪里,哪里没有分配。
        运行良好,也可以写入init3。

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

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

        【讨论】:

        • +1 @John 非常感谢您的努力,这很有帮助。
        • “未知 IRQ”可能来自哪里?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-11
        • 2014-07-08
        • 1970-01-01
        相关资源
        最近更新 更多