Некоторое время назад мне в очередной раз потребовался USB-Serial адаптер. И не просто адаптер c RX/TX, а чтобы еще присутствовали управляющие сигналы. И не один UART, а несколько. И еще желательно, в виде одного композитного устройства, чтобы все это хозяйство не занимало больше одного USB-порта. Так и началась эта история...
Готовые устройства из тех, что можно приобрести в магазине, меня не вдохновили. Либо дорого и сложно найти в продаже, либо не подходят по одному или нескольким критериям выше. Композитных USB-Serial адаптеров в дешевом сегменте я и вовсе не смог найти. Зато на столе обнаружилась отладочная плата STM32 Blue Pill на микроконтроллере STM32F103C8T6. Сверившись с документацией, я убедился, что этот микроконтроллер содержит в себе все, что мне нужно для полного счастья. А именно: интерфейс USB 2.0 full-speed, три USART, семиканальный DMA контроллер и достаточное количество линий GPIO для реализации всех интересующих меня управляющих сигналов.
Осталось только раздобыть соответствующую прошивку, которая превратит безжизненный кусок железа в нужное и полезное устройство. И тут меня поджидало большое разочарование: оказывается, несмотря на бешеную популярность этого микроконтроллера и отладочной платы, никто до сих пор так и не сделал ничего подобного. Вернее, попытка была, но это скорее экспериментально-образовательный proof-of-concept, а не законченный продукт. Чего только стоит блокирующая передача данных по UART: один порт передает, остальные ждут. Нет, этот микроконтроллер определенно заслуживает большего! Пришлось засучить рукава и написать прошивку самому.
Что у меня получилось
Я решил по-максимуму задействовать возможности периферии микроконтроллера, но вместе с тем не впадать в пучину предварительных оптимизаций. Читаемость и поддерживаемость кода для меня гораздо важнее, чем незначительный прирост производительности. Тем более, что с производительностью и так все вышло очень хорошо.
Что реализовано:
- три независимых последовательных порта;
- поддержка аппаратного контроля потока (RTS/CTS) на двух из трех портов;
- поддержка управляющих сигналов DSR/DTR/DCD/RI;
- поддержка 7 и 8-битной длины слова;
- поддержка контроля четности;
- 1, 1.5 и 2 стоповых бита;
- совместимость со стандартными драйверами Linux, macOS и Windows;
- подписанный INF файл для Windows XP, 7 и 8;
- поддержка произвольных скоростей (более 2 Мбит/с);
- сигнал TXA для управления трансиверами RS-485 (DE, /RE);
- DMA на передачу и прием данных USART;
- встроенный командный интерпретатор для конфигурации;
- нет зависимостей от сторонних библиотек кроме CMSIS;
- проект с открытым исходным кодом, лицензия MIT;
Командный интерпретатор позволяет настраивать следующие параметры:
- тип выхода: двухтактный, открытый сток;
- тип подтяжки входных линий: вверх, вниз, плавающая;
- инверсия для управляющих сигналов: активный высокий/низкий;
Командный интерпретатор активируется на первом CDC порту при подключении пина PB5 к земле, поддерживает часть управляющих последовательностей ANSI (стрелочки, backspace), и принимает вполне дружелюбные на вид команды:
*******************************
* Configuration Shell Started *
*******************************
>uart 1 tx output pp dcd active low pull up
>
Командный интерпретатор не позволяет переназначать сигналы с одних пинов на другие. Во-первых, далеко не все сигналы можно переназначить, а во-вторых, такая возможность по-настоящему становится востребованной только при использовании прошивки в контексте какой-либо иной платы. В этом случае проще и правильнее переназначить сигналы используя конфигурацию, хранящуюся в исходном коде.
Распиновка вышла следующей:
Signal | Direction | UART1 | UART2 | UART3 |
---|---|---|---|---|
RX | IN | PA10 | PA3 | PB11 |
TX | OUT | PA9 | PA2 | PB10 |
RTS | OUT | N/A | PA1 | PB14 |
CTS | IN | N/A | PA0 | PB13 |
DSR | IN | PB7 | PB4 | PB6 |
DTR | OUT | PA4 | PA5 | PA6 |
DCD | IN | PB15 | PB8 | PB9 |
RI | IN | PB3 | PB12 | PA8 |
TXA | OUT | PB0 | PB1 | PA7 |
Пины, выделенные жирным шрифтом, являются толерантными к 5 В.
Сигнал TXA (TX Active) служит для управления микросхемами трансиверов RS-485 (DE, /RE). TXA активен во время передачи данных и переключается в неактивное состояние не более чем за 0.6 мкс после завершения передачи. Это соответствует спецификациям RS-485 на скоростях до 920 кБод c почти двукратным запасом по времени переключения.
К сожалению, реализовать RTS/CTS на UART1 не вышло из-за того, что соответсвующие пины заняты сигналами USB. Можно было вывести RTS на какой-нибудь другой пин, поскольку RTS управляется программно, в зависимости от степени заполнения кольцевых буферов на прием, но порт c RTS и без CTS мне показался странной штукой и я решил, что так делать не надо.
Проект написан на языке C, и подразумевает использование arm-none-eabi-gcc для сборки. Я использовал специфичный для GCC синтаксис атрибутов и расширения языка С. Совместимость проекта с проприетарными компиляторами меня не интересует, но если кто-то считает это важным, то я готов принять соответствующий пул-реквест.
В результате у меня получилось удобное и мощное устройство которое полностью закрывает все мои потребности в последовательных портах. STM32 Blue Pill можно использовать как самостоятельно, так и в составе схем обеспечивающих согласование уровней и развязку. Возможность настройки сигнальных линий позволяет упростить разработку таких схем.
Подробная документация, исходный код и собранная прошивка доступна в репозитории проекта на GitHub.
Дальнейшие планы
Нет предела совершенству и этот проект – не исключение. В каких-то местах код можно было написать чище, понятнее и эффективнее. Я обязательно этим в какой-то момент займусь. Кроме того, я очень рассчитываю на обратную связь от пользователей для поиска и устранения возможных багов.
Обновления
Поддержка RI
28.11.2020
Добавил поддержку сигнала RI на всех портах (спасибо plyatov), добавил в статью описание сигнала RI.
Устранил ошибку, приводящую к возможному повреждению данных на приеме
01.12.2020
Код не проверял готовность BULK IN endpoint перед отправкой CDC ZLP-пакетов. При определенных условиях это могло приводить к повреждению принимаемых данных. Очень странно, но эта ошибка в реальной жизни проявлялась существенно реже, чем можно было бы ожидать. Исправлено в версии 2.1.1.
Поддержка управления трансиверами RS-485
02.12.2020
Добавил поддержку управления трансиверами RS-485 в версии 2.2.0, обновил статью, добавил описание сигнала TXA. Спасибо dernuss и остальным тем, кто просил сделать эту фичу.
GarryC
Сразу спрошу — на каждый канал два endpoint? — а то я сейчас думаю, как на 4-1 endpointa натянуть 2 uarta и никак :(.
Kirillius Автор
Один CDC требует для своей работы следующие endpoints: BULK IN, BULK OUT, INTERRUPT IN. В STM32F103C8T6 есть 8 двунаправленных endpoints. Один CDC ACM занимает два из них. Если надо ужаться и не жалко потерять возможность отправки notifications, то в качестве INTERRUPT IN endpoint в дескрипторе можно указать несуществующий номер endpoint. Таким образом на STM32F103C8T6 можно реализовать 7 CDC. Если надо ужаться, но при этом не хочется терять возможность отправки notifications, то рекомендую попробовать поделить один INTERRUPT IN endpoint между всеми CDC устройствами. Из стандарта не следует, что это не будет работать, более того, в структуре notification есть поле для номера интерфейса. Но я не тестировал такой вариант с реальными драйверами и ничего не могу сказать о том, как это работает на практике.
GarryC
То есть Вы сделали IN OUT совмещенным на одном endpoint и получили 3*2=6 требуемых.
О несуществующем для notification я уже думал, но что скажет драйвер?
А вот общий канал управления для всех — это очень интересно, тогда я уложусь 1+1+2*1=4.
Kirillius Автор
Драйвер ничего не скажет, даже не узнает, что этот endpoint в действительности отсутствует. Драйвер в этот endpoint ничего сам не отправляет, это IN endpoint. Но это все-таки хак с точки зрения периферии микроконтроллера. Вот пример того как это сделано: https://github.com/eddyem/stm32samples/tree/master/F1-nolib/SevenCDCs
romanetz_omsk
Хост не отправляет, хост спрашивает и не получает от МК ничего (МК не понимает, что от него хотят — конечная точка не существует и не обрабатывается)
Kirillius Автор
Да, я несколько некорректно выразился. Хотел подчеркнуть, что это IN endpoint. Естественно, в USB инициатором любого обмена выступает хост.