Язык C для встраиваемых систем и архитектура BeeFC … ↩
mod. Четверг, 15 октября 2026 22:55 . См. 7
BeeFC · Урок S4 из 33 · Уровень «Старт»
Какие возможности языка C особенно важны при программировании микроконтроллеров, как разделить прошивку на слои и почему благодаря этому почти весь код BeeFC проверяется модульными тестами на компьютере без платы.
Начиная с этого урока ядро BeeFC не обращается к регистрам напрямую. Правило проверяется автоматически при каждой отправке изменений.
Содержание
1. Цели урока
Продолжительность урока — четыре часа: один час теории и три часа практики. После урока слушатель:
- применяет типы фиксированного размера, квалификатор
volatileи битовые операции по назначению; - объясняет правила зависимостей между слоями BeeFC;
- пишет модуль ядра, который работает через интерфейс уровня аппаратных абстракций;
- пишет и запускает модульные тесты с поддельной реализацией портов ввода-вывода.
2. Термины и сокращения
| Термин | Определение |
|---|---|
| уровень аппаратных абстракций (HAL) | набор функций, скрывающих от остального кода особенности конкретного микроконтроллера |
| интерфейс | заголовочный файл с объявлениями функций без их реализации |
| поддельная реализация | реализация интерфейса для компьютера, которая запоминает вызовы вместо работы с регистрами |
| модульный тест | функция, проверяющая поведение одного модуля при заданных входных данных |
| Unity | библиотека модульного тестирования на языке C |
квалификатор volatile | указание компилятору, что значение переменной может измениться без участия текущего кода |
| атомарная операция | операция, которую не может прервать обработчик прерывания |
3. Язык C во встраиваемых системах
Прошивка BeeFC написана на языке C стандарта C99. Курс не повторяет основы языка, а разбирает возможности, без которых программирование микроконтроллера невозможно. Слушателям, не прошедшим входной тест по языку C, предлагается подготовительный модуль «C для BeeFC» с автоматической проверкой.
Типы фиксированного размера
Размер типа int стандарт языка не фиксирует: на микроконтроллере он равен 32 битам, на некоторых других процессорах — 16. Регистры периферии имеют точно заданную разрядность, поэтому в BeeFC используются типы из заголовочного файла stdint.h.
| Тип | Разрядность | Применение в BeeFC |
|---|---|---|
uint8_t | 8 бит | байты протоколов, номера выводов |
int16_t | 16 бит | показания датчиков |
uint32_t | 32 бита | время в микросекундах, значения регистров |
float | 32 бита | фильтры и регуляторы |
double выполняются программно и в десятки раз медленнее. Поэтому константы в BeeFC записываются с суффиксом f: 0.5f, а не 0.5. Без суффикса выражение x * 0.5 вычисляется в двойной точности.Квалификатор volatile
Компилятор вправе прочитать переменную один раз и дальше использовать сохранённое значение. Для обычной переменной это правильная оптимизация. Для регистра периферии или переменной, которую меняет обработчик прерывания, это ошибка: цикл ожидания никогда не закончится. Квалификатор volatile запрещает компилятору такую оптимизацию, и каждое обращение выполняется заново.
Квалификатор volatile не делает операцию атомарной. Выражение counter++ для переменной с квалификатором volatile по-прежнему состоит из трёх инструкций: чтения, сложения и записи. Если между ними сработает прерывание, изменяющее ту же переменную, одно изменение потеряется.
Битовые операции
| Действие | Выражение | Пример |
|---|---|---|
| установить бит | reg |= (1u << n) | включить тактирование порта |
| сбросить бит | reg &= ~(1u << n) | выключить прерывание |
| переключить бит | reg ^= (1u << n) | переключить светодиод |
| проверить бит | (reg >> n) & 1u | прочитать флаг готовности |
| записать поле | reg = (reg & ~mask) | (value << pos) | выбрать режим вывода в MODER |
Суффикс u у единицы обязателен: сдвиг знакового числа 1 << 31 даёт неопределённое поведение по стандарту языка.
Правила BeeFC
- Динамическое выделение памяти функцией
mallocне используется: вся память распределяется при компиляции, и её объём виден в выводе утилитыsize. - Рекурсия не используется: глубина стека должна быть известна заранее.
- Глобальные переменные модуля объявляются с квалификатором
staticи недоступны другим модулям. - Таблицы констант объявляются с квалификатором
constи размещаются во флеш-памяти, а не в ОЗУ.
4. Архитектура BeeFC
Правила зависимостей
Каждый слой BeeFC может обращаться только к слоям ниже себя. Ядро и драйверы видят только интерфейсы уровня аппаратных абстракций из каталога hal/include и ничего не знают о регистрах STM32.
| Слой | Может включать | Не может включать |
|---|---|---|
app | все слои | — |
core | hal/include, другие модули core | stm32f4xx.h, core_cm4.h, drivers |
drivers | hal/include, core | stm32f4xx.h, core_cm4.h |
hal/stm32f4 | CMSIS, hal/include | core, drivers |
hal/fake | hal/include | CMSIS |
Команда make arch-check ищет в каталогах core и drivers включение заголовочных файлов микроконтроллера. Если такое включение найдено, проверка не пройдена.

hal/include.Интерфейс портов ввода-вывода
Файл hal/include/hal_gpio.h
Вывод в BeeFC обозначается одним числом: номер порта, умноженный на 16, плюс номер бита. Вывод PB5 имеет номер \( 1 \cdot 16 + 5 = 21 \).
Реализация для STM32
Файл hal/stm32f4/gpio_stm32.c
Запись уровня выполняется через регистр BSRR, а не через ODR. Запись в ODR требует чтения, изменения и записи регистра. Если между чтением и записью прерывание изменит другой вывод того же порта, его изменение будет потеряно. Запись в BSRR выполняется одной инструкцией и затрагивает только выбранный вывод.
Поддельная реализация для тестов
Поддельная реализация хранит состояние выводов в массиве и считает число записей. Функции-перехватчики позволяют тесту подставить своё поведение: например, имитировать микросхему, которая отвечает на импульсы тактового сигнала. Такой перехватчик понадобится в уроке S10 для драйвера тензодатчика.
Файл hal/fake/gpio_fake.h
5. Модульные тесты
Модуль светодиода — самый простой модуль ядра. На многих платах светодиод включается низким уровнем, и модуль скрывает эту особенность от остального кода.
Файл core/led.h
Файл core/led.c
Тест создаёт модуль, вызывает его функции и проверяет состояние поддельного вывода. Каждый тест построен по одной схеме: подготовка, действие, проверка.
Файл tests/unit/test_level1.c
Proteus и Renode
Пакет Proteus моделирует микроконтроллер STM32F401, а не STM32F405. Порты ввода-вывода у них устроены одинаково, поэтому реализацию hal/stm32f4/gpio_stm32.c можно проверить в Proteus с моделью светодиода и кнопки. Для слушателей без Proteus те же проверки выполняет Renode на сервере автоматической проверки.
6. Практика
Задание S4.1. Модуль светодиода
Продолжительность — 40 минут, выполняется на компьютере.
Реализовать функции модуля core/led.c так, чтобы тест test_led_active_low_inverts_level был пройден. Модуль не должен включать заголовочные файлы микроконтроллера.
Критерий автоматической проверки: тест пройден, проверка make arch-check пройдена.
Задание S4.2. Модуль звукового сигнализатора
Продолжительность — 80 минут, выполняется на компьютере.
Создать модуль core/beeper с функциями beeper_init, beeper_play и beeper_update. Функция beeper_play принимает последовательность длительностей звучания и пауз в миллисекундах, функция beeper_update вызывается периодически и переключает вывод. Написать не менее трёх собственных тестов.
Критерий автоматической проверки: собственные тесты пройдены; скрытые тесты сервера проверяют последовательность из трёх сигналов, повторный вызов beeper_play во время звучания и инверсный вывод.
Задание S4.3. Светодиод через уровень аппаратных абстракций
Продолжительность — 60 минут.
Перевести программу мигания на модуль led и реализацию hal/stm32f4/gpio_stm32.c. В функции main не должно остаться обращений к регистрам портов.
Критерий автоматической проверки: прошивка собирается, в модели Renode вывод светодиода переключается.
7. Как это сделано в больших прошивках
| Прошивка | Версия | Файл в исходном коде |
|---|---|---|
| Betaflight | 2026.6.2 | src/main/drivers/io.c, src/platform/STM32/io_stm32.c, src/platform/AT32 |
| INAV | 9.1.0 | src/main/drivers/io.c |
| ArduPilot | Copter-4.7.2 | libraries/AP_HAL/GPIO.h, libraries/AP_HAL_ChibiOS/GPIO.cpp |
| PX4 | v1.17.0 | platforms/common/include/px4_platform_common/Serial.hpp |
- В Betaflight вывод представлен типом
IO_t, а функцииIOWrite,IOReadиIOConfigGPIOобъявлены в общем файлеio.c. Их реализация для STM32 находится в каталогеsrc/platform/STM32, а для микроконтроллеров Artery — в каталогеsrc/platform/AT32. Так одна прошивка поддерживает микроконтроллеры разных производителей. - В INAV устройство модуля
io.cтакое же, как в Betaflight: обе прошивки унаследовали его от Cleanflight. - В ArduPilot уровень аппаратных абстракций построен на классах языка C++. Файл
AP_HAL/GPIO.hобъявляет абстрактный класс с виртуальными методами, аAP_HAL_ChibiOS/GPIO.cppреализует его для ChibiOS. Для программной имитации существует отдельная реализация, поэтому ArduPilot целиком запускается на компьютере. - В PX4 платформенно-независимые интерфейсы собраны в каталоге
px4_platform_common; файлSerial.hpp— пример интерфейса последовательного порта, у которого есть реализации для NuttX и для Linux.
8. Проверьте себя
- Почему в BeeFC используются типы из
stdint.h, а неintиlong? - Делает ли квалификатор
volatileоперациюcounter++атомарной? - Почему запись уровня вывода выполняется через
BSRR, а не черезODR? - Какой заголовочный файл нельзя включать в модули каталога
coreи почему? - Почему константа
0.5без суффикса замедляет вычисления на Cortex-M4F?
9. Чек-лист самопроверки
10. Источники
- Библиотека модульного тестирования Unity.
- Справочное руководство RM0090, раздел 8.4.7 о регистре
GPIOx_BSRR. - Руководство программиста PM0214, раздел о сопроцессоре плавающей точки.
- Руководство разработчика ArduPilot: уровень аппаратных абстракций.
- Пакет Proteus компании Labcenter Electronics.
11. Что дальше
- Следующий урок: S5. Порты ввода-вывода, прерывания и контроллер прерываний NVIC.
- Полный перечень уроков приведён в статье «Курс BeeFC: содержание уровня „Старт“».
Проверено: 13.10.2026
↑ К оглавлению- мод. : Четверг, 15 октября 2026