Неожиданный результат чтения глобальной переменной в C++ с использованием avr-gcc for (доступ к локальной переменной соответствует ожидаемому)

Я получаю неожиданные результаты чтения глобальной переменной при компиляции следующего кода в avr-gcc 4.6.2 для ATmega328:

#include <avr/io.h>
#include <util/delay.h>

#define LED_PORT            PORTD
#define LED_BIT             7
#define LED_DDR             DDRD

uint8_t latchingFlag;

int main() {
    LED_DDR = 0xFF;
    for (;;) {
        latchingFlag=1;
        if (latchingFlag==0) {
            LED_PORT ^= 1<<LED_BIT; // Toggle the LED
            _delay_ms(100);         // Delay
            latchingFlag = 1;
        }
    }
}

Это весь код. Я ожидаю, что переключение светодиода никогда не будет выполняться, поскольку latchingFlag установлено на 1, однако светодиод постоянно мигает. Если latchingFlag объявлен локальным для main(), программа выполняется так, как ожидалось: светодиод никогда не мигает.

Дизассемблированный код не обнаруживает каких-либо ошибок, которые я вижу, вот разборка основного цикла версии с использованием глобальной переменной (с закомментированным вызовом процедуры задержки; такое же поведение)

  59                .L4:
  27:main.cpp      ****     for (;;) {
  60                    .loc 1 27 0
  61 0026 0000              nop
  62                .L3:
  28:main.cpp      ****         latchingFlag=1;
  63                    .loc 1 28 0
  64 0028 81E0              ldi r24,lo8(1)
  65 002a 8093 0000         sts latchingFlag,r24
  29:main.cpp      ****         if (latchingFlag==0) {
  66                    .loc 1 29 0
  67 002e 8091 0000         lds r24,latchingFlag
  68 0032 8823              tst r24
  69 0034 01F4              brne .L4
  30:main.cpp      ****             LED_PORT ^= 1<<LED_BIT; // Toggle the LED
  70                    .loc 1 30 0
  71 0036 8BE2              ldi r24,lo8(43)
  72 0038 90E0              ldi r25,hi8(43)
  73 003a 2BE2              ldi r18,lo8(43)
  74 003c 30E0              ldi r19,hi8(43)
  75 003e F901              movw r30,r18
  76 0040 3081              ld r19,Z
  77 0042 20E8              ldi r18,lo8(-128)
  78 0044 2327              eor r18,r19
  79 0046 FC01              movw r30,r24
  80 0048 2083              st Z,r18
  31:main.cpp      ****             latchingFlag = 1;
  81                    .loc 1 31 0
  82 004a 81E0              ldi r24,lo8(1)
  83 004c 8093 0000         sts latchingFlag,r24
  27:main.cpp      ****     for (;;) {
  84                    .loc 1 27 0
  85 0050 00C0              rjmp .L4

Строки 71-80 отвечают за доступ к порту: согласно даташиту, PORTD находится по адресу 0x2B, который является десятичным 43 (ср. строки 71-74).

Единственная разница между локальным/глобальным объявлением переменной latchingFlag заключается в том, как осуществляется доступ к latchingFlag: версия с глобальной переменной использует sts (сохранение непосредственно в пространстве данных) и lds (загрузка непосредственно из пространства данных) для доступа к latchingFlag, тогда как версия с локальной переменной использует ldd (Косвенная загрузка из пространства данных в регистр) и std (Сохранение косвенного из регистра в пространство данных) с использованием регистра Y в качестве адресного регистра (который может использоваться в качестве указателя стека, avr-gcc AFAIK). Вот соответствующие строки из разборки:

  63 002c 8983              std Y+1,r24

  65 002e 8981              ldd r24,Y+1

  81 004a 8983              std Y+1,r24

Глобальная версия также имеет latchingFlag в разделе .bss. Мне действительно не к чему приписывать различное поведение глобальных и локальных переменных. Вот командная строка avr-gcc (обратите внимание -O0):

/usr/local/avr/bin/avr-gcc \
    -I. -g -mmcu=atmega328p -O0 \
    -fpack-struct \                                                 
    -fshort-enums \                                         
    -funsigned-bitfields \                                        
    -funsigned-char \                                                 
    -D CLOCK_SRC=8000000UL \
    -D CLOCK_PRESCALE=8UL \
    -D F_CPU="(CLOCK_SRC/CLOCK_PRESCALE)" \
    -Wall \
    -ffunction-sections \
    -fdata-sections \
    -fno-exceptions \
    -Wa,-ahlms=obj/main.lst \
    -Wno-uninitialized \
    -c main.cpp -o obj/main.o

С флагами компилятора -Os цикл исчезает из разборки, но может быть вынужден снова появиться, если latchingFlag объявлен volatile, и в этом случае неожиданное для меня сохраняется.


person angelatlarge    schedule 16.04.2013    source источник
comment
Возможно, глобальная память периодически очищается ошибочным кодом в другой части программы.   -  person Egor Skriptunoff    schedule 16.04.2013
comment
@EgorSkriptunoff Как вы думаете, какая еще часть программы может это делать? Я выложил весь исходный код.   -  person angelatlarge    schedule 16.04.2013
comment
Когда я добавляю -Werror -O0 в командную строку, компилятор выдает ошибку: functions from <util/delay.h> won't work as designed" [-Werror=cpp]. Когда я меняю его на -Werror -Os, ошибка исчезает.   -  person jippie    schedule 16.04.2013
comment
Да, функции задержки зависят от -O != 0. Непредвиденное поведение возникает независимо от того, используются функции задержки или нет, и поведение, описанное выше, одинаково независимо от того, является ли util/delay.h #included или нет.   -  person angelatlarge    schedule 16.04.2013
comment
Где _delay_ms(100) строки в вашем листинге дизассемблера?   -  person Egor Skriptunoff    schedule 16.04.2013
comment
@EgorSkriptunoff Как отмечалось в вопросе, вызов _delay_ms(100) был удален при дизассемблировании. Поведение идентично независимо от того, сделан ли вызов _delay_ms() или нет.   -  person angelatlarge    schedule 17.04.2013


Ответы (3)


Согласно листингу вашего дизассемблера, глобальная переменная latchingFlag расположена по адресу ОЗУ 0. Этот адрес соответствует зеркальному регистру r0 и не является действительным адресом ОЗУ для глобальной переменной.

person Egor Skriptunoff    schedule 16.04.2013
comment
Я не уверен, что вижу, где именно вы видите это, но идея может быть на правильном пути. Используя avr-objdump для дизассемблирования кода я вижу lds r24, 0x0060. 0x0060 сопоставляется с WDTCSR на ATmega328, но WDTCSR не должно читаться как 0, так как WDTON не запрограммировано, и поэтому должно читаться как 1. Или я что-то упускаю? - person angelatlarge; 17.04.2013

После пары проверок и сравнения кода в чате EE я заметил, что моя версия avr-gcc (4.7.0) сохраняет значение для latchFlag в 0x0100, тогда как Егор Скриптунов упомянул, что адрес SRAM 0 находится в списке сборки OP.

Глядя на разборку OP (версия avr-dump), я заметил, что компилятор OP (4.6.2) хранит значение latchFlag по другому адресу (в частности, 0x060), чем мой компилятор (версия 4.7.0), который хранит значение latchFlag по адресу 0x0100.

Мой совет: обновите версию avr-gcc как минимум до версии 4.7.0. Преимущество версии 4.7.0 по сравнению с последней и самой доступной состоит в возможности снова сравнить сгенерированный код с моими выводами.

Конечно, если 4.7.0 решает проблему, то переход на более новую версию (если она доступна) вреден.

person jippie    schedule 16.04.2013
comment
Ну, дело не в компиляторе как таковом: при использовании 4.7.3 на cygwin я получил latchingFlag при 0x060, а при использовании 4.7.0 в Ubuntu я получил 0x100. Только что отправил это в список avr-gcc. - person angelatlarge; 18.04.2013

Предложение Egor Skriptunoff почти точно верно: переменная SRAM сопоставлена ​​с неправильным адресом памяти. Переменная latchingFlag находится не по адресу 0x0100, который является первым действительным адресом SRAM, а отображается на 0x060, перекрывая регистр WDTCSR. Это можно увидеть в линиях разборки, подобных следующей:

lds r24, 0x0060

Предполагается, что эта строка загружает значение latchingFlag из SRAM, и мы видим, что вместо 0x100 используется адрес 0x060.

Проблема связана с ошибкой в ​​binutils, при которой выполняются два условия:

При выполнении обоих этих условий раздел .data удаляется. Когда раздел .data отсутствует, переменные SRAM начинаются с адреса 0x060 вместо 0x100.

Одним из решений является переустановка binutils: в текущих версиях эта ошибка исправлена. Другое решение — отредактировать ваши скрипты компоновщика: в Ubuntu это, вероятно, находится в /usr/lib/ldscripts. Для ATmega168/328 скрипт, который необходимо отредактировать, это avr5.x, но вам действительно следует отредактировать их все, иначе вы можете столкнуться с этой ошибкой на других платформах AVR. Необходимо внести следующие изменения:

   .data   : AT (ADDR (.text) + SIZEOF (.text))
   {
      PROVIDE (__data_start = .) ;
-     *(.data)
+     KEEP(*(.data))

Поэтому замените строку *(.data) на KEEP(*(.data)). Это гарантирует, что раздел .data не будет отброшен, и, следовательно, адреса переменных SRAM начинаются с 0x0100.

person angelatlarge    schedule 18.04.2013