Arduino, watchdog, bootloop и прошивка загрузчика optiboot

Watchdog

Watchdog

Как вам может быть известно, Arduino (а вернее, микроконтроллер на плате) поддерживает watchdog (сторожевой таймер) который позволяет установить промежуток времени, через который микроконтроллер будет перезагружен, даже если программа зависнет. При этом, сторожевой таймер может быть сброшен программно. Схема работы примерно следующая: «взодим» таймер на некоторое количество секунд, а далее в основной программе постоянно обнуляем счётчик, если всё идёт нормально. Как только мы перестанем обнулять счётчик (а это и может означать зависание), контроллер автоматически перезагрузится по истечению заданного времени. Однако, работа с watchdog должна поддерживаться загрузчиком платы. Дело в том, что после перезагрузки, которая была вызвана watchdog, контроллеры последних выпусков оставляют включенным сторожевой таймер на минимальный период, т.е. 15ms. Это нужно для того, чтобы была возможность внутри программы узнать, что перезагрузка была по watchdog.Поэтому первоочередная задача загрузчика — сохранить информацию о том, что перезагрузка была «неожиданной» и сразу же выключить сторожа. Если этого не сделать, то система уйдет в bootloop, т.е. будет вечно перегружаться. Как известно, в Arduino есть специальный загрузчик, который выполняется в первую очередь после перезагрузки системы. И, к огромному сожалению, стандартный загрузчик не сбрасывает watchdog! Таким образом, система заходит в жестокий bootloop (состояние «crazy led», при котором светодиод на 13-м пине мигает как сумасшедший). Из статьи вы узнаете, как проверить загрузчик своей платы на поддержку watchdog и что делать, если он не поддерживается.

Проверка загрузчика

Для проверки загрузчика следует загрузить в плату следующий скетч:

#include <avr/wdt.h>

void setup() {
  wdt_disable(); // бесполезная строка до которой не доходит выполнение при bootloop
  Serial.begin(9600);
  Serial.println("Setup..");
  
  Serial.println("Wait 5 sec..");
  delay(5000); // Задержка, чтобы было время перепрошить устройство в случае bootloop
  wdt_enable (WDTO_8S); // Для тестов не рекомендуется устанавливать значение менее 8 сек.
  Serial.println("Watchdog enabled.");
}

int timer = 0;

void loop(){
  // Каждую секунду мигаем светодиодом и значение счетчика пишем в Serial
  if(!(millis()%1000)){
    timer++;
    Serial.println(timer);
    digitalWrite(13, digitalRead(13)==1?0:1); delay(1);
  }
//  wdt_reset();
}

После подачи питания встроенный светодиод мигнет, сигнализируя о том, что запустился загрузчик. Далее в секции setup происходит включение watchdog с таймером на 8 сек. Через 8 минут работы должна произойти перезагрузка. Если после перезагрузки платы весь цикл повторяется, то ваша плата поддерживает watchdog:

Arduino не подвержена bootloop

Arduino не подвержена bootloop

В противном случае вы увидите, как светодиод на 13 пине начнёт лихорадочно мигать. Это как раз и есть внешнее проявление bootloop. В мониторе порта вы увидите только первый цикл. После него новые строки выводиться перестанут:

Arduino подвержена bootloop

Arduino подвержена bootloop

Нажатие кнопки reset не изменить ситуации. Для «лечения» платы, её нужно отключить от питания, затем подключить снова и залить любой другой скетч.

Watchdog не поддерживается. Что делать?

Рекомендуется использовать загрузчики из пакета optiboot. В принципе, эти загрузчики идут в инсталляции самой платформы Arduino, но лучше скачать и установить последнюю версию optiboot с сайта разработчиков: https://github.com/Optiboot/optiboot или копию с моего сайта: https://uscr.ru/share/optiboot.zip

После прошивки загрузчика optiboot вы уже не сможете загружать в плату скетчи со стандартными настройками среды. Поэтому, рекомендую все последующие действия производить на копии среды разработки. В ОС linux для этого достаточно скопировать каталог, из которого запускается среда разработки с новым именем. В ОС Windows, кажется, тоже достаточно просто скопировать папку (по умолчанию C:\Program Files (x86)\Arduino). Далее все пути к папкам и файлам указаны относительно корневого каталога со средой разработки.

Папку optiboot-master/optiboot/bootloaders/optiboot из архива нужно переместить в hardware/arduino/avr/bootloaders/optiboot

Содержимое файла optiboot-master/optiboot/boards.txt дописать в конец файла hardware/arduino/avr/boards.txt

После (пере)запуска среды разработки, в списке плат появятся новые платы с пометкой [optiboot].
Для прошивки загрузчика вам понадобится 2 платы Arduino. Одна из них будет использоваться в качестве программатора. Теоретически, в качестве программатора подходит любая плата. Но у меня получилось заставить работать только Arduino Uno. Ни Nano, ни leonardo работать программатором не захотели. Если Arduino Uno у вас нет, купить её у проверенного мной продавца можно здесь.

Подключите плату-программатор к компьютеру и залейте в неё скетч Arduino ISP, который входит в стандартную поставку среды разработки (Файл->Arduino ISP->Arduino ISP).

Прошивка скетча Arduino ISP

Прошивка скетча Arduino ISP

Теперь можно временно отключить нашу плату-программатор от компьютера. И соединить её с платой, в которую хотим записать загрузчик. В моём случае, это будет Arduino Nano. Прошивка производится через отдельный разъем ICSP, который есть почти на всех платах. MISO соединяется с MISO, MOSI с MOSI и SCK с SCK. При этом подключать ICSP целевой платы нужно к обычным выводам платы-программатора. Это довольно странно, но соединение ICSP-ICSP не работает. Хотя контакты там физически соединены друг с другом. Соединение нужно выполнить вот по такой схеме:

Номер вывода на плате-программаторе Вывод ISCP разъёма
10 RST (Reset)
11 MOSI
12 MISO
13 SCK

Вот распиновка ICSP разъёма (плата ориентирована USB разъёмом вправо):

ICSP разъём Arduino

ICSP разъём Arduino

Дополнительно, необходимо на плате-программаторе установить электролитический конденсатор ёмкостью не менее 10мкФ(uF) между выводом Reset и GND, для препятствования автоматическому сбросу программатора при начале прошивки. Не забудьте подключить питание к целевой плате.

Ещё можно установить светодиоды (через резистор), с помощью которых программатор сигнализирует об ошибках:

Номер вывода на плате-программаторе Название Описание
9 Heartbeat Плавно меняет яркость сигнализируя о нормальной работе
8 Error Ошибка во время прошивки
7 Programming Мигает во время прошивки

Вот так всё это должно выглядеть в итоге:

Программатор подключён к Arduino Nano

Программатор подключён к Arduino Nano

На схеме не показаны резисторы для светодиодов, что бы не загромождать иллюстрацию. Если вы не знаете, зачем светодиодам нужны резисторы, то я вам целую статью написал: Светодиод .

В случае прошивки Arduino Pro Mini, у которого нет IСSP, подключение производится непосредственно к выводам:

Номер вывода на плате-программаторе Вывод Arduino Mini
11 11
12 12
13 13

После подключения целевой платы к программатору, нужно подключить программатор к компьютеру, в качестве программатора выбрать «Arduino as ISP»:

Выбор программатора

Выбор программатора

А в качестве платы для прошивки указать целевую плату, а не программатор. Т.Е. в моём случае, в качестве платы выбираю Arduino Nano:

Выбор платы для прошивки

Выбор платы для прошивки

Теперь достаточно выбрать пункт «Инструменты»->»Записать загрузчик»:

Запись загрузчика в плату

Запись загрузчика в плату

После записи загрузчика нужно ещё исправить конфиг. Загрузчики optiboot имеют еще одну особенность — они увеличивают скорость взаимодействия по последовательному порту, поэтому при использовании плат с optiboot нужно внести соответствующие изменения в boards.txt:

Для Arduino Nano:
menu.cpu.nano.atmega328.upload.speed=115200
Для Arduino Mini:
menu.cpu.mini.atmega328.upload.speed=115200

С такими настройками вы уже не сможете заливать скетчи в платы с оригинальным загрузчиком. Поэтому, вам нужно либо прошить все свои платы под optiboot, либо иметь копию среды разработки с изменёнными настройками. Поэтому в начале статьи я и предлагал создать копию среды.

После всех манипуляций подключаем целевую плату к компьютеру и вновь заливаем проверочный скетч из начала статьи. Всё должно работать.

Возможные проблемы

Процесс прошивки может не стартовать и выдаваться ошибка:

avrdude: stk500_getsync(): not in sync: resp=0x00

Для решения этой проблемы откройте скетч программатора и в секции setup выберите другую скорость последовательного порта.
Во время заливки в Arduino Mega может появляться ошибка, которую следует игнорировать:

avrdude: verification error, first mismatch at byte 0x3e000
                               0x0d != 0xff
                      avrdude: verification error; content mismatch 

При возникновении других ошибок с платами optiboot, можно поступить проще: в файле hardware/arduino/avr/boards.txt заменить следующие строчки:

Для Arduino Nano:
menu.cpu.nano.atmega328.bootloader.file=optiboot/optiboot_atmega328.hex

Для Arduino Mini:
menu.cpu.mini.atmega328.bootloader.file=optiboot/optiboot_atmega328.hex

Ну и для любой другой платы по аналогии. Кроме Arduino Mega. В Mega больше памяти и используется другой протокол. Поэтому мы используем стандартный, но модифицированный загрузчик, который качаем отсюда: https://github.com/arduino/Arduino-stk500v2-bootloader/tree/master/goodHexFiles или копию с моего сайта: https://uscr.ru/share/stk500boot_v2_mega2560.hex. Файл переименовываем в stk500boot_v2_mega2560_2.hex и перемещаем в hardware\arduino\avr\bootloaders\stk500v2.

Далее меняем в уже знакомом файле boards.txt следующую строчку:
mega2560.bootloader.file=stk500v2/stk500boot_v2_mega2560_2.hex

После этих изменений можно прошивать загрузчики, выбирая в меню обычные платы (не [optiboot]!). В этом случае прошиваться будут именно те файлы hex, которые мы указали в файле board.txt.

Статья основана на следующих материалах, в которых затронутые темы рассмотрены более подробно:

http://inet-deal.mpa.ru/articles/arduino-003.html
https://geektimes.ru/post/255800/
https://habr.com/post/144620/