AVR ВАЧДОГ И FRAM

 

Контроллер с диагностикой перезагрузок и энергонезависимой памятью FRAM

В этой статье представлен пример прошивки для AVR-микроконтроллера (например, ATmega328P), которая сохраняет своё состояние между перезагрузками и умеет выводить через UART подробную информацию о причине последнего сброса. Также используется энергонезависимая память FRAM, значительно превосходящая EEPROM по скорости и ресурсу перезаписи.

FRAM chart 1

Подробнее о микросхеме 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)

🧠 Принцип работы

  1. Сначала считываются данные из FRAM
  2. Если сигнатура совпадает — данные восстанавливаются
  3. Если нет — инициализируются значения по умолчанию
  4. Происходит увеличение resetCount и повторная запись
  5. UART выводит текущие параметры и причину последнего сброса

🔌 Схема подключения

Подключение FRAM по I²C:

  • SCL → PD5 (пример)
  • SDA → PD7
  • VCC → +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);
}

 

 

watchdog fram

✅ Заключение

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

Полный исходный код доступен внутри проекта, а если интересно — можно адаптировать его для дисплеев, датчиков и управляющих интерфейсов.