Embedded HandbookПриложения
Все разделы › VI. Практикум
STM32F103, STM32F407 · уровень продвинутый · 14 мин чтения
Открыть в интерактивном справочнике

37. Приложения IoT

Прибор, который отправляет показания на сервер, — самая частая современная задача, и у неё есть два уровня: как подключиться к сети и что по ней передавать. Раздел о том, какие способы подключения существуют для F103 и F407, чем они отличаются в цене и сложности, и почему протокол обмена с сервером почти всегда MQTT.

Как это называется в документации

Оригинал Перевод
network stack / TCP/IP stack сетевой стек
AT command команда модулю в текстовом виде
hardwired TCP/IP стек, реализованный в контроллере аппаратно
MAC / PHY контроллер канального уровня / физический приёмопередатчик
socket сокет, точка соединения
broker брокер, сервер-посредник MQTT
topic топик, имя канала публикации
publish / subscribe опубликовать / подписаться
QoS (quality of service) уровень гарантии доставки
keep-alive интервал контроля соединения
retained message сохранённое сообщение
last will «завещание» — сообщение при обрыве
remaining length длина остатка пакета

37.1 Три способа подключиться

У F103 сетевого контроллера нет вовсе. У F407 есть контроллер Ethernet, но ему нужны внешняя микросхема физического уровня и стек TCP/IP в прошивке. Отсюда три пути.

Модуль по UART Модуль по SPI Встроенный Ethernet
камень любой любой только F407, F107
интерфейс UART, 2 провода SPI, 4 провода + INT RMII, 9 проводов + PHY
стек TCP/IP в модуле в модуле, аппаратно в вашей прошивке
память на STM32 буферы, ~1 КБ буферы, ~1 КБ десятки КБ
скорость до ~1 Мбит/с реально до ~10 Мбит/с 100 Мбит/с
Wi‑Fi да обычно провод нет
сложность разбор строк регистры и сокеты полноценный стек
«зависает и не отвечает» часто редко ваша ответственность

Три способа подключиться: модуль по UART, модуль по SPI и встроенный Ethernet — где живёт стек TCP/IP

⚠️ Заголовок не скажет, есть ли блок. Файл stm32f10x.h един на всё семейство F1 и объявляет ETH_BASE, ETH_TypeDef и указатель ETH для всех, включая F103, у которого контроллера нет. Обращение ETH->MACCR = … на F103 соберётся без предупреждения и даст BusFault по несуществующему адресу (раздел 35). Под условием STM32F10X_CL там только вектор ETH_IRQn и два регистра RCC. У F4 заголовки раздельные по камню: stm32f407xx.h объявляет Ethernet, stm32f401xe.h — нет. Наличие блока проверяют по даташиту, а не по тому, что компилируется.

Модуль по UART: AT-команды

Самый дешёвый и самый частый путь — Wi‑Fi модуль, который принимает текстовые команды и отвечает текстом. Обычно это ESP8266 или ESP32 с прошивкой AT-команд — команды ниже из её набора:

AT+CWJAP="имя_сети","пароль"     → OK      (секунды)
AT+CIPSTART="TCP","192.168.1.10",1883
AT+CIPSEND=14                     → >       (ждёт данные)
<14 байт>                         → SEND OK

Просто на вид и коварно в реализации. Три вещи, на которых спотыкаются.

Ответ приходит потоком, а не сразу. На 115 200 бод один байт — 87 мкс, строка в 30 байт — почти 3 мс. Ждать в цикле нельзя: приём через потоковый буфер (раздел 32) в задачу-разборщик, которая собирает строки и распознаёт OK и ERROR.

Таймауты разного порядка. Простая команда отвечает за десятки миллисекунд, подключение к сети — за секунды, до пятнадцати. Единый таймаут «на всё» либо мал для подключения, либо велик для всего остального. У каждой команды свой.

Модуль — отдельный компьютер со своими сбоями. Он может зависнуть, перезагрузиться, потерять сеть, выдать незапрошенное сообщение посреди вашего ответа. Программа обязана переживать всё это: сторожевой таймер связи (раздел 29), сброс модуля по выводу, повторное подключение по расписанию.

⚠️ Незапрошенные сообщения. Модуль присылает WIFI DISCONNECT, +IPD,n: с входящими данными и подобное в любой момент — в том числе между вашей командой и ответом на неё. Разборщик, который ждёт строго «команда → ответ», сломается на первом же. Разбирать нужно каждую строку по её началу, а не по ожиданию.

Модуль по SPI: стек в кремнии

Микросхема с контроллером Ethernet и стеком TCP/IP внутри, типовая — W5500: наружу отдаёт восемь сокетов через регистры по SPI. Ни строк, ни разбора — записал адрес и порт, выставил бит «открыть», проверил статус, положил байты в буфер отправки.

Плюс Минус
детерминировано: регистры, а не текст только провод, без Wi‑Fi
не зависает так, как модули с прошивкой нужен разъём и кабель
32 КБ буферов в самой микросхеме пропускная ограничена SPI
прерывание по событию сокетов восемь и не больше

При 10 МГц SPI — 1,25 МБ/с сырых, реально вдвое меньше: для датчиков избыток, для видео мало. Из 32 КБ буферов 16 — на передачу и 16 — на приём; при равном делении каждому сокету достаётся 2 КБ на передачу и 2 КБ на приём.

Правильный выбор для стационарного прибора с проводом: ничего не зависает, ничего не разбирается, вся сложность в кремнии.

Бывают и модули Wi‑Fi по SPI со стеком внутри, но типовой модуль по SPI — проводной. И есть четвёртый путь, между вторым и третьим: микросхема Ethernet без стека, например ENC28J60. В ней по SPI доступны только контроллер канального уровня и физический уровень, 10 Мбит/с, а стек TCP/IP — в прошивке, как у F407, только на любом камне. На F103C8 помещается лишь урезанный стек (37.3).

Встроенный Ethernet на F407

Контроллер канального уровня на кристалле, физический уровень — внешней микросхемой по девяти проводам RMII, стек TCP/IP — в прошивке. Обычно это lwIP, и он весит десятки килобайт оперативной памяти — больше, чем есть у F103C8 целиком.

Байт
буферы приёма и передачи с дескрипторами 8–16 КБ
стек и его память под соединения 16–24 КБ
задача стека стек 1–2 КБ

Зато 100 Мбит/с, любое число соединений, полный контроль. И полная ответственность: стек в вашей прошивке, значит ваша задача его конфигурировать, обслуживать и отлаживать. Путь для приборов, где сеть — основная функция, а не довесок.

Под ядром стек живёт в отдельной задаче, а прикладной код работает через сокеты с блокирующими вызовами — как на компьютере. Без ядра стек тоже работает, но в режиме опроса, и это заметно неудобнее.


37.2 Что передавать: почему MQTT

Подключились. Дальше нужен протокол, по которому прибор говорит с сервером. Кандидатов два.

HTTP MQTT
модель запрос — ответ публикация — подписка через брокера
соединение обычно новое на запрос; HTTP/1.1 умеет держать открытым одно, постоянное
служебных байт на сообщение сотни около десятка
сервер → прибор только по запросу прибора в любой момент
несколько получателей нет да, по подписке
обрыв связи не замечает брокер видит, шлёт «завещание»
разбор на приборе текст, заголовки двоичный, десяток байт

MQTT выбирают за первые три строки: постоянное соединение, минимум байт и возможность серверу говорить первым. Для прибора на батарейке главное из них — не устанавливать соединение заново: рукопожатие TCP — три пакета, а TLS добавляет ещё несколько обменов и килобайты сертификатов. Заголовки TCP/IP — 40 байт на пакет — платятся в обоих протоколах одинаково, и на их фоне сотня служебных байт против десятка заметна, но не решает.

Устройство пакета

Всего два поля в заголовке: тип с флагами в одном байте и длина остатка.

байт 0:   тип << 4 | флаги
байт 1…4: длина остатка, по 7 бит на байт, старший бит — «есть ещё»
далее:    остаток — зависит от типа
Тип Байт Назначение
CONNECT 0x10 представиться брокеру
CONNACK 0x20 брокер принял
PUBLISH 0x30 + флаги опубликовать в топик
PUBACK 0x40 подтверждение при QoS1
SUBSCRIBE 0x82 подписаться; флаг 2 обязателен
SUBACK 0x90 подписка принята
PINGREQ 0xC0 «я жив»
PINGRESP 0xD0 «и я»
DISCONNECT 0xE0 отключаюсь

Флаги PUBLISH: бит 0 — сохранить на брокере, биты 1–2 — QoS, бит 3 — повтор.

Длина остатка кодируется переменным числом байт: до 127 — один, до 16 383 — два, до 2 097 151 — три, до 268 435 455 — четыре; больше четырёх не бывает. Для приборных сообщений почти всегда один.

Пример целиком — публикация «21.5» в топик «home/t»:

30                      PUBLISH, QoS0
0C                      длина остатка: 12
00 06  h o m e / t      топик: длина 6, шесть байт
2 1 . 5                 тело: 4 байта, без длины — до конца пакета

Четырнадцать байт, из них десять служебных. Тот же HTTP-запрос — двести с лишним.

Пакет MQTT PUBLISH по байтам: 30 — тип с QoS0, 0C — длина остатка 12, 00 06 — длина топика, шесть байт home/t, четыре байта тела 21.5 без длины до конца пакета; всего 14 байт, служебных 10

⚠️ Топик и тело — не строки. Ни в топике, ни в теле нет завершающего нуля: топик предваряется длиной в двух байтах, тело занимает всё до конца пакета. strlen на теле — мусор при первом двоичном байте. Длину берут из заголовка, а не ищут ноль.

QoS: три уровня и два полезных

QoS Гарантия Цена
0 «отправил и забыл» ничего
1 «дойдёт хотя бы раз» +2 байта идентификатора, ожидание PUBACK, возможен дубль
2 «дойдёт ровно раз» четыре пакета на сообщение

Для показаний датчика — QoS0: пропало одно, придёт следующее. Для команд прибору — QoS1 и готовность к дублю. QoS2 на микроконтроллере почти не применяют: цена не окупается.

Keep-alive: обязательство, а не опция

В CONNECT прибор называет интервал в секундах. Брокер ждёт полтора интервала молчания и рвёт соединение. При 60 секундах — 90 секунд тишины, и прибор отключён без предупреждения.

Значит, прибор обязан слать PINGREQ чаще интервала — с запасом, раз в 45 секунд при интервале 60. Программный таймер (раздел 29) с перезапуском при каждой отправке: если что-то ушло, пинг не нужен; замолчали — пинг сработает сам.

Ответ ждут и в обратную сторону. Если на PINGREQ за несколько секунд не пришёл PINGRESP, соединение считают потерянным: закрывают сокет и переходят к переподключению. Сам TCP заметит обрыв нескоро — в разделе 106.6 до отказа по повторам проходит 924,6 с, — и всё это время прибор копит показания для брокера, которого уже нет.

⚠️ Обрыв связи брокер замечает по keep-alive, а не по TCP. Радио пропало, TCP-соединение висит — брокер узнает об этом только через полтора интервала. Тогда он публикует «завещание» — сообщение, которое прибор оставил в CONNECT на случай обрыва. Так сервер узнаёт, что прибор пропал, не опрашивая его.

Библиотека или своими руками

Разбирать и собирать пакеты самому — работа на вечер: формат из десятка байт. Готовые клиенты для микроконтроллеров есть, и они берут на себя keep-alive, QoS1 и переподключение. Брать стоит готовый, но понимать формат — обязательно: отладка по байтам в канале, когда «брокер отвечает CONNACK с кодом 5», без понимания невозможна.


37.3 Схема прибора

Типовая структура под ядром, с модулем по UART:

Задача Приоритет Что делает
приём UART → потоковый буфер прерывание байты в буфер
разборщик модуля 3 строки, OK/ERROR, незапрошенные сообщения
сетевой автомат 2 подключение, переподключение, keep-alive, отправка
датчики 2 измерения → очередь
основная логика 1 что показывать, что слать

Сетевой автомат — конечный автомат с состояниями «нет сети», «подключаюсь к точке», «подключаюсь к брокеру», «работаю», «жду перед повтором». Каждое состояние — со своим таймаутом; каждый обрыв возвращает в начало с задержкой, растущей по экспоненте: секунда, две, четыре, до минуты, — причём внутри этого предела задержку выбирают случайно (раздел 88.6). Без растущей задержки прибор, потерявший сервер, будет долбить его каждую секунду, а без разброса тысяча приборов, потерявших сервер одновременно, будет стучаться в него тоже одновременно.

Сетевой автомат прибора: пять состояний, ожидание подключения к точке до 15 с, пинг раз в 45 с при keep-alive 60 с и предел задержки повтора 1, 2, 4 … 60 с со случайным выбором внутри

Показания от датчиков не идут в сеть напрямую: они ложатся в очередь, сетевой автомат забирает их, когда соединение живо. Пока сети нет, очередь копит; переполнилась — теряем старые, не новые. Очередь ядра при переполнении отвергает новое значение, поэтому старые теряют явно: если xQueueSend с нулевым ожиданием вернул errQUEUE_FULL, вынимают одно значение xQueueReceive(q, &old, 0) и отправляют новое ещё раз. Писатель в очередь при этом один — задача датчиков.

Память на F103C8

Байт
ядро с кучей под задачи ≈ 6 КБ
буферы связи 768
очередь показаний на 32 записи ≈ 400
стеки пяти задач 3–5 КБ
итого ≈ 12 КБ из 20

Помещается — с внешним стеком. Полный стек в прошивке, как у F407, сюда не влезет, поэтому Ethernet к F103 обычно подключают модулем со стеком внутри. Урезанный lwIP — одно соединение TCP, DHCP и DNS, режим без ОС — в опыте добавил к прошивке 6,1 КБ ОЗУ и 33 КБ Flash: вместе с таблицей выше это 18 КБ из 20, впритык. Так работает четвёртый путь из 37.1 — микросхема Ethernet без стека.


37.4 Безопасность: то, что нельзя пропустить

Пароль сети и брокера — не в исходниках. Исходники попадают в репозитории. Пароль записывают при производстве в отдельную страницу внутренней флеш-памяти (раздел 11.5), а камень закрывают защитой чтения (раздел 11.6). Внешняя флеш-память (раздел 41) убирает пароль из репозитория, но не защищает: её читают программатором, прицепившись к выводам. Резервные регистры малы — 20 байт у F103C8, 80 у F407, а пароль Wi‑Fi бывает до 63 символов — и стираются без батарейки.

Шифрование — TLS поверх TCP. На модуле по UART его делает модуль командой; на F407 с собственным стеком — библиотека, и она весит ещё десятки килобайт и заметно нагружает процессор при установлении соединения. F103 с TLS в прошивке — нереалистично.

Брокер без пароля и без шифрования — только в лаборатории, никогда в изделии. Открытый брокер с приборами на нём — первое, что находят сканеры.

Обновление прошивки по сети — отдельная тема с загрузчиком, проверкой подписи и защитой от полуобновления при обрыве. Без неё прибор, ушедший в поле, не исправить.


37.5 Грабли

Модуль отвечает ERROR на всё. Не та скорость UART, либо предыдущая команда не завершена, либо модуль в другом режиме.

Разборщик путается. Незапрошенное сообщение пришло между командой и ответом. Разбирать каждую строку по началу.

Подключение к сети — ERROR через полсекунды. Таймаут слишком короткий; подключение занимает секунды.

Брокер отключает раз в полторы минуты. Нет PINGREQ чаще keep-alive.

Прибор минутами «на связи» с пропавшим брокером. На PINGREQ не ждут PINGRESP, а TCP сам сдастся нескоро.

CONNACK с кодом не ноль. 1 — версия протокола, 2 — плохой идентификатор, 4 — логин, 5 — не авторизован.

Сообщение доходит обрезанным. strlen на теле с двоичным байтом.

Сервер получает дубли. QoS1 работает как задумано; обрабатывать идемпотентно.

После обрыва прибор долбит сервер каждую секунду. Нет растущей задержки между попытками.

Поднятый сервер снова падает под своими же приборами. Все повторяют попытки разом: у задержки нет случайного разброса.

Модуль по UART теряет байты входящих данных. Приём в цикле вместо потокового буфера с прерыванием.

lwIP не помещается. Полный на F103C8 и не поместится: внешний стек или урезанный lwIP на одно соединение (37.3).

Работает в лаборатории, не работает на объекте. Другая сеть, другой брокер, другой брандмауэр, порт 1883 закрыт.


37.6 Итоги

Дальше: раздел 37а «Сколько стоит TLS на микроконтроллере: Mbed TLS по флешу и ОЗУ» — из чего складывается цена клиента TLS во флеш-памяти, куче и времени.

Открыть в интерактивном справочнике