AVR ВАЧДОГ И FRAM
Контроллер с диагностикой перезагрузок и энергонезависимой памятью FRAM
В этой статье представлен пример прошивки для AVR-микроконтроллера (например, ATmega328P), которая сохраняет своё состояние между перезагрузками и умеет выводить через UART подробную информацию о причине последнего сброса. Также используется энергонезависимая память FRAM, значительно превосходящая EEPROM по скорости и ресурсу перезаписи.
Подробнее о микросхеме FRAM FM24C04B-GTR читайте в ЗДЕСЬ.
🔧 Основные возможности
- Сохранение критически важных переменных в энергонезависимую память
- Диагностика причины перезагрузки (питание, WDT, кнопка и т.д.)
- Автоматический счётчик количества перезапусков
- Вывод всех параметров в UART (9600 бод)
- Поддержка Watchdog Timer для защиты от зависаний
📦 Что сохраняется в FRAM?
Создаётся структура FRAMData
, которая содержит такие поля:
Поле | Назначение |
---|---|
signature | Сигнатура 0xA5A5 для проверки корректности |
resetCount | Сколько раз микроконтроллер был перезапущен |
num | Рабочее значение (например, температура, скорость и т.д.) |
chDelay | Задержка переключения |
heatInCamera | Температура внутри камеры |
componentP, I, D | Параметры PID-регулятора |
Airflow | Скорость воздушного потока |
iteration | Общий счётчик итераций |
💡 Почему именно FRAM?
Обычная EEPROM — это не самое надёжное решение для часто изменяемых параметров:
- Низкая скорость записи (нужны задержки)
- Ограниченное количество циклов (до 100 тыс)
FRAM (например, FM24C04):
- Пишет данные так же быстро, как читает — без задержек
- Ресурс ~1014 циклов записи
- Поддерживает I²C-интерфейс и легко подключается
📋 UART-вывод после запуска
madmentat.ru controller setup initiated Reason for the last reset was: Watchdog emergency reboot. resetCount = 5 num = 8888 chDelay = 15 heatInCamera = 150 componentP = 10 componentI = 10 componentD = 10 Airflow = 0 iteration = 0
🔍 Распознавание причины сброса
Программа считывает регистр MCUSR
и по флагам определяет источник сброса:
WDRF
— Watchdog сбросEXTRF
— внешняя кнопкаPORF
— подача питанияBORF
— просадка питания (Brown-Out)
🧠 Принцип работы
- Сначала считываются данные из FRAM
- Если сигнатура совпадает — данные восстанавливаются
- Если нет — инициализируются значения по умолчанию
- Происходит увеличение
resetCount
и повторная запись - UART выводит текущие параметры и причину последнего сброса
🔌 Схема подключения
Подключение FRAM по I²C:
SCL
→ PD5 (пример)SDA
→ PD7VCC
→ +5ВGND
→ Земля- Подтягивающие резисторы 4.7 кОм на линии SDA и SCL
🧾 Полный исходный код прошивки
Ниже представлен полный исходный код. Пины управления FRAM можно переназначить в файле fram.h. Код совместим с AVR (например, ATmega169PA) и легко расширяется:
/* * main.c * * Created: 20.05.2025 9:50:01 * Author: madmentat */
#define F_CPU 16000000UL // Частота микроконтроллера #include <avr/io.h> #include <avr/interrupt.h> #include <avr/wdt.h> // Для работы с Watchdog #include <stdlib.h> // для utoa #include <util/delay.h> #include "fram.h" // Константы для UART #define BAUD 9600 #define MYUBRR F_CPU/16/BAUD-1 #define FRAM_SIGNATURE 0xA5A5 // Определение структуры для хранения данных struct FRAMData { 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; }; // Переменные в оперативной памяти 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]; // Функция utoa (перенесена из оригинального кода) 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; } // Функция для инициализации UART void UART_init(unsigned int ubrr) { UBRR0H = (unsigned char)(ubrr >> 8); UBRR0L = (unsigned char)ubrr; UCSR0B = (1 << TXEN0); 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++); } } // Функция для отправки данных из FRAM через UART void sendFRAMData() { 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"); } // Функция для записи uint16_t в FRAM (2 байта) void fram_write_uint16(uint16_t addr, uint16_t value) { fram_write(addr, (uint8_t)(value >> 8)); // Старший байт fram_write(addr + 1, (uint8_t)value); // Младший байт } // Функция для чтения uint16_t из FRAM (2 байта) uint16_t fram_read_uint16(uint16_t addr) { uint8_t high = fram_read(addr); uint8_t low = fram_read(addr + 1); return ((uint16_t)high << 8) | low; } // Функция для записи данных в FRAM void writeToFRAM() { struct FRAMData data = { FRAM_SIGNATURE, resetCount, num, chDelay, heatInCamera, componentP, componentI, componentD, Airflow, iteration }; uint16_t addr = 0; fram_write_uint16(addr, data.signature); addr += 2; fram_write_uint16(addr, data.resetCount); addr += 2; fram_write_uint16(addr, data.num); addr += 2; fram_write_uint16(addr, data.chDelay); addr += 2; fram_write_uint16(addr, data.heatInCamera); addr += 2; fram_write_uint16(addr, data.componentP); addr += 2; fram_write_uint16(addr, data.componentI); addr += 2; fram_write_uint16(addr, data.componentD); addr += 2; fram_write_uint16(addr, data.Airflow); addr += 2; fram_write_uint16(addr, data.iteration); addr += 2; } // Функция для чтения данных из FRAM void readFromFRAM() { struct FRAMData data; uint16_t addr = 0; data.signature = fram_read_uint16(addr); addr += 2; data.resetCount = fram_read_uint16(addr); addr += 2; data.num = fram_read_uint16(addr); addr += 2; data.chDelay = fram_read_uint16(addr); addr += 2; data.heatInCamera = fram_read_uint16(addr); addr += 2; data.componentP = fram_read_uint16(addr); addr += 2; data.componentI = fram_read_uint16(addr); addr += 2; data.componentD = fram_read_uint16(addr); addr += 2; data.Airflow = fram_read_uint16(addr); addr += 2; data.iteration = fram_read_uint16(addr); addr += 2; if (data.signature == FRAM_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 { // Если сигнатура не совпадает, инициализируем FRAM writeToFRAM(); } } // Функция для инициализации Watchdog void WDT_init() { wdt_enable(WDTO_2S); } int main(void) { // Инициализация cli(); // Отключение глобальных прерываний // Инициализация UART UART_init(MYUBRR); // Инициализация Watchdog WDT_init(); // Чтение данных из FRAM при старте readFromFRAM(); // Увеличение счетчика перезагрузок resetCount++; // Запись данных в FRAM writeToFRAM(); 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)) { uart_send_string("Watchdog emergency reboot.\r\n"); } else if (resetReason & (1 << EXTRF)) { 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"); } // Отправка данных из FRAM через UART sendFRAMData(); // Исправлено с sendFR externaData() // Включение глобальных прерываний sei(); // Основной цикл while (1) { // Сброс Watchdog таймера wdt_reset(); // Пример задержки _delay_ms(100); } return 0; }
Здесь мы будем определять пины управление микросхемой памяти:
/* * fram.h * * Created: 20.05.2025 9:50:01 * Author: madmentat */
#define F_CPU 16000000UL #ifndef FRAM_H #define FRAM_H #include <stdint.h> #include <stdbool.h> #include <util/delay.h> #include <avr/io.h> #ifndef FRAM_SDA_PORT #define FRAM_SDA_PORT PORTD #endif #ifndef FRAM_SDA_DDR #define FRAM_SDA_DDR DDRD #endif #ifndef FRAM_SDA_PINR #define FRAM_SDA_PINR PIND #endif #ifndef FRAM_SDA_BIT #define FRAM_SDA_BIT PD7 #endif #ifndef FRAM_SCL_PORT #define FRAM_SCL_PORT PORTD #endif #ifndef FRAM_SCL_DDR #define FRAM_SCL_DDR DDRD #endif #ifndef FRAM_SCL_PINR #define FRAM_SCL_PINR PIND #endif #ifndef FRAM_SCL_BIT #define FRAM_SCL_BIT PD5 #endif void fram_write(uint16_t addr, uint8_t value); uint8_t fram_read(uint16_t addr); void fram_fill(uint8_t value); void fram_dump(uint16_t start, uint16_t len, void (*print_byte)(uint8_t)); bool fram_test(uint16_t addr, uint8_t value); #endif
Кстати, не забываем, что на обеих из них должна быть подтяжка VCC - иначе лыжи не поедут.
/* * fram.c * * Created: 20.05.2025 9:50:01 * Author: madmentat */ #include "fram.h" #define SDA_HIGH() (FRAM_SDA_DDR &= ~(1 << FRAM_SDA_BIT)) #define SDA_LOW() do { FRAM_SDA_DDR |= (1 << FRAM_SDA_BIT); FRAM_SDA_PORT &= ~(1 << FRAM_SDA_BIT); } while (0) #define SDA_READ (FRAM_SDA_PINR & (1 << FRAM_SDA_BIT)) #define SCL_HIGH() (FRAM_SCL_DDR &= ~(1 << FRAM_SCL_BIT)) #define SCL_LOW() do { FRAM_SCL_DDR |= (1 << FRAM_SCL_BIT); FRAM_SCL_PORT &= ~(1 << FRAM_SCL_BIT); } while (0) #define I2C_DELAY() _delay_us(5) #define FRAM_I2C_ADDR 0xA0 // Базовый адрес устройства (0x50 << 1) static void i2c_start(void) { SDA_HIGH(); SCL_HIGH(); I2C_DELAY(); SDA_LOW(); I2C_DELAY(); SCL_LOW(); I2C_DELAY(); } static void i2c_stop(void) { SDA_LOW(); I2C_DELAY(); SCL_HIGH(); I2C_DELAY(); SDA_HIGH(); I2C_DELAY(); } static bool i2c_write(uint8_t data) { for (uint8_t i = 0; i < 8; i++) { if (data & 0x80) SDA_HIGH(); else SDA_LOW(); I2C_DELAY(); SCL_HIGH(); I2C_DELAY(); SCL_LOW(); I2C_DELAY(); data <<= 1; } SDA_HIGH(); I2C_DELAY(); SCL_HIGH(); I2C_DELAY(); bool ack = !(SDA_READ); SCL_LOW(); I2C_DELAY(); return ack; } static uint8_t i2c_read(bool ack) { uint8_t data = 0; SDA_HIGH(); for (uint8_t i = 0; i < 8; i++) { data <<= 1; SCL_HIGH(); I2C_DELAY(); if (SDA_READ) data |= 1; SCL_LOW(); I2C_DELAY(); } if (ack) SDA_LOW(); else SDA_HIGH(); SCL_HIGH(); I2C_DELAY(); SCL_LOW(); I2C_DELAY(); SDA_HIGH(); return data; } void fram_write(uint16_t addr, uint8_t value) { uint8_t device_addr = FRAM_I2C_ADDR | (((addr >> 8) & 0x01) << 1); // Учитываем бит 9 адреса памяти i2c_start(); if (!i2c_write(device_addr)) { i2c_stop(); return; // Если нет ACK, прерываем } if (!i2c_write((uint8_t)addr)) { i2c_stop(); return; } if (!i2c_write(value)) { i2c_stop(); return; } i2c_stop(); _delay_ms(5); // Задержка для завершения записи } uint8_t fram_read(uint16_t addr) { uint8_t device_addr = FRAM_I2C_ADDR | (((addr >> 8) & 0x01) << 1); i2c_start(); if (!i2c_write(device_addr)) { i2c_stop(); return 0xFF; // Если нет ACK, возвращаем FF } if (!i2c_write((uint8_t)addr)) { i2c_stop(); return 0xFF; } i2c_start(); if (!i2c_write(device_addr | 0x01)) { i2c_stop(); return 0xFF; } uint8_t val = i2c_read(false); i2c_stop(); return val; } void fram_fill(uint8_t value) { for (uint16_t addr = 0; addr < 512; addr++) { fram_write(addr, value); } } void fram_dump(uint16_t start, uint16_t len, void (*print_byte)(uint8_t)) { for (uint16_t i = 0; i < len; i++) { print_byte(fram_read(start + i)); } } bool fram_test(uint16_t addr, uint8_t value) { fram_write(addr, value); return (fram_read(addr) == value); }
✅ Заключение
Эта прошивка позволяет создавать надёжные устройства с устойчивым восстановлением состояния после любых сбоев. FRAM показывает себя как отличное решение для задач, где важно сохранять данные без задержек и ограничения по ресурсу записи.
Полный исходный код доступен внутри проекта, а если интересно — можно адаптировать его для дисплеев, датчиков и управляющих интерфейсов.