AVR ВАЧДОГ И EEPROM
Был такой прикол, я решил представить начальству одно из своих изобретений, которое до этого прекрасно работало, и вот, как только начальник немного на него посмотрел и потыкал кнопки, управляющий микроконтроллер к хуям собачьим завис. Естественно, я сразу же принялся искать решение подобных проблем и пришел к осознанию необходимости, помимо оптимизации кода, внедрения во всякое промышленное ПО механизма дампа основных переменных, которые отвечают за работу производимого нами оборудования. Как мне видится, задачу можно разделить на несколько подпунктов:
- Организация записи дампа в энергонезависимую память. Отсюда идут свои разветвления, одно из которых довольно существенно. AVR микроконтроллеры имеют на борту EEPROM память, которая отличается ограниченным ресурсом перезаписи - всего от 10000 до 100000 итераций. Поэтому необходимо либо использовать какие-то внешние чипы, либо оптимизировать условия записи дампа. Например, делать его не по 60 раз в миллисекунду, а по каким-то определенным причинам. Но это отдельная история, в рамках данной статьи я ее рассматривать не буду.
- Настройка самого Вачдога и условий срабатывания.
Ну вот, базовый пример, который я теперь использую в качестве основы для своих проектов:
#define F_CPU 16000000UL // Частота микроконтроллера #include <avr/io.h> #include <avr/eeprom.h> #include <avr/interrupt.h> #include <avr/wdt.h> // Для работы с Watchdog #include <stdlib.h> // для utoa #include <util/delay.h> // Константы для UART #define BAUD 9600 #define MYUBRR F_CPU/16/BAUD-1 #define EEPROM_SIGNATURE 0xA5A5 char* utoa(unsigned int value, char* str, int base) { char* rc; char* ptr; char* low; static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; if (base < 2 || base > 36) { *str = '\0'; return str; } rc = ptr = str; low = ptr; do { *ptr++ = digits[value % base]; value /= base; } while (value); *ptr-- = '\0'; while (low < ptr) { char tmp = *low; *low++ = *ptr; *ptr-- = tmp; } return rc; } // Определение структуры для хранения данных struct EEPROMData { uint16_t signature; uint16_t resetCount; uint16_t num; uint16_t chDelay; uint16_t heatInCamera; uint16_t componentP; uint16_t componentI; uint16_t componentD; uint16_t Airflow; uint16_t iteration; }; // Переменная для хранения данных в EEPROM struct EEPROMData EEMEM ee_data; // Переменные в оперативной памяти volatile uint16_t resetCount = 0; volatile uint16_t num = 8888; volatile uint16_t chDelay = 15; volatile uint16_t heatInCamera = 150; volatile uint16_t componentP = 10; volatile uint16_t componentI = 10; volatile uint16_t componentD = 10; volatile uint16_t Airflow = 0; volatile uint16_t iteration = 0; // Служебные массивы char iterationStr[10]; char numStr[6]; char heatStr[6]; char chDelayStr[6]; char cPstr[6]; char cIstr[6]; char cDstr[6]; char AirflowStr[6]; char resetCountStr[12]; // Функция для инициализации UART void UART_init(unsigned int ubrr) { // Установка скорости передачи UBRR0H = (unsigned char)(ubrr >> 8); UBRR0L = (unsigned char)ubrr; // Включение передатчика UCSR0B = (1 << TXEN0); // Установка формата кадра: 8 бит данных, 1 стоп-бит UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); } // Функция для отправки одного символа через UART void uart_send_char(char c) { // Ожидание освобождения буфера while (!(UCSR0A & (1 << UDRE0))); // Запись данных в буфер, инициирующая передачу UDR0 = c; } // Функция для отправки строки через UART void uart_send_string(const char* str) { while (*str) { uart_send_char(*str++); } } // Функция для отправки данных из EEPROM через UART void sendEEPROMData() { utoa(resetCount, resetCountStr, 10); uart_send_string("resetCount = "); uart_send_string(resetCountStr); uart_send_string("\r\n"); utoa(num, numStr, 10); uart_send_string("num = "); uart_send_string(numStr); uart_send_string("\r\n"); utoa(chDelay, chDelayStr, 10); uart_send_string("chDelay = "); uart_send_string(chDelayStr); uart_send_string("\r\n"); utoa(heatInCamera, heatStr, 10); uart_send_string("heatInCamera = "); uart_send_string(heatStr); uart_send_string("\r\n"); utoa(componentP, cPstr, 10); uart_send_string("componentP = "); uart_send_string(cPstr); uart_send_string("\r\n"); utoa(componentI, cIstr, 10); uart_send_string("componentI = "); uart_send_string(cIstr); uart_send_string("\r\n"); utoa(componentD, cDstr, 10); uart_send_string("componentD = "); uart_send_string(cDstr); uart_send_string("\r\n"); utoa(Airflow, AirflowStr, 10); uart_send_string("Airflow = "); uart_send_string(AirflowStr); uart_send_string("\r\n"); utoa(iteration, iterationStr, 10); uart_send_string("iteration = "); uart_send_string(iterationStr); uart_send_string("\r\n"); } // Функция для записи данных в EEPROM void writeToEEPROM() { struct EEPROMData data = { EEPROM_SIGNATURE, resetCount, num, chDelay, heatInCamera, componentP, componentI, componentD, Airflow, iteration }; eeprom_update_block((const void*)&data, (void*)&ee_data, sizeof(data)); } // Функция для чтения данных из EEPROM void readFromEEPROM() { struct EEPROMData data; eeprom_read_block((void*)&data, (const void*)&ee_data, sizeof(data)); if (data.signature == EEPROM_SIGNATURE) { resetCount = data.resetCount; num = data.num; chDelay = data.chDelay; heatInCamera = data.heatInCamera; componentP = data.componentP; componentI = data.componentI; componentD = data.componentD; Airflow = data.Airflow; iteration = data.iteration; } else { // Если сигнатура не совпадает, инициализируем EEPROM writeToEEPROM(); } } // Функция для инициализации Watchdog void WDT_init() { // Устанавливаем значение тайм-аута WDT на 2 секунды wdt_enable(WDTO_2S); } int main(void) { // Инициализация cli(); // Отключение глобальных прерываний // Инициализация UART UART_init(MYUBRR); // Инициализация Watchdog WDT_init(); // Чтение данных из EEPROM при старте readFromEEPROM(); // Увеличение счетчика перезагрузок resetCount++; // Запись данных в EEPROM writeToEEPROM(); uint8_t resetReason = MCUSR; // Считываем причину последней перезагрузки MCUSR = 0; // Очищаем регистр MCUSR Кстати, не уверен, что его необходимо очищать. uart_send_string("\r\nmadmentat.ru controller setup initiated\r\n"); uart_send_string("Reason for the last reset was: "); // Проверяем причину перезагрузки if (resetReason & (1 << WDRF)) { // Аварийная перезагрузка (Watchdog) uart_send_string("Watchdog emergency reboot. \r\n"); } else if (resetReason & (1 << EXTRF)) { // Штатная перезагрузка (Reset button) uart_send_string("Hardware button reboot\r\n"); } else if (resetReason & (1 << PORF)) { // Перезагрузка по питанию uart_send_string("Power-supply shutdown.\r\n"); } else if (resetReason & (1 << BORF)) { // Перезагрузка по низкому напряжению uart_send_string("Brown-out Reset\r\n"); } else { // Неизвестная причина перезагрузки uart_send_string("Unknown Reset\r\n"); } // Отправка данных из EEPROM через UART sendEEPROMData(); // Включение глобальных прерываний sei(); // Основной цикл while (1) { // Ваш код тут // Сброс Watchdog таймера wdt_reset(); // Пример задержки (здесь можно добавить ваш основной код) _delay_ms(100); } return 0; }
Ну, подробно объяснять, что тут происходит, я никому ничего не буду, так как мне очень сильно лень. Отмечу лишь один прикол, над которым проломал голову пару часов рабочего времени. Если у вас сложная программа с инициацией в setup() всяких там интерфейсов типа UART, I2C и прочее, то сам Вачдог лучше всего проинициировать в начале. Иначе он, сука, глючит. В моем случае микроконтроллер выдавал частично какое-то приветствие через UART и уходил в циклическую перезагрузку.