Skip to content

butvinm-itmo/computing-lab2

Repository files navigation

Логотип Университета ИТМО

Федеральное государственное автономное образовательное учреждение высшего образования

Университет ИТМО

Проектирование вычислительных систем

Лабораторная работа №2

"Последовательный интерфейс UART"

Вариант 1

Выполнили:

Бутвин Михаил Павлович, Группа P3430

Хабнер Георгий Евгеньевич, Группа P3431

СПб – 2025

1. Задание к лабораторной работе

Вариант 1: Управление светофором через UART

Доработать программу «светофор», добавив возможности отключения кнопки и задания величины тайм-аута (период, в течение которого горит красный).

Команды UART (скорость 57600 бит/с):

Команда Описание
? Запрос текущего состояния светофора
set mode 1 Включить обработку нажатий кнопки
set mode 2 Отключить обработку нажатий кнопки
set timeout X Установить длительность красного света (X – период в секундах)
set interrupts on Включить режим прерываний для UART
set interrupts off Выключить режим прерываний (переключиться на режим опроса)

Требования к реализации:

  1. Разработать два варианта драйверов UART:

    • С использованием прерываний: неблокирующий режим работы с буферизацией данных для предотвращения потери данных
    • Без использования прерываний (режим опроса): функция приема данных не должна блокироваться в ожидании данных
  2. Каждый принимаемый символ должен отсылаться обратно (эхо)

  3. Каждое новое сообщение должно выводиться с новой строки

  4. На каждую команду должен выводиться ответ «OK» или сообщение об ошибке для неподдерживаемых команд

  5. Скорость работы UART: 57600 бит/с


2. Цели работы

  1. Изучить протокол передачи данных по интерфейсу UART

    • Асинхронная передача данных
    • Конфигурация скорости передачи (baud rate)
    • Структура фрейма данных (стартовый бит, биты данных, стоповый бит)
  2. Получить базовые знания об организации системы прерываний в микроконтроллерах

    • Контроллер прерываний NVIC (Nested Vectored Interrupt Controller)
    • Приоритеты прерываний
    • Обработчики прерываний (ISR - Interrupt Service Routines)
  3. Изучить устройство и принципы работы контроллера интерфейса UART

    • Регистры USART микроконтроллера STM32F4
    • Флаги состояния (RXNE, TXE, TC)
    • Режимы работы UART
  4. Получить навыки организации обмена данными по UART в режимах опроса и прерываний

    • Реализация неблокирующего ввода-вывода
    • Буферизация данных с использованием кольцевых буферов
    • Переключение между режимами работы в runtime
  5. Разработать управляющую программу для светофора с командным интерфейсом

    • Автомат состояний для управления светофором
    • Парсинг и обработка команд UART
    • Интеграция кнопочного ввода и UART-управления

3. Описание организации программы

3.1 Архитектура приложения

Программа имеет следующую структуру:

Приложение
├── 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

3.2 Структура UART драйвера

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;

Режим прерываний (Interrupt Mode)

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

  • При поступлении байта 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)

Режим опроса (Polling Mode)

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

  • Программа периодически проверяет флаги 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):

При включении режима прерываний:

  1. Устанавливает приоритет прерывания USART6: HAL_NVIC_SetPriority(USART6_IRQn, 0, 0)
  2. Включает прерывание в NVIC: HAL_NVIC_EnableIRQ(USART6_IRQn)
  3. Устанавливает флаг irq_enabled = true
  4. Запускает прием одного байта: HAL_UART_Receive_IT(huart, &hal_rx_byte, 1)

При выключении режима прерываний:

  1. Прерывает текущие операции HAL: HAL_UART_AbortReceive_IT(), HAL_UART_AbortTransmit_IT()
  2. Отключает прерывание в NVIC: HAL_NVIC_DisableIRQ(USART6_IRQn)
  3. Сбрасывает флаг irq_enabled = false
  4. Сбрасывает флаг передачи tx_busy = 0

Безопасность переключения:

  • Буферы не очищаются при переключении (данные сохраняются)
  • Главная программа автоматически переключается на соответствующие функции чтения/записи

Неблокирующий API в обоих режимах

  • RX функции всегда неблокирующие (возвращают false, если нет данных)
  • TX в режиме IT неблокирующая, в режиме Polling – блокирующая (приемлемо для простых приложений)

3.3 Автомат состояний светофора

Программа реализует классический автомат состояний (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):

  1. Выключает все светодиоды
  2. Устанавливает новое состояние traffic_state = new_state
  3. Запоминает время входа state_start_time = HAL_GetTick()
  4. Включает соответствующий светодиод

3.4 Обработка команд UART

Программа реализует простой построчный парсер команд.

Схема обработки

  1. Главный цикл читает байты из UART (IT или Polling в зависимости от режима)

  2. Эхо каждого символа обратно пользователю

  3. Специальная обработка символов:

    • CR (\r) или LF (\n): конец команды → вызов process_command()
    • Backspace (127 или 8): удаление последнего символа из буфера
    • Остальные символы: добавление в буфер команды
  4. Парсинг команды в 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() в зависимости от текущего режима.


4. Алгоритмы и блок-схемы

4.1 Диаграмма состояний светофора

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
Loading

Описание:

  • Автомат имеет 4 состояния, образующих замкнутый цикл
  • Каждое состояние активирует соответствующий светодиод
  • Переходы происходят по истечении фиксированных таймаутов
  • Состояние RED имеет переменную длительность (может быть сокращена кнопкой)
  • Из состояния RED возможен досрочный переход при двойном клике кнопки

4.2 Блок-схема работы UART драйвера

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
Loading

Описание:

  • Алгоритм разветвляется в зависимости от текущего режима UART (IT или Polling)
  • Режим прерываний: неблокирующее чтение из кольцевого буфера, заполняемого обработчиком прерываний
  • Режим опроса: прямая проверка флага RXNE регистра статуса UART
  • Обе ветви обеспечивают неблокирующее поведение (возвращают false, если данных нет)
  • После получения полной команды (по символу \r или \n) происходит парсинг и выполнение соответствующего действия
  • Каждая команда генерирует ответ (статус, "OK" или сообщение об ошибке)

4.3 Поток данных в режиме прерываний

Для полноты картины, дополнительная схема показывает взаимодействие между прерываниями, буферами и приложением:

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
Loading

5. Исходный код

В данном разделе приведены исходные коды собственной разработки. Код из стандартных библиотек HAL и автогенерированный код STM32CubeMX не включены.

5.1 Заголовочный файл UART драйвера (uart_driver.h)

#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_ */

5.2 Реализация UART драйвера (uart_driver.c)

#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));
}

5.3 Главная программа (ключевые фрагменты main.c)

Определение типов и глобальных переменных

/* 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 */

HAL колбэки для UART

/* 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);
    }
}

Вспомогательные функции для UART

/* 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 */
}

6. Выводы

В ходе выполнения лабораторной работы был изучен протокол асинхронной последовательной передачи данных UART, реализован драйвер для приема и передачи данных по протоколы UART, поддерживающий режим опроса и прерывания.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages