Последовательный порт, прямой доступ к памяти и консоль … ↩
mod. Четверг, 15 октября 2026 22:55 . См. 9
BeeFC · Урок S7 из 33 · Уровень «Старт»
Как устроен обмен по последовательному порту, почему крупные прошивки принимают данные через контроллер прямого доступа к памяти, как без блокировок передавать байты из обработчика прерывания в основную программу и как построить консоль с командами.
После этого урока BeeFC можно управлять с компьютера: консоль принимает команды status, get, set и bl. Через ту же консоль в уроке S10 будет управляться тяговый стенд.
Содержание
1. Цели урока
Продолжительность урока — четыре часа: 1,5 часа теории и 2,5 часа практики. После урока слушатель:
- рассчитывает делитель скорости последовательного порта и оценивает погрешность;
- настраивает приём по прерыванию и через контроллер прямого доступа к памяти;
- реализует кольцевой буфер для передачи данных между обработчиком прерывания и задачей без запрета прерываний;
- строит консоль с таблицей команд и проверяет разбор команд модульными тестами.
2. Термины и сокращения
| Термин | Определение |
|---|---|
| универсальный асинхронный приёмопередатчик (UART) | блок последовательного обмена без общего тактового сигнала; в STM32 называется USART |
| бод | единица скорости передачи: число символов в секунду, для UART — бит в секунду |
| прямой доступ к памяти (DMA) | передача данных между периферией и памятью без участия процессора |
| кольцевой буфер | массив, в котором запись и чтение идут по кругу, а индексы переходят с конца на начало |
| интерфейс командной строки (CLI) | текстовая консоль для управления прошивкой |
| виртуальный последовательный порт (VCP) | порт USB, который компьютер видит как последовательный |
| барьер компилятора | указание, запрещающее компилятору переставлять обращения к памяти |
3. Последовательный порт
Формат кадра
В формате 8N1 каждый байт передаётся кадром из десяти битов: стартовый бит с низким уровнем, восемь битов данных начиная с младшего и стоповый бит с высоким уровнем. Линия в покое находится в высоком уровне. На скорости 115 200 бод передаётся 11 520 байт в секунду, то есть один байт примерно за 87 мкс.
Выход передатчика одного устройства соединяется со входом приёмника другого, общий провод обязателен. Уровни сигналов STM32 — 3,3 В. Преобразователь USB–UART должен работать с уровнями 3,3 В: подключение к выходу с уровнем 5 В может повредить вывод микроконтроллера, который не допускает такого напряжения.

Делитель скорости
При стандартной передискретизации в шестнадцать раз значение регистра BRR равно частоте тактирования порта, делённой на скорость, с округлением до целого.
\( BRR = \frac{84\,000\,000}{115\,200} \approx 729{,}17 \to 729, \quad f_{факт} = \frac{84\,000\,000}{729} \approx 115\,226\ \text{бод} \)
Погрешность составляет около 0,02 %. Приёмник UART допускает расхождение скоростей в несколько процентов, поэтому такая точность с запасом достаточна.
Файл hal/stm32f4/uart_stm32.c
4. Кольцевой буфер
Обработчик прерывания помещает принятый байт в буфер, задача консоли забирает его оттуда. Если запись выполняет только обработчик, а чтение — только задача, запрещать прерывания не нужно. Индекс записи head изменяет только производитель, индекс чтения tail — только потребитель.
Файл core/ringbuf.h
Файл core/ringbuf.c
Индексы растут без ограничения и переходят через ноль вместе с типом uint16_t. Разность head - tail при этом остаётся верной по той же причине, что и разность времени в уроке S6. Размер буфера — степень двойки, поэтому положение в массиве вычисляется маской, а не делением.
head. Иначе потребитель увидит новый индекс и прочитает старое содержимое ячейки. Квалификатор volatile у индекса не запрещает компилятору перенести запись в обычный массив за запись индекса. Поэтому между ними стоит барьер компилятора RB_BARRIER.Файл tests/unit/test_level1.c

head, задача консоли читает по индексу tail.5. Прямой доступ к памяти
При приёме по прерыванию процессор отвлекается на каждый байт. На скорости 115 200 бод это происходит раз в 87 мкс, на скорости 420 000 бод, которая применяется для приёмников ExpressLRS в уроке P5, — раз в 24 мкс. Контроллер прямого доступа к памяти переносит байты из регистра данных порта в буфер сам, а процессор забирает их, когда ему удобно.
В STM32F405 приём USART1 обслуживает контроллер DMA2, поток 2, канал 4. В кольцевом режиме контроллер записывает байты по кругу, а регистр NDTR показывает, сколько байт осталось до конца буфера. По нему вычисляется позиция записи.
Файл hal/stm32f4/uart_stm32.c
Если задача консоли не забирает данные дольше, чем требуется для заполнения 256 байт, контроллер перезапишет непрочитанные данные. На скорости 115 200 бод это примерно 22 мс, что с большим запасом перекрывает период задачи консоли 10 мс.
↑ К оглавлению6. Консоль
Разбор строки
Строка команды делится на слова на месте: пробелы заменяются нулевыми символами, а в массив argv записываются указатели на начала слов. Знак «=» выделяется в отдельное слово, поэтому записи set gyro_hz=1000 и set gyro_hz = 1000 разбираются одинаково.
Файл core/cli_parse.h
Файл core/cli_parse.c
Файл tests/unit/test_level1.c
Команды BeeFC
| Команда | Действие |
|---|---|
status | версия прошивки, время работы, загрузка процессора, число опозданий задач |
tasks | таблица задач: период, число запусков, наибольшее время выполнения |
get имя | вывести значение параметра |
set имя = значение | изменить значение параметра в ОЗУ |
bl | перезагрузка во встроенный загрузчик для загрузки новой прошивки |
Перезагрузка в загрузчик
Команда bl избавляет от необходимости нажимать кнопку BOOT. Прошивка записывает признак в область ОЗУ, которую код запуска не обнуляет, и выполняет программный сброс. После сброса код запуска находит признак, стирает его и передаёт управление загрузчику по адресу 0x1FFF0000: устанавливает указатель стека из первого слова загрузчика и переходит по адресу из второго слова.
7. Практика
Задание S7.1. Кольцевой буфер
Продолжительность — 30 минут, выполняется на компьютере.
Реализовать модуль core/ringbuf по интерфейсу из урока.
Критерий автоматической проверки: тест test_ringbuf_wraps_and_reports_overflow пройден; скрытый тест выполняет 70 000 записей, чтобы индексы перешли через ноль.
Задание S7.2. Разбор команд и таблица команд
Продолжительность — 40 минут, выполняется на компьютере.
Реализовать функцию cli_tokenize и таблицу команд из раздела «Команды BeeFC». Каждая команда — строка таблицы с именем, кратким описанием и функцией-обработчиком. Команда help выводит таблицу.
Критерий автоматической проверки: тесты test_cli_* пройдены; скрытые тесты проверяют неизвестную команду, пустую строку и строку длиннее буфера.
Задание S7.3. Консоль на плате
Продолжительность — 40 минут, требуется преобразователь USB–UART.
Подключить консоль к задаче планировщика с частотой 100 Гц. При запуске прошивка выводит строку приветствия с версией.
Критерий автоматической проверки: в модели Renode первая строка, принятая с USART1, начинается с BeeFC; на команду status приходит ответ не позднее чем через 20 мс.
Задание S7.4. Приём через прямой доступ к памяти
Продолжительность — 40 минут.
Перевести приём на контроллер DMA и добавить в планировщик задачу длительностью 2 мс. Убедиться, что консоль не теряет символы при вставке длинной строки из буфера обмена.
Критерий автоматической проверки: в модели Renode в порт передаётся 1000 байт подряд на скорости 115 200 бод, все байты приняты без потерь.
Задание S7.5. Виртуальный последовательный порт (дополнительное)
Продолжительность — по желанию слушателя.
Подключить консоль к порту USB платы через библиотеку TinyUSB, чтобы работать без преобразователя USB–UART. Задание не влияет на зачёт уровня.
8. Как это сделано в больших прошивках
| Прошивка | Версия | Файл в исходном коде |
|---|---|---|
| Betaflight | 2026.6.2 | src/main/drivers/serial_uart.c, src/platform/STM32/serial_uart_stm32f4xx.c, src/main/cli/cli.c |
| INAV | 9.1.0 | src/main/drivers/serial_uart.c |
| ArduPilot | Copter-4.7.2 | libraries/AP_HAL_ChibiOS/UARTDriver.cpp |
| PX4 | v1.17.0 | platforms/common/Serial.cpp, platforms/nuttx/src/px4/common/SerialImpl.cpp |
- В Betaflight общий код последовательных портов находится в
serial_uart.c, а работа с регистрами и потоками DMA для F4 — вserial_uart_stm32f4xx.c. Консоль описана таблицейcmdTable[]в файлеcli.c, командаblв ней перезагружает плату в загрузчик. - В INAV устройство драйвера
serial_uart.cтакое же, как в Betaflight. - Драйвер
UARTDriver.cppв ArduPilot принимает данные через DMA в два промежуточных буфераrx_bounce_buf, память для которых выделяется с признакомMEM_DMA_SAFE, то есть заведомо не в CCM. Пока DMA заполняет один буфер, содержимое другого переносится в основной кольцевой буфер. - В PX4 последовательные порты обслуживает драйвер операционной системы NuttX, а класс
SerialизSerial.cppи его реализацияSerialImpl.cppдают модулям прошивки одинаковый интерфейс на любой платформе.
9. Проверьте себя
- Сколько времени занимает передача одного байта в формате 8N1 на скорости 115 200 бод?
- Почему кольцевой буфер с одним производителем и одним потребителем работает без запрета прерываний?
- Зачем в функции
rb_pushнужен барьер компилятора? - Как определить позицию записи DMA в кольцевом режиме?
- Почему буфер приёма DMA нельзя размещать в CCM?
10. Чек-лист самопроверки
11. Источники
- Справочное руководство RM0090, раздел 10, контроллер DMA, и раздел 30, USART.
- Указания по применению AN4031: контроллер DMA в STM32F2, STM32F4 и STM32F7.
- Указания по применению AN2606: встроенный загрузчик STM32.
- Консоль Betaflight.
- Библиотека TinyUSB.
12. Что дальше
- Следующий урок: S8. Таймеры и широтно-импульсная модуляция: первый запуск двигателя.
- Полный перечень уроков приведён в статье «Курс BeeFC: содержание уровня „Старт“».
Проверено: 13.10.2026
↑ К оглавлению- мод. : Четверг, 15 октября 2026