STM32F030K6T6: Система управления светодиодами с защитой от сбоев

В этой статье разбираем прошивку для микроконтроллера STM32F030K6T6, реализующую:

  • Управление 6 светодиодами через UART-команды
  • Счетчик перезагрузок с сохранением во Flash-память
  • Автоматический сброс по сторожевому таймеру (IWDG)
  • RS-485 интерфейс с защитой от коллизий

Аппаратная конфигурация

Для работы системы используются:

  • Пины светодиодов: PA8-PA12, PA15
  • UART1: PA9 (TX), PA10 (RX)
  • Управление RS-485: PF1 (DIR)
  • Тактовая частота: 8 МГц от внутреннего генератора (HSI)

Ключевые компоненты прошивки

1. Инициализация периферии

Настройка тактирования, GPIO, UART и таймеров:

// Настройка системной частоты
void SystemClock_Config(void) {
RCCOscInitTypeDef RCCOscInitStruct = {0};
// Конфигурация HSI 8 МГц
// ...
}

// Инициализация UART (115200 бод)
void UART_Init(void) {
USART1->BRR = SYSCLK_HZ / BAUDRATE;
USART1->CR1 |= USARTCR1UE | USARTCR1TE | USARTCR1RE;
// ...
}

// Настройка watchdog (5 сек)
void IWDG_Init(void) {
IWDG->PR = 0x04; // Делитель /32
IWDG->RLR = 6250; // Таймаут 5 сек
IWDG->KR = 0xCCCC; // Запуск
}

2. Работа с Flash-памятью

Хранение счетчика перезагрузок по адресу 0x08007C00:

define FLASHRESETCOUNT_ADDR 0x08007C00

void UpdateResetCount(void) {
uint32_t count = ReadResetCount();
count++;
WriteResetCount(count, FLASHMAGICVALUE);
}

3. Механизм RS-485

Динамическое переключение режимов приёма/передачи:

void RS485_SetTransmitMode(void) {
HALGPIOWritePin(GPIOF, GPIOPIN1, GPIOPINSET);
}

void RS485_SetReceiveMode(void) {
HALGPIOWritePin(GPIOF, GPIOPIN1, GPIOPINRESET);
}

4. Обработка команд

Примеры поддерживаемых команд:

  • Enable 3 - включить светодиод 3
  • Disable 5 - выключить светодиод 5
  • Reset - программная перезагрузка

5. Защита от сбоев

Реализованы три уровня защиты:

  1. Сторожевой таймер (IWDG) с автосбросом
  2. Таймаут эхо-ответа (100 мс)
  3. Восстановление счетчика при повреждении Flash

Принцип работы системы

Блок-схема работы системы

  1. Инициализация периферии и диагностика причины перезагрузки
  2. Обновление счетчика перезагрузок во Flash
  3. Цикл с периодическим сбросом watchdog
  4. Обработка команд с UART с контролем таймаутов

Особенности реализации

Оптимизация работы с Flash:

  • Использование магического числа 0xDEADBEEF для проверки целостности
  • Блочное стирание памяти перед записью
  • Контроль ошибок записи

Безопасность RS-485:

  • Задержки 50 мкс при переключении режимов
  • Буферизация данных
  • Обработка ошибок UART в прерываниях

Пример сеанса работы

System started
Reset cause: IWDG Reset
Reset count: 12
> Enable 3
LED 3 Enabled
> Disable 5
LED 5 Disabled
> Reset
Performing software reset...

Типичные проблемы и решения

ПроблемаРешение
Микроконтроллер постоянно перезагружается Проверить частоту LSI, корректность настройки IWDG
Не сохраняется счетчик перезагрузок Убедиться в доступности адреса 0x08007C00 для записи
Искажение данных в RS-485 Проверить задержки переключения DIR, целостность земли

Представленная реализация демонстрирует надежную работу в промышленных условиях с поддержкой основных защитных механизмов. Исходный код полностью готов к использованию в проектах на базе STM32F0xx.

меточка

#include "main.h"
#include <string.h>
#include <stdio.h>
 
#define SYSCLK_HZ 8000000UL   // Тактовая частота системы: 8 МГц
#define BAUDRATE  115200UL    // Скорость UART: 115200 бод
#define TIMEOUT_MS 100        // Таймаут для эхо 100 мс
#define IWDG_TIMEOUT_MS 2000  // Фиксированный таймаут watchdog: 2 секунды
#define FLASH_STATE_ADDR 0x08007C00 // Адрес для структуры
#define FLASH_MAGIC_VALUE 0xDEADBEEF // Магическое число
 
// Пин для управления направлением RS485
#define RS485_CONTROL_PIN GPIO_PIN_1
#define RS485_CONTROL_PORT GPIOF
 
// Буфер для приёма данных
#define RX_BUFFER_SIZE 64
char rx_buffer[RX_BUFFER_SIZE] = {0};
uint8_t rx_index = 0;
 
// Переменные для millis()
volatile uint32_t timer_overflows = 0;
volatile uint32_t last_rx_time = 0;
 
// Переменные для Watchdog
volatile uint32_t WatchdogTIMER_1 = 0; // Счетчик времени
volatile uint32_t WatchdogTIMER_2 = 1000; // Таймаут кормления (по умолчанию 1 сек)
 
// Структура для хранения состояния
typedef struct {
    uint8_t led_states[6];  // Состояние светодиодов (0 = выкл, 1 = вкл)
    uint32_t reset_count;   // Счётчик перезагрузок
    uint32_t magic;         // Магическое число
    uint32_t checksum;      // Контрольная сумма
} SystemState;
 
// Текущее состояние в RAM
SystemState system_state = {0};
 
// Прототипы функций
void SystemClock_Config(void);
void UART_Init(void);
void Timer_Init(void);
void WatchdogTimer_Init(void);
void IWDG_Init(void);
void UART_SendString(char *str);
void MX_GPIO_Init(void);
void RS485_Init(void);
void RS485_SetTransmitMode(void);
void RS485_SetReceiveMode(void);
uint32_t millis(void);
void Delay_us(uint32_t us);
void CheckResetCause(uint8_t *is_iwdg_reset);
void LoadSystemState(void);
void SaveSystemState(void);
uint32_t CalculateChecksum(SystemState *state);
void ApplyLedStates(void);
 
// Задержка для таймингов (в микросекундах)
void Delay_us(uint32_t us) {
    uint32_t cycles = (SYSCLK_HZ / 1000000UL) * us;
    while (cycles--) {
        __NOP();
    }
}
 
// Функция для получения текущего времени в миллисекундах
uint32_t millis(void) {
    uint32_t overflows;
    uint32_t cnt;
    __disable_irq();
    overflows = timer_overflows;
    cnt = TIM3->CNT;
    __enable_irq();
    return overflows * 1000 + cnt;
}
 
// Инициализация RS485
void RS485_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOF_CLK_ENABLE();
    GPIO_InitStruct.Pin = RS485_CONTROL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(RS485_CONTROL_PORT, &GPIO_InitStruct);
    RS485_SetReceiveMode();
}
 
// Установка режима передачи для RS485
void RS485_SetTransmitMode(void) {
    HAL_GPIO_WritePin(RS485_CONTROL_PORT, RS485_CONTROL_PIN, GPIO_PIN_SET);
    Delay_us(50);
}
 
// Установка режима приёма для RS485
void RS485_SetReceiveMode(void) {
    HAL_GPIO_WritePin(RS485_CONTROL_PORT, RS485_CONTROL_PIN, GPIO_PIN_RESET);
    Delay_us(50);
}
 
// Инициализация UART
void UART_Init(void) {
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
 
    GPIOA->MODER &= ~(GPIO_MODER_MODER2_Msk | GPIO_MODER_MODER3_Msk);
    GPIOA->MODER |= (GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1);
    GPIOA->AFR[0] &= ~((0xF << GPIO_AFRL_AFSEL2_Pos) | (0xF << GPIO_AFRL_AFSEL3_Pos));
    GPIOA->AFR[0] |= ((1 << GPIO_AFRL_AFSEL2_Pos) | (1 << GPIO_AFRL_AFSEL3_Pos));
 
    USART1->CR1 = 0;
    USART1->CR2 = 0;
    USART1->BRR = SYSCLK_HZ / BAUDRATE;
    USART1->CR1 |= (USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE);
 
    NVIC_SetPriority(USART1_IRQn, 0);
    NVIC_EnableIRQ(USART1_IRQn);
}
 
// Отправка строки через UART
void UART_SendString(char *str) {
    RS485_SetTransmitMode();
    while (*str) {
        while (!(USART1->ISR & USART_ISR_TXE)) {}
        USART1->TDR = *str++;
    }
    while (!(USART1->ISR & USART_ISR_TC)) {}
    RS485_SetReceiveMode();
}
 
// Инициализация таймера TIM3 для millis()
void Timer_Init(void) {
    __HAL_RCC_TIM3_CLK_ENABLE();
    TIM3->PSC = (SYSCLK_HZ / 1000) - 1; // 1 мс на тик
    TIM3->ARR = 1000 - 1; // Переполнение каждую 1 с
    TIM3->DIER |= TIM_DIER_UIE;
    NVIC_SetPriority(TIM3_IRQn, 1);
    NVIC_EnableIRQ(TIM3_IRQn);
    TIM3->CR1 |= TIM_CR1_CEN;
}
 
// Инициализация таймера TIM14 для watchdog
void WatchdogTimer_Init(void) {
    __HAL_RCC_TIM14_CLK_ENABLE();
    TIM14->PSC = (SYSCLK_HZ / 1000) - 1; // 1 мс на тик
    TIM14->ARR = 1; // Прерывание каждую 1 мс
    TIM14->DIER |= TIM_DIER_UIE;
    NVIC_SetPriority(TIM14_IRQn, 2);
    NVIC_EnableIRQ(TIM14_IRQn);
    TIM14->CR1 |= TIM_CR1_CEN;
}
 
// Обработчик прерывания TIM14
void TIM14_IRQHandler(void) {
    if (TIM14->SR & TIM_SR_UIF) {
        TIM14->SR &= ~TIM_SR_UIF;
        WatchdogTIMER_1++;
    }
}
 
// Инициализация IWDG (фиксированный таймаут 2 сек)
void IWDG_Init(void) {
    __HAL_RCC_LSI_ENABLE();
    while ((RCC->CSR & RCC_CSR_LSIRDY) == 0) {}
 
    IWDG->KR = 0x5555;
    IWDG->PR = 0x04; // Делитель /32: 40 кГц / 32 ≈ 1250 Гц (0.8 мс/тик)
    IWDG->RLR = 2500; // 2000 мс / 0.8 мс = 2500
    IWDG->KR = 0xAAAA;
    IWDG->KR = 0xCCCC;
}
 
// Расчёт контрольной суммы
uint32_t CalculateChecksum(SystemState *state) {
    uint32_t sum = 0;
    for (int i = 0; i < 6; i++) {
        sum += state->led_states[i];
    }
    sum += state->reset_count;
    return sum;
}
 
// Чтение и проверка структуры из флэш
void LoadSystemState(void) {
    SystemState *flash_state = (SystemState *)FLASH_STATE_ADDR;
    if (flash_state->magic == FLASH_MAGIC_VALUE && flash_state->checksum == CalculateChecksum(flash_state)) {
        memcpy(&system_state, flash_state, sizeof(SystemState));
    } else {
        memset(system_state.led_states, 0, 6);
        system_state.reset_count = 0;
        system_state.magic = FLASH_MAGIC_VALUE;
        system_state.checksum = CalculateChecksum(&system_state);
        SaveSystemState();
    }
}
 
// Сохранение структуры во флэш
void SaveSystemState(void) {
    HAL_StatusTypeDef status;
    FLASH_EraseInitTypeDef erase_init = {0};
    uint32_t page_error = 0;
 
    system_state.checksum = CalculateChecksum(&system_state);
    system_state.magic = FLASH_MAGIC_VALUE;
 
    HAL_FLASH_Unlock();
 
    erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
    erase_init.PageAddress = FLASH_STATE_ADDR;
    erase_init.NbPages = 1;
 
    status = HAL_FLASHEx_Erase(&erase_init, &page_error);
    if (status != HAL_OK) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Flash erase error: %d\r\n", status);
        UART_SendString(msg);
        HAL_FLASH_Lock();
        return;
    }
 
    uint32_t *state_ptr = (uint32_t*)&system_state;
    for (uint32_t i = 0; i < sizeof(SystemState)/4; i++) {
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_STATE_ADDR + i*4, state_ptr[i]);
        if (status != HAL_OK) break;
    }
 
    if (status != HAL_OK) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Flash write error: %d\r\n", status);
        UART_SendString(msg);
    }
 
    HAL_FLASH_Lock();
}
 
// Применение состояния светодиодов
void ApplyLedStates(void) {
    uint16_t pins[6] = {GPIO_PIN_15, GPIO_PIN_12, GPIO_PIN_11, GPIO_PIN_10, GPIO_PIN_9, GPIO_PIN_8};
    for (int i = 0; i < 6; i++) {
        HAL_GPIO_WritePin(GPIOA, pins[i], system_state.led_states[i] ? GPIO_PIN_SET : GPIO_PIN_RESET);
    }
}
 
// Проверка причины перезагрузки
void CheckResetCause(uint8_t *is_iwdg_reset) {
    char msg[64];
    uint32_t reset_flags = RCC->CSR;
 
    *is_iwdg_reset = (reset_flags & RCC_CSR_IWDGRSTF) ? 1 : 0;
 
    UART_SendString("Reset cause: ");
    if (reset_flags & RCC_CSR_PORRSTF) {
        UART_SendString("Power-On Reset\r\n");
    } else if (reset_flags & RCC_CSR_SFTRSTF) {
        UART_SendString("Software Reset\r\n");
    } else if (reset_flags & RCC_CSR_IWDGRSTF) {
        UART_SendString("IWDG Reset\r\n");
    } else if (reset_flags & RCC_CSR_PINRSTF) {
        UART_SendString("Pin Reset\r\n");
    } else if (reset_flags & RCC_CSR_WWDGRSTF) {
        UART_SendString("WWDG Reset\r\n");
    } else if (reset_flags & RCC_CSR_LPWRRSTF) {
        UART_SendString("Low-Power Reset\r\n");
    } else {
        UART_SendString("Unknown Reset\r\n");
    }
 
    snprintf(msg, sizeof(msg), "RCC_CSR: 0x%08lX\r\n", reset_flags);
    UART_SendString(msg);
 
    RCC->CSR |= RCC_CSR_RMVF;
}
 
// Обработчик прерывания таймера TIM3
void TIM3_IRQHandler(void) {
    if (TIM3->SR & TIM_SR_UIF) {
        TIM3->SR &= ~TIM_SR_UIF;
        timer_overflows++;
    }
}
 
// Обработчик прерывания UART
void USART1_IRQHandler(void) {
    if (USART1->ISR & USART_ISR_RXNE) {
        char received = USART1->RDR;
        last_rx_time = millis();
        if (rx_index < RX_BUFFER_SIZE - 1) {
            rx_buffer[rx_index++] = received;
        } else {
            rx_index = 0;
        }
    }
    if (USART1->ISR & (USART_ISR_ORE | USART_ISR_PE | USART_ISR_FE)) {
        USART1->ICR |= USART_ICR_ORECF | USART_ICR_PECF | USART_ICR_FECF;
        RS485_SetTransmitMode();
        Delay_us(50);
        UART_SendString("UART Error\r\n");
        RS485_SetReceiveMode();
    }
}
 
// Инициализация GPIO
void MX_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
 
    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_15, GPIO_PIN_RESET);
}
 
// Настройка системной частоты
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
 
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}
 
// Основная функция
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    UART_Init();
    RS485_Init();
    Timer_Init();
    WatchdogTimer_Init();
    IWDG_Init();
    UART_SendString("madmentat.ru\r\n");
    uint8_t is_iwdg_reset = 0;
    CheckResetCause(&is_iwdg_reset);
    LoadSystemState();
    system_state.reset_count++;
    if (!is_iwdg_reset) {
        memset(system_state.led_states, 0, 6);
    }
    ApplyLedStates();
    SaveSystemState();
 
    char msg[64];
    snprintf(msg, sizeof(msg), "Reset count: %lu\r\n", system_state.reset_count);
    UART_SendString(msg);
    UART_SendString("System started\r\n");
 
    while (1) {
        // Кормим watchdog по таймауту WatchdogTIMER_2
        if (WatchdogTIMER_1 >= WatchdogTIMER_2) {
            IWDG->KR = 0xAAAA;
            WatchdogTIMER_1 = 0;
            //UART_SendString("Watchdog fed\r\n");
        }
 
        if (rx_index > 0 && (millis() - last_rx_time >= TIMEOUT_MS)) {
            rx_buffer[rx_index] = '\0';
            if (rx_index > 0 && rx_buffer[rx_index-1] == '\r') {
                rx_buffer[rx_index-1] = '\0';
            }
            RS485_SetTransmitMode();
            Delay_us(50);
            char msg[128];
            int led_num;
            uint32_t new_feed_interval;
 
            if (strcmp(rx_buffer, "Reset") == 0) {
                UART_SendString("Performing software reset...\r\n");
                Delay_us(100000);
                NVIC_SystemReset();
            }
            else if (sscanf(rx_buffer, "Enable %d", &led_num) == 1 && led_num >= 1 && led_num <= 6) {
                system_state.led_states[led_num-1] = 1;
                ApplyLedStates();
                SaveSystemState();
                snprintf(msg, sizeof(msg), "LED %d Enabled\r\n", led_num);
                UART_SendString(msg);
            }
            else if (sscanf(rx_buffer, "Disable %d", &led_num) == 1 && led_num >= 1 && led_num <= 6) {
                system_state.led_states[led_num-1] = 0;
                ApplyLedStates();
                SaveSystemState();
                snprintf(msg, sizeof(msg), "LED %d Disabled\r\n", led_num);
                UART_SendString(msg);
            }
            else if (sscanf(rx_buffer, "w%lu", &new_feed_interval) == 1) {
                if (new_feed_interval < 100) new_feed_interval = 100;
                WatchdogTIMER_2 = new_feed_interval;
                WatchdogTIMER_1 = 0;
                snprintf(msg, sizeof(msg), "Watchdog feed interval set to %lu ms\r\n", WatchdogTIMER_2);
                UART_SendString(msg);
            }
            else {
                snprintf(msg, sizeof(msg), "ECHO: %s\r\n", rx_buffer);
                UART_SendString(msg);
                if (rx_index > 0) {
                    snprintf(msg, sizeof(msg), "First byte: 0x%02X (%c)\r\n",
                           rx_buffer[0],
                           rx_buffer[0] >= 32 && rx_buffer[0] <= 126 ? rx_buffer[0] : '.');
                    UART_SendString(msg);
                }
            }
            RS485_SetReceiveMode();
            rx_index = 0;
        }
    }
}
 
// Обработчик ошибок
void Error_Handler(void) {
    __disable_irq();
    while (1) {}
}
 
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line) {
}
#endif