Несколько лет назад сделал себе на Arduino блок мониторинга питания дачного котла от UPS. Как показала практика, связка Arduino MEGA + шилд на SIM900 со стандартными библиотеками работает очень нестабильно. Периодически всё зависает, само перегружается и т.д. Отладить это невозможно, поэтому стал искать другие варианты. В результате решил всё переделать на современных технологиях: взял за основу STM32 Bluepill, приобрел на али модуль SIM800L, но самое главное – весь код решил написать на Rust, купился на обещания его высокой надёжности. Что из этого получилось читайте дальше. Несколько слов о том, что хотелось получить от новой самоделки. У меня на даче стоит UPS Inelt, в котором есть порт RS232 для управления UPS. Он поддерживает довольно древний протокол обмена Megatec, с помощью которого можно выполнять несложные действия по управлению UPS и узнавать его текущее состояние. Управление меня не особо интересовало, а вот состояние работы знать хотелось: как там сетевое напряжение, живы ли батареи и пр. На рынке я не нашёл готовых решений, которые могли бы решить эту задачу, поэтому решил всё сделать сам. Всё в духе DIY. Поле недолгих раздумий решил, что хочу получать ежедневное СМС сообщение о состоянии UPS и иметь возможность запросить его просто позвонив на модуль. Ну и для полноты картины решил добавить в модуль датчик температуры теплоносителя в системе отопления. Пока проектировал электронику, решил добавить к модулю свой маленький UPS, т.е. микроконтроллер может у меня питаться либо от сети, либо от встроенной батареи. Раз такое дело, решил еще мониторить модулем напряжения питания сети и батареи.  Всё это было реализовано, как уже писал выше, на Arduino MEGA + SIM900. Теперь же нужно было всё это переделать на Bluepill с небольшими доработками схем питания и подключения датчиков. Аппаратные возможности bluepill существенно больше, чем Arduino, поэтому особых проблем с переходом нет, тем более, что многие контакты bluepill толерантны к 5 В сигналам. Единственная проблема, что в bluepill нет eeprom, поэтому пришлось подключить к конструкции дополнительную микросхему. В кладовке нашлась бесхозная 24LC16, поэтому использовал её, хотя мне нужно было всего несколько байт хранить. Как до меня дошло позже, эти байты можно было бы сохранить в ячейках SIM карты, но цели сделать оптимальное решение я не ставил. Наоборот, поскольку я решил использовать новые для себя технологии, то мне интересно было всё попробовать – испытать как можно больше аппаратных возможностей STM32 и попробовать embedded Rust в целях самообразования. В конце статьи есть ссылка на репозитарий с исходным кодом, если посмотрите, то увидите, что я использовал очень многие блоки STM32. АЦП, таймеры, USART, прерывания, RTC и пр. Руки не дотянулись разве что до использования DMA и новомодной асинхронщины Rust. Если на STM32 у меня уже был некоторый опыт программирования на C++, то Rust был совершенно в новинку. Вообще использовать Rust пришло в голову, когда наткнулся на несколько заметок, что кому-то удалось помигать светодиодом на bluepill используя Rust. К сожалению, более содержательных статей я тогда не встретил, поэтому решил для начала почитать поподробнее документацию Rust. Язык оказался довольно сложный для меня, надо прямо сказать, намного труднее того, что используется в Arduino. Самое печальное было то, что в книгах по Rust очень много уделяется внимания стандартным библиотекам, которые, что позже стало для меня неожиданностью, вообще не используются в embedded версии. Все эти String, Vec, сотни адаптеров и замыканий, которым посвящены тысячи страниц в документации вообще бесполезны. Печаль. Но ради главного преимущества Rust, предсказуемого поведения программы, я готов был с этим всем примериться. Наверняка те, кто не потратил много бессонных ночей в поисках seg fault, не поймут меня, ну да и ладно. Основным источником информации для меня стали исходники примеров использования библиотек на crates.io. Надо сказать, что даже имеющиеся там примеры нужно было допиливать чтоб они заработали на bluepill. Но тем интереснее и продуктивнее изучение языка. Начинающим сразу предлагаю изучить крейт heapless и использовать его методы вместо Vec, String стандартной библиотеки. Далее в порядке важности, нужно разобраться в крейте stm32f1xx-hal. Собственно, благодаря ему обычно и идёт взаимодействие с оборудованием bluepill. Увидев слово HAL, сразу приходит мысль о STM32 CubeMX, но это обманчиво, тут HAL совсем другой. Другие функции, способы доступа к железу. Хорошо хоть на совсем низком уровне сохранили названия регистров и блоков микроконтроллера, иначе вообще ничего понять было бы невозможно без примеров. Документация на крейт есть, но довольно посредственная. Мне постоянно приходилось смотреть исходный код библиотеки для понимания как там всё реально работает. Отдельная тема — это поиск драйверов для всякого оборудования. Если для Arduino есть, видимо, драйвер для любого устройства, то с Rust не всё так радужно. Меня с самого начала интересовало, есть ли готовые библиотеки для eeprom, датчика температуры DS18B20, да и SIM800. К счастью, для памяти и датчика крейты есть, а для SIM800 всё пришлось делать самому. Для обучения самое то. Я изучил исходники ардуиновской библиотеки SIM900 и решил их творчески портировать на Rust в необходимом мне объёме. Кстати, для DS18B20 есть несколько крейтов и я сперва взял первый попавшийся. И не попал. Он оказался очень нестабильным – датчик то считывал температуру, то выдавал ошибку CRC. Я сначала менял датчики, отключал прерывания, но ничего не помогало. В результате перешёл на другой крейт и всё заработало стабильно без танцев с бубном. Надо сказать, что для работы с AT командами есть готовый крейт atat. Можно было попробовать его использовать. Но мне он показался сложным. Да и идеологически он обрабатывает ответы от модуля SIM800 не так, как это делает Arduino. Последний определяет окончание передачи по таймауту, а atat использует маркером конца передачи перевод строки. В результат добиваться работоспособности atat я не стал, а просто свой код написал.  Для меня основной головной болью работы с SIM800 оказались так называемые unsolicited result codes. Это когда вы посылаете в модуль какую-то команду и ждете в ответ ОК, а получаете сообщение о новом СМС или RING. Очень неудобно. К счастью в документации на SIM800 написано как большинство из них отключить. Но при этом теряется возможность принимать входящие звонки. В итоге я включаю возможность принимать RING только на определённое время ожидания звонка, в остальное время звонки отклоняются. Это несколько снизило удобство работы с модулем, поскольку иногда дозвониться до него с первого раза не удаётся, хотя и редко. Зато надёжность работы остальных команд возросла. И да, я использую довольно “наивный” способ выполнения AT команд: просто делается несколько последовательных попыток выполнить команду, если с первого раза не получилось. С авторизацией звонков поступил просто – беру “доверенные” номера телефонов из первых трёх ячеек SIM карты. Модуль будет отвечать на звонки только с этих номеров и отправлять СМС на последний набранный номер. Пришлось повозиться с проблемой связи между SIM800 и bluepill. Казалось бы, обычный RS232 и всё должно было бы сразу запуститься. Но нет, при независимом перезапуске или подаче питания на SIM800 и МК связь часто терялась. Пришлось использовать для устойчивого коннекта последовательную программную перезагрузку сначала SIM800, а потом, в случае неуспеха, и bluepill. Больным вопросом оказалось быстрое распухание кода бинарника. Строчек 200 кода в отладочной сборке ещё влезали во флеш, а потом всё. Практически сразу пришлось установить несколько опций оптимизации и отлаживать уже релизную версию. Большой скачок в использовании памяти произошёл, как только я стал использовать математику с плавающей точкой. Библиотеки fp занимают около 9 кб, которые сразу прибавились к бинарнику. Точнее, всё перестало просто собираться и я не знал, что делать дальше. Помог stackoverflow, где посоветовали поставить опцию opt-level = "z". Только с ней и удалось сделать сборку. При этом финальный размер бинарника составил 45 килобайт при 1300 строках исходного кода. Не могу оценить эффективность компилятора, но такой размер не проблема даже для bluepill, где есть 64 Кб флеша.  Паттерны проектирования ООП. Тут у меня не сложилось со state machines. Хотелось бы всё сделать по канонам «лучших паттернов проектирования», тем более в коде я использую несколько машин состояний. Но вот плодить десяток структур и типажей, которые при переходе состояний пожирают друг друга мне совсем не понравилось. Отдельно стоит упомянуть отсутствие доступа в embedded к куче и, как следствие, таких вещей как Box<> и прочих контейнеров. Сейчас весь код машины состояний в “С” стиле умещается на странице, прост и понятен. Разубедите меня, если сможете. Использование Result<> для возврата ошибок. Наверное это удобно, когда стек вызовов глубокий и активно применяется оператор “?”. В моем же случае всё довольно тривиально, а использование Result<> с разными типами ошибок только всё усложняет. Умные книги советуют для унификации передачи кодов ошибок опять же применять Box<>, которого в embedded нет. Ну или просто ради него не хочется делать свой аллокатор кучи и получать связанные с этим потенциальные проблемы. Для отладки программы мне нужно было связываться с UPS, который на даче, а второго у меня нет. Поэтому решил в качестве эмулятора использовать бесхозный модуль Arduino Nano. Написал для него несколько строк кода, которые обеспечивают эмуляцию нужной мне части протокола. Посмотреть можно здесь: Работа с эмулятором оказалась очень удобна для отладки. Рекомендую. Всё задуманное получилось! По ходу дела не обошлось без проблем, но где их нет? Можно сказать, что связка технологий вполне рабочая и подходит для DIY.  Теперь модуль присылает мне такие сообщения: А вот и обещанная ссылка на исходники в GITHUB.