395 lines
29 KiB
Markdown
395 lines
29 KiB
Markdown
|
|
Слушай, хочешь расскажу про операционки реального времени? Тема на первый взгляд сложная, но на самом деле это как диспетчер на стройке, только для крошечных мозгов микроконтроллеров. Я сам когда-то путался, а сейчас объясню так, что сам будешь друзьям пересказывать.
|
|||
|
|
|
|||
|
|
## Как вообще появились эти RTOS?
|
|||
|
|
|
|||
|
|
На самом деле идее уже полвека. В 60-х годах, когда NASA собиралось лететь на Луну, им понадобился компьютер, который мог бы обрабатывать кучу датчиков и реагировать мгновенно — никаких тебе «подожди, я загружаюсь». Так появилась первая RTOS для Apollo Guidance Computer. Потом, в 80–90-х, подтянулись военные и промышленники: VxWorks, QNX, pSOS — всё это было зверски дорого и закрыто. Обычному студенту или стартапу такое не светило. И только в 2003 году парень по имени Ричард Бэрри сказал: «А почему бы не сделать бесплатную и открытую RTOS для всех?» Так родился FreeRTOS. Сейчас она принадлежит Amazon, но код по‑прежнему бесплатный, и её ставят куда угодно — от китайских датчиков до марсоходов.
|
|||
|
|
|
|||
|
|
## Суть на пальцах
|
|||
|
|
|
|||
|
|
Представь, что ты бригадир на стройке, а у тебя в распоряжении всего одна дрель. И куча рабочих, которым эта дрель нужна: один должен дыру сверлить, другой — шуруп закрутить, третий — вообще срочно выключатель починить, а то проводка заискрит. Обычная операционка (как Windows на твоём ноуте) сказала бы: «Ждите, пока первый закончит, потом второй, потом третий». Но если третий — экстренный случай, проводка уже дымится, а Windows ещё и «Пожалуйста, подождите, устанавливаются обновления» — пиши пропало.
|
|||
|
|
|
|||
|
|
RTOS работает иначе. Она назначает каждому заданию **приоритет**. Если задача с самым высоким приоритетом (потушить пожар) просыпается, планировщик тут же отбирает дрель у того, кто сверлит, и отдаёт спасателю. Как только опасность устранена — возвращаем дрель обратно. Всё это происходит за миллисекунды, и никто не замечает подмены.
|
|||
|
|
|
|||
|
|
## Как это устроено внутри (схема)
|
|||
|
|
|
|||
|
|
Чтобы ты визуально понял, набросал схемку. Тут три задачи с разными приоритетами, и планировщик решает, кто сейчас работает.
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
Start[Старт системы] --> Init[Инициализация RTOS]
|
|||
|
|
Init --> Scheduler{Планировщик}
|
|||
|
|
|
|||
|
|
subgraph "Задачи"
|
|||
|
|
Task1[Задача 1: выс. приоритет<br>Аварийное отключение]
|
|||
|
|
Task2[Задача 2: ср. приоритет<br>Чтение датчика]
|
|||
|
|
Task3[Задача 3: низк. приоритет<br>Мигание светодиодом]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
Scheduler -->|приор. 1| Task1Running[Выполняется Задача1]
|
|||
|
|
Scheduler -->|приор. 2| Task2Running[Выполняется Задача2]
|
|||
|
|
Scheduler -->|приор. 3| Task3Running[Выполняется Задача3]
|
|||
|
|
|
|||
|
|
Task1Running --> Event{Прерывание таймера<br>или событие}
|
|||
|
|
Task2Running --> Event
|
|||
|
|
Task3Running --> Event
|
|||
|
|
Event --> Scheduler
|
|||
|
|
|
|||
|
|
style Scheduler fill:#f9f,stroke:#333,stroke-width:2px
|
|||
|
|
style Event fill:#bbf,stroke:#333
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждая задача может находиться в одном из состояний: «Готова к работе» (Ready), «Выполняется» (Running) или «Ждёт события» (Blocked). Планировщик (это розовый ромбик) постоянно смотрит: кто сейчас самый важный из готовых? И переключает контекст — сохраняет регистры одной задачи, загружает регистры другой. Это происходит так быстро, что кажется, будто задачи работают одновременно.
|
|||
|
|
|
|||
|
|
## Живой пример: кофемашина с экраном
|
|||
|
|
|
|||
|
|
Представь умную кофемашину. В ней одновременно должны работать:
|
|||
|
|
|
|||
|
|
- **Задача 1 (Термостат):** следить за температурой воды. Если вода остыла — срочно включить нагрев. Приоритет — **самый высокий**, иначе кофе будет холодным.
|
|||
|
|
- **Задача 2 (Кнопки и дисплей):** реагировать на нажатия, рисовать анимацию. Приоритет средний — можно чуть‑чуть подождать, пока вода греется.
|
|||
|
|
- **Задача 3 (Логирование статистики):** записывать во flash-память, сколько чашек сварили. Приоритет низкий — делать, когда все важные дела переделаны.
|
|||
|
|
|
|||
|
|
RTOS следит, чтобы нагреватель включался моментально, а мигание экрана никого не отвлекало. Если ты нажмёшь кнопку «Капучино», система среагирует за десятки миллисекунд — никаких тебе «зависших» интерфейсов.
|
|||
|
|
|
|||
|
|
## FreeRTOS против коммерческих монстров
|
|||
|
|
|
|||
|
|
Ты наверняка слышал про VxWorks (ставят в беспилотники и марсоходы) или QNX (используется в автомобилях и медицинских приборах). Они реально мощные, но:
|
|||
|
|
|
|||
|
|
- **Цена:** лицензии стоят миллионы долларов, документация — под NDA.
|
|||
|
|
- **Сложность:** чтобы просто запустить пример, нужна команда профи.
|
|||
|
|
|
|||
|
|
FreeRTOS же:
|
|||
|
|
|
|||
|
|
- **Бесплатный и открытый:** можешь залезть в код и посмотреть, как он там тикает.
|
|||
|
|
- **Легчайший:** ядро занимает 4–9 килобайт оперативки. Помещается даже в самый дешёвый STM32 за 50 рублей.
|
|||
|
|
- **Массовый:** если возникнет проблема — гугл выдаст тысячу готовых решений. На нём строят свои проекты и студенты, и SpaceX.
|
|||
|
|
|
|||
|
|
Поэтому, если ты хочешь сделать умный замок, метеостанцию или робота‑пылесоса, бери FreeRTOS. Это индустриальный стандарт, с которым ты не пропадёшь.
|
|||
|
|
|
|||
|
|
## Итог
|
|||
|
|
|
|||
|
|
RTOS — это не какая‑то страшная магия, а просто очень дисциплинированный диспетчер, который не даёт задачам мешать друг другу. Он знает, что важно сейчас, а что может подождать. Благодаря ему даже дешёвый микроконтроллер способен одновременно варить кофе, рисовать на экране и записывать статистику — и всё это с гарантированным временем отклика.
|
|||
|
|
|
|||
|
|
Если хочешь, следующим заходом расскажу, как с нуля собрать проект на FreeRTOS в CubeIDE или VSCode. Там тоже всё просто, главное — понять принцип.
|
|||
|
|
|
|||
|
|
# Создание проекта с FreeRTOS
|
|||
|
|
О, отличный запрос! Из теории про бригадира с дрелью мы переходим к суровой практике — как этого "диспетчера" (FreeRTOS) поселить в твоём VSCode и заставить работать на железе. Расскажу по шагам, как я сам это делаю.
|
|||
|
|
|
|||
|
|
## 🎯 Самый простой и современный путь (рекомендую)
|
|||
|
|
|
|||
|
|
Если честно, раньше это была боль — надо было ручками пути прописывать, компиляторы настраивать. Но сейчас STMicroelectronics сделала офигенное официальное расширение **"STM32Cube for Visual Studio Code"** . Оно берёт на себя всю грязную работу.
|
|||
|
|
|
|||
|
|
### Что ставим
|
|||
|
|
|
|||
|
|
1. **STM32CubeMX** — это программа, где мы мышкой тыкаем, какие ножки на что задействовать и включаем FreeRTOS галочкой.
|
|||
|
|
2. **VSCode** с расширением "STM32Cube for VS Code" (ищешь прямо в маркете расширений).
|
|||
|
|
3. **ARM тулчейн** — проще всего поставить **STM32CubeCLT** (Command Line Tools) с сайта ST. Там и компилятор, и программатор в одном флаконе .
|
|||
|
|
|
|||
|
|
### Процесс сборки проекта
|
|||
|
|
|
|||
|
|
**Шаг 1. Генерируем основу в CubeMX**
|
|||
|
|
Открываешь CubeMX, выбираешь свой контроллер (например, STM32F103C8 — классика для старта). Во вкладке `Pinout` находишь `Middleware` → `FreeRTOS` и включаешь его (я советую версию CMSIS_V2 — она современнее) .
|
|||
|
|
|
|||
|
|
Дальше самое важное: идёшь во вкладку `Project Manager`:
|
|||
|
|
- **Toolchain/IDE** выбираешь **Makefile** (это ключевой момент! именно так VSCode поймёт структуру) .
|
|||
|
|
- Жмёшь `GENERATE CODE`. На выходе получаешь папку с исходниками, драйверами HAL и FreeRTOS.
|
|||
|
|
|
|||
|
|
**Шаг 2. Открываем в VSCode**
|
|||
|
|
Запускаешь VSCode, открываешь папку с проектом. Расширение STM32 само предложит импортировать проект — соглашаешься. Оно прочитает Makefile, подхватит все пути к заголовочным файлам, настроит автодополнение (IntelliSense) .
|
|||
|
|
|
|||
|
|
**Шаг 3. Компилируем и шьём**
|
|||
|
|
Внизу слева появится панелька STM32. Там кнопки:
|
|||
|
|
- **Build** — собрать проект.
|
|||
|
|
- **Run** — прошить в контроллер.
|
|||
|
|
Если у тебя плата Nucleo или Discovery с встроенным программатором ST-Link, всё заработает из коробки .
|
|||
|
|
|
|||
|
|
## 🛠️ Альтернативный путь (EIDE)
|
|||
|
|
|
|||
|
|
Если по каким-то причинам официальное расширение не зашло, есть классный плагин **Embedded IDE (EIDE)** . Принцип похожий:
|
|||
|
|
- В CubeMX генерируешь Makefile проект.
|
|||
|
|
- В VSCode ставишь EIDE, создаёшь новый проект "STM32 Cube Project" и ручками копируешь в него папки `Core`, `Drivers`, `Middlewares` из сгенерированного проекта .
|
|||
|
|
- В EIDE нужно будет добавить пути к заголовочным файлам и определить макросы (типа `USE_HAL_DRIVER`, `STM32F407xx`) — это как раз те галочки, которые в CubeMX проставляются .
|
|||
|
|
|
|||
|
|
## ⚠️ Две типичные проблемы (чтобы ты не матерился)
|
|||
|
|
|
|||
|
|
Когда будешь собирать, могут вылезти две классические ошибки. Я их проходил, так что вот решение.
|
|||
|
|
|
|||
|
|
### 1. Ошибка компиляции "FPU does not support instruction"
|
|||
|
|
|
|||
|
|
**Симптом:** компилятор ругается на инструкции `vstmdb`, `vldmia` в файле `port.c` .
|
|||
|
|
**Причина:** у твоего контроллера (например, STM32F4) есть аппаратное FPU (сопроцессор для float), а компилятор пытается собрать без поддержки float'ов.
|
|||
|
|
**Лечение:** в настройках компилятора нужно включить аппаратный float. В EIDE или в расширении STM32 ищешь опцию FPU и ставишь **fpv4-sp-d16** (для Cortex-M4) или соответствующую твоему камню .
|
|||
|
|
|
|||
|
|
### 2. Ошибка линковки "syntax error" в .ld файле
|
|||
|
|
|
|||
|
|
**Симптом:** линковщик падает с ошибкой в файле `*_FLASH.ld` на строках типа `_estack = ORIGIN() + LENGTH();` .
|
|||
|
|
**Причина:** CubeMX генерирует кривоватый скрипт линковки — забывает указать, откуда брать начало памяти.
|
|||
|
|
**Лечение:** открываешь `.ld` файл и правишь 4 места :
|
|||
|
|
```c
|
|||
|
|
// Было:
|
|||
|
|
_estack = ORIGIN() + LENGTH();
|
|||
|
|
// Стало:
|
|||
|
|
_estack = ORIGIN(RAM) + LENGTH(RAM);
|
|||
|
|
|
|||
|
|
// Было (в секции .data):
|
|||
|
|
} > AT> FLASH
|
|||
|
|
// Стало:
|
|||
|
|
} >RAM AT> FLASH
|
|||
|
|
|
|||
|
|
// Было (в секции .bss и в конце файла):
|
|||
|
|
} >
|
|||
|
|
// Стало:
|
|||
|
|
} >RAM
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
После этих правок всё собирается на ура.
|
|||
|
|
|
|||
|
|
## 🐞 Отладка и RTOS View
|
|||
|
|
|
|||
|
|
Самое крутое, что VSCode умеет показывать, что творится внутри FreeRTOS в реальном времени. Ставишь расширение **Cortex-Debug** , запускаешь отладку (F5) и в панели Debug открываешь **RTOS View** . Там увидишь список задач, их приоритеты, состояние (Running, Ready, Blocked) — прямо как в учебнике! Безумно полезно, когда что-то виснет.
|
|||
|
|
|
|||
|
|
## Короткий итог
|
|||
|
|
|
|||
|
|
1. **CubeMX** → генерируешь проект с FreeRTOS и **Makefile**.
|
|||
|
|
2. **VSCode + официальное расширение STM32** → открываешь, магия автоконфигурации.
|
|||
|
|
3. Правишь **.ld файл** (4 строчки) и включаешь **FPU**.
|
|||
|
|
4. Жмёшь **Build**, потом **Run** — и твоя первая многозадачная прошивка летит в контроллер.
|
|||
|
|
|
|||
|
|
Дальше уже можно писать задачи — создавать их через `xTaskCreate`, настраивать очереди (queues) для общения между задачами, и всё это дебажить с RTOS View. Если хочешь, следующим шагом расскажу, как реально развести несколько мигающих светодиодов разными задачами — это как "Hello, World!" в мире RTOS.
|
|||
|
|
|
|||
|
|
# IMU-мышь с FreeRTOS
|
|||
|
|
|
|||
|
|
Отличная идея для проекта! Это классическая задача, где FreeRTOS раскрывается во всей красе: нужно одновременно читать сенсор по I2C, обрабатывать данные и эмулировать USB-устройство. Поехали.
|
|||
|
|
|
|||
|
|
## 🧠 Архитектура проекта: как это будет работать
|
|||
|
|
|
|||
|
|
Прежде чем писать код, давай прикинем структуру. У нас будет несколько задач (tasks), которые общаются между собой через очереди (queues). Это самый наглядный способ понять межзадачное взаимодействие в RTOS.
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph "Задачи FreeRTOS"
|
|||
|
|
A[Task_ReadIMU<br>приоритет 2]
|
|||
|
|
B[Task_ProcessData<br>приоритет 1]
|
|||
|
|
C[Task_USB_HID<br>приоритет 2]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "Очереди"
|
|||
|
|
Q1["Queue_RawData<br>вектор (ax,ay,az,gx,gy,gz)"]
|
|||
|
|
Q2["Queue_MouseDelta<br>dx, dy, кнопки"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
A -->|"отправляет сырые данные"| Q1
|
|||
|
|
Q1 -->|"забирает на обработку"| B
|
|||
|
|
B -->|"отправляет движения"| Q2
|
|||
|
|
Q2 -->|"забирает и шлёт по USB"| C
|
|||
|
|
|
|||
|
|
IMU[(MPU6050<br>сенсор)] -->|I2C| A
|
|||
|
|
C -->|"HID Report"| USB[(USB<br>компьютер)]
|
|||
|
|
|
|||
|
|
style A fill:#f9f,stroke:#333
|
|||
|
|
style B fill:#bbf,stroke:#333
|
|||
|
|
style C fill:#9f9,stroke:#333
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Как это работает:**
|
|||
|
|
- **Task_ReadIMU** — зудит по таймеру, читает акселерометр и гироскоп, кидает в очередь .
|
|||
|
|
- **Task_ProcessData** — забирает данные, превращает сырые значения в смещение мыши (dx, dy), определяет клики (по резкому ускорению).
|
|||
|
|
- **Task_USB_HID** — самый ответственный, отправляет HID-отчёты в компьютер.
|
|||
|
|
|
|||
|
|
## 🛠️ Шаг 1. Настройка проекта в CubeMX
|
|||
|
|
|
|||
|
|
### 1.1. Выбираем контроллер и периферию
|
|||
|
|
Запускаешь CubeMX, создаёшь проект под **STM32F401CCU6** (Black Pill). Включаем:
|
|||
|
|
|
|||
|
|
- **RCC** → HSE: Crystal/Ceramic Resonator .
|
|||
|
|
- **Clock Configuration** — разгоняем до 84 MHz (это штатная частота для F401).
|
|||
|
|
- **I2C1** (или любой другой) — для подключения MPU6050. Пины: обычно **PB8 (SCL), PB9 (SDA)**. Скорость 100 kHz .
|
|||
|
|
- **TIM2** — для тактирования чтения сенсора (скажем, 100 Гц). Prescaler и Counter Period считаешь под свою частоту .
|
|||
|
|
- **USB** — включаем **USB_OTG_FS** в режиме **Device_Only**.
|
|||
|
|
- **Middleware** → **USB_DEVICE** → выбираем **Human Interface Device Class (HID)**. Там можно выбрать готовый шаблон **Mouse**.
|
|||
|
|
- **FreeRTOS** — включаем, версию **CMSIS_V2** (она проще и современнее) .
|
|||
|
|
|
|||
|
|
### 1.2. Настройка FreeRTOS
|
|||
|
|
Во вкладке `Pinout & Configuration` → `Middleware` → `FreeRTOS`:
|
|||
|
|
|
|||
|
|
- В `Task and Queues` создаём три задачи с именами (например, `defaultTask` переименовываем в `TaskReadIMU`, добавляем `TaskProcessData` и `TaskUSBMouse`).
|
|||
|
|
- Создаём две очереди: `QueueRawData` (длина 5, размер — под структуру с float-ами) и `QueueMouseDelta` (длина 3, размер — под структуру с int16_t).
|
|||
|
|
|
|||
|
|
### 1.3. Генерируем код
|
|||
|
|
В `Project Manager`:
|
|||
|
|
- **Toolchain/IDE** → **Makefile** (чтобы потом работать в VSCode) .
|
|||
|
|
- Жмём `GENERATE CODE`.
|
|||
|
|
|
|||
|
|
## 📝 Шаг 2. Пишем код (самое интересное)
|
|||
|
|
|
|||
|
|
### 2.1. Структуры данных
|
|||
|
|
В каком-нибудь заголовочном файле (например, `imu_data.h`) определяем:
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
typedef struct {
|
|||
|
|
float ax, ay, az; // акселерометр
|
|||
|
|
float gx, gy, gz; // гироскоп
|
|||
|
|
} IMU_RawData_t;
|
|||
|
|
|
|||
|
|
typedef struct {
|
|||
|
|
int16_t dx; // смещение по X
|
|||
|
|
int16_t dy; // смещение по Y
|
|||
|
|
uint8_t buttons; // биты: 0x01 — левая кнопка
|
|||
|
|
} MouseReport_t;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2. Задача чтения IMU (TaskReadIMU)
|
|||
|
|
В файле `TaskReadIMU.c` (или в коде, сгенерированном CubeMX внутри `/* USER CODE BEGIN */`):
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
void TaskReadIMU(void *argument) {
|
|||
|
|
IMU_RawData_t raw;
|
|||
|
|
uint32_t tick = osKernelGetTickCount(); // для точных интервалов
|
|||
|
|
|
|||
|
|
for(;;) {
|
|||
|
|
// Читаем акселерометр (регистры 0x3B..0x40)
|
|||
|
|
uint8_t accel_data[6];
|
|||
|
|
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B, 1, accel_data, 6, HAL_MAX_DELAY);
|
|||
|
|
|
|||
|
|
// Преобразуем два байта в 16-битное число, затем в g (чувствительность ±2g = 16384 LSB/g)
|
|||
|
|
raw.ax = (int16_t)((accel_data[0] << 8) | accel_data[1]) / 16384.0f;
|
|||
|
|
raw.ay = (int16_t)((accel_data[2] << 8) | accel_data[3]) / 16384.0f;
|
|||
|
|
raw.az = (int16_t)((accel_data[4] << 8) | accel_data[5]) / 16384.0f;
|
|||
|
|
|
|||
|
|
// Читаем гироскоп (регистры 0x43..0x48)
|
|||
|
|
uint8_t gyro_data[6];
|
|||
|
|
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x43, 1, gyro_data, 6, HAL_MAX_DELAY);
|
|||
|
|
raw.gx = (int16_t)((gyro_data[0] << 8) | gyro_data[1]) / 131.0f; // для ±250°/s
|
|||
|
|
raw.gy = (int16_t)((gyro_data[2] << 8) | gyro_data[3]) / 131.0f;
|
|||
|
|
raw.gz = (int16_t)((gyro_data[4] << 8) | gyro_data[5]) / 131.0f;
|
|||
|
|
|
|||
|
|
// Отправляем в очередь
|
|||
|
|
osMessageQueuePut(QueueRawDataHandle, &raw, 0, 0);
|
|||
|
|
|
|||
|
|
// Ждём строго 10 мс (100 Гц)
|
|||
|
|
osDelayUntil(tick, 10);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
*Примечание:* это упрощённый вариант. В реальности нужно учесть, что регистры MPU6050 читаются последовательно, и правильно обработать ошибки I2C .
|
|||
|
|
|
|||
|
|
### 2.3. Задача обработки данных (TaskProcessData)
|
|||
|
|
Здесь мы превращаем сырые данные в движение мыши. Идея: по углу наклона платы (из акселерометра) определяем скорость курсора.
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
void TaskProcessData(void *argument) {
|
|||
|
|
IMU_RawData_t raw;
|
|||
|
|
MouseReport_t mouse = {0, 0, 0};
|
|||
|
|
const float threshold = 0.3f; // порог для клика
|
|||
|
|
|
|||
|
|
for(;;) {
|
|||
|
|
// Ждём данные из очереди
|
|||
|
|
osMessageQueueGet(QueueRawDataHandle, &raw, NULL, osWaitForever);
|
|||
|
|
|
|||
|
|
// Вычисляем углы наклона (в радианах)
|
|||
|
|
float pitch = atan2(-raw.ax, sqrt(raw.ay*raw.ay + raw.az*raw.az));
|
|||
|
|
float roll = atan2(raw.ay, raw.az);
|
|||
|
|
|
|||
|
|
// Превращаем углы в смещение курсора (коэффициенты подбираются)
|
|||
|
|
mouse.dx = (int16_t)(roll * 100);
|
|||
|
|
mouse.dy = (int16_t)(pitch * 100);
|
|||
|
|
|
|||
|
|
// Определяем клик: если есть резкое ускорение по Z
|
|||
|
|
if (raw.az < -1.5f) { // тряхнули платой вниз
|
|||
|
|
mouse.buttons = 0x01; // левая кнопка
|
|||
|
|
} else {
|
|||
|
|
mouse.buttons = 0x00;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Отправляем в очередь для USB-задачи
|
|||
|
|
osMessageQueuePut(QueueMouseDeltaHandle, &mouse, 0, 0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4. Задача USB HID (TaskUSBMouse)
|
|||
|
|
Тут самое простое — берём готовый HID-класс от ST и шлём отчёты.
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
extern USBD_HandleTypeDef hUsbDeviceFS; // глобальный дескриптор USB
|
|||
|
|
|
|||
|
|
void TaskUSBMouse(void *argument) {
|
|||
|
|
MouseReport_t mouse;
|
|||
|
|
|
|||
|
|
for(;;) {
|
|||
|
|
// Ждём новые данные
|
|||
|
|
osMessageQueueGet(QueueMouseDeltaHandle, &mouse, NULL, osWaitForever);
|
|||
|
|
|
|||
|
|
// Формируем HID-отчёт (4 байта: кнопки, X, Y, колесо)
|
|||
|
|
uint8_t hid_report[4] = {
|
|||
|
|
mouse.buttons,
|
|||
|
|
(uint8_t)(mouse.dx & 0xFF),
|
|||
|
|
(uint8_t)(mouse.dy & 0xFF),
|
|||
|
|
0 // колесо не используем
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Отправляем через USB
|
|||
|
|
USBD_HID_SendReport(&hUsbDeviceFS, hid_report, 4);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 Шаг 3. Настройка в VSCode и сборка
|
|||
|
|
|
|||
|
|
### 3.1. Открываем проект
|
|||
|
|
Запускаешь VSCode, открываешь папку с проектом. Если у тебя установлено расширение **STM32Cube for VS Code**, оно само подхватит настройки. Если нет — придётся немного подредактировать Makefile, как в статье про добавление C++ файлов , но у нас всё на C, так что должно собраться и так.
|
|||
|
|
|
|||
|
|
### 3.2. Важный нюанс с USB
|
|||
|
|
В сгенерированном коде от CubeMX уже есть файл `usbd_custom_hid_if.c`. Там нужно поправить структуру отчёта, чтобы она соответствовала тому, что мы шлём. В функции `USBD_CUSTOM_HID_ReportDesc_FS` должно быть примерно так:
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] = {
|
|||
|
|
0x05, 0x01, // Usage Page (Generic Desktop)
|
|||
|
|
0x09, 0x02, // Usage (Mouse)
|
|||
|
|
0xA1, 0x01, // Collection (Application)
|
|||
|
|
0x09, 0x01, // Usage (Pointer)
|
|||
|
|
0xA1, 0x00, // Collection (Physical)
|
|||
|
|
0x05, 0x09, // Usage Page (Button)
|
|||
|
|
0x19, 0x01, // Usage Minimum (1)
|
|||
|
|
0x29, 0x03, // Usage Maximum (3)
|
|||
|
|
0x15, 0x00, // Logical Minimum (0)
|
|||
|
|
0x25, 0x01, // Logical Maximum (1)
|
|||
|
|
0x95, 0x03, // Report Count (3)
|
|||
|
|
0x75, 0x01, // Report Size (1)
|
|||
|
|
0x81, 0x02, // Input (Data,Var,Abs)
|
|||
|
|
0x95, 0x01, // Report Count (1)
|
|||
|
|
0x75, 0x05, // Report Size (5)
|
|||
|
|
0x81, 0x03, // Input (Const,Var,Abs)
|
|||
|
|
0x05, 0x01, // Usage Page (Generic Desktop)
|
|||
|
|
0x09, 0x30, // Usage (X)
|
|||
|
|
0x09, 0x31, // Usage (Y)
|
|||
|
|
0x16, 0x00, 0x80, // Logical Minimum (-32768)
|
|||
|
|
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
|
|||
|
|
0x75, 0x10, // Report Size (16)
|
|||
|
|
0x95, 0x02, // Report Count (2)
|
|||
|
|
0x81, 0x06, // Input (Data,Var,Rel)
|
|||
|
|
0xC0, // End Collection
|
|||
|
|
0xC0 // End Collection
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Этот дескриптор говорит, что мы шлём 3 кнопки (по 1 биту), затем 2 относительных значения по 16 бит — X и Y.
|
|||
|
|
|
|||
|
|
## 🐞 Отладка: смотрим, что внутри
|
|||
|
|
|
|||
|
|
Самое крутое, что VSCode с расширением **Cortex-Debug** и **RTOS View** позволяет заглянуть внутрь FreeRTOS . Ты увидишь:
|
|||
|
|
|
|||
|
|
- Все три задачи, их приоритеты и состояние (Running, Ready, Blocked).
|
|||
|
|
- Очереди: сколько элементов внутри, сколько свободно.
|
|||
|
|
- Если какая-то задача зависла — сразу видно.
|
|||
|
|
|
|||
|
|
Также в коде можно использовать `printf` через UART (как в статье про `_write`) , чтобы выводить отладочную информацию, пока мышь не заработала.
|
|||
|
|
|
|||
|
|
## 🎯 Что тут тренируется в FreeRTOS
|
|||
|
|
|
|||
|
|
1. **Создание задач** с разными приоритетами.
|
|||
|
|
2. **Очереди** — передача данных между задачами (сырые данные → обработка → USB).
|
|||
|
|
3. **Таймеры** — задача чтения IMU работает по строгому расписанию (100 Гц).
|
|||
|
|
4. **Межзадачное взаимодействие** без глобальных переменных и гонок данных.
|
|||
|
|
5. **Реальное время** — задача USB должна отвечать быстро, иначе хост отвалится.
|
|||
|
|
|
|||
|
|
Попробуй собрать — и у тебя получится настоящая беспроводная (по проводу, но всё же) мышь из платы и датчика от дрона. Если что-то зависнет — пиши, разберёмся.
|