что такое usb cdc
Stm32 + USB на шаблонах C++. Продолжение. Делаем CDC
Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.
Интерфейсы
Устройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.
Интерфейс управления представляет собой расширение базового класса интерфейса с тем отличием, что содержит одну конечную точку (хотя, насколько я понял, без необходимости поддержки всех возможностей можно обойтись вообще без конечной точки) и набор «функциональностей», определяющих возможности устройства. В рамках разрабатываемой библиотеки данный интерфейс представлен следующим классом:
В базовом случае интерфейс должен поддерживать три управляющих (setup) пакета:
SET_LINE_CODING: установка параметров линии: Baudrate, Stop Bits, Parity, Data bits. Некоторые проекты, на которые я ориентировался (основным источников вдохновения стал этот проект), игнорируют данный пакет, однако в этом случае некоторые терминалы (например, Putty), отказываются работать.
GET_LINE_CODING: обратная операция, в ответ на эту команду устройство должно вернуть текущие параметры.
SET_CONTROL_LINE_STATE: установка состояния линии (RTS, DTR и т.д.).
Код обработчика setup-пакетов:
Ключевой момент нумерации, а именно формирование дескрипторов, выполнен по уже привычной схеме раскрытия variadic-ов, что позволяет избавиться от зависимости классов в иерархии:
Второй интерфейс, предназначенный для непосредственно обмена данными, абсолютно примитивный, он не должен поддерживать управляющих сообщений, а является просто контейнером для двух конечный точек (точнее одной двунаправленной). Объявление класса:
Поскольку мои познания в CDC-устройствах весьма небольшие, из просмотренных примеров я сделал вывод, что управляющий интерфейс почти всегда одинаковый и содержит 4 функциональности: Header, CallManagement, ACM, Union, поэтому добавил упрощенный шаблон интерфейса:
Применение разработанных классов
Для использования разработанных классов достаточно объявить две конечные точки (Interrupt для первого интерфейса и двунаправленную Bulk для второго), объявить оба интерфейса, конфигурацию с ними и, наконец, инстанцировать класс устройства:
Непосредственно логика заключается лишь в обработке входящих пакетов, что умещается в одну функцию (в качестве примера управляю светодиодом и выдаю сообщение):
Отладка и тестирование
Написать код правильно с первого раза практически невозможно, поэтому очень полезным оказалось все-таки разобраться с инструментами перехвата USB-пакетов, поэтому кратко опишу особенности и проблемы, с которыми столкнулся лично я.
Так и не удалось применить логический анализатор, он просто ничего не показывает. Полагаю, что дело в том, что это самый дешевый клон Seale Logic и если бы был в наличи нормальный аппарат, то все бы получилось. Главное преимущество логического анализатора заключается в том, что он позволяет отслеживать обмен данными еще в процессе нумерации, в то время как программы на стороне хоста показывают пакеты только для тех устройств, которые эту нумерацию успешно прошли.
Сначала отфильтровать по заведомо известному значению. Например, по значению PID, которое присутствует в ответе устройства на запрос GET_DEVICE_DESCRIPTOR. Фильтр: «usb.idProduct == 0x5711». Это позволит быстро определить адрес устройства.
Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: «usb.addr contains «1.19»».
Однако стоит заметить, что UsbPcap может доставить некоторые трудности, под катом опишу ситуацию, в которую недавно попал и потратил кучу времени и нервов.
Проблема с usbpcap
Для большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.
Однажды Windows просто не загрузилась с синим экраном «inaccessible boot device». Потратил целые выходные, восстановить так и не смог, пришлось все переустановить. Через некоторое время та же проблема и снова потраченные на переустановку выходные. Спустя пару дней система опять не грузится, начал вспоминать и анализировать, что я такого делал. Выяснил, что проблема возникала после установки как раз WireShark с usbpcap. На одном из форумов наткнулся на сообщение от пользователя, который жаловался на проблему с мышкой/клавиатурой после установки usbpcap. Снес через LiveCD драйвер и Windows запустилась. Не уверен на 100%, но предположение такое: при запуске компьютера Windows начинается загружаться, подгружает драйвера usbpcap, тот блокирует USB, система дальше грузиться не может и падает в BSOD. Очень неочевидное поведение, жаль потраченного времени.
Тестировал написанный код в программе Terminal v1.9b, на скриншоте приведен результат отправки на устройство сообщений «0» и «1».
Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания.
Stm32 + USB на шаблонах C++. Продолжение. Делаем CDC
Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.
Интерфейсы
Устройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.
Интерфейс управления представляет собой расширение базового класса интерфейса с тем отличием, что содержит одну конечную точку (хотя, насколько я понял, без необходимости поддержки всех возможностей можно обойтись вообще без конечной точки) и набор «функциональностей», определяющих возможности устройства. В рамках разрабатываемой библиотеки данный интерфейс представлен следующим классом:
В базовом случае интерфейс должен поддерживать три управляющих (setup) пакета:
SET_LINE_CODING: установка параметров линии: Baudrate, Stop Bits, Parity, Data bits. Некоторые проекты, на которые я ориентировался (основным источников вдохновения стал этот проект), игнорируют данный пакет, однако в этом случае некоторые терминалы (например, Putty), отказываются работать.
GET_LINE_CODING: обратная операция, в ответ на эту команду устройство должно вернуть текущие параметры.
SET_CONTROL_LINE_STATE: установка состояния линии (RTS, DTR и т.д.).
Код обработчика setup-пакетов:
Ключевой момент нумерации, а именно формирование дескрипторов, выполнен по уже привычной схеме раскрытия variadic-ов, что позволяет избавиться от зависимости классов в иерархии:
Второй интерфейс, предназначенный для непосредственно обмена данными, абсолютно примитивный, он не должен поддерживать управляющих сообщений, а является просто контейнером для двух конечный точек (точнее одной двунаправленной). Объявление класса:
Поскольку мои познания в CDC-устройствах весьма небольшие, из просмотренных примеров я сделал вывод, что управляющий интерфейс почти всегда одинаковый и содержит 4 функциональности: Header, CallManagement, ACM, Union, поэтому добавил упрощенный шаблон интерфейса:
Применение разработанных классов
Для использования разработанных классов достаточно объявить две конечные точки (Interrupt для первого интерфейса и двунаправленную Bulk для второго), объявить оба интерфейса, конфигурацию с ними и, наконец, инстанцировать класс устройства:
Непосредственно логика заключается лишь в обработке входящих пакетов, что умещается в одну функцию (в качестве примера управляю светодиодом и выдаю сообщение):
Отладка и тестирование
Написать код правильно с первого раза практически невозможно, поэтому очень полезным оказалось все-таки разобраться с инструментами перехвата USB-пакетов, поэтому кратко опишу особенности и проблемы, с которыми столкнулся лично я.
Так и не удалось применить логический анализатор, он просто ничего не показывает. Полагаю, что дело в том, что это самый дешевый клон Seale Logic и если бы был в наличи нормальный аппарат, то все бы получилось. Главное преимущество логического анализатора заключается в том, что он позволяет отслеживать обмен данными еще в процессе нумерации, в то время как программы на стороне хоста показывают пакеты только для тех устройств, которые эту нумерацию успешно прошли.
Сначала отфильтровать по заведомо известному значению. Например, по значению PID, которое присутствует в ответе устройства на запрос GET_DEVICE_DESCRIPTOR. Фильтр: «usb.idProduct == 0x5711». Это позволит быстро определить адрес устройства.
Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: «usb.addr contains «1.19»».
Однако стоит заметить, что UsbPcap может доставить некоторые трудности, под катом опишу ситуацию, в которую недавно попал и потратил кучу времени и нервов.
Проблема с usbpcap
Для большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.
Однажды Windows просто не загрузилась с синим экраном «inaccessible boot device». Потратил целые выходные, восстановить так и не смог, пришлось все переустановить. Через некоторое время та же проблема и снова потраченные на переустановку выходные. Спустя пару дней система опять не грузится, начал вспоминать и анализировать, что я такого делал. Выяснил, что проблема возникала после установки как раз WireShark с usbpcap. На одном из форумов наткнулся на сообщение от пользователя, который жаловался на проблему с мышкой/клавиатурой после установки usbpcap. Снес через LiveCD драйвер и Windows запустилась. Не уверен на 100%, но предположение такое: при запуске компьютера Windows начинается загружаться, подгружает драйвера usbpcap, тот блокирует USB, система дальше грузиться не может и падает в BSOD. Очень неочевидное поведение, жаль потраченного времени.
Тестировал написанный код в программе Terminal v1.9b, на скриншоте приведен результат отправки на устройство сообщений «0» и «1».
Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания.
mcu.by
Услуги разработки встраиваемого ПО и контрактная разработка электроники на ShuraCore
Старт ARM. Поднимаем USB CDC.
Всем привет! Сегодня будем поднимать USB CDC (VCP) на плате stm32f401c-disco.
USB communications device class (коммуникационный класс устройства) — является составным классом устройства Универсальной последовательной шины. Класс может включать один (или более) интерфейс, такой как интерфейс пользовательского элемента управления, интерфейс передачи данных, аудио или интерфейс запоминающего устройства.
VCP – Виртуальный COM-порт
Базовый класс 02h (Communications and CDC Control)
Этот базовый класс определен для устройств, которые относятся к классу устройств спецификации связи. Эта спецификация определяет используемый набор подкласса и Протокола значений. Значения за пределами определения спецификации защищены. Обратите внимание, что связи класса устройств спецификации требуется несколько значений Кода класса (три), которые будут использоваться в описания устройств, а некоторые, которые будут использоваться в интерфейсе дескрипторов.
Базовый класс 02h
Подкласс xxh
протокол xxh
смысл Communication device class
Базовый класс 0Ah (CDC-Data)
Этот базовый класс определен для устройств, которые относятся к классу устройств спецификации связи. Это спецификация определяет используемый набор подкласса и протокола значений. Значения за пределами определения спецификации защищены. Эти коды класса могут быть использованы только в интерфейсе дескрипторов.
Базовый класс 0Ah
Подкласс xxh
протокол xxh
смысл CDC data device
1. Находим строку HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0); и меняем на HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
2. Находим строку #define CDC_DATA_HS_MAX_PACKET_SIZE 512 /* Endpoint IN & OUT Packet size */ и меняем на #define CDC_DATA_HS_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */
3. Производим изменения в usbd_cdc_if.c
4. Теперь нужно добавить в usbd_cdc_if.h несколько строк кода
5. Устраняем баг для USBD_CDC_TransmitPacket()
6. И добавляем пример в main.c
И все готова!, подключаем микро USB к плате, второй вывод к host-у и у нас появляется виртуальный com порт.
6 thoughts on “ Старт ARM. Поднимаем USB CDC. ”
When i debbuged this code i found that for really big messages this doesn’t work right. There was some issue with size off offset. IT send multiple time the same think.
I corrected it with:
if (size > CDC_DATA_HS_OUT_PACKET_SIZE) <
int offset;
for (offset = 0; offset
with large data is not working, thanks
Точно, спасибо за инфу.
Есть одна проблема. Не передаются пакеты более 64 байт. Кто-нибудь решил ее?
Начинаем работать в STM32CubeMX. Часть 3
Продолжаем цикл про основы работы STM32MXCube и программированию микроконтроллеров STM32.
Небольшое вступление к третьей части
Вначале я хочу сказать, что с этой части я буду использовать отладочную плату NUCLEO-F767ZI. Эта плата более доступна, чем STM32F746G Discovery, использует микроконтроллер в корпусе LQFP144, а не BGA, и сама плата более удобна для встраивания в разные DIY-проекты. Она имеет Ethernet и USB, а также JTAG-отладчик. Недостатком платы является отсутствие LCD, но он нам пока не нужен.
Хотя плата имеет другой микроконтроллер, все проекты из предыдущих частей переносятся на неё почти без изменений (нужно только поменять номера пинов). Также следует учесть, что на этой плате микроконтроллер тактируется источником 8 МГц. Кварц для тактирования микроконтроллера предусмотрен схемой, но не распаян, сигнал 8 МГц снимается с JTAG-отладчика. Если ваш проект использует интерфейс USB, то желательно включать тактирование от HSE, а не от внутреннего RC-осциллятора, так как RC-осциллятор не обладает достаточной точностью и стабильностью частоты. При попытке включить RC-осциллятор при наличии в проекте USB, STM32CubeMX выдаст предупреждение и предложит переключиться на HSE (то есть на внешний высокостабильный источник тактирования). Для того, чтобы задействовать внешний источник тактирования (8МГц) на вкладке PinOut следует в пункте RCC включить HCE и выбрать пункт BYPASS Clock Source. На практике USB-интерфейс всё равно работает, даже от RC, но лучше не рисковать.
Я перевёл на эту плату проекты из предыдущих частей и залил их на гитхаб.
В комментариях к предыдущим частям были вопросы по поводу IDE. STM32CubeMX позволяет автоматически создавать проекты для различных IDE: IAR (EWARM), MDK ARM v4, MDK ARM v5, Atollic TRUEStudio, SW4STM32 и др. Я пользуюсь Atollic TRUEStudio, который доступен для скачивания с официального сайта бесплатно.
Также я проверил материал из предыдущих частей и внёс ряд поправок.
Хочу поблагодарить Shamrel за ценные комментарии к предыдущей части.
USB VCP
Одним из самых простых режимов работы USB является режим VCP — Virtual COM Port. Настройка работы с ним потребует от вас минимальных усилий.
В STM32CubeMX находим на вкладке Pinout раздел USB_OTG_FS и устанавливаем Mode=Device_Only:
В разделе USB_DEVICE устанавливаем Class For FS IP в режим CDC VCP (Communication Device Class Virtual Com Port):
Теперь нужно настроить конфигурацию тактирования так, чтобы частота USB составляла 48 MHz:
Идём дальше, на вкладку Configuration, и отключаем параметр VBUS Sensing:
Генерируем код и открываем проект в IDE.
Находим файл usbd_cdc_if.c и в него вставляем следующее:
Здесь реализован режим эха: всё, что приходит в порт, мы немедленно отправляем обратно.
Компилируем и прошиваем микроконтроллер. Затем подключаем разъём User USB платы к компьютеру. Система должна обнаружить новый COM-порт.
Для Linux: проверяем ls /dev/tty*, появилось устройство /dev/ttyACM0. Проверяем, и здесь нас ждут ещё сюрприз: отказано в доступе. Нужно добавить себя в группу dialout:
(где user — ваше имя пользователя)
Для работы с устройством в Windows вам понадобится скачать и установить драйвер. Для работы в OS X и Linux специальный драйвер не нужен.
Запускаем (например) Putty, настраиваем параметры порта. Они должны совпадать с параметрами, указанными в свойствах порта (см. «диспетчер устройств/порты»).
Пробуем открыть порт в Putty и что-то послать в порт:
Если порт не открывается, можно попробовать выйти из режима отладки в IDE и перезапустить плату. Всё должно заработать.
как бороться с ошибкой код 10 виртуального порта?
Помогает в файле usbd_cdc.h вместо 512 поставить 256 в строке:
#define CDC_DATA_HS_MAX_PACKET_SIZE (512 было) 256 /* Endpoint IN & OUT Packet size */
Как мы увидели, работа с USB в режиме виртуального COM-порта очень проста. Единственный недостаток этого режима — очень низкая скорость передачи данных. Интерфейс USB в режиме Full Speed обеспечивает до 12 Мбит/c, в режиме High Speed — до 480 Мбит/c, но VCP ограничивает скорость жалкими 128 кбит/c.
Можно сделать высокую скорость передачи данных, но пока отложим это до следующего раза.
Сейчас попробуем запустить АЦП, получить с него значения и отправить на компьютер, реализовав очень простой (и очень медленный) «осциллограф». Чтобы было интереснее, мы подадим на АЦП синусоидальный сигнал, сформированный ЦАП. Так как мы уже делали это в прошлой части, я просто скопирую код в новый проект (с небольшими изменениями, которые большой роли не играют).
Сначала немного об АЦП, встроенном в микроконтроллер. Микроконтроллер STM32F767ZI имеет три 12-разрядных АЦП, типа SAR (последовательного приближения), имеющие производительность до 2 MSPS (млн. выборок в секунду). Этот тип АЦП отличается высокой скоростью преобразования, но меньшей точностью, чем сигма-дельта АЦП. Вход опорного напряжения VREF соединён с VDDA, и, через индуктивность, с VDD. Таким образом, опорное напряжение в нашем случае равно 3,3В. Особенностью SAR ADC является использование на входе схемы выборки-хранения, содержащей конденсатор. В момент выборки значения сигнала конденсатор подключается ко входу и заряжается до величины входного сигнала. Если источник сигнала будет иметь слишком большое внутреннее сопротивление, конденсатор не успеет зарядиться полностью, и мы получим заниженное значение. Этот и другие моменты использования АЦП изложены в [1].
АЦП данного микроконтроллера имеет множество режимов работы [2], мы рассмотрим только один из них. Попробуем получить одновременно два значения сигнала с двух АЦП, строго синхронно, и записать их в буфер через DMA.
Итак, создаём новый проект, добавляем в него уже готовый код для генерации синусоиды на DAC и для USB VCP (через него мы будем отсылать данные на компьютер). Далее (проводами) соединяем выход ЦАП с входами АЦП1 и АЦП2. Для того, чтобы как-то различать сигналы на аналоговых входах, я соединил АЦП1 c ЦАП напрямую, а АЦП2 — через делитель напряжения на переменном резисторе, чтобы можно было менять амплитуду сигнала.
Если вы будете подавать на вход АЦП сигнал от внешнего источника, следует помнить, что сигнал на любом входе микроконтроллера должен быть ограничен значениями 0 — Vcc, что в большинстве практических случаев приводит к необходимости сдвига и усиления (или ослабления) сигнала.
Также следует учесть, что в микроконтроллерах STM32 используются АЦП последовательного приближения (SAR), которые потребляют от источника сигнала довольно большой ток в момент измерения, и требуют источника сигнала с низким импедансом.
Рис. 1. Схема выборки-хранения SAR ADC (не из STM32, но совершенно аналогичный)
В моменты выборки сигнала конденсаторы (рис. 1) подключаются ко входу АЦП и должны зарядиться до полного уровня сигнала за очень короткое время, потребляя при этом значительный ток. Если источник сигнала будет иметь большое сопротивление, они не успеют зарядиться, и показания АЦП будут неверными. На практике это означает, что мы должны в большинстве случаев использовать внешний буферный усилитель. Так как сегодня мы сосредоточимся на программных аспектах задачи, мы можем обойтись без усилителя, но следует помнить, что без усилителя показания АЦП будут существенно искажены, и в реальных проектах он нужен.
Рис. 2. Схема выборки-хранения вызывает провалы уровня сигнала на входе АЦП.
К сожалению, в документации STM32 эти вопросы рассмотрены слабо, но я могу порекомендовать руководство [3].
Если читателям будет интересно, я могу рассмотреть основы схемотехники аналоговых узлов сопряжения сигналов с АЦП в следующей статье.
Нам нужны будут в нашем проекте два таймера. Один из них будет задавать период работы ЦАП, второй — АЦП. Настроим аналого-цифровой преобразователь на работу в двухканальном режиме с одновременной выборкой. Выборка будет происходить по таймеру TIM2. Полученные значения будут складываться в буфер с помощью DMA.
Мы будем использовать однократный режим работы DMA (есть также циклический, с ним мы уже познакомились при изучении ЦАП). После того, как буфер заполнится значениями с АЦП, мы копируем его содержимое в другой буфер (с некоторой обработкой), передаём его через USB и запускаем процесс снова. Также для отладки и индикации режима работы мы используем два порта GPIO, к которым подключены светодиоды.
Итак, у нас создан проект, в который мы добавили DAC и таймер TIM1. Ещё нам нужно добавить ADC1 (вход IN9), ADC2 (вход IN12) и таймер TIM2. Также нам понадобится USB_OTG_FS.
Настраиваем ADC1 на работу в режиме одновременной выборки, с запуском по таймеру 2:
ADC2 при этом настраивается автоматически:
Обращаем внимание, что размер передаваемых данных Word, а не Half Word, т.к. за один раз передаются данные с двух АЦП, упакованные в 32-битное слово. Настраиваем таймер TIM2:
USB настраиваем так же, как мы это уже делали. Генерируем код.
Я не буду здесь расписывать весь исходник проекта, остановлюсь лишь на ключевых моментах. Запуск цепочки таймер-ADC-DMA:
Почему мы не можем передать через USB непосредственно содержимое исходного буфера? Так как скорость VCP слишком мала, мы не сможем передавать весь поток данных с АЦП. Мы захватываем кусок сигнала, передаём его «наверх», потом захватываем следующий кусок и т. д. Если мы не предпримем специальных мер, то в порт будут передаваться случайные фрагменты исходного сигнала. Поэтому нужно сделать программный аналог «триггера», как у цифрового осциллографа. Мы будем передавать в порт не рандомный фрагмент, захваченный АЦП, а кусок сигнала после возникновения некоторого условия. Таким условием может быть пересечение сигналом некоторого уровня в направлении снизу вверх: sample0_0 = threshold, где threshold — порог срабатывания.
Именно для этого мы делаем буфер АЦП в два раза больше буфера VCP, и просматриваем его до середины в поисках такого условия. Если условие не наступило, не отправляем в порт ничего, и запускаем следующий цикл АЦП.
DMA упаковывает сигналы с двух АЦП в одно 32-битное слово. Не будем менять этот формат, просто добавим единицу в старший разряд первого отчёта в буфере, чтобы ПО верхнего уровня могло распознать начало «кадра»:
Для отображения сигнала на компьютере я написал маленькую программу на C#:
Она в основном собрана из компонентов в Visual Studio и содержит минимум кода. Её исходники также доступны на Github.
Что дальше
В следующей части мы рассмотрим интерфейс Ethernet и немного операционную систему реального времени FreeRTOS.
Ссылки
Исходники проектов к всему циклу статей можно скачать на github. Все проекты сделаны для платы Nucleo F767ZI и используют IDE Atollic TRUEStudio.
Благодарю за внимание, о замеченных ошибках и опечатках прошу сообщать в личку. Продолжение следует.