Доброго здравия всем! Сегодня я хочу вам рассказать, как постепенно студенты учатся разрабатывать ПО для микроконтроллера на примере драйвера UART на STM32F411. Код и архитектуру с небольшими моими изменениями и доработками я попытаюсь привести здесь.  Сразу отмечу, что все сделано статикой, как я учил :) (статические классы, статическая подписка, статический странно-рекурсивный шаблон, статический контейнер для команд и так далее), но можно реализовать то же самое с помощью обычных объектов и обычного подхода. В обычном подходе архитектура была бы такая же, но кода немного больше, как по объему так и по количеству строк кода. Данная статья не претендует на истину, а лишь показывает подход к реализации некоторых задач, в данном случае реализацию Uart драйвера на С++. Итак была поставлена следующая задача: Картинка ниже поясняет назначение драйвера, чтобы иметь представление что такое вообще драйвер UART в данном контексте.  В общем случае — это слой не зависящий от аппаратной реализации UART, предоставляющий очень абстрактный интерфейс для приема, передачи данных.  Поскольку драйвер должен предоставлять не так много функций, его архитектуру очень упрощенно можно представить в таком виде:  У драйвера есть два метода: А также события: Драйвер будет иметь списки статических подписчиков. Всего 2 списка:  Подписаться на драйвер может хоть кто, у кого есть метод OnReceiveComplete() или
OnTransmitComplete(). На картинке в качестве примера показаны два подписчика SomeProtocol и OtherObserver.  Давайте посмотрим, как реализован метод посылки данных. Для начала немного для себя определим спецификацию метода: Теперь можно это перевести в код: Теперь, когда передача инициирована, каждый раз как байт будет отправлен (из регистра данных в защелку) UART модуль вызовет событие OnTransmit() драйвера UartDriver и нужно будет отослать следующий символ. Собственно это все чем занимается OnTransmit() — отсылает следующий байт.  Как вы знаете у UART обычно есть два события: одно по опустошению регистра данных, а второе по опустошению защелки (т.е. реально когда байт вышел в линию), поэтому логично, чтобы убедиться что последний байт вышел в линию, использовать прерывание по опустошению защелки. То же самое для метода чтение данных.  Смотрим код: Это событие вызывается модулем UART каждый раз, как был принят байт. Необходимо считать количество принятых байт и как только оно станет равно количеству запрашиваемых пользователем, нужно закончить прием и оповестить подписчиков, что прием закончен. Код драйвера полностью можно посмотреть под спойлером Например, мы хотим реализовать очень простенький протокол SomeProtocol, который всегда принимает 10 байт и отсылает 10 байт. Нулевой байт — это команда, последний — это контрольная сумма. А данных 8 байт, т.е. окончание приема будем определять по количеству принятых байт, если 10, то посылка закончилась. (По хорошему так делать не надо, окончание посылки лучше делать по таймеру, но чтобы не плодить кода, я упростил до такого вот супер пупер протокола)  Все что нам нужно будет сделать это реализовать два метода OnTransmitComplete() и OnReceiveComplete(). Теперь нужно произвести настройку драйвера -подписать протокол на UartDriver Заметьте, можно сделать сколь угодно много подписчиков на завершение приема или передачи. Например, на завершение передачи я подписал два класса TestObserverи SomeProtocol, а на завершение приема только один — SomeProtocol. Также можно настроить драйвер на любой UART модуль. и теперь можно запускать протокол на работу: Если вы еще читаете, наверное у вас возник резонный вопрос, что такое HardwareUart UART модуль и откуда он взялся. Его упрощенная модель выглядит так:   По большому счету — это обертка над аппаратным UART микроконтроллера, в которую через список подключаются 3 дополнительных класса для обработки прерываний:  Обработчики прерывания вызываются из метода HandleInterrupt() класса HardwareUartBase, который должен подставляться в таблицу векторов прерываний Настройка UART и подписчиков будет выглядеть так: Теперь легко можно подписывать на разные прерывания разных клиентов. Количество клиентов практически не ограничено, в данном случае мы подписали на все три прерывания UartDriver, но могли бы еще что-нибудь подписать. Также можно подключиться к любому UART, в примере подключено к USART2. Затем настроенный Uart модуль можно передавать в драйвер, как было показано чуть выше. Драйвер же также в свою очередь должен подписаться на события от Uart модуля. В общем и целом перед поставленной задачей товарищи студенты справились. При включенном принудительном inline, весь код проекта занимает 1600 байт без оптимизации. Сделаны две команды: запись и чтение 12 параметров во Flash микроконтроллера. В проекте, можно настроить драйвер, чтобы он работал в синхронном режиме, можно в асинхронном. Можно подключать любое количество подписчиков, к любому UART. Собственно все задачи были выполнены и работа тянет на отлично :)  Да, затрачено на кодирование было 2 целых дня (думаю часов 20 в сумме). Полагаю, из-за того, что архитектура мною уже была разработана на практических занятиях, а реализация — это дело уже не таке сложное. Код был проверен мною в PVS-Studio. Изначально были найдены 4 предупреждения.
Все предупреждения уже не помню, отчет не сохранил: но точно были V2516 и V519, ошибки не критичные, но точно так делать не надо было :) Все исправлено, кроме V2516, он указывает на код, который используется для отладки, там поставил FIXME:. Можно посмотреть код рабочего примера на IAR8.40.2 здесь, никаких доп библиотек не нужно, но нужна плата Nucleo-F411RE, сам проект лежит в папке FlashUart\DinamicStand. Основной код драйвера и Uart модуля лежит на гитхабе . PS: Спасибо fougasse, apro, gleb_l и besitzeruf за дельные замечания Хоккеист — на микроконтроллерах программист