Почему atmega168/328p начинает перезагружаться?

Сначала мы устанавливаем фьюз-биты:

avrdude -c usbasp -p atmega328p -U lfuse:w:0xFF:m -U hfuse:w:0xDF:m -U efuse:w:0x07:m # same for atmega168

В следующих примерах мы используем следующие команды для компиляции и записи всех программ:

avr-gcc -Os -mmcu=atmega328p -c -o serial.o serial.c
avr-ld -o serial.elf serial.o
avr-objcopy -O ihex serial.elf serial.hex
avrdude -c usbasp -p atmega328p -U flash:w:serial.hex

Запишем следующую программу:

#define F_CPU 16000000UL
#include <avr/io.h>
void main (void) {
  DDRB |= (1<<PB5);
  PORTB |= (1<<PB5);
  while (1);
}

Светодиод горит постоянно. И так будет бесконечно долго.

Затем мы записываем следующую программу:

#define F_CPU 16000000UL
#define BAUD 9600
#include <avr/io.h>
char data = 0;
void main(void) {
  // Инициализировать USART:
  #include <util/setbaud.h> 
  UBRR0H = UBRRH_VALUE; // устанавливаем скорость (старший бит)
  UBRR0L = UBRRL_VALUE; // устанавливаем скорость (Младший бит)
  UCSR0B |= (1<<RXEN0); // включить приемник
  UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00); // установить режим (8N1)
  DDRB |= (1<<PB5); // включаем вывод на пин PB5 (светодиод по умолчанию на arduino)
  while(1) {
    while(!(UCSR0A&(1<<RXC0))); // ждем пока не будет получен байт
    data = UDR0; // прочитать его
    if (data) PORTB |= (1<<PB5); // включаем светодиод
  }
}

Затем открываем терминал (9600,8N1) и нажимаем несколько клавиш, пока не погаснет светодиод. Теперь мы снова записываем первую программу, и светодиод постоянно мигает. Причина этого в том, что время сторожевого таймера запускается, когда мы нажимаем клавиши в терминале. Но почему и когда запускается WDT? Как сделать так, чтобы он не запускался?

Также следует отметить, что отключить мигание мы можем только при полном выключении/включении питания - только после этого светодиод горит постоянно, не мигая, по мере необходимости.

Это происходит на atmega168 и atmega328p, воспроизводимость 100%.

avr-gcc версии 4.8.1 avrdude версии 6.1

ПРИМЕЧАНИЕ: загрузчик вообще не используется.

, 👍0

Обсуждение

void main(void) { #include <util/setbaud.h> Это привлекло мое внимание. Довольно необычно делать включение в код? Я не уверен, что это может вызвать проблемы., @Paul

Кроме того, я не уверен, как сторожевой таймер связан с этим вопросом? Вы можете отключить его фьюзбит?, @Paul

и ваш if (data) PORTB |= (1<<PB5); будет включать и выключать светодиод всякий раз, когда данные установлены на что-то. Вы должны сбросить значение? Несмотря на то, что while(!() может заблокировать это., @Paul


3 ответа


Лучший ответ:

1

Одна вещь, которую я обнаружил, экспериментируя с компиляцией командной строки, это то, что по какой-то странной причине код запуска не был связан с моим кодом, так что все пошло наперекосяк.

Наконец-то я обнаружил, что проблема заключается в отсутствующем флаге командной строки при связывании.

Во-первых, вам нужно связать с avr-gcc, а не с avr-ld. Во-вторых, вам необходимо передать флаг -mmcu=atmega328p в команду связывания, чтобы она знала, какой код запуска следует связать.

Это моя полная последовательность командной строки для компиляции простой программы мигания:

avr-gcc -g -mmcu=atmega328p -ffunction-sections -fdata-sections   -c -o blink.o blink.c
avr-gcc -mmcu=atmega328p -Wl,--gc-sections -o blink.elf blink.o 
avr-objdump -S blink.elf > blink.dis
avr-objcopy -O ihex -R .eeprom blink.elf blink.hex

Вы заметили, что я также создаю разделы функций и данных, а затем собираю в мусор неиспользуемые разделы — хорошая практика, так как это может очень сильно уменьшить размер вашей готовой программы.

Я делаю это через Makefile, который выглядит так:

PREFIX=avr-
CC=${PREFIX}gcc
CXX=${PREFIX}g++
LD=${PREFIX}ld
AS=${PREFIX}as
OBJCOPY=${PREFIX}objcopy
OBJDUMP=${PREFIX}objdump

BIN=blink
MCU=atmega328p
OBJS=blink.o

CFLAGS=-g -mmcu=${MCU} -ffunction-sections -fdata-sections
CXXFLAGS=-g -mmcu=${MCU} -ffunction-sections -fdata-sections -fno-exceptions
LDFLAGS=-mmcu=${MCU} -Wl,--gc-sections

${BIN}.hex: ${BIN}.elf
    ${OBJCOPY} -O ihex -R .eeprom $< $@

${BIN}.elf: ${OBJS}
    ${CC} ${LDFLAGS} -o $@ $? 
    ${OBJDUMP} -S $@ > ${BIN}.dis

install: ${BIN}.hex
    avrdude -c usbasp -p ${MCU} -U flash:w:${BIN}.hex -qq

clean:
    rm -f *.o *.elf *.hex

fuses:
    avrdude  -c usbasp -p ${MCU} -U lfuse:w:0xff:m -U hfuse:w:0xd6:m -U efuse:w:0x05:m -qq
,

Нет, как я уже сказал в своем ответе на EE, это не имеет ничего общего с загрузчиком или WDT. Вы, кажется, не понимаете принципиально, как все это дело работает..., @Kurt E. Clothier


1

Настройки WDT останутся неизменными даже после сброса. Таким образом, таймер будет продолжать работать и, следовательно, продолжать сбрасывать устройство. Что вызывает мерцание. Выключение и включение питания очистит настройки WDT.

PS Я не читал код, так как незакомментированные записи в регистр практически нечитаемы.

,

1

Код проблемы был сокращен до этого:

#define F_CPU 16000000UL
#include <avr/io.h>
char data;
void main(void) {
  UBRR0H = 0x00;
  UBRR0L = 0x67; // baud=9600
  UCSR0B = 0x10; // 00010000
  UCSR0C = 0x06; // 00000110
  DDRB |= (1<<PB5);
  PORTB |= (1<<PB5);
  while(1) {
    data = UDR0;
  }
}

Это код, который работает:

#define F_CPU 16000000UL
#include <avr/io.h>
void main(void) {
  char data;
  UBRR0H = 0x00;
  UBRR0L = 0x67; // baud=9600
  UCSR0B = 0x10; // 00010000
  UCSR0C = 0x06; // 00000110
  DDRB |= (1<<PB5);
  PORTB |= (1<<PB5);
  while(1) {
    data = UDR0;
  }
}

Но я не знаю, как объяснить, что char внутри main устраняет проблему...

ИЗМЕНИТЬ Просто для протокола, вот еще два примера. Первый пример работает с обоими методами компиляции. Второй пример работает только со вторым методом компиляции. Примеры отличаются только тем, что в первом используется прямой код, а во втором — через функции.

#define F_CPU 16000000UL
#define BAUD 9600

#include <avr/io.h>
void main(void) {
  char data;
  #include <util/setbaud.h> 
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;
  UCSR0B = (1<<RXEN0)|(1<<TXEN0);
  UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);
  DDRB = (1<<PB5);
  while (1) {
    PORTB ^= (1<<PB5);
    while(!(UCSR0A&(1<<RXC0)));
    data = UDR0;
    UDR0 = data;
    while(!(UCSR0A&(1<<TXC0)));
  }
}
#define F_CPU 16000000UL
#define BAUD 9600

#include <avr/io.h>
void serialSetup(void) {
  #include <util/setbaud.h> 
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;
  UCSR0B = (1<<RXEN0)|(1<<TXEN0);
  UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);
}
void serialSend(char data) {
  UDR0 = data;
  while(!(UCSR0A&(1<<TXC0)));
}
char serialGet(void) {
  while(!(UCSR0A&(1<<RXC0)));
  return UDR0;
}
void main(void) {
  char data;
  serialSetup();
  DDRB = (1<<PB5);
  while (1) {
    PORTB ^= (1<<PB5);
    data = serialGet();
    serialSend(data);
  }
}
,

Это может быть связано со странной вещью, которую я видел при ручной компиляции, которую я просто не мог понять - казалось, что crt0 не был связан, за исключением случаев, когда у вас была глобальная переменная, которая вызывала только часть BSS crt. существовать. Если я вдруг добавлял в программу другую функцию, эта функция становилась точкой входа вместо main(). Все было перепутано и полностью расплавило мозг. И все же я вызывал те же параметры компилятора и командной строки, что и при компиляции API Arduino. Это не имело никакого смысла., @Majenko

На самом деле я только что решил свою проблему - и, вероятно, решит и вашу. Я опубликую ответ с подробностями., @Majenko

@Majenko: Да, это решило проблему!, @Igor Liferenko

@Majenko: Но я не понял, почему он самопроизвольно начинает перезагружаться, и что делают ваши команды, чтобы это исправить. Не могли бы вы дать несколько рекомендаций для дальнейшего чтения, чтобы понять, что вызывает проблему и как ее вылечить?, @Igor Liferenko

С WDT, как только он начал стрелять, его почти невозможно отключить. Вы можете отключить его только в «холодном» состоянии, чтобы он никогда не выстрелил. Кроме того, без надлежащего кода запуска (crt0) можно только догадываться, что на самом деле произойдет с программой при ее запуске - переменные не будут инициализированы, векторы исключений не будут настроены и т. д. и т. д., @Majenko