board-h723: Доска H723: плата, ПО и рецепты на C # Доска H723 > Доска H723 — отладочная плата на STM32H723ZGT6 (550 МГц) с SDRAM 32 МБ, Wi-Fi/BLE на ESP32-C2 и слотом microSD: характеристики, распиновка разъёмов, быстрый старт, схема. Распиновка Доски H723: разъёмы J1 и J2, SWD-разъём, кнопка BOOT/USER ## Характеристики - Микроконтроллер: STM32H723ZGT6 - Ядро: Arm Cortex-M7, кэш инструкций 32 КБ и кэш данных 32 КБ - Частота ядра: 550 МГц - Flash: 1 МБ - ОЗУ: 564 КБ - Напряжение питания: 1,62–3,6 В - 2× 16-битных АЦП, 1× 12-битный АЦП - Таймеры (20) - SPI/I2S/SAI, ETH, FDCAN, FMC, I2C, OCTOSPI, UART, USB_OTG - DCMI, DMA2D, LTDC (до 24 бит) - PSSI (до 16 бит) - RNG - CORDIC, CRC, DFSDM, FMAC - GPIO на разъёмах: 51 - SDRAM MT48LC16M16A2, 256 Мбит (32 МБ), 16-битная шина - Wi-Fi/BLE модуль ESP32-C2, связь с STM32 по SPI 62,5 МГц - Слот microSD (SDMMC, 4 бита, 50 МГц) - USB-C: питание, сеть USB-NCM, прошивка по USB (DFU) - Кнопки RESET и BOOT/USER, светодиод - Разъём SWD для отладчика - Схема: [h723-sch.pdf](/media/boards/h723-sch.pdf) - [GitHub — прошивка и примеры](https://github.com/Kits-Lab-Dev/Board-H723) - [Рецепты — готовые модули на C](/boards/bh723/recipes/) - [ПО для разработки](/boards/bh723/software/) Назначение выводов STM32H723 на плате (CubeMX) ## Описание Отладочная плата ***Доска-H723*** предназначена для разработки и тестирования высокопроизводительных встраиваемых приложений. Она сочетает быстрое ядро, большую внешнюю память, Wi-Fi и BLE, microSD и разъёмы с выведенными линиями LTDC, I2C, SPI, UART, CAN, АЦП и ШИМ — подойдёт для IoT-устройств, устройств с графическим экраном, промышленной автоматики и других требовательных проектов. ## Быстрый старт Плата поставляется с прошитыми STM32H723 и ESP32-C2. Чтобы начать: 1. Распакуйте `sd-card.zip` из [релизов](https://github.com/Kits-Lab-Dev/Board-H723/releases) в корень карты FAT32 и вставьте карту в плату. 2. Подключите плату к ПК кабелем USB-C. Она появится в системе как сетевая карта (USB-NCM, драйвер есть в Windows 10/11, Linux и macOS): плата — `192.168.4.1`, ПК получает `192.168.4.2` по DHCP. Проверка — `ping 192.168.4.1`. 3. Войдите по SSH: `ssh root@192.168.4.1`. Пароль плата создаёт сама при первом запуске и сохраняет в `/SD/.ssh/password` на карте. Подробнее — на странице [ПО для разработки](/boards/bh723/software/). 4. Файлы на карту заливаются по SFTP, скрипты Lua запускаются командой `lua -f /SD/x.lua`. 5. Для своей прошивки на C — [ПО для разработки](/boards/bh723/software/) и [рецепты](/boards/bh723/recipes/). ## Что умеет готовая прошивка | Возможность | Подробности | |---|---| | Сеть | USB-NCM (USB-Ethernet), Wi-Fi клиент и точка доступа, DHCP-сервер | | Скорость Wi-Fi | TCP до 25 Мбит/с на приём и до 35 Мбит/с на отправку (зависит от эфира) — рецепт [tcp_speed](/boards/bh723/recipes/tcp_speed/) | | SSH и SFTP | оболочка, загрузка файлов на SD-карту и в RAM-диск | | Файловые системы | `/SD` (FatFs, microSD в 4 бита), `/RAM` (RAM-диск 4 МБ в SDRAM) | | Lua | интерпретатор для быстрых опытов: GPIO, SPI, I2C, UART, потоки, Wi-Fi, сокеты, HTTPS | | BLE | Nordic UART, плата видна как `Mynewt_BLEuart` | | Дисплей | RGB 800×480 по LTDC через переходник на J1/J2 и сенсорный экран GT911 — рецепты [lcd_ltdc](/boards/bh723/recipes/lcd_ltdc/), [lvgl_demo](/boards/bh723/recipes/lvgl_demo/), [lvgl_clock](/boards/bh723/recipes/lvgl_clock/) | ## Wi-Fi/BLE Модуль на основе ESP32-C2 связывает плату с внешним миром: - Wi-Fi 802.11 b/g/n (2,4 ГГц): - инфраструктурный режим (STA/AP) - подключение к локальной сети и интернету - серверы и клиенты TCP/UDP, HTTP, MQTT и т. д. - Bluetooth Low Energy (BLE 5.0): - GATT-серверы и клиенты - обмен данными с мобильными устройствами - использование ESP32-C2 как BLE-адаптера для STM32 ESP32-C2 подключён к STM32 по SPI, поэтому стек TCP/IP можно держать как на STM32 (удобные сокеты), так и на ESP32-C2 (экономия ресурсов STM32). UART позволяет загружать прошивку в ESP32-C2. Соединение STM32 и ESP32-C2: ``` STM32 ESP32 PF6 --- (CS) ---------- IO10 PF7 --- (CLK) --------- IO6 PF8 --- (MISO) -------- IO2 PF9 --- (MOSI) -------- IO7 PF10 -- (EN) ---------- EN PE2 --- (BOOT) -------- IO9 PE3 --- (DATA_READY) -- IO4 PB6 --- (HANDSHAKE) --- IO3 PB3 --- (TX) ---------- TXD PB4 --- (RX) ---------- RXD IO1 --> LED ``` ## microSD Карта подключена к STM32H723ZGT6 через SDMMC2 в 4-битном режиме, High Speed 50 МГц. Файловая система FAT (FAT32) через библиотеку FatFs. Скорость зависит от карты: и запись, и чтение — до 10 МБ/с (рецепт [sd_file](/boards/bh723/recipes/sd_file/)). ## SDRAM Память подключена к FMC (Flexible Memory Controller) микроконтроллера STM32H723ZGT6: - прямой доступ к памяти как к внешней SRAM - адресно-данный интерфейс, 16-битная шина - режим Burst для ускорения последовательного доступа SDRAM расширяет внутреннюю SRAM микроконтроллера: в прошивке под неё — `sdramMalloc()`, RAM-диск и память Lua. ## Разъёмы J1 и J2 — гребёнки 2×20 с шагом 2,54 мм. Номера контактов — как на схеме; нечётные и чётные контакты — два ряда гребёнки. Выводы SDRAM, microSD и USB на разъёмы не выведены; линии ESP32-C2 выведены частично (см. ниже). Картинка в начале страницы — те же разъёмы на фотографии платы. | Контакты | J1, нечётный | J1, чётный | J2, нечётный | J2, чётный | |---|---|---|---|---| | 1 / 2 | GND | GND | GND | GND | | 3 / 4 | 3V3 | 3V3 | 3V3 | 3V3 | | 5 / 6 | PC7 | PC6 | RST | GND | | 7 / 8 | PC9 | PC8 | PG7 | PG6 | | 9 / 10 | PA9 | PA8 | PG3 (LED) | PD13 | | 11 / 12 | PA15 | PA10 | PD12 | PD11 | | 13 / 14 | PC11 | PC10 | PB15 (лог RX) | PB14 (лог TX) | | 15 / 16 | PD2 | PC12 | PB13 | PB12 | | 17 / 18 | PD4 | PD3 | PB11 | PB10 | | 19 / 20 | GND | GND | GND | GND | | 21 / 22 | PG14 | PG13 | PB2 | PB1 | | 23 / 24 | PB7 | PB5 | PB0 | PC5 | | 25 / 26 | PB9 | PB8 | PC4 | PA7 | | 27 / 28 | PE4 | PE5 | PA6 | PA5 | | 29 / 30 | PE6 | PB4 / ESP RXD | PA4 | PA3 | | 31 / 32 | PB3 / ESP TXD | ESP IO18 | PC1 | PA2 | | 33 / 34 | ESP IO0 | ESP IO5 | PA1 | PA0 | | 35 / 36 | 3V3 | VBAT | VREF+ | 3V3 | | 37 / 38 | 5V | 5V | 5V | 5V | | 39 / 40 | GND | GND | GND | GND | - `ESP RXD`, `ESP TXD`, `ESP IO0/IO5/IO18` — линии ESP32-C2. PB4 и PB3 соединены с UART ESP32-C2 перемычками JP3 и JP4 (замкнуты). - PG3 — светодиод платы, он мигает 2 Гц из задачи простоя прошивки. - PB14 и PB15 — лог USART1 (115200 8N1), они же на SWD-разъёме. - 5V на разъёмах — напряжение VBUS с USB-C. Плата питается от USB-C: 5 В → преобразователь SY8089 → 3,3 В. - Перемычки JP1 (VREF+ ↔ 3V3) и JP2 (VBAT ↔ 3V3) разомкнуты: внешнее опорное напряжение АЦП подключают на VREF+ (J2, контакт 35), батарейку RTC — на VBAT (J1, контакт 36). - Свободные для своих задач: I2C1 (PB8 — SCL, PB7 — SDA), FDCAN2 (PB12 — RX, PB13 — TX, нужен внешний трансивер), ШИМ на TIM1–TIM4, TIM8. - Подробная таблица занятой периферии, память и такты — [docs/board.md](https://github.com/Kits-Lab-Dev/Board-H723/blob/main/docs/board.md) в репозитории. ### Разъём SWD (J6) 2×4: 1 — 3V3, 2 — SWDIO, 3 — GND, 4 — SWCLK, 5 — GND, 6 — не подключён, 7 — PB15 (USART1 RX), 8 — PB14 (USART1 TX). Отладчик [«Лобзик»](https://github.com/Kits-Lab-Dev/lobzik-skill) подключается сюда и сразу даёт лог USART1. ### Кнопки - **RESET** — сброс. - **BOOT/USER** (PC13) — если нажать её при сбросе, плата входит в системный загрузчик STM32 (USB DFU). В готовой прошивке долгое нажатие (около 1 с) — сброс, короткое нажатие свободно для своих задач (рецепт [gpio_button](/boards/bh723/recipes/gpio_button/)). ## Прошивка - **По USB, без кнопок:** `python tools/dfu_flash.py` из репозитория. Скрипт отправляет плате UDP-пакет `dfu`, плата переходит в загрузчик, скрипт записывает прошивку и ждёт, пока плата снова ответит на ping. Команда `reboot dfu` в SSH делает то же самое. - **С кнопками:** зажать BOOT, нажать RESET (или переподключить USB), отпустить BOOT — плата в DFU. Дальше `STM32_Programmer_CLI -c port=usb1 -w BoardH723.hex -s 0x08000000`. На Windows нужен драйвер STM32 DFU, он ставится вместе со STM32CubeProgrammer. - **Через отладчик «Лобзик»** по SWD — прошивка, чтение памяти и регистров, разбор HardFault. - **Модуль ESP32-C2** прошивается через STM32: команда `reboot esploader` в SSH включает мост USB-UART, дальше esptool. Подробности — в [AGENTS.md](https://github.com/Kits-Lab-Dev/Board-H723/blob/main/AGENTS.md). # Рецепты для Доски-H723 > Рецепт — небольшой законченный модуль на C: recipes/<имя>/<имя>.c + .h и README с объяснением, как он устроен. Рецепты проверены на плате, в README — настоящий вывод лога; исключение — i2c_bme280,… Рецепт — небольшой законченный модуль на C: `recipes/<имя>/<имя>.c` + `.h` и README с объяснением, как он устроен. Рецепты проверены на плате, в README — настоящий вывод лога; исключение — `i2c_bme280`, проверенный без датчика (см. его README). Рецепты подключаются к основному проекту `examples/BoardH723` без правки его кода. | Рецепт | Что показывает | |---|---| | [gpio_button](/boards/bh723/recipes/gpio_button/) | кнопка BOOT по прерыванию, подавление дребезга, светодиод | | [uart_dma](/boards/bh723/recipes/uart_dma/) | приём UART через DMA в кольцевой буфер, команды из терминала | | [i2c_bme280](/boards/bh723/recipes/i2c_bme280/) | сканер шины I2C, датчик BME280/BMP280: температура, давление, влажность | | [wifi_http](/boards/bh723/recipes/wifi_http/) | подключение к Wi-Fi, HTTP GET на сокетах FreeRTOS+TCP | | [tcp_speed](/boards/bh723/recipes/tcp_speed/) | скорость TCP по Wi-Fi: окна TCP, энергосбережение, клиент для ПК | | [sd_file](/boards/bh723/recipes/sd_file/) | дозапись в файл на SD, замер скорости записи и чтения | | [rtos_queue](/boards/bh723/recipes/rtos_queue/) | две задачи FreeRTOS и очередь между ними | | [lcd_ltdc](/boards/bh723/recipes/lcd_ltdc/) | дисплей RGB 800×480: LTDC, DMA2D, двойной буфер в SDRAM, картинка с SD | | [lvgl_demo](/boards/bh723/recipes/lvgl_demo/) | интерфейс на LVGL 9: сенсорный экран GT911, графики, русский шрифт TTF с SD | | [lvgl_clock](/boards/bh723/recipes/lvgl_clock/) | часы (NTP) и погода (open-meteo) на LVGL, анимация пузырей; Wi-Fi и город — в файле на SD | ## Как включить Список рецептов через `;` в опции `H723_RECIPES`: ```bash cd examples/BoardH723 cmake --preset Release -DH723_RECIPES="gpio_button;sd_file" cmake --build build/Release python ../../tools/dfu_flash.py ``` CMake добавляет в сборку все `.c` из папок рецептов и генерирует `build/Release/recipes_start.c`, который вызывает `<имя>_start()` каждого рецепта. `appTask` (`app/app.c`) вызывает его, когда уже работают USB-сеть, SDRAM, ESP32-C2, SD-карта, Lua и SSH. Выключить рецепты — `-DH723_RECIPES=""`. Рецепты не зависят от Lua и работают в прошивке без него (`-DH723_WITH_LUA=OFF`); только `wifi_http`, `tcp_speed` и `lvgl_clock` тогда подключаются к сети сами — имя и пароль задаются в `wifi_http.h`, опциями `-DTCP_SPEED_SSID`/`-DTCP_SPEED_PASSWORD` и в `/SD/clock.ini`. `lcd_ltdc`, `lvgl_demo` и `lvgl_clock` выводят на один дисплей — вместе их не включают. Вывод рецептов — в лог USART1 115200 (PB14, на разъёме отладчика J6 и на J2). ## Свой рецепт 1. Папка `recipes/my_thing/` с `my_thing.c`, где есть `void my_thing_start(void)`. 2. `my_thing_start()` только создаёт задачу FreeRTOS и сразу возвращается: `appTask` на ней не задерживается. 3. `cmake --preset Release -DH723_RECIPES="my_thing"`. 4. Если рецепту нужны библиотеки или код другого рецепта — они подключаются в `recipes/my_thing/recipe.cmake` (образец — `lvgl_demo/recipe.cmake`). Правила, которые соблюдают все рецепты: - Задачи, очереди и стеки — статически (`xTaskCreateStatic`): их размер виден при сборке, а не выясняется при нехватке кучи во время работы. - Буферы для DMA — не в обычных `static`-массивах: они попадают в DTCM, куда DMA1/DMA2 доступа не имеют. Подходят `pvPortMalloc` (RAM_D1) или `sdramMalloc` (SDRAM), с выравниванием по 32 байта и очисткой/инвалидацией кэша. - Прерывания, из которых зовутся функции `...FromISR`, — с приоритетом 5 и ниже по важности (число ≥ 5). - Свободные выводы и периферия — в `docs/board.md` репозитория. --- Рецепты в репозитории: [recipes](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes) # gpio_button — кнопка по прерыванию и светодиод > Короткое нажатие кнопки BOOT/USER переключает светодиод D2 (PG3). Кнопка обрабатывается по прерыванию с подавлением дребезга контактов. Короткое нажатие кнопки BOOT/USER переключает светодиод D2 (PG3). Кнопка обрабатывается по прерыванию с подавлением дребезга контактов. ## Включить ```bash cmake --preset Release -DH723_RECIPES="gpio_button" ``` ## Вывод на плате ``` gpio_button: started, press BOOT to toggle LED gpio_button: press 1 (196 ms), LED on gpio_button: press 2 (202 ms), LED off gpio_button: press 3 (181 ms), LED on gpio_button: press 4 (152 ms), LED off ``` Подавление дребезга: если прерывание пришло, а уровень на выводе не изменился, в лог попадает `gpio_button: bounce, ignored`, и светодиод не переключается. ## Как это работает - **Схема.** Кнопка замыкает PC13 на 3,3 В, резистор 10 кОм на плате тянет вывод к земле: нажата — 1, отпущена — 0. Та же кнопка подаёт 1 на BOOT0, поэтому если **держать её во время сброса**, стартует системный загрузчик (DFU), а не прошивка. - **Долгое нажатие занято.** Прошивка (`app/app.c`) сбрасывает плату, если кнопку держать ~1 с. Рецепт считает только нажатия короче `SHORT_PRESS_MS` (800 мс). - **Прерывание по обоим фронтам.** PC13 переводится в режим `GPIO_MODE_IT_RISING_FALLING` — линия EXTI13. Её обработчик `EXTI15_10_IRQHandler` и `HAL_GPIO_EXTI_Callback` живут в `app/exti.c` и отдают прерывание приложению через `appGpioExti(pin)`; что приложение не забрало, уходит в Lua (`pio.attachInterrupt`), если прошивка собрана с ним. Рецепт переопределяет `appGpioExti` и забирает PC13 себе. - **Дребезг.** Контакты кнопки при нажатии несколько миллисекунд замыкаются и размыкаются, давая пачку прерываний. Прерывание только будит задачу (`vTaskNotifyGiveFromISR`): ждать и печатать в нём нельзя, пока оно идёт, стоит вся система. Задача ждёт 20 мс, сбрасывает накопившиеся уведомления и читает вывод. Если уровень не изменился — это был дребезг. - **Приоритет прерывания 6** — из него можно звать `...FromISR` (см. правила в [общем README](/boards/bh723/recipes/)). - **Светодиод.** Обычно им мигает idle-задача (`appLedHeartbeat` в `app/app.c`); рецепт выключает мигание и управляет PG3 сам. Состояние хранится в переменной, а не читается с вывода: `HAL_GPIO_ReadPin` сразу после записи в выход может вернуть старое значение. ## Переделать под себя - Внешняя кнопка: любой свободный вывод (`docs/board.md`), кнопка на GND и подтяжка `GPIO_PULLUP` — тогда нажата = 0. Меняются `BUTTON_PORT`, `BUTTON_PIN` и проверка уровня. Для выводов 0–4 у линий EXTI свои обработчики, для 5–9 — `EXTI9_5_IRQHandler`, тоже в `app/exti.c`. - `appGpioExti` переопределяется только в одном модуле, поэтому при нескольких кнопках все выводы обрабатываются в этой одной функции. - Двойное нажатие, удержание и т. п. — в `buttonTask` есть время нажатия (`ms`), этого достаточно. --- Исходники рецепта: [recipes/gpio_button](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/gpio_button) # i2c_bme280 — датчик на I2C > Сканирует шину I2C1, находит датчик Bosch BME280 (температура, давление, влажность) или BMP280 (без влажности) и печатает показания раз в 2 секунды. Драйвер — один файл без сторонних библиотек,… Сканирует шину I2C1, находит датчик Bosch BME280 (температура, давление, влажность) или BMP280 (без влажности) и печатает показания раз в 2 секунды. Драйвер — один файл без сторонних библиотек, формулы — из даташита. ## Подключение | Модуль датчика | Доска-H723 | |---|---| | VCC / VIN | 3V3 (J1-3) | | GND | GND (J1-1) | | SCL | PB8 (J1-26) | | SDA | PB7 (J1-23) | Адрес 0x76, если вывод SDO модуля на GND, и 0x77 — если на 3,3 В; рецепт пробует оба. ## Включить ```bash cmake --preset Release -DH723_RECIPES="i2c_bme280" ``` ## Вывод на плате Без датчика (шина с подтяжками, никто не отвечает; задача ждёт датчик): ``` i2c_bme280: started i2c_bme280: no devices on I2C1 (PB8 SCL, PB7 SDA) ``` С датчиком ожидается такой вывод (на живом BME280 на плате не проверялось): ``` i2c_bme280: device at 0x76 i2c_bme280: BME280 at 0x76 i2c_bme280: 24.31 C, 99012 Pa (742 mmHg), 41.5 %RH ``` ## Как это работает - **Шина.** I2C1 настроен в CubeMX (`hi2c1`, ~100 кГц). Подтяжек на плате нет — у модулей датчиков они свои; рецепт включает ещё встроенные (~40 кОм), чтобы шина без модуля не «висела в воздухе». - **Сканер.** `HAL_I2C_IsDeviceReady` посылает каждый адрес 0x08–0x77; ответил ACK — устройство есть. Полезно первым делом при любом новом датчике: виден адрес и что проводка цела. - **Регистры.** Датчик читается как память: `HAL_I2C_Mem_Read(адрес, регистр, буфер, длина)`. `0xD0` — ID (0x60 BME280, 0x58 BMP280), `0x88…0xA1` и `0xE1…0xE7` — калибровка, `0xF7…0xFE` — сырые измерения. - **Калибровка.** Каждый датчик на заводе прошит своими коэффициентами. Сырые числа АЦП без них бессмысленны; рецепт читает коэффициенты один раз при старте. - **Компенсация.** Целочисленные формулы из даташита (раздел 4.2.3): сначала температура (`tFine`), потом давление и влажность — они от неё зависят. Все 8 байт измерений читаются одной транзакцией, чтобы значения были из одного цикла измерения. - **Режим.** «Нормальный»: датчик сам меряет раз в секунду, прошивка просто читает последнее. Для батарейных устройств — «принудительный» (`ctrl_meas` с режимом `01`) перед каждым чтением. - **Таймауты.** Все обмены по I2C — с таймаутом 10 мс: отвалившийся датчик даёт ошибку, а не зависание. ## Переделать под себя - Другой датчик I2C — та же схема: `i2cScan`, чтение ID, `readRegs`/`writeReg`, формулы из его даташита. - Точнее и с меньшим шумом: передискретизация (биты `osrs` в `0xF2`/`0xF4`) и фильтр IIR (`0xF5`). - Высота по давлению: `h ≈ 44330 × (1 − (p / p0)^0.1903)` м, `p0` — давление на уровне моря. - Lua-модуль `wire` тоже работает с I2C1, но на PB8/PB9 — одновременно с рецептом его лучше не запускать. --- Исходники рецепта: [recipes/i2c_bme280](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/i2c_bme280) # lcd_ltdc — дисплей RGB 800×480 > Выводит на дисплей с параллельным интерфейсом RGB картинку с SD-карты и гоняет по ней квадрат. Контроллер LTDC сам читает кадр из SDRAM и выдаёт его на дисплей, рисует DMA2D — процессор в этом… Выводит на дисплей с параллельным интерфейсом RGB картинку с SD-карты и гоняет по ней квадрат. Контроллер LTDC сам читает кадр из SDRAM и выдаёт его на дисплей, рисует DMA2D — процессор в этом почти не участвует. ## Подключение Дисплей 800×480 с интерфейсом RGB (24 бита данных, CLK, HSYNC, VSYNC, DE) — через переходник на разъёмы J1 и J2. Подсветка и питание дисплея — от переходника. | Сигнал | Вывод | Контакт | | Сигнал | Вывод | Контакт | | Сигнал | Вывод | Контакт | |---|---|---|---|---|---|---|---|---|---|---| | R0 | PG13 | J1-22 | | G0 | PE5 | J1-28 | | B0 | PE4 | J1-27 | | R1 | PA2 | J2-32 | | G1 | PE6 | J1-29 | | B1 | PC10 | J1-14 | | R2 | PA1 | J2-33 | | G2 | PA6 | J2-27 | | B2 | PA3 | J2-30 | | R3 | PB0 | J2-23 | | G3 | PC9 | J1-7 | | B3 | PA8 | J1-10 | | R4 | PA5 | J2-28 | | G4 | PB10 | J2-18 | | B4 | PA10 | J1-12 | | R5 | PA9 | J1-9 | | G5 | PC1 | J2-31 | | B5 | PB5 | J1-24 | | R6 | PB1 | J2-22 | | G6 | PC7 | J1-5 | | B6 | PA15 | J1-11 | | R7 | PC4 | J2-25 | | G7 | PD3 | J1-18 | | B7 | PD2 | J1-15 | CLK — PG7 (J2-7), HSYNC — PC6 (J1-6), VSYNC — PA4 (J2-29), DE — PC5 (J2-24). Эти 28 выводов заняты дисплеем целиком: ШИМ Lua-модуля `pio` на PA1–PA3, PA5, PA6, PA8, PB0, PB1, PC6–PC9 с ним не совместим. Свободными остаются, например, I2C1 (PB8, PB7), FDCAN2 (PB12, PB13) и USART1. ## Включить ```bash cmake --preset Release -DH723_RECIPES="lcd_ltdc" ``` Картинка — `/SD/lcd.bmp`: BMP 24 бита без сжатия, лучше 800×480 (меньшая встанет по центру). Без файла на экране цветные полосы. ## Вывод на плате ``` lcd_ltdc: started lcd_ltdc: 800x480 RGB565, pixel clock 25000 kHz, refresh 43 Hz lcd_ltdc: /SD/lcd.bmp 800x480 loaded in 175 ms lcd_ltdc: 43 fps, draw 506 us/frame ``` 43 кадра в секунду — по одному на каждый кадр развёртки; на рисование кадра уходит 0,5 мс. С работающим дисплеем SD-карта пишет 10,1 МБ/с и читает 10,7 МБ/с (`sd_file`), SSH, SFTP и Lua работают как без дисплея. ## Как это работает - **Такт пикселя.** LTDC тактируется от выхода R PLL3: 250 МГц / 10 = 25 МГц (`PeriphCommonClock_Config` в `main.c`). Полный кадр с порогами — 1057 × 549 тактов, отсюда 43 Гц. - **Тайминги развёртки** — `LCD_HSYNC`, `LCD_HBP`, `LCD_HFP` и вертикальные в `lcd_ltdc.h`, из документации панели. В регистры LTDC пишутся накопленные суммы: импульс, + задний порог, + видимая часть, + передний порог, каждая минус 1. - **Регистры, а не HAL.** Модули HAL для LTDC и DMA2D в проекте не включены (их нет в `.ioc`), а регистров немного — рецепт не зависит от настроек CubeMX. - **Кадровые буферы — в SDRAM**, два по 768 КБ (RGB565, 2 байта на пиксель). На экране один, рисуется другой; `lcdSwap` меняет адрес слоя с перезагрузкой по кадровому гасящему импульсу (`LTDC_SRCR_VBR`) и ждёт его — поэтому картинка не рвётся. - **RGB565 вместо ARGB8888** — вдвое меньше памяти и нагрузки на шину SDRAM. Линии R0–R2, G0–G1, B0–B2 LTDC при этом всё равно выдаёт, дополняя цвет до 24 бит. - **DMA2D рисует сам**: заливка прямоугольника (режим «регистр → память»), копия картинки («память → память») и перевод BMP из RGB888 в RGB565 («память → память с преобразованием»). - **Перерисовывается только изменившееся.** Шину SDRAM делят LTDC (~33 МБ/с на чтение кадра), DMA2D, процессор, Lua и RAM-диск. Копия всего фона в каждом кадре добавила бы ~65 МБ/с и 17 мс работы DMA2D. Поэтому фон пишется в оба буфера один раз, а в каждом кадре закрывается фоном только место, где квадрат был два кадра назад (буферы чередуются). - **Своя периферия с DMA рядом с дисплеем.** Пока LTDC читает кадр, доступ к SDRAM идёт с очередью. Буферы DMA для периферии, которой важно успевать (SD-карта, быстрый UART, SPI), лучше держать во внутренней памяти (`pvPortMalloc`, AXI SRAM) — так сделан и драйвер SD-карты. - **Кэш.** SDRAM кэшируется. Память под буферы инвалидируется после выделения: иначе «грязные» строки кэша от прежних владельцев могли бы вытесниться поверх того, что пишет DMA2D. Данные BMP, прочитанные процессором, перед DMA2D выгружаются из кэша (`SCB_CleanDCache_by_Addr`). Буферы выровнены по 32 байта — размер строки кэша. - **BMP** хранит строки снизу вверх и выравнивает их до 4 байт, поэтому DMA2D переводит картинку по строке. Файл читается через FatFs блоками по 32 КБ: драйвер SD передаёт их одной командой, мелкие чтения через stdio в разы медленнее. ## Переделать под себя - Другая панель — размер и тайминги в `lcd_ltdc.h`; частоту пикселя меняет делитель `PLL3R` (в `.ioc`, от него же тактируется АЦП). Полярности сигналов — регистр `LTDC->GCR`. - Текст, кнопки, графики и сенсорный ввод — рецепт [lvgl_demo](/boards/bh723/recipes/lvgl_demo/): LVGL рисует прямо в кадровые буферы этого драйвера (`lcdFrameBuffer`, `lcdShow`). - Рисование процессором по пикселям — в `backBuffer`, а перед `lcdSwap` выгрузить изменённое из кэша (`SCB_CleanDCache_by_Addr`), иначе LTDC покажет старые данные. - Сенсорная панель дисплея обычно подключается по I2C — см. рецепт [i2c_bme280](/boards/bh723/recipes/i2c_bme280/). --- Исходники рецепта: [recipes/lcd_ltdc](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/lcd_ltdc) # lvgl_clock — часы и погода на LVGL > Часы с секундами, дата по-русски и погода за окном: температура, «ощущается как», минимум и максимум на сегодня, влажность, ветер, значок погоды. Время — из интернета (NTP), погода — с… Часы с секундами, дата по-русски и погода за окном: температура, «ощущается как», минимум и максимум на сегодня, влажность, ветер, значок погоды. Время — из интернета (NTP), погода — с [open-meteo.com](https://open-meteo.com) (бесплатно, без ключа). На фоне всплывают разноцветные пузыри случайного размера и скорости, касание экрана выпускает новые. Сеть Wi-Fi и город задаются в файле на SD-карте — прошивку под другой город пересобирать не нужно. ## Подключение Дисплей 800×480 и тач GT911 — как в рецепте [lvgl_demo](/boards/bh723/recipes/lvgl_demo/). На карте — шрифты `/SD/fonts/PT_Sans-Web-*.ttf` и настройки `/SD/clock.ini` (оба есть в `sd-card.zip`): ```ini ssid = my_wifi password = my_password city = Москва ``` - `city` — город по-русски или латиницей; координаты и часовой пояс находит open-meteo. - Вместо города — `latitude` и `longitude` в градусах (север и восток — со знаком плюс). - `utc_offset` — часовой пояс вручную, часы от UTC (бывает дробным: `5.5`). Без него пояс берётся по месту. - Пустой `ssid` — рецепт сам к Wi-Fi не подключается и ждёт, пока это сделает кто-то другой (`/SD/boot.lua` в прошивке с Lua). Файл — в UTF-8; правится в любом редакторе на ПК или по SFTP на живой плате (`/SD/clock.ini`), новые настройки читаются при загрузке. ## Включить Как и `lvgl_demo`, рецепт собирается без Lua — иначе прошивка не помещается во Flash: ```bash cmake --preset Release -DH723_WITH_LUA=OFF -DH723_RECIPES="lvgl_clock" cmake --build build/Release ``` Прошивка с SSH — 727 КБ. Код LVGL, драйверы дисплея и тача берутся из `lvgl_demo`, HTTP — из `wifi_http` (`recipe.cmake`), поэтому вместе с ними, а также с `lcd_ltdc`, рецепт не включают. ## Вывод на плате ``` lvgl_clock: started lvgl_clock: Wi-Fi: подключение к my_wifi… gt911: product 911, panel 800x480 lvgl_clock: screen ready IP Address: 192.168.1.50 lvgl_clock: Wi-Fi: my_wifi, адрес 192.168.1.50 lvgl_clock: Москва -> 55.7520, 37.6178 (585 ms) wifi_http: connect api.open-meteo.com (94.130.142.35):80 error -116 lvgl_clock: weather 10 C (feels 8), code 2, humidity 81 %, wind 2.9 m/s, UTC+10800 s, historical-forecast-api.open-meteo.com 487 ms ``` Если провайдер не пускает к адресу `api.open-meteo.com` (строка с `error -116`), прогноз приходит со второго сервера (см. ниже). Загрузка процессора (`top` в SSH) — около 23 % с пузырями и 3 % без них. Что видно на экране, если что-то не так, — в строке состояния внизу слева: нет файла настроек, подключение к Wi-Fi, город не найден, сервер погоды не отвечает. ## Как это работает - **Две задачи.** `clockNet` подключается к Wi-Fi, находит город и раз в 15 минут спрашивает погоду — сетевые запросы длятся секундами, и экран в это время не замирает. `lvglClock` рисует. LVGL не потокобезопасна, поэтому сетевая задача экрана не трогает: она кладёт данные в общую структуру (`shared`) в критической секции и увеличивает счётчик `seq`. Таймер LVGL раз в 250 мс копирует структуру и перерисовывает погоду, только если счётчик изменился. - **Город → координаты.** `geocoding-api.open-meteo.com/v1/search?name=...` — название в адресе кодируется побайтно в `%XX` (UTF-8). В ответе — широта, долгота и название по-русски. - **Погода.** `/v1/forecast` с параметрами `current=` (сейчас) и `daily=` (минимум и максимум на сегодня), `timezone=auto` — open-meteo добавляет в ответ смещение пояса места `utc_offset_seconds`, по нему идут часы. Код погоды WMO (0 — ясно, 61 — дождь, 95 — гроза…) превращается в текст и значок в `weatherText`. - **Два сервера погоды.** Тот же запрос понимают `api.open-meteo.com` и `historical-forecast-api.open-meteo.com` — они на разных адресах. Некоторые провайдеры не пускают к первому; рецепт пробует оба и запоминает тот, что ответил. - **HTTP без TLS.** open-meteo отвечает и по обычному HTTP, поэтому хватает сокетов FreeRTOS+TCP (`wifiHttpGet` из `wifi_http`) — без Mbed TLS и его ~60 КБ Flash. Ответ приходит кусками `chunked`; `wifiHttpGet` склеивает их, тело — обычная строка. - **JSON без библиотеки.** Ответ короткий и одинаковый по структуре: `jsonNumber` ищет `"ключ":` после начала нужного объекта (`"current":{`) и читает число `strtof`. Для чего-то сложнее подойдёт библиотека разбора JSON, например cJSON. - **Время.** NTP-клиент (`libs/ntp`) ставит часы RTC по поясу `TIME_ZONE` (по умолчанию +3). Рецепт переводит их в пояс места: `RTC − TIME_ZONE + utc_offset`. Пока время не пришло (в RTC 2023 год или раньше), на экране «--:--». Секунды меняются раз в секунду, часы и минуты — раз в минуту, дата — раз в сутки: LVGL перерисовывает только изменённые надписи. - **Значок погоды** — несколько объектов LVGL с закруглением: круг — солнце (ночью — луна, `is_day`), три круга и прямоугольник — облако, полоски — дождь, кружки — снег. - **Пузыри.** Таймер раз в 330 мс с вероятностью 1/3 выпускает пузырь в случайном месте снизу (`lv_rand`, зерно — счётчик тактов ядра). У пузыря две анимации `lv_anim`: подъём за верх экрана (мелкие быстрее) и бесконечное покачивание по горизонтали (`lv_anim_set_reverse_duration`, `lv_anim_path_ease_in_out`). В конце подъёма `lv_obj_delete_anim_completed_cb` удаляет пузырь. Пузыри — в отдельном слое, созданном первым, поэтому рисуются под надписями. Касание (`LV_EVENT_PRESSED` экрана) — четыре пузыря из точки касания; надписи и карточка не кликабельны, чтобы касание доходило до экрана. - **Нагрузка.** Каждый сдвиг пузыря — перерисовка куска градиентного фона и цифр под ним. Экран обновляется 30 раз в секунду вместо 50 (`lv_display_get_refr_timer`), пузырей — не больше 10. ## Переделать под себя - Другие данные погоды — параметры `current=` и `daily=` в `fetchWeather` (список — в [документации open-meteo](https://open-meteo.com/en/docs)) и `jsonNumber` с нужным ключом. Прогноз на несколько дней — `forecast_days=3`, значения в `daily` — массивы по дням. - Число и размер пузырей — `MAX_BUBBLES`, `bubble()`; без пузырей — убрать таймер `bubbleTimer`, загрузка процессора упадёт до 3 %. - Цвета и раскладка — `buildScreen`. Шрифты с карты рисуются любого размера (`lvglLoadFont`). --- Исходники рецепта: [recipes/lvgl_clock](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/lvgl_clock) # lvgl_demo — интерфейс на LVGL с сенсорным экраном > Экран с показаниями платы на библиотеке LVGL 9: температура кристалла, свободная память, адреса сети, графики температуры, загрузки процессора, ОЗУ и SDRAM за две минуты, переключатель светодиода.… Экран с показаниями платы на библиотеке [LVGL 9](https://lvgl.io): температура кристалла, свободная память, адреса сети, графики температуры, загрузки процессора, ОЗУ и SDRAM за две минуты, переключатель светодиода. Русский текст — шрифтом TTF с SD-карты. ## Подключение Дисплей RGB 800×480 (RGB050M1) — как в рецепте [lcd_ltdc](/boards/bh723/recipes/lcd_ltdc/). Сенсорная панель GT911 — на I2C1: | Сигнал тача | Вывод | Контакт | |---|---|---| | SCL | PB8 | J1-26 | | SDA | PB7 | J1-23 | | INT | PB9 | J1-25 | | RST | PD4 | J1-17 | На SD-карте — шрифты `/SD/fonts/PT_Sans-Web-Regular.ttf` и `PT_Sans-Web-Bold.ttf` (есть в архиве `sd-card.zip`; без них экран работает со встроенным шрифтом без кириллицы). ## Включить LVGL занимает ~240 КБ Flash, поэтому вместе со всем остальным прошивка не помещается: выключается Lua (или SSH, `-DH723_WITH_SSH=OFF`). ```bash cmake --preset Release -DH723_WITH_LUA=OFF -DH723_RECIPES="lvgl_demo" cmake --build build/Release ``` Прошивка с SSH и LVGL — 724 КБ. ## Вывод на плате ``` lvgl_demo: started gt911: product 911, panel 800x480 lvgl_demo: fonts loaded in 105 ms lvgl_demo: screen ready, full redraw 52881 us ``` Полная перерисовка экрана — 53 мс; дальше LVGL перерисовывает только изменившееся (раз в секунду — цифры и графики). Загрузка процессора при этом — около 5 %. ## Как это работает - **Задача LVGL одна.** LVGL не потокобезопасна: интерфейс создаётся и меняется только в задаче `lvgl`, которая крутит `lv_timer_handler()`. Данные от других задач — через очередь или флаги, которые читает таймер LVGL (`lv_timer_create`). - **Вывод — прямо в кадровые буферы LTDC** (`LV_DISPLAY_RENDER_MODE_DIRECT`). Буферов два, в SDRAM; LVGL рисует в невидимый и сама переносит изменённые места из видимого. Функция вывода (`flush` в `lvgl_port.c`) выгружает кэш и показывает готовый буфер с ближайшего кадра (`lcdShow` из `lcd_ltdc`). - **Кэш.** Процессор рисует через кэш (write-back), а LTDC читает память напрямую, поэтому перед показом весь кэш данных (32 КБ) выгружается в SDRAM — это быстрее, чем выгружать буфер построчно. Операции DMA2D LVGL сама окружает выгрузкой и инвалидацией кэша. - **DMA2D** (`LV_USE_DRAW_DMA2D`) заливает прямоугольники и копирует картинки, процессору остаются буквы, скругления и линии графиков. - **Память LVGL — в SDRAM**: `lv_malloc_core` и соседние функции в `lvgl_port.c` вызывают `sdramMalloc` (`LV_USE_STDLIB_MALLOC = LV_STDLIB_CUSTOM`). - **Шрифты с карты.** Tiny TTF (`LV_USE_TINY_TTF`) рисует буквы из файла TTF любого размера. Файл один раз читается в SDRAM (`lvglLoadFont`), следующие размеры используют те же данные; нарисованные буквы кэшируются. Во Flash шрифтов нет, кроме запасного латинского. - **Файлы для LVGL** — буква `S:`: `"S:fonts/x.ttf"` открывается как `/SD/fonts/x.ttf` (`LV_USE_FS_STDIO`). Так же загружаются картинки. - **Тач GT911** опрашивается по I2C при каждом обновлении LVGL (раз в 20 мс): регистр состояния `0x814E` (бит 7 — новые данные), координаты первого касания с `0x8150`, затем состояние сбрасывается записью нуля. При сбросе уровень INT задаёт адрес контроллера (0 — 0x5D). Разрешение панели контроллер хранит сам — `gt911: panel 800x480`. - **Размер.** В `config/lv_conf.h` выключены ненужные форматы пикселей программного рисования (каждый — десятки КБ), LVGL собирается с `-Os` (`recipe.cmake`). - **Зависимости рецепта** — в `recipe.cmake`: исходники LVGL из `third_party/lvgl` и драйвер дисплея из рецепта `lcd_ltdc` (его пример при этом не запускается). ## Переделать под себя - Свой экран — функция `buildScreen` в `lvgl_demo.c`; документация и примеры виджетов — [docs.lvgl.io](https://docs.lvgl.io/9.6/). - Экраны можно собирать мышкой в [SquareLine Studio](https://squareline.io) или [EEZ Studio](https://www.envox.eu/studio/) — они выдают код на C для LVGL 9. - Другой шрифт — любой TTF с кириллицей на карте и путь в `lvgl_demo.h`. Шрифты бывают большими: для экономии SDRAM подходят версии с урезанным набором символов. - Картинки — PNG или BMP на карте (`LV_USE_LODEPNG` / `LV_USE_BMP` в `lv_conf.h`) и `lv_image_set_src(img, "S:images/logo.png")`. - На той же шине I2C1 можно держать и другие датчики (например, BME280 из рецепта [i2c_bme280](/boards/bh723/recipes/i2c_bme280/)), но HAL I2C не потокобезопасен — обращения к шине из разных задач нужно закрыть мьютексом. --- Исходники рецепта: [recipes/lvgl_demo](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/lvgl_demo) # rtos_queue — две задачи FreeRTOS и очередь > Задача-датчик раз в секунду меряет температуру кристалла (встроенный датчик DTS) и кладёт измерение в очередь. Задача-обработчик забирает измерения и раз в 5 штук печатает среднее. Так обычно и… Задача-датчик раз в секунду меряет температуру кристалла (встроенный датчик DTS) и кладёт измерение в очередь. Задача-обработчик забирает измерения и раз в 5 штук печатает среднее. Так обычно и делят работу в прошивке: кто добывает данные, не ждёт того, кто их обрабатывает. ## Включить ```bash cmake --preset Release -DH723_RECIPES="rtos_queue" ``` ## Вывод на плате ``` rtos_queue: started, period 1000 ms rtos_queue: #5 t=7475 ms, die 58 C (avg of 5), queued 0 rtos_queue: #10 t=12475 ms, die 59 C (avg of 5), queued 0 rtos_queue: #15 t=17475 ms, die 59 C (avg of 5), queued 0 ``` `queued 0` — обработчик успевает, очередь не копится. `t` растёт ровно на 5000 мс. ## Как это работает - **Очередь копирует данные.** `xQueueSend` кладёт в очередь копию структуры `rtosQueueSample`, а не указатель — после отправки датчик может спокойно менять свою переменную. - **Обработчик не тратит процессор, пока ждёт.** `xQueueReceive` с таймаутом усыпляет задачу до прихода данных. Таймаут (2 периода) нужен, чтобы заметить, что датчик замолчал. - **Датчик не ждёт обработчика.** `xQueueSend(..., 0)`: если очередь полна, измерение теряется с предупреждением, а не стопорит датчик. Для данных, которые терять нельзя, ставят таймаут или `portMAX_DELAY`. - **Ровный период.** `vTaskDelayUntil` отсчитывает от прошлого пробуждения, а не от конца работы, поэтому период не «плывёт» из-за времени измерения. - **Приоритеты.** Обработчик на уровень выше датчика: как только в очереди что-то появилось, планировщик сразу переключается на него. - **Статическая память.** Очередь (`xQueueCreateStatic`) и задачи (`xTaskCreateStatic`) — в статических массивах, их размер виден при сборке. ## Переделать под себя - Своё измерение — поля в `rtosQueueSample` и код в `sensorTask`. - Частота и усреднение — `RTOS_QUEUE_PERIOD_MS`, `RTOS_QUEUE_BATCH`. - Если обработчик делает долгую работу (запись на SD, отправка по сети), стоит увеличить `RTOS_QUEUE_LENGTH`, чтобы очередь пережила паузы. - Стек: `uxTaskGetStackHighWaterMark(NULL)` внутри задачи покажет, сколько слов стека ни разу не использовалось. --- Исходники рецепта: [recipes/rtos_queue](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/rtos_queue) # sd_file — файл на SD-карте > Раз в 10 секунд дописывает строку с датой и временем работы в /SD/recipes/log.txt. При старте один раз меряет скорость: пишет и читает файл 4 МБ блоками по 32 КБ. Раз в 10 секунд дописывает строку с датой и временем работы в `/SD/recipes/log.txt`. При старте один раз меряет скорость: пишет и читает файл 4 МБ блоками по 32 КБ. ## Включить ```bash cmake --preset Release -DH723_RECIPES="sd_file" ``` Нужна карта microSD с FAT32 или exFAT. ## Вывод на плате ``` sd_file: started, log /SD/recipes/log.txt every 10 s sd_file: 4096 KB write 10240 KB/s, read 10694 KB/s sd_file: /SD/recipes/log.txt += 2026-03-14 10:02:03 #1 uptime 3 s sd_file: size 68 bytes sd_file: /SD/recipes/log.txt += 2026-03-14 10:02:13 #2 uptime 13 s sd_file: size 103 bytes ``` Размер 68 байт после первой строки — в файле уже была строка с прошлого запуска: файл дописывается, а не перезаписывается. Скорость зависит от карты: на разных картах запись 1,8–10 МБ/с. ## Как это работает - **Пути.** Тома FatFs называются `/SD` и `/RAM` (RAM-диск 4 МБ в SDRAM). Эти пути понимают и стандартный C (`fopen`, `fprintf` — обёртки в `libs/disks/wrappers.c`), и FatFs напрямую (`f_open`, `f_write`). - **Текст — через stdio.** `sdFileAppend` открывает файл в режиме `"a"`, пишет строку и сразу закрывает. `fclose` сбрасывает буфер и обновляет запись в каталоге; если держать файл открытым и пропадёт питание, последние данные потеряются. - **Большие объёмы — через FatFs и крупными блоками.** Драйвер SD (`libs/disks/sddisk.c`) передаёт до 64 секторов одной командой. Блоки по 32 КБ дают скорость в разы выше, чем запись по байту или по строке; буфер такого размера берётся из SDRAM (`sdramMalloc`). - **Карта появляется не сразу.** Её монтирует `appTask`, задача рецепта ждёт `sdDiskIsMounted()`. - **Время.** Из RTC: его ставит NTP-клиент, когда есть интернет через Wi-Fi. У HAL есть правило: сначала `HAL_RTC_GetTime`, потом `HAL_RTC_GetDate`: чтение времени фиксирует теневые регистры до чтения даты, и при обратном порядке следующие показания будут старыми. ## Переделать под себя - Логгер данных: `sdFileAppend("/SD/data.csv", строка)` из своей задачи. При записи чаще раза в секунду строки выгоднее копить (в буфере или файле на RAM-диске `/RAM/...`) и переносить на SD пачкой. - Коды ошибок FatFs (`FRESULT`) — в `third_party/fatfs/source/ff.h`. --- Исходники рецепта: [recipes/sd_file](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/sd_file) # tcp_speed — скорость TCP по Wi-Fi > Плата подключается к точке доступа Wi-Fi и держит два TCP-порта: на 5001 ПК шлёт данные, плата принимает их и выбрасывает; с 5002 плата шлёт поток, пока ПК не закроет соединение. Скорость считают… Плата подключается к точке доступа Wi-Fi и держит два TCP-порта: на 5001 ПК шлёт данные, плата принимает их и выбрасывает; с 5002 плата шлёт поток, пока ПК не закроет соединение. Скорость считают обе стороны. Клиент для ПК — `tcp_speed.py` в этой папке. ## Включить Имя и пароль сети — опциями CMake, в код они не попадают: ```bash cmake --preset Release -B build/Speed -DH723_WITH_LUA=OFF -DH723_WITH_SSH=OFF -DH723_WITH_TLS=OFF \ -DH723_RECIPES="tcp_speed" -DTCP_SPEED_SSID="my_wifi" -DTCP_SPEED_PASSWORD="my_password" cmake --build build/Speed python ../../tools/dfu_flash.py build/Speed/BoardH723.hex ``` Без `TCP_SPEED_SSID` рецепт сам не подключается и ждёт, пока Wi-Fi поднимет `/SD/boot.lua` (прошивка с Lua). Адрес платы — в логе (`IP Address: ...`), дальше на ПК: ```bash python recipes/tcp_speed/tcp_speed.py 192.168.1.50 10 ``` ## Вывод ПК: ``` PC->board: 25.5 Mbit/s (32833536 B, 10.3 s) board->PC: 34.8 Mbit/s (43487800 B, 10.0 s) ``` Плата: ``` tcp_speed: power save 1 -> 0 IP Address: 192.168.1.50 tcp_speed: listening, rx port 5001, tx port 5002 tcp_speed: rx 32833536 bytes in 10319 ms, 25.4 Mbit/s tcp_speed: tx 43540480 bytes in 10013 ms, 34.7 Mbit/s ``` Замеры с роутером TP-Link в диапазоне 2,4 ГГц (ПК подключён к тому же роутеру на 5 ГГц): | Прошивка | ПК → плата | Плата → ПК | |---|---|---| | без Lua, SSH и TLS, окна рецепта, энергосбережение выключено | 24–25 Мбит/с | 33–35 Мбит/с | | то же, окна TCP по умолчанию | 12 Мбит/с | 14 Мбит/с | | с Lua и SSH, точка доступа и BLE работают, энергосбережение включено | 14–15 Мбит/с | 35–37 Мбит/с | Эфир 2,4 ГГц меняется от минуты к минуте: прогоны подряд расходятся на 10–20 %. ## Как это работает - **Окно TCP.** Отправитель держит в пути не больше окна получателя и ждёт подтверждений, поэтому скорость не выше «окно / время ответа». В FreeRTOS+TCP окно по умолчанию — половина буфера сокета, 8 сегментов (11,7 КБ). Рецепт задаёт буферы и окна опцией `FREERTOS_SO_WIN_PROPERTIES` на слушающем сокете, принятые соединения её наследуют. Окно приёма — 32 сегмента: с большим ПК шлёт пачками, очередь на ESP32-C2 переполняется, и TCP на ПК после потерь сбрасывает скорость. Окно отправки — 44 сегмента (64 КБ, предел без масштабирования окна). Глобальные размеры в `FreeRTOSIPConfig.h` не увеличены: каждый активный сокет (SSH, Lua) занимал бы до 90 КБ кучи. - **Подтверждения.** FreeRTOS+TCP откладывает ACK на полный сегмент до 20 мс — при приёме потока это потолок около 18 Мбит/с. Сборка заменяет задержку на 2 мс (`patch_source` в `examples/BoardH723/CMakeLists.txt`), это действует на все сокеты. - **Энергосбережение.** По умолчанию радио ESP32-C2 спит между маяками точки доступа (`WIFI_PS_MIN_MODEM`), и приём идёт рывками. Рецепт выключает его: `wifiSetPowerSaveMode(WIFI_PS_NONE)` — плата потребляет больше. - **Приём без копии.** `FreeRTOS_recv` с флагом `FREERTOS_ZERO_COPY` отдаёт указатель прямо в буфер сокета, `FreeRTOS_ReleaseTCPPayloadBuffer` освобождает место. - **Потолок.** Кадры между STM32 и ESP32-C2 идут по SPI 62,5 МГц транзакциями по 1600 байт — около 0,2 мс на кадр, это примерно 40 Мбит/с на отправку. Приём ограничивает ESP32-C2: радио и около 25 КБ свободной памяти на буферы. - **Прошивка ESP32-C2.** Цифры выше получены с актуальной прошивкой [esp32c2phy](https://github.com/Kits-Lab-Dev/esp32c2phy) (ветка `main`): двойной буфер SPI, приём из эфира без копии. Как прошить ESP32-C2 — `AGENTS.md`, раздел «Прошивка». --- Исходники рецепта: [recipes/tcp_speed](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/tcp_speed) # uart_dma — приём UART через DMA > Принимает символы с USART1 (тот же порт, куда идёт лог) через DMA в кольцевой буфер, собирает строки и выполняет команды: help, led on, led off, uptime. Любая другая строка печатается обратно с… Принимает символы с USART1 (тот же порт, куда идёт лог) через DMA в кольцевой буфер, собирает строки и выполняет команды: `help`, `led on`, `led off`, `uptime`. Любая другая строка печатается обратно с длиной. ## Включить ```bash cmake --preset Release -DH723_RECIPES="uart_dma" ``` Терминал на USART1 115200 8N1: PB15 — вход платы (RX), PB14 — выход (TX). Через отладчик «Лобзик» это его виртуальный COM-порт; можно и любой USB-UART 3,3 В на J2 (13 — PB15, 14 — PB14). ## Вывод на плате Команды отправлены с ПК в терминал USART1: ``` uart_dma: started, type 'help' in USART1 terminal uart_dma: commands: help, led on, led off, uptime uart_dma: led on uart_dma: uptime 15503 ms uart_dma: rx 'hello board 123' (15 bytes) ``` При переполнении строка из 300 символов принимается и обрезается до 127 (длина буфера строки), восемь строк подряд по 42 байта принимаются целыми, хотя кольцевой буфер DMA (256 байт) за это время оборачивается несколько раз. ## Как это работает - **DMA в кольцевом режиме.** DMA1 Stream3 по запросу `USART1_RX` пишет каждый принятый байт в буфер 256 байт и, дойдя до конца, начинает сначала. Процессор в приёме байтов не участвует. - **Когда разбирать буфер.** `HAL_UARTEx_ReceiveToIdle_DMA` вызывает колбэк в трёх случаях: буфер заполнен наполовину, заполнен целиком и линия замолчала (событие IDLE — пауза в один символ). Последнее важно: команда из 6 символов не заполнит буфер, но придёт сразу после паузы. - **Новые данные — от прошлой позиции до текущей.** Колбэк получает позицию DMA в буфере; байты между прошлой и новой позицией (с переходом через край) уходят в `StreamBuffer` FreeRTOS. - **Прерывание короткое.** В колбэке только копирование в `StreamBuffer`; строки собирает и команды выполняет обычная задача `uartRx`, которой можно печатать и ждать. - **Память для DMA.** Буфер — в RAM_D1 (`pvPortMalloc`), выровнен по 32 байта (строка кэша); RAM_D1 кэшируется, поэтому перед чтением — `SCB_InvalidateDCache_by_Addr`. - **Шум на линии.** Без подтяжки неподключённый вход ловит помехи и ошибки кадра; рецепт включает подтяжку PB15 к 3,3 В. После ошибки UART приём перезапускается в `rxError`. - **Передача не тронута.** Лог печатает через тот же `huart1` (DMA1 Stream2); приём и передача у HAL независимы. ## Переделать под себя - Свои команды — в `handleLine`. - Свой протокол (бинарный, Modbus, NMEA) — байты берутся из `uartDmaRead()` и разбираются своим кодом, без `uartTask`. - Другой UART: свободны USART2 (PA2/PA3), USART3 (PB10/PB11), UART4 (PA0/PA1) и другие — их нужно сначала включить (CubeMX или вручную: такты, выводы, `HAL_UART_Init`), затем поменять `huart`, `DMA_REQUEST_...` и, при занятом Stream3, номер потока DMA. --- Исходники рецепта: [recipes/uart_dma](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/uart_dma) # wifi_http — Wi-Fi и HTTP-запрос > Подключается к точке доступа Wi-Fi и раз в минуту делает GET http://example.com/: адрес по DNS, TCP-соединение, запрос, код ответа и объём. Только сокеты FreeRTOS+TCP, без библиотек. Подключается к точке доступа Wi-Fi и раз в минуту делает `GET http://example.com/`: адрес по DNS, TCP-соединение, запрос, код ответа и объём. Только сокеты FreeRTOS+TCP, без библиотек. ## Включить Имя и пароль сети — в `wifi_http.h`: ```c #define WIFI_HTTP_SSID "my_wifi" #define WIFI_HTTP_PASSWORD "my_password" ``` Если оставить пустыми, рецепт сам не подключается и ждёт, пока Wi-Fi поднимет кто-то другой — `/SD/boot.lua` (`wifi.connect(...)`) в прошивке с Lua. В прошивке без Lua (`-DH723_WITH_LUA=OFF`) имя и пароль нужно задать здесь. ```bash cmake --preset Release -DH723_RECIPES="wifi_http" ``` ## Вывод на плате Wi-Fi поднял `boot.lua`, плата получила адрес 192.168.1.50. Строки `->` с адресом — отладочный уровень лога (`DBG`): ``` wifi_http: started, GET http://example.com/ every 60 s IP Address: 192.168.1.50 wifi_http: example.com -> 8.6.112.6 wifi_http: GET http://example.com/ -> 200, 932 bytes, 3160 ms wifi_http: example.com -> 8.6.112.6 wifi_http: GET http://example.com/ -> 200, 929 bytes, 146 ms ... wifi_http: example.com -> 8.6.112.0 wifi_http: connect example.com (8.6.112.0):80 error -116 wifi_http: example.com -> 8.47.69.0 wifi_http: GET http://example.com/ -> 200, 930 bytes, 5417 ms ``` Первый запрос после подключения дольше (3 с). В конце — перебор адресов: первый не ответил за 5 с (`-116` — таймаут), сработал второй. При ошибке рецепт пишет, на каком шаге: 1 — DNS, 2 — сокет, 3 — соединение (с адресом и кодом ошибки), 4 — отправка, 5 — нет ответа HTTP. ## Как это работает - **Wi-Fi.** ESP32-C2 на плате — сетевой адаптер: TCP/IP работает на STM32 (FreeRTOS+TCP), а ESP передаёт кадры Ethernet по SPI. Подключение — те же вызовы, что в Lua: `espFeatureEnable(ftWifi)`, `wifiSetWiFiMode(WIFI_MODE_STA)`, `wifiConnect()` из `libs/esp/control.h`. `autoReconnect = true` — ESP сам переподключается при обрыве. - **Адрес.** Плата получает IP по DHCP от роутера. Задача ждёт, пока интерфейс поднят и у него есть шлюз: если DHCP не ответил, стек сам назначает адрес 169.254.x.x без шлюза, и с ним в интернет не выйти. - **Три сети одновременно.** У платы есть USB-сеть (192.168.4.1), точка доступа (192.168.11.1) и Wi-Fi-клиент. Пакет в интернет уходит через тот интерфейс, где есть шлюз, — Wi-Fi-клиент. - **DNS и несколько адресов.** `FreeRTOS_getaddrinfo` спрашивает DNS-сервер, выданный роутером, и возвращает список адресов. У крупных сайтов адресов несколько, и какой-то может не отвечать — `httpConnect` пробует их по очереди. Кэш DNS хранит до 4 адресов на имя (`ipconfigDNS_CACHE_ADDRESSES_PER_ENTRY` в `config/FreeRTOSIPConfig.h`), поэтому повторный запрос из кэша отдаёт весь список, а не один последний адрес ответа — он может оказаться мёртвым. Ошибки FreeRTOS+TCP — отрицательные `pdFREERTOS_ERRNO_*`. - **Таймауты.** `FREERTOS_SO_RCVTIMEO`/`SNDTIMEO` по 5 с: без них `recv` ждёт вечно, если сервер замолчал. - **Конец ответа.** Заголовок `Connection: close` — сервер закрывает соединение после ответа, и `FreeRTOS_recv` возвращает ошибку: так конец ответа виден без разбора `Content-Length`. - **Тело ответа.** Если передать `wifiHttpGet` буфер, ответ принимается в него целиком; тело начинается после пустой строки `"\r\n\r\n"` и возвращается в `res.body`. Ответ с `Transfer-Encoding: chunked` (куски «длина в hex, данные») склеивается в обычную строку. Не поместился в буфер — код `-6`, начало ответа при этом в буфере есть. - **Закрытие.** `FreeRTOS_shutdown`, дождаться ответного закрытия, затем `FreeRTOS_closesocket`. Если сразу звать `closesocket`, соединение у сервера может остаться висеть. ## Переделать под себя - Другой адрес — `WIFI_HTTP_HOST`, `WIFI_HTTP_PATH`. Пример разбора тела (JSON с погодой) и подключения к сети из своего кода (`wifiHttpJoin`, `wifiHttpOnline`) — рецепт [lvgl_clock](/boards/bh723/recipes/lvgl_clock/). - Отправка данных на сервер (POST) — тот же код, в запросе `POST`, заголовки `Content-Type` и `Content-Length` и тело после пустой строки. - HTTPS — через Mbed TLS (`libs/tls`, его уже использует Lua-модуль `ssl`); в рецепт не входит. --- Исходники рецепта: [recipes/wifi_http](https://github.com/Kits-Lab-Dev/Board-H723/tree/main/recipes/wifi_http) # ПО для разработки > ПО для Доски H723: STM32CubeCLT, CMake, VSCode, сборка и прошивка по USB, отладка, SSH и Lua, готовый проект с FreeRTOS, Wi-Fi, USB-Ethernet и рецепты на C. Для программирования Доски H723 понадобятся компилятор и утилиты ST, CMake, Git и Python 3. Редактор — любой; для VSCode в проекте уже есть задачи сборки, прошивки и отладки. ## Что установить - [STM32CubeCLT](https://www.st.com/en/development-tools/stm32cubeclt.html) 1.18 или новее — компилятор `arm-none-eabi-gcc`, CMake, Ninja, `STM32_Programmer_CLI` и GDB-сервер. Все эти программы должны быть в `PATH`. - [STM32CubeMX](https://www.st.com/en/development-tools/stm32cubemx.html) — генератор кода периферии; нужен, только если меняются выводы, такты или DMA. - [Git](https://git-scm.com/) и [Python 3](https://www.python.org/) — для скриптов прошивки и сборки архива SD-карты. - [Visual Studio Code](https://code.visualstudio.com/) и расширения: - [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools-extension-pack) — подсветка и навигация по коду; - [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) — сборка из редактора; - [Cortex-Debug](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug) — прошивка и отладка из редактора. :::note Скачать STM32CubeCLT, STM32CubeMX и пакеты с библиотеками для STM32H7 можно с нашего [облака](https://disk.yandex.ru/d/Fjw2cZVs_fMvog). ::: ## Получение исходников Сторонние библиотеки подключены как git submodule, поэтому после клонирования их нужно скачать: ```bash git clone https://github.com/Kits-Lab-Dev/Board-H723.git cd Board-H723 git submodule update --init ``` Архив [zip](https://github.com/Kits-Lab-Dev/Board-H723/archive/refs/heads/main.zip) для сборки не подходит: в нём нет сторонних библиотек. На Windows: - перед клонированием включите длинные пути: `git config --global core.longpaths true`, иначе часть файлов wolfSSL не создастся; - клонируйте в короткий путь, например `C:\src\Board-H723`: при длинном пути CMake предупреждает о пределе в 250 символов на путь объектного файла. ## Что в репозитории В папке `examples` два проекта: - `BoardH723` — готовый проект для платы: - драйвер Wi-Fi и BLE (ESP32-C2 по SPI с DMA), совместимый с сокетами Unix; - USB-NCM (USB-Ethernet) и DHCP-сервер для USB и точки доступа Wi-Fi; - FreeRTOS и FreeRTOS+TCP; - FatFs: microSD (`/SD`) и RAM-диск в SDRAM (`/RAM`), менеджер памяти SDRAM; - SSH-сервер с SFTP (загрузка файлов на SD-карту) и терминалом; - интерпретатор Lua: GPIO, SPI, I2C, UART, потоки, Wi-Fi, сокеты, HTTPS; - `ESP-Loader` — мост USB-UART для прошивки ESP32-C2 через STM32. Рядом лежат [рецепты](/boards/bh723/recipes/) — готовые модули на C, `docs/board.md` с описанием железа для прошивки и `tools/` со скриптами. Состав и лицензии сторонних библиотек — `THIRD_PARTY.md`. ## Сборка ```bash cd examples/BoardH723 cmake --preset Release cmake --build build/Release ``` Результат — `build/Release/BoardH723.elf`, `.hex` и `.bin`; ELF содержит отладочную информацию. Собирается GCC 13 (из STM32CubeCLT) и GCC 14 (Arm GNU Toolchain). Опции CMake добавляются к первой команде: | Опция | Что делает | |---|---| | `-DH723_WITH_SSH=OFF` | убирает SSH и SFTP | | `-DH723_WITH_TLS=OFF` | убирает HTTPS для Lua | | `-DH723_WITH_LUA=OFF` | убирает Lua целиком, вместе с HTTPS для него — освобождается Flash под LVGL | | `-DH723_IDLE_SLEEP=ON` | в простое ядро засыпает командой WFI: кристалл остывает с 66 до 51 °C | | `-DH723_RECIPES="a;b"` | подключает рецепты, [список](/boards/bh723/recipes/) | Размер прошивки при GCC 13: около 808 КБ со всем, 629 КБ без SSH, 530 КБ без SSH и TLS; без Lua, но с SSH — 482 КБ. ## Сборка и отладка из VSCode 1. Откройте папку `examples/BoardH723`. CMake Tools предложит выбрать пресет — выберите `Release`. 2. При настройке проекта CMake создаёт в `.vscode` файлы `tasks.json` и `launch.json` из шаблонов `tasks.json.in` и `launch.json.in`: - задача `build` — сборка; - задача `DFU Upload` — сборка и запись прошивки через `STM32_Programmer_CLI` по USB DFU; - конфигурация отладки `stlink` для Cortex-Debug. 3. В переменной среды `STM32CLT_PATH` должен быть путь к STM32CubeCLT: по нему конфигурации находят программатор и файл описания регистров `STM32H723.svd`. ## Прошивка **По USB, без кнопок и отладчика.** Плата подключена кабелем USB-C, в ней работает прошивка из этого проекта: ```bash python tools/dfu_flash.py ``` Скрипт отправляет плате UDP-пакет `dfu`, плата перезагружается в системный загрузчик STM32, скрипт записывает `build/Release/BoardH723.hex`, запускает прошивку и ждёт ответа на ping — около 40 секунд. Другой файл: `python tools/dfu_flash.py путь/к/файлу.hex`. **Если плата не отвечает** (нет USB-сети, прошивка старая или зависла): зажмите BOOT, нажмите RESET (или переподключите USB), отпустите BOOT — плата входит в DFU — и запустите скрипт ещё раз. На Windows для DFU нужен драйвер STM32 DFU, он ставится вместе со STM32CubeProgrammer; устройство в диспетчере называется «STM32 BOOTLOADER». **Отладчик «Лобзик»** по SWD (разъём J6) — прошивка, чтение памяти и регистров на работающей плате, разбор HardFault, лог USART1: [навык и скрипты](https://github.com/Kits-Lab-Dev/lobzik-skill). **Модуль ESP32-C2** прошивается через STM32 командой `reboot esploader` в SSH (или `rtos.reboot("esploader")` в Lua): плата запускает мост ESP-Loader с SD-карты, и на ПК появляется USB-UART для esptool. Прошивка модуля — проект [esp32c2phy](https://github.com/Kits-Lab-Dev/esp32c2phy), собирается ESP-IDF 5.4. ## SD-карта Плата работает и без карты, но SSH, Lua и многие рецепты используют её. Содержимое карты — файлы из `examples/BoardH723/app/SD` (скрипт загрузки `boot.lua`, библиотеки Lua, шрифты, `utils/ESPLoader.bin`): - готовый архив `sd-card.zip` лежит в [релизах](https://github.com/Kits-Lab-Dev/Board-H723/releases); его распаковывают в корень карты FAT32; - собрать архив самостоятельно: `python tools/make_sd_zip.py` (результат — `build/sd-card.zip`); - на работающую плату файлы заливаются по SFTP в `/SD/`. ## Вход по SSH Плата в USB-сети — `192.168.4.1`, вход `ssh root@192.168.4.1`; по Wi-Fi — на адрес, выданный роутером (команда `ip` в терминале). Пароль при первом запуске плата создаёт сама, сохраняет в файл `/SD/.ssh/password` на SD-карте и печатает в лог (USART1). Сменить пароль — исправить этот файл и перезагрузить плату. Без карты войти по паролю нельзя. Вход по ключу: создайте пару `ssh-keygen -t rsa -b 4096`, скопируйте открытый ключ на карту в `/SD/.ssh/mcu.key.pub` и входите командой `ssh root@192.168.4.1 -i ~/.ssh/mcu.key`. Пока на карте есть этот файл, вход по паролю выключен. Команды терминала: `help`, `ls`, `cat`, `cd`, `top`, `ip`, `reboot` (`reboot dfu` — в загрузчик USB, `reboot esploader` — режим прошивки ESP32-C2), `lua -f /SD/x.lua`, `exit`. Терминал режет аргументы по пробелам и не понимает кавычки — Lua-код запускайте файлом. ## Lua Lua — дополнительный интерпретатор для быстрых опытов; основной язык проекта — C. Скрипты кладут на SD-карту и запускают командой `lua -f /SD/x.lua`; `/SD/boot.lua` выполняется при загрузке. Описание модулей — в репозитории, в `examples/BoardH723/libs/lua/docs`: `pio` (GPIO, ШИМ, АЦП), `serial`, `spi`, `i2c`, `lthread`, `wifi`, `rtos`. ## Рецепты Готовые модули на C — кнопка, UART с DMA, датчик на I2C, Wi-Fi и HTTP, скорость TCP, файлы на SD, задачи FreeRTOS, дисплей и LVGL — с объяснением, как они устроены: [Рецепты для Доски H723](/boards/bh723/recipes/). Подключаются к проекту `BoardH723` одной опцией CMake и не требуют правки его кода. ## Свой код и CubeMX - Своё приложение проще всего начать с ближайшего рецепта. Иначе — файлы в `app/` или `libs/<имя>/`, добавленные в `target_sources` и `board_includes` в `CMakeLists.txt`. Новые задачи FreeRTOS создаются из `appTask` (`app/app.c`) после запуска нужных подсистем. - В файлах, созданных CubeMX (`Core/`, `Drivers/`), правьте только внутри блоков `/* USER CODE BEGIN ... */ ... /* USER CODE END ... */` — остальное перезаписывается при генерации. Такты, выводы, MPU, DMA и периферию меняют в `BoardH723.ioc` и генерируют код заново. - Выводы SDRAM, microSD, ESP32-C2 и USB разведены на плате — не переназначайте их ([таблица занятых выводов](https://github.com/Kits-Lab-Dev/Board-H723/blob/main/docs/board.md)). MPU-регион SDRAM должен остаться «не Shareable». - Внутренней памяти мало: большие буферы берите из SDRAM (`sdramMalloc`) или из кучи FreeRTOS (`pvPortMalloc`; `malloc` уже перенаправлен в неё). - Сторонние библиотеки в `third_party/` не правятся: настройки лежат в `config/`, неизбежные правки — в `cmake/patch_source.cmake`. - В коде проекта (`app/`, `libs/`, `Core/`, `recipes/`) неявное объявление функции, смешение указателя с целым и `return` без значения — ошибки сборки: объявляйте функции в заголовках. - Лог выводится в USART1 (115200 8N1, PB14 TX / PB15 RX; разъём J6 и J2); макросы `INF`, `WRN`, `ERR` из `app/log.h`. :::tip[Для ИИ-агентов] В репозитории есть [AGENTS.md](https://github.com/Kits-Lab-Dev/Board-H723/blob/main/AGENTS.md) — как собрать, прошить и проверить плату. Вся вики одним файлом: [llms-full.txt](/llms-full.txt), только Доска H723 — [board-h723.txt](/_llms-txt/board-h723.txt). :::