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
- включить светодиод 3Disable 5
- выключить светодиод 5Reset
- программная перезагрузка
5. Защита от сбоев
Реализованы три уровня защиты:
- Сторожевой таймер (IWDG) с автосбросом
- Таймаут эхо-ответа (100 мс)
- Восстановление счетчика при повреждении Flash
Принцип работы системы
- Инициализация периферии и диагностика причины перезагрузки
- Обновление счетчика перезагрузок во Flash
- Цикл с периодическим сбросом watchdog
- Обработка команд с 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