Доработать программу «светофор», добавив возможности отключения кнопки и задания величины тайм-аута (период, в течение которого горит красный).
Команды UART (скорость 57600 бит/с):
| Команда | Описание |
|---|---|
? |
Запрос текущего состояния светофора |
set mode 1 |
Включить обработку нажатий кнопки |
set mode 2 |
Отключить обработку нажатий кнопки |
set timeout X |
Установить длительность красного света (X – период в секундах) |
set interrupts on |
Включить режим прерываний для UART |
set interrupts off |
Выключить режим прерываний (переключиться на режим опроса) |
Требования к реализации:
-
Разработать два варианта драйверов UART:
- С использованием прерываний: неблокирующий режим работы с буферизацией данных для предотвращения потери данных
- Без использования прерываний (режим опроса): функция приема данных не должна блокироваться в ожидании данных
-
Каждый принимаемый символ должен отсылаться обратно (эхо)
-
Каждое новое сообщение должно выводиться с новой строки
-
На каждую команду должен выводиться ответ «OK» или сообщение об ошибке для неподдерживаемых команд
-
Скорость работы UART: 57600 бит/с
-
Изучить протокол передачи данных по интерфейсу UART
- Асинхронная передача данных
- Конфигурация скорости передачи (baud rate)
- Структура фрейма данных (стартовый бит, биты данных, стоповый бит)
-
Получить базовые знания об организации системы прерываний в микроконтроллерах
- Контроллер прерываний NVIC (Nested Vectored Interrupt Controller)
- Приоритеты прерываний
- Обработчики прерываний (ISR - Interrupt Service Routines)
-
Изучить устройство и принципы работы контроллера интерфейса UART
- Регистры USART микроконтроллера STM32F4
- Флаги состояния (RXNE, TXE, TC)
- Режимы работы UART
-
Получить навыки организации обмена данными по UART в режимах опроса и прерываний
- Реализация неблокирующего ввода-вывода
- Буферизация данных с использованием кольцевых буферов
- Переключение между режимами работы в runtime
-
Разработать управляющую программу для светофора с командным интерфейсом
- Автомат состояний для управления светофором
- Парсинг и обработка команд UART
- Интеграция кнопочного ввода и UART-управления
Программа имеет следующую структуру:
Приложение
├── UART драйвер (uart_driver.c/h)
│ ├── Режим прерываний (IT) с кольцевыми буферами
│ └── Режим опроса (Polling) с прямым доступом к регистрам
│
├── Главная программа (main.c)
│ ├── Автомат состояний светофора (4 состояния)
│ ├── Обработка команд UART
│ └── Главный цикл (неблокирующий)
│
├── GPIO драйвер (gpio_driver.c/h)
│ ├── Управление кнопкой с дебаунсингом
│ └── Управление светодиодами
│
└── Интеграция с STM32 HAL
├── Инициализация периферии (usart.c, gpio.c)
└── Обработчики прерываний (stm32f4xx_it.c)
Ключевые принципы проектирования:
- Неблокирующая архитектура: главный цикл никогда не блокируется на операциях ввода-вывода
- Модульность: каждый компонент имеет четко определенный интерфейс
- Абстракция: UART драйвер скрывает детали работы с HAL и предоставляет единый API для обоих режимов
- Интеграция через колбэки: HAL вызывает функции драйвера при возникновении событий UART
UART драйвер реализует двухрежимную работу с единым программным интерфейсом.
typedef struct {
UART_HandleTypeDef *huart; // HAL USART6
/* Кольцевые буферы для режима прерываний */
volatile uint8_t rx_buf[64]; // Буфер приема
volatile uint16_t rx_head; // Указатель записи (head)
volatile uint16_t rx_tail; // Указатель чтения (tail)
volatile uint8_t tx_buf[64]; // Буфер передачи
volatile uint16_t tx_head; // Указатель записи
volatile uint16_t tx_tail; // Указатель чтения
volatile uint8_t tx_busy; // Флаг активной передачи
uint8_t hal_rx_byte; // Однобайтовый буфер для HAL
volatile bool irq_enabled; // Флаг режима (IT/Polling)
} UART;Принцип работы:
- При поступлении байта UART генерирует прерывание
- Обработчик прерывания помещает байт в кольцевой буфер
- Приложение читает данные из буфера в удобное время
Ключевые функции:
| Функция | Описание |
|---|---|
uart_it_try_get_byte(UART *uart, uint8_t *byte) |
Неблокирующее чтение из RX буфера. Возвращает false, если буфер пуст |
uart_it_send_byte(UART *uart, uint8_t byte) |
Неблокирующая отправка байта. Помещает байт в TX буфер, возвращает false, если буфер полон |
uart_it_send_string(UART *uart, const char *str) |
Отправка строки через TX буфер (может обрезаться при переполнении) |
uart_rx_complete_callback(UART *uart) |
Колбэк HAL: вызывается при приеме байта, помещает его в RX буфер |
uart_tx_complete_callback(UART *uart) |
Колбэк HAL: вызывается при завершении передачи байта, запускает передачу следующего из буфера |
Кольцевой буфер:
- Размер: 64 байта (достаточно для обработки всплесков данных на скорости 57600 бод)
- Пустой буфер:
rx_head == rx_tail - Полный буфер:
(rx_head + 1) % 64 == rx_tail - При переполнении: новые данные отбрасываются (RX) или функция возвращает
false(TX)
Принцип работы:
- Программа периодически проверяет флаги UART регистров
- Прямое чтение/запись в регистр данных (DR)
- Прерывания от UART отключены
Ключевые функции:
| Функция | Описание |
|---|---|
uart_poll_try_get_byte(UART *uart, uint8_t *byte) |
Неблокирующее чтение: проверяет флаг RXNE, если установлен – читает DR, иначе возвращает false |
uart_poll_send_byte(UART *uart, uint8_t byte) |
Блокирующая отправка: ожидает флаг TXE, затем записывает байт в DR |
uart_poll_send_string(UART *uart, const char *str) |
Блокирующая отправка строки с ожиданием флага TC в конце |
Прямой доступ к регистрам:
- Чтение флага:
__HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE) - Чтение данных:
uart->huart->Instance->DR & 0xFF - Запись данных:
uart->huart->Instance->DR = byte
Функция uart_set_irq_mode(UART *uart, bool enable):
При включении режима прерываний:
- Устанавливает приоритет прерывания USART6:
HAL_NVIC_SetPriority(USART6_IRQn, 0, 0) - Включает прерывание в NVIC:
HAL_NVIC_EnableIRQ(USART6_IRQn) - Устанавливает флаг
irq_enabled = true - Запускает прием одного байта:
HAL_UART_Receive_IT(huart, &hal_rx_byte, 1)
При выключении режима прерываний:
- Прерывает текущие операции HAL:
HAL_UART_AbortReceive_IT(),HAL_UART_AbortTransmit_IT() - Отключает прерывание в NVIC:
HAL_NVIC_DisableIRQ(USART6_IRQn) - Сбрасывает флаг
irq_enabled = false - Сбрасывает флаг передачи
tx_busy = 0
Безопасность переключения:
- Буферы не очищаются при переключении (данные сохраняются)
- Главная программа автоматически переключается на соответствующие функции чтения/записи
- RX функции всегда неблокирующие (возвращают
false, если нет данных) - TX в режиме IT неблокирующая, в режиме Polling – блокирующая (приемлемо для простых приложений)
Программа реализует классический автомат состояний (Finite State Machine) для управления светофором.
typedef enum {
STATE_RED, // Красный свет
STATE_GREEN, // Зеленый свет
STATE_GREEN_BLINKING, // Мигающий зеленый
STATE_YELLOW // Желтый свет
} TrafficState;| Текущее состояние | Длительность | Следующее состояние | Условие перехода |
|---|---|---|---|
| RED | red_timeout_ms (по умолчанию 4000 мс) |
GREEN | Истек таймаут ИЛИ двойной клик кнопки |
| GREEN | 4000 мс | GREEN_BLINKING | Истек таймаут |
| GREEN_BLINKING | 4000 мс (мигание каждые 400 мс) | YELLOW | Истек таймаут |
| YELLOW | 4000 мс | RED | Истек таймаут |
Цикл светофора:
- Полный цикл: RED → GREEN → GREEN_BLINKING → YELLOW → RED
- Общая длительность:
red_timeout_ms + 4000 + 4000 + 4000мс (по умолчанию 16 секунд)
Режим 1 (кнопка активна):
| Состояние | Действие при нажатии кнопки |
|---|---|
| GREEN_BLINKING или YELLOW | Установить флаг red_shortened = true (следующий красный будет сокращен до red_timeout_ms / 4) |
| RED (первый клик) | Сократить текущий красный до red_timeout_ms / 4 |
| RED (второй клик) | Немедленно перейти в GREEN, сбросить флаг red_shortened |
Режим 2 (кнопка игнорируется):
- Нажатия кнопки не обрабатываются
- Светофор работает по фиксированному циклу
Дебаунсинг кнопки:
- Интервал дебаунсинга: 10 мс
- Обнаружение по переднему фронту (переход от нажатого к отпущенному состоянию)
- Предотвращает ложные срабатывания от дребезга контактов
Глобальные переменные состояния:
static TrafficState traffic_state = STATE_RED; // Текущее состояние
static uint32_t state_start_time = 0; // Время входа в состояние
static uint32_t red_timeout_ms = 4000; // Длительность красного
static uint8_t button_mode = 1; // 1 = активна, 2 = игнорируется
static bool red_shortened = false; // Флаг сокращения красногоФункция update_traffic_light():
- Вызывается в каждой итерации главного цикла
- Проверяет время, прошедшее с начала текущего состояния:
elapsed = HAL_GetTick() - state_start_time - При истечении таймаута вызывает
set_state()для перехода в следующее состояние - Для GREEN_BLINKING дополнительно переключает LED каждые 400 мс
Функция set_state(TrafficState new_state):
- Выключает все светодиоды
- Устанавливает новое состояние
traffic_state = new_state - Запоминает время входа
state_start_time = HAL_GetTick() - Включает соответствующий светодиод
Программа реализует простой построчный парсер команд.
-
Главный цикл читает байты из UART (IT или Polling в зависимости от режима)
-
Эхо каждого символа обратно пользователю
-
Специальная обработка символов:
- CR (
\r) или LF (\n): конец команды → вызовprocess_command() - Backspace (127 или 8): удаление последнего символа из буфера
- Остальные символы: добавление в буфер команды
- CR (
-
Парсинг команды в
process_command():- Простое сопоставление строк (
str_eq(),str_starts()) - Извлечение числовых параметров (
parse_int())
- Простое сопоставление строк (
static char cmd_buf[64]; // Буфер для накопления команды
static uint8_t cmd_len = 0; // Текущая длина команды| Команда | Функция обработки | Ответ |
|---|---|---|
? |
Запрос статуса | <state> mode <1/2> timeout <X> <I/P>\r\n |
set mode 1 |
button_mode = 1 |
OK\r\n |
set mode 2 |
button_mode = 2 |
OK\r\n |
set timeout <X> |
red_timeout_ms = X * 1000 (если X > 0) |
OK\r\n или ERROR: invalid timeout\r\n |
set interrupts on |
uart_set_irq_mode(&uart6, true) |
OK\r\n |
set interrupts off |
uart_set_irq_mode(&uart6, false) |
OK\r\n |
| Любая другая | — | ERROR: unknown command\r\n |
if (str_eq(cmd_buf, "?")) {
uint32_t effective_timeout = red_shortened ? (red_timeout_ms / 4) : red_timeout_ms;
int timeout_sec = effective_timeout / 1000;
char mode_char = uart_is_irq_mode(&uart6) ? 'I' : 'P';
send_str(get_state_name()); // "red", "green", "blinking green", "yellow"
send_str(" mode ");
send_char('0' + button_mode); // "1" или "2"
send_str(" timeout ");
// Вывод числа (вручную, без sprintf для экономии памяти)
if (timeout_sec >= 10) send_char('0' + (timeout_sec / 10) % 10);
send_char('0' + (timeout_sec % 10));
send_str(" ");
send_char(mode_char); // "I" или "P"
send_str("\r\n");
}Примечание: Функция send_str() автоматически выбирает uart_it_send_string() или uart_poll_send_string() в зависимости от текущего режима.
stateDiagram-v2
[*] --> RED
RED --> GREEN: Таймаут истек<br/>(или двойной клик кнопки)
GREEN --> GREEN_BLINKING: 4 секунды
GREEN_BLINKING --> YELLOW: 4 секунды
YELLOW --> RED: 4 секунды
note right of RED
Длительность: red_timeout_ms<br/>Может быть сокращена до /4<br/>нажатием кнопки
end note
note right of GREEN
Длительность: 4 секунды<br/>Нажатие кнопки устанавливает флаг<br/>для сокращения следующего RED
end note
note right of GREEN_BLINKING
Длительность: 4 секунды<br/>Переключение каждые 400мс<br/>Нажатие сокращает следующий RED
end note
note right of YELLOW
Длительность: 4 секунды
end note
Описание:
- Автомат имеет 4 состояния, образующих замкнутый цикл
- Каждое состояние активирует соответствующий светодиод
- Переходы происходят по истечении фиксированных таймаутов
- Состояние RED имеет переменную длительность (может быть сокращена кнопкой)
- Из состояния RED возможен досрочный переход при двойном клике кнопки
flowchart TD
A["Данные UART доступны?"] -->|Режим прерываний| B["Операция с<br/>кольцевым буфером"]
A -->|Режим опроса| C["Прямой опрос<br/>регистров"]
B --> B1["uart_it_try_get_byte()"]
B1 --> B2{RX буфер<br/>пуст?}
B2 -->|Да| B3["Вернуть false"]
B2 -->|Нет| B4["Взять байт из<br/>rx_tail"]
B4 --> B5["Увеличить<br/>rx_tail"]
B5 --> B6["Вернуть байт"]
C --> C1["Читать флаг RXNE"]
C1 --> C2{Флаг<br/>установлен?}
C2 -->|Нет| C3["Вернуть false"]
C2 -->|Да| C4["Читать регистр DR"]
C4 --> C5["Вернуть байт"]
B6 --> D["Обработать команду"]
C5 --> D
D --> E{"Команда<br/>распознана?"}
E -->|'?'| F["Отправить статус"]
E -->|'set mode'| G["Установить режим"]
E -->|'set timeout'| H["Установить таймаут"]
E -->|'set interrupts'| I["Переключить режим"]
E -->|Неизвестна| J["Отправить ошибку"]
F --> K["Отправить ответ"]
G --> K
H --> K
I --> K
J --> K
Описание:
- Алгоритм разветвляется в зависимости от текущего режима UART (IT или Polling)
- Режим прерываний: неблокирующее чтение из кольцевого буфера, заполняемого обработчиком прерываний
- Режим опроса: прямая проверка флага RXNE регистра статуса UART
- Обе ветви обеспечивают неблокирующее поведение (возвращают
false, если данных нет) - После получения полной команды (по символу
\rили\n) происходит парсинг и выполнение соответствующего действия - Каждая команда генерирует ответ (статус, "OK" или сообщение об ошибке)
Для полноты картины, дополнительная схема показывает взаимодействие между прерываниями, буферами и приложением:
flowchart TD
A[["UART Hardware<br/>(Периферия STM32)"]]
B["HAL_UART_IRQHandler<br/>(Обработчик прерывания)"]
C["HAL_UART_RxCpltCallback<br/>(HAL колбэк)"]
D["uart_rx_complete_callback<br/>(Драйвер)"]
E[("RX Ring Buffer<br/>rx_buf[64]")]
F["Main Loop<br/>uart_it_try_get_byte()"]
G["Application<br/>Command Processing"]
A -->|"Прерывание<br/>(байт получен)"| B
B -->|"Вызов колбэка"| C
C -->|"Переадресация"| D
D -->|"Запись<br/>rx_head++"| E
E -->|"Чтение<br/>rx_tail++"| F
F -->|"Байт команды"| G
D -.->|"Перезапуск приема<br/>HAL_UART_Receive_IT()"| A
style A fill:#e1f5ff
style B fill:#fff3e0
style C fill:#fff3e0
style D fill:#fff3e0
style E fill:#f3e5f5
style F fill:#e8f5e9
style G fill:#e8f5e9
В данном разделе приведены исходные коды собственной разработки. Код из стандартных библиотек HAL и автогенерированный код STM32CubeMX не включены.
#ifndef INC_UART_DRIVER_H_
#define INC_UART_DRIVER_H_
#include <stdbool.h>
#include <stdint.h>
#include "stm32f4xx_hal.h"
#define UART_RX_BUF_SIZE 64
#define UART_TX_BUF_SIZE 64
typedef struct {
UART_HandleTypeDef* huart;
volatile uint8_t rx_buf[UART_RX_BUF_SIZE];
volatile uint16_t rx_head;
volatile uint16_t rx_tail;
volatile uint8_t tx_buf[UART_TX_BUF_SIZE];
volatile uint16_t tx_head;
volatile uint16_t tx_tail;
volatile uint8_t tx_busy;
uint8_t hal_rx_byte;
volatile bool irq_enabled;
} UART;
/**
* Initializes the UART driver instance.
* Resets all buffers and state to defaults. IRQ mode is disabled initially.
*/
void uart_init(UART* uart, UART_HandleTypeDef* huart);
/**
* Enables or disables interrupt mode.
* When enabled, the driver uses ring buffers and HAL interrupts.
* When disabled, polling mode is active.
*/
void uart_set_irq_mode(UART* uart, bool enable);
/**
* Checks if interrupt mode is currently enabled.
* @return true if IRQ mode is active, false if polling mode
*/
bool uart_is_irq_mode(UART* uart);
/**
* Attempts to retrieve a byte from the RX ring buffer (interrupt mode).
* Non-blocking operation.
* @param byte Pointer to store the received byte
* @return true if byte was retrieved, false if buffer is empty
*/
bool uart_it_try_get_byte(UART* uart, uint8_t* byte);
/**
* Sends a byte via the TX ring buffer (interrupt mode).
* Non-blocking operation. Automatically starts transmission if not already active.
* @return true if byte was queued, false if buffer is full
*/
bool uart_it_send_byte(UART* uart, uint8_t byte);
/**
* Sends a string via the TX ring buffer (interrupt mode).
* Non-blocking operation. Stops if buffer becomes full.
*/
void uart_it_send_string(UART* uart, const char* str);
/**
* Returns the number of bytes available in the RX buffer (interrupt mode).
*/
uint16_t uart_it_rx_available(UART* uart);
/**
* Returns the number of free bytes in the TX buffer (interrupt mode).
*/
uint16_t uart_it_tx_free(UART* uart);
/**
* RX complete callback handler. Call from HAL_UART_RxCpltCallback.
* Stores received byte in ring buffer and restarts receive.
*/
void uart_rx_complete_callback(UART* uart);
/**
* TX complete callback handler. Call from HAL_UART_TxCpltCallback.
* Continues transmitting next byte from buffer if available.
*/
void uart_tx_complete_callback(UART* uart);
/**
* Attempts to retrieve a byte by polling the RXNE flag (polling mode).
* Non-blocking operation.
* @param byte Pointer to store the received byte
* @return true if byte was received, false if no data available
*/
bool uart_poll_try_get_byte(UART* uart, uint8_t* byte);
/**
* Sends a byte by polling the TXE flag (polling mode).
* Blocking operation - waits until byte can be sent.
*/
void uart_poll_send_byte(UART* uart, uint8_t byte);
/**
* Sends a string in polling mode.
* Blocking operation - waits until entire string is transmitted.
*/
void uart_poll_send_string(UART* uart, const char* str);
#endif /* INC_UART_DRIVER_H_ */#include "uart_driver.h"
#include <string.h>
static void start_rx_it(UART* uart) {
HAL_UART_Receive_IT(uart->huart, &uart->hal_rx_byte, 1);
}
static void start_tx_from_buffer(UART* uart) {
if (uart->tx_tail == uart->tx_head) {
return;
}
uint8_t byte = uart->tx_buf[uart->tx_tail];
uart->tx_tail = (uart->tx_tail + 1) % UART_TX_BUF_SIZE;
uart->tx_busy = 1;
HAL_UART_Transmit_IT(uart->huart, &byte, 1);
}
void uart_init(UART* uart, UART_HandleTypeDef* huart) {
uart->huart = huart;
uart->rx_head = 0;
uart->rx_tail = 0;
uart->tx_head = 0;
uart->tx_tail = 0;
uart->tx_busy = 0;
uart->hal_rx_byte = 0;
uart->irq_enabled = false;
}
void uart_set_irq_mode(UART* uart, bool enable) {
if (uart->huart == NULL) return;
if (enable && !uart->irq_enabled) {
HAL_NVIC_SetPriority(USART6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART6_IRQn);
uart->irq_enabled = true;
start_rx_it(uart);
} else if (!enable && uart->irq_enabled) {
HAL_UART_AbortReceive_IT(uart->huart);
HAL_UART_AbortTransmit_IT(uart->huart);
HAL_NVIC_DisableIRQ(USART6_IRQn);
uart->irq_enabled = false;
uart->tx_busy = 0;
}
}
bool uart_is_irq_mode(UART* uart) {
return uart->irq_enabled;
}
bool uart_it_try_get_byte(UART* uart, uint8_t* byte) {
if (uart->rx_tail == uart->rx_head) {
return false;
}
*byte = uart->rx_buf[uart->rx_tail];
uart->rx_tail = (uart->rx_tail + 1) % UART_RX_BUF_SIZE;
return true;
}
bool uart_it_send_byte(UART* uart, uint8_t byte) {
uint16_t next_head = (uart->tx_head + 1) % UART_TX_BUF_SIZE;
if (next_head == uart->tx_tail) {
return false;
}
uart->tx_buf[uart->tx_head] = byte;
uart->tx_head = next_head;
if (!uart->tx_busy) {
start_tx_from_buffer(uart);
}
return true;
}
void uart_it_send_string(UART* uart, const char* str) {
while (*str) {
if (!uart_it_send_byte(uart, (uint8_t)*str)) {
break;
}
str++;
}
}
uint16_t uart_it_rx_available(UART* uart) {
if (uart->rx_head >= uart->rx_tail) {
return uart->rx_head - uart->rx_tail;
}
return UART_RX_BUF_SIZE - uart->rx_tail + uart->rx_head;
}
uint16_t uart_it_tx_free(UART* uart) {
if (uart->tx_head >= uart->tx_tail) {
return UART_TX_BUF_SIZE - 1 - (uart->tx_head - uart->tx_tail);
}
return uart->tx_tail - uart->tx_head - 1;
}
void uart_rx_complete_callback(UART* uart) {
uint16_t next_head = (uart->rx_head + 1) % UART_RX_BUF_SIZE;
if (next_head != uart->rx_tail) {
uart->rx_buf[uart->rx_head] = uart->hal_rx_byte;
uart->rx_head = next_head;
}
if (uart->irq_enabled) {
start_rx_it(uart);
}
}
void uart_tx_complete_callback(UART* uart) {
uart->tx_busy = 0;
if (uart->tx_tail != uart->tx_head) {
start_tx_from_buffer(uart);
}
}
bool uart_poll_try_get_byte(UART* uart, uint8_t* byte) {
if (__HAL_UART_GET_FLAG(uart->huart, UART_FLAG_RXNE)) {
*byte = (uint8_t)(uart->huart->Instance->DR & 0xFF);
return true;
}
return false;
}
void uart_poll_send_byte(UART* uart, uint8_t byte) {
while (!__HAL_UART_GET_FLAG(uart->huart, UART_FLAG_TXE));
uart->huart->Instance->DR = byte;
}
void uart_poll_send_string(UART* uart, const char* str) {
while (*str) {
uart_poll_send_byte(uart, (uint8_t)*str);
str++;
}
while (!__HAL_UART_GET_FLAG(uart->huart, UART_FLAG_TC));
}/* USER CODE BEGIN Includes */
#include "gpio_driver.h"
#include "uart_driver.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PTD */
typedef enum {
STATE_RED,
STATE_GREEN,
STATE_GREEN_BLINKING,
STATE_YELLOW
} TrafficState;
/* USER CODE END PTD */
/* USER CODE BEGIN PD */
#define DEFAULT_TIMEOUT_SEC 4
#define GREEN_DURATION_MS 4000
#define GREEN_BLINK_DURATION_MS 4000
#define GREEN_BLINK_TOGGLE_MS 400
#define YELLOW_DURATION_MS 4000
#define CMD_BUF_SIZE 64
/* USER CODE END PD */
/* USER CODE BEGIN PV */
static UART uart6;
/* Traffic light state */
static TrafficState traffic_state = STATE_RED;
static uint32_t state_start_time = 0;
static uint32_t last_blink_time = 0;
static uint32_t red_timeout_ms = DEFAULT_TIMEOUT_SEC * 1000;
static uint8_t button_mode = 1; /* 1 = button enabled, 2 = button ignored */
static bool red_shortened = false; /* flag: next red timeout will be /4 */
/* Command buffer */
static char cmd_buf[CMD_BUF_SIZE];
static uint8_t cmd_len = 0;
/* LED and button instances */
static Button btn;
static LED red_led, green_led, yellow_led;
/* USER CODE END PV *//* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART6) {
uart_rx_complete_callback(&uart6);
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART6) {
uart_tx_complete_callback(&uart6);
}
}/* Send string (auto-selects IT or poll mode) */
static void send_str(const char *s) {
if (uart_is_irq_mode(&uart6)) {
uart_it_send_string(&uart6, s);
} else {
uart_poll_send_string(&uart6, s);
}
}
/* Send single char */
static void send_char(char c) {
if (uart_is_irq_mode(&uart6)) {
uart_it_send_byte(&uart6, (uint8_t)c);
} else {
uart_poll_send_byte(&uart6, (uint8_t)c);
}
}
/* Get state name */
static const char* get_state_name(void) {
switch (traffic_state) {
case STATE_RED: return "red";
case STATE_GREEN: return "green";
case STATE_GREEN_BLINKING: return "blinking green";
case STATE_YELLOW: return "yellow";
default: return "unknown";
}
}/* Process command */
static void process_command(void) {
cmd_buf[cmd_len] = '\0';
if (str_eq(cmd_buf, "?")) {
/* Status query */
uint32_t effective_timeout = red_shortened ? (red_timeout_ms / 4) : red_timeout_ms;
int timeout_sec = effective_timeout / 1000;
char mode_char = uart_is_irq_mode(&uart6) ? 'I' : 'P';
/* Build response manually */
send_str(get_state_name());
send_str(" mode ");
send_char('0' + button_mode);
send_str(" timeout ");
if (timeout_sec >= 100) send_char('0' + (timeout_sec / 100) % 10);
if (timeout_sec >= 10) send_char('0' + (timeout_sec / 10) % 10);
send_char('0' + (timeout_sec % 10));
send_str(" ");
send_char(mode_char);
send_str("\r\n");
} else if (str_eq(cmd_buf, "set mode 1")) {
button_mode = 1;
send_str("OK\r\n");
} else if (str_eq(cmd_buf, "set mode 2")) {
button_mode = 2;
send_str("OK\r\n");
} else if (str_starts(cmd_buf, "set timeout ")) {
int val = parse_int(cmd_buf + 12);
if (val > 0) {
red_timeout_ms = val * 1000;
send_str("OK\r\n");
} else {
send_str("ERROR: invalid timeout\r\n");
}
} else if (str_eq(cmd_buf, "set interrupts on")) {
uart_set_irq_mode(&uart6, true);
send_str("OK\r\n");
} else if (str_eq(cmd_buf, "set interrupts off")) {
uart_set_irq_mode(&uart6, false);
send_str("OK\r\n");
} else if (cmd_len > 0) {
send_str("ERROR: unknown command\r\n");
}
cmd_len = 0;
}/* Set traffic light state */
static void set_state(TrafficState new_state) {
/* Turn off all LEDs */
led_deactivate(&red_led);
led_deactivate(&green_led);
led_deactivate(&yellow_led);
traffic_state = new_state;
state_start_time = HAL_GetTick();
last_blink_time = state_start_time;
/* Turn on appropriate LED */
switch (new_state) {
case STATE_RED: led_activate(&red_led); break;
case STATE_GREEN: led_activate(&green_led); break;
case STATE_YELLOW: led_activate(&yellow_led); break;
default: break;
}
}
/* Update traffic light state machine */
static void update_traffic_light(void) {
uint32_t now = HAL_GetTick();
uint32_t elapsed = now - state_start_time;
/* Calculate effective red timeout (shortened if flag set) */
uint32_t effective_red_timeout = red_shortened ? (red_timeout_ms / 4) : red_timeout_ms;
switch (traffic_state) {
case STATE_RED:
if (elapsed >= effective_red_timeout) {
red_shortened = false; /* reset after red phase completes */
set_state(STATE_GREEN);
}
break;
case STATE_GREEN:
if (elapsed >= GREEN_DURATION_MS) {
set_state(STATE_GREEN_BLINKING);
}
break;
case STATE_GREEN_BLINKING:
if (elapsed >= GREEN_BLINK_DURATION_MS) {
set_state(STATE_YELLOW);
} else if (now - last_blink_time >= GREEN_BLINK_TOGGLE_MS) {
led_toggle(&green_led);
last_blink_time = now;
}
break;
case STATE_YELLOW:
if (elapsed >= YELLOW_DURATION_MS) {
set_state(STATE_RED);
}
break;
}
}
/* Handle button press (mode 1 only) */
static void handle_button(void) {
if (button_mode != 1) return;
if (button_is_clicked(&btn)) {
if (traffic_state == STATE_GREEN_BLINKING || traffic_state == STATE_YELLOW) {
/* First click during blink/yellow: shorten next red */
red_shortened = true;
} else if (traffic_state == STATE_RED) {
if (red_shortened) {
/* Second click during red: skip to green, reset */
red_shortened = false;
set_state(STATE_GREEN);
} else {
/* First click during red: shorten current red */
red_shortened = true;
}
}
}
}int main(void)
{
/* Инициализация HAL, системной тактовой частоты, периферии */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART6_UART_Init();
/* USER CODE BEGIN 2 */
uart_init(&uart6, &huart6);
/* Initialize button and LEDs */
btn.GPIOx = GPIOC;
btn.GPIO_Pin = GPIO_PIN_15;
btn.last_pressed_time = 0;
red_led.GPIOx = GPIOD;
red_led.GPIO_Pin = GPIO_PIN_15;
green_led.GPIOx = GPIOD;
green_led.GPIO_Pin = GPIO_PIN_13;
yellow_led.GPIOx = GPIOD;
yellow_led.GPIO_Pin = GPIO_PIN_14;
/* Start in IRQ mode */
uart_set_irq_mode(&uart6, true);
/* Initialize traffic light */
set_state(STATE_RED);
send_str("Traffic light ready\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint8_t c;
while (1)
{
/* Update traffic light state machine */
update_traffic_light();
/* Handle button (mode 1 only) */
handle_button();
/* Process UART input */
bool got_char = false;
if (uart_is_irq_mode(&uart6)) {
got_char = uart_it_try_get_byte(&uart6, &c);
} else {
got_char = uart_poll_try_get_byte(&uart6, &c);
}
if (got_char) {
/* Echo the character */
send_char(c);
if (c == '\r' || c == '\n') {
/* End of command - process it */
send_str("\r\n");
process_command();
} else if (c == 127 || c == 8) {
/* Backspace */
if (cmd_len > 0) {
cmd_len--;
send_str("\b \b");
}
} else if (cmd_len < CMD_BUF_SIZE - 1) {
/* Add to buffer */
cmd_buf[cmd_len++] = c;
}
}
}
/* USER CODE END WHILE */
}В ходе выполнения лабораторной работы был изучен протокол асинхронной последовательной передачи данных UART, реализован драйвер для приема и передачи данных по протоколы UART, поддерживающий режим опроса и прерывания.
