AVR ВАЧДОГ И EEPROM

Был такой прикол, я решил представить начальству одно из своих изобретений, которое до этого прекрасно работало, и вот, как только начальник немного на него посмотрел и потыкал кнопки, управляющий микроконтроллер к хуям собачьим завис. Естественно, я сразу же принялся искать решение подобных проблем и пришел к осознанию необходимости, помимо оптимизации кода, внедрения во всякое промышленное ПО механизма дампа основных переменных, которые отвечают за работу производимого нами оборудования. Как мне видится, задачу можно разделить на несколько подпунктов:

  1. Организация записи дампа в энергонезависимую память. Отсюда идут свои разветвления, одно из которых довольно существенно. AVR микроконтроллеры имеют на борту EEPROM память, которая отличается ограниченным ресурсом перезаписи - всего от 10000 до 100000 итераций. Поэтому необходимо либо использовать какие-то внешние чипы, либо оптимизировать условия записи дампа. Например, делать его не по 60 раз в миллисекунду, а по каким-то определенным причинам. Но это отдельная история, в рамках данной статьи я ее рассматривать не буду.
  2. Настройка самого Вачдога и условий срабатывания.

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

#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 и уходил в циклическую перезагрузку.