1. Водная часть
Во всех микроконтроллерах (мк) есть такая полезная штука, как сторожевая псина (watchdog), призванная для одной цели: искать и уничтожать ребутать мк в случае его зависания. Если основная программа в каком-то месте не успела сбросить таймер watchdog, то ее ждет аварийный ребут. И если в любительских устройствах поговорка «семь бед ― один резет» еще допустима, то в случае более серьезного использования желательно узнать причину перезагрузки. Но ведь после перезагрузки все данные потеряны, искать больше нечего. Или нет?
2. Инструменты
В качестве среды разработки мы будем использовать IAR (IAR Embedded Workbench for Arm, version 8.50.6), в качестве подопытного мк ― stm32f030f4p6, в качестве тоника ― чай, кофе, шило в...
3. Ой, а кто это сдееелал?
Если верить мануалу, то при перезагрузке мк сбрасываются в дефолт все регистры, кроме RCC_CSR. По его состоянию можно определить, почему произошел ребут. Вотчдогу выделен бит IWDGRSTF, вот его и будем использовать:
... //инициализация переменных итд ... void main(void) { if (RCC->CSR & RCC_CSR_IWDGRSTF) { while(1) { lcd_str("Ошибка в программе", 0); //вывод сообщения на дисплей/в терминал/etc sleep(100); } } //whatchdog init - обратите внимание, что собаку инитим ПОСЛЕ сообщения об ошибке IWDG->KR = 0xCCCC; /* (1) */ IWDG->KR = 0x5555; /* (2) */ IWDG->PR = IWDG_PR_PR_1; /* (3) */ IWDG->RLR = 0xFFF; /* (4) */ while(IWDG->SR); /* (5) */ while (1) { //тут код программы }
Бит IWDGRSTF сбрасывается вручную, либо по питанию (просто замкнуть reset не прокатит). Это очень удобно — после аварийного ребута можно например корректно остановить все оборудование, которым управляет мк, вывести на экран сообщение об ошибке, включить сигнализацию и ждать ответного гудка решения оператора. Но вот оператор подошел, увидел неполадку, а дальше что? Максимум что он может сделать — это включить/выключить устройство из розетки и написать разработчику, что его поделка зависает. Это, конечно, лучше, чем просто бездумный ребут, но хотелось бы больше информации. И тут на помощь приходит... Оперативка!
Все дело в том, что ОЗУ при перезагрузке мк через watchdog практически остается нетронутой. Вот если бы можно было заранее записать туда полезную информацию (например, номер строки зависания), а при ребуте прочитать...
4. Память: краткий экскурс по секциям
При компиляции программы формируется объектный файл, который поделен на секции:
Строго говоря, секция bss физически в файле отсутствует, т.к. она инициализирована нулем, но это тема отдельной статьи, для упрощения примем следующую схему: код (.text) попадает во флеш-память, данные (.data) — тоже во флеш, но из расчета, что они будут доступны в оперативной памяти, .bss — в оперативную память.
5. Хачим конфиг линкера
Сначала идем в конфиг проекта и выбираем раздел Linker

Во вкладке Config надо нажать галку Override default, потом скопировать дефотный файл icf в папку с проектом и выбрать новый путь к файлу.
Далее создаем следующие файлы
extern unsigned int debugvar; // - explicit init
#include "separately_inited_vars.h" unsigned int debugvar=0; // - explicit init
/*###ICF### Section handled by ICF editor, don't touch! ****/ /*-Editor annotation file-*/ /* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */ /*-Specials-*/ define symbol __ICFEDIT_intvec_start__ = 0x08000000; /*-Memory Regions-*/ define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__ = 0x08003FFF; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__ = 0x20000FFF; /*-Sizes-*/ define symbol __ICFEDIT_size_cstack__ = 0x400; define symbol __ICFEDIT_size_heap__ = 0x400; /**** End of ICF editor section. ###ICF###*/ define memory mem with size = 4G; define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__]; define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { }; define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { }; initialize by copy { readwrite }; place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; /* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv CUT HERE vvvvvvvvvvvvvvvvvvvvvvvvvv */ place in ROM_region { readonly }; place in RAM_region { readwrite, block CSTACK, block HEAP }; /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CUT HERE ^^^^^^^^^^^^^^^^^^^^^^^^^^ */ export symbol __ICFEDIT_region_RAM_start__; export symbol __ICFEDIT_region_RAM_end__;
/*###ICF### Section handled by ICF editor, don't touch! ****/ /*-Editor annotation file-*/ /* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */ /*-Specials-*/ define symbol __ICFEDIT_intvec_start__ = 0x08000000; /*-Memory Regions-*/ define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__ = 0x08003FFF; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__ = 0x20000FFF; /*-Sizes-*/ define symbol __ICFEDIT_size_cstack__ = 0x400; define symbol __ICFEDIT_size_heap__ = 0x400; /**** End of ICF editor section. ###ICF###*/ define memory mem with size = 4G; define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__]; define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { }; define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { }; initialize by copy { readwrite }; place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; /* vvvvvvvvvvvvvvvvvvvvvvvvvvvvv PASTE HERE vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ do not initialize { section .bss object separately_inited_vars.o }; /* 1 */ initialize manually { section .data object separately_inited_vars.o }; /* 2 */ define block MYBLOCK { section .data object separately_inited_vars.o }; /* 3 */ place in ROM_region { readonly }; place in RAM_region { readwrite, block MYBLOCK, /* 4 */ block CSTACK, block HEAP }; /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PASTE HERE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ export symbol __ICFEDIT_region_RAM_start__; export symbol __ICFEDIT_region_RAM_end__;
Что тут происходит? Попробуем разобраться:
Далее прописываем в main.c следующую директиву:
#pragma section = "MYBLOCK"
Готово! Теперь напишем простейшую функцию:
void ram_debug(int line) { if (RCC->CSR & RCC_CSR_IWDGRSTF) return; //при аварийной перезагрузке не переназначать переменную debugvar debugvar = line; //иначе - вписать в переменную номер строки }
Как это работает. Вставляем функцию в критические участки кода:
...
ram_debug(__LINE__);
while ((USART1->ISR & USART_ISR_TC) != USART_ISR_TC); //line 156
...__LINE__ — встроенный макрос, который вставит номер строки из исходника программы. Таким образом, при аварийном ребуте мы получим сообщение, выполнение какой строки кода этому предшествовало. Конечно, нет 100% гарантий, что причиной была именно эта строка, но в любом случае это хорошее подспорье для разбора полетов. Поправим первоначальный код:
... //инициализация переменных итд ... void main(void) { if (RCC->CSR & RCC_CSR_IWDGRSTF) { sprintf(buf, "%03d", debugvar); while(1) { lcd_str("Ошибка в строке ", 0); lcd_str(buf, 64); lcd_str(" !!!", -1); ram_debug(__LINE__); sleep(100); } } ... //инит вотчдога и остальная программа
Результат:
