Pull to refresh

Comments 77

Сразу спрошу — на каждый канал два endpoint? — а то я сейчас думаю, как на 4-1 endpointa натянуть 2 uarta и никак :(.
Один 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 есть поле для номера интерфейса. Но я не тестировал такой вариант с реальными драйверами и ничего не могу сказать о том, как это работает на практике.
То есть Вы сделали IN OUT совмещенным на одном endpoint и получили 3*2=6 требуемых.
О несуществующем для notification я уже думал, но что скажет драйвер?
А вот общий канал управления для всех — это очень интересно, тогда я уложусь 1+1+2*1=4.

Драйвер ничего не скажет, даже не узнает, что этот endpoint в действительности отсутствует. Драйвер в этот endpoint ничего сам не отправляет, это IN endpoint. Но это все-таки хак с точки зрения периферии микроконтроллера. Вот пример того как это сделано: https://github.com/eddyem/stm32samples/tree/master/F1-nolib/SevenCDCs

Хост не отправляет, хост спрашивает и не получает от МК ничего (МК не понимает, что от него хотят — конечная точка не существует и не обрабатывается)

Да, я несколько некорректно выразился. Хотел подчеркнуть, что это IN endpoint. Естественно, в USB инициатором любого обмена выступает хост.

UFO just landed and posted this here

Без аппаратной реализации не заработает, а ее на stm без внешних микросхем не сделаешь.

Rs-485 можно сделать аппаратно на STM32F0, я на 042 делал.

Почему нельзя? Вроде можно, у ST даже есть App note AN3070 на эту тему. Понятно, что это не так быстро, но все же лучше, чем ничего.

Да не, это не то, это про то как DE управлять, если используешь DMA. Но микросхема-драйвер (типа ADM485) все равно нужна для физического уровня. Подключить A и B к самой СТМке нельзя, а вот на PSoC скорее всего можно (но надо написать код драйвера RS485)

Да, тогда понятно. Вы правы, без драйвера никак.

Теоретически можно, но лично мне трудно оценить, на какую скорость хватит быстродействия МК. Если напряжение на линии соответствует питанию МК то там по факту только определять, какая из линий выше по напряжению. Возможно можно встроенный в мк компаратор использовать. Или АЦП. В любом случае нужна аналоговая периферия. На цифре тоже работать будет, но на небольшом расстоянии.
Передача проще — 1 и 0 цифровой на A и B.


Но опять же, под PSoC это проще сделать, чем кодить на Си и использовать стандартную встроенную периферию.

Софтовая реализация RS-485 могла бы быть отличными упражнением, но для практического применения боюсь будет слишком медленно. Особенно, если надо сделать больше одного порта.

Софтовая реализация RS-485 могла бы быть отличными упражнением

Вот да, постоянно тревожит эта тема, надеюсь как-нибудь занятся ей в свободное время. Плюс, можно будет сравнит это с аппаратной реализацией в PSoC и написат статью.


но для практического применения боюсь будет слишком медленно

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


Особенно, если надо сделать больше одного порта.

Если скорость одинаковая, и тактировать все одним таймером, то расходы на каждый дополнительный порт будут очень низкие. Только память для хранения состояния (прием/передача, номер бита и указатели на буферы)

В таких проектах очень важна мотивация. А что может мотивировать лучше, чем создание устройства, которым в первую очередь будешь пользоваться самостоятельно? Мне в повседневной жизни RS-485 совершенно ни зачем не нужен, а вот RS-232 очень даже. Но если вдруг понадобится RS-485, и я не найду подходящего мне устройства, то обязательно займусь.

RS-485 все же будет. К сожалению, аппаратной поддержки для этого в микроконтроллере нет, делаю программно. Пока непонятно до каких скоростей получится выдерживать требуемое стандартом время переключения DE.

Добавил поддержку RS-485, обновил статью.

UFO just landed and posted this here

Я постарался (и продолжаю стараться) сделать обработчик прерывания от USART как можно более коротким, и с хорошей вероятностью все будет работать на 460 кБод на 3-х портах одновременно. Но мне всё-таки нужно больше обратной связи от тех, кто будет это использовать в реальной жизни. Просили сделать RS-485 — я сделал. Теперь я, в свою очередь, прошу протестировать результат :)


<зануда моде он>
Бод = это бит/с, поэтому бод/с — это не скорость, это ускорение :)
</зануда моде офф>

UFO just landed and posted this here

Довел время сброса TXA до 0.6 мкс применив bit banding. Теперь еще больше шансов, что все заработает на 460 кБод на трех портах одновременно.

Можно так:

Непосредственно перед запуском передачи включаем линию DE.

Включаем прерывание DMA по завершению передачи.

В прерывании DMA по завершению включаем прерывание UART, которое сигнализирует о пустом регитре данных (TX reg. empty).

В прерывании TXE включаем прерывание TX complete (TXC) и выключаем TXE.

В прерывание TXC деактивируем линию DE и выключаем это прерывание.

Как это работает.

Регистр данных UART буферизован посредством FIFO буфера. Не помню точно, какова глубина буфера, (2 байта скорее всего), но прерывание TXE сигнализирует о том, что можно записать следующий байт, пока идёт отправка предыдущего. Прерывание TXC сигнализирует о том, что отправка текущего байта завершена. Оно будет вызываться всегда, когда завершается отправка очередного байта.

Идея состоит в том, что как только DMA просигнализирует о завершении последней транзакции, мы начнём ждать, когда регистр данных UART освободится. Так мы будем знать, что отправляется последний байт.

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

Как только он отправится, можно отключить линию DE

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

Нет. Из Reference Manual на STM32F103:

TXE interrupt. A USART interrupt is generated whenever TC=1 in the USART_SR register (RM0008, p. 821)

TC: Transmission complete. This bit is set by hardware if the transmission of a frame containing data is complete and if TXE is set. (RM0008, p. 818)

Поэтому не всегда, а только если USART больше нечего отправлять. Как следствие, достаточно разрешить в обработчике DMA прерывание по TC и по нему сбросить DE. Именно это тогда и было сделано. Корректность работы я проверил осциллографом и им же померил задержку.

UFO just landed and posted this here

Возможно кому то пригодится FT4232 — USB to 4 UART.
Главные его преимущества:


  • не нужно прошивать;
  • полноценные UART;
  • скорости повыше, т.к. USB High Speed.

Вы забыли упомянуть, что FT4232 — это микросхема, а не готовая плата. Да, ее не нужно прошивать, но зато её нужно к чему-то припаять. Максимальная скорость действительно впечатляет — 12Mbaud, согласно datasheet. Но и цена кратно больше: отладочная плата на AliExpress стоит 800 руб. против 120 руб за STM32 Blue Pill. И если можно, расскажите поподробнее про "полноценность". Единственное различие, которое бросилось в глаза — это наличие на FT4232 сигнала RI, но с другой стороны, на FT4232 отсутствует возможность тонкой настройки входов/выходов (полярность, подтяжка, тип выхода).

А я и не заявлял, что это готовая плата.
Готовых плат на этом чипе полно на Aliexpress. Хочешь на 2 порта (FT2232H), хочешь на 4 (FT4232H).
Я не говорю, что ваш проект чем то плох.
Бывают ситуации когда время дороже, чем деньги и тогда проще купить готовый прибор не требующий дополнительных телодвижений для использования.
Почитайте datasheet и увидите, что FT4232H — это не только UART-ы с хорошими буферами FIFO, но и GPIO которые много где применяются (BitBang JTAG, SPI, I2C и т.п.).
Так-же есть автоматический контроль потока для RS485, настраиваемая мощность выходов (4-16мА).
Я давно использую эту микросхему и доволен её работой и поддержкой в Linux и Win32.

Да FT4232H (FT2232H) — это прекрасная микросхема, кто бы спорил. И datasheet на нее я читал. Просто, как говорится, right sizing, для одних применений лучше она, для каких-то других — вариант на STM32 Blue Pill. Особенно, если последняя уже валяется в ящике стола. Опять же, у FT GPIO, а у STM32 Blue Pill возможность установки выходов в открытый сток… Под каждую задачу — свое решение.

«Полноценный» — это порт UART со всеми сигналами RXD, TXD, /RTS, /CTS, /DTR, /DSR, /DCD, /RI.

В моем варианте, из всего перечисленного, отсутствует только /RI. Не самый нужный сигнал. "Полноценность" не сильно пострадала. Зато присутствует возможность управления параметрами сигнальных линий. Впрочем, надо будет подумать о реализации и /RI тоже, благо это делается тривиально. Спасибо за подсказку!

Добавил поддержку RI, обновил статью. Спасибо!

UFO just landed and posted this here

Да, похоже от судьбы не уйдешь и придется все-таки делать поддержку управления конвертерами RS-485. Базово уже сделал, но не выкладывал. Там надо выдержать достаточно жесткие тайминги по управлению DE при переходе от передачи к приему и я сейчас пытаюсь это обеспечить.

UFO just landed and posted this here

Спасибо, я уже тоже выше про этот AN написал. Примерно так и буду делать.

Если есть возможность использовать ногу RTS (но на ней обычно слишком нужные функции висят), то аппаратно будет работать прекрасно.
А вот если нет — то придется делать софтово (натыкаясь на грабли).

У STM32F072 и STM32F103C8T6 разные USART. У первого есть режим в котором сигнал RTS может управлять DE(/RE) RS-485, а у второго — нет. В целом, USART на STM32F072 более продвинутый. Но что поделать, ведь STM32F103C8T6 гораздо более распространен и дешев, поэтому выбор очевиден.


Что касается граблей, то насколько я понял все грабли у вас заключались в том, что 5-ти вольтовый MAX485 то ли работал, то ли нет с 3.3 вольтовыми уровнями (или питанием?). Или я что-то пропустил? Может в комментариях?


В-общем, я пока работаю над софтовым управлением TXA (DE, /RE). Пришлось перепахать всю прошивку, отказаться от прерываний USB в пользу поллинга, задействовать вытесняющие прерывания и много всего по мелочи. Когда-нибудь потом напишу статью об этом.


А пока задержка переключения TXA по окончанию передачи составляет менее 1 мкс. Измерено осциллографом на одном из портов на фоне занятых постоянной передачей двух других. Это позволяет работать с RS-485 на скоростях не менее 460 кБод и на мой взгляд, это очень прилично. Хотя я собираюсь еще немного ужаться.

Думаю, стоит пока остановиться. Уже слишком круто)

Я не остановился и довел задержку до 0.6 мкс. Вот на этом пока остановлюсь :)

Кстати, по поводу ваших "граблей" в статье. Я посмотрел ваш код на GitHub и мне бросилось в глаза, что у вас USART RX настроен как floating. А он должен быть подтянут к Vcc. И тогда все будет хорошо, не будет никакого "мусора" и не надо будет запрещать прием на время передачи.

UFO just landed and posted this here
UFO just landed and posted this here

В этом проекте буфера на приём / передачу равны 1024 байтам. Если ПО успевает вычитывать данные из последовательного порта быстрее, чем они поступают по uart, то все будет хорошо. Если нет, то однажды случится переполнение буфера и часть данных будет потеряна. О таком событии прошивка сообщает хосту. На тестах я передавал многие мегабайты на скорости более 2 Мбит/с с порта на порт, и никаких переполнений не возникало. Но, конечно, если ПО или ОС «задумается», то шанс потерять данные есть. Если нужно гарантированно не терять данные, следует использовать аппаратный контроль потока (RTS/CTS).

UFO just landed and posted this here

Если увеличивать только буфера на прием, то до 2 Кбайт точно можно. До 4 Кбайт возможно можно, надо смотреть. Только я не понимаю, зачем? Какую проблему вы хотите таким образом решить?

UFO just landed and posted this here

Максимальная скорость FT2232 по RS232 — 12 Mbaud, это оправдывает наличие больших буферов для того, чтобы не потерять данные в ситуации, когда мы приняли сразу большой пакет, а ПО не успело среагировать. Максимальная скорость, которая у меня получилась на STM32F103C8T6 — около 2 Mbaud, то есть в 6 раз медленнее. При этом, отношение размера буфера к максимальной скорости в моем случае даже выше.


Я очень консервативно отношусь к внесению изменений в прошивку. Изменение должно либо решать существующую и воспроизводимую проблему, либо добавлять новую функциональность. Избегание непонятных проблем вида «вдруг, где это надо…» не являются поводом для модификации прошивки.


Однако, если вы действительно встретитесь с проблемой правильным решением которой будет увеличение размера буферов, я с удовольствием это сделаю.

UFO just landed and posted this here

Сделал экспериментальную версию, обновил статью.

В STM32F103 ограничивает скорость USB FullSpeed до 12 Мбит/с и размеры Endpoint 64 байта. У FT используется USB HighSpeed 480МБит/с и размер endpoint 512 байт. Когда в STM32 заполнится FIFO, то должно сработать ограничение — надо подождать, для этого RTS/CTS и нужны.
В целом проект очень интересный, только код без комментариев — это как машинный код, кроме компилятора его никто не понимает. Даже автор через год забудет.

У нас с вами разный подход к комментированию кода. Я считаю, что сам код должен быть написан так, чтобы был понятен без комментариев. От этого у меня в коде длинные и осмысленные названия имен переменных, функций и так далее. Комментарии я использую для описания важных и неочевидных вещей, когда это действительно требуется, а также для отделения логических блоков в коде друг от друга. До сих пор я как-то умудрялся разбираться в своих проектах даже 20 летней давности. И другие люди тоже разбирались без проблем. А еще есть blame в Git и commit messages…


А если уж совсем позанудничать...

То я посмотрел код вашего проекта MIDI2USB, давайте для примера возьмем main() из main.c


WDT_Init();            // Disable WDTimer (not used)
PORT_Init();            // Initialize ports (UART, LEDs)
SYSCLK_Init();            // Set system clock to 48MHz
UART0_Init();            // Initialize UART0 @31250, 8-N-1
USBD_Init( &usbInitStruct );            // Initialize USB, clock calibrate
LED_IN  = 1;            // Blink LED
LED_OUT = 1;            // Blink LED
IE_EA   = 1;            // Global enable IRQ

Если у вас функция WDT_Init не инициализирует watchdog, а запрещает его, функция PORT_Init() инициализирует не порт, а порты и так далее, то да, такой код лучше комментировать.


Я уже не говорю о том, какие проблемы бывают при расхождениях в коде и комментариях. И о том, что код вызывающий UART0_Init() не должен и не может знать о том что это на самом деле "// Initialize UART0 @31250, 8-N-1" в случае если он не передает эти параметры явно при вызове функции.


Да в конце-концов, посмотрите как оформлены дескрипторы у меня и у вас. И учитесь писать код так, чтобы не надо было комментировать каждую строчку. Судя по коду в вашем репозитории, вы пока еще это не очень хорошо умеете.

Вы пишите «очень рассчитываю на обратную связь», а сами токсично реагируете и в мой огород кидаете камень «А у вас негров линчуют».
Я отвечу на критику
Проект реализован на контроллере EFM8EB20F64 — это современный разогнанный 8051 от SiLabs.
1) Для инициализации и настройки периферии используется конфигуратор (вроде CubeMX, для тех кто не в теме), это такой норматив, правило в этой среде разработки. И этот конфигуратор генерирует код функций: WDT_Init(); PORT_Init(); SYSCLK_Init(); и т.д. Так принято в SiLabs 8051. В Windows API, например, принято имена давать lpszClassName; Никто не возмущается.
2) Функции инициализации не имеют аргументов. Более того, в каждом проекте они разные. Поэтому комментарий дать — хорошая практика.
3) Ядро 8051 живёт более 40 лет и есть много кода для него. Раньше (и иногда сейчас) используются «магические константы» для записи в регистры. Это не красиво и не понятно. Эта проблема частично решается оформлением кода в два столбца с комментариями. Это не я придумал, это традиция. «When in Rome do as romans do».
4) Оформление дескрипторов у меня выполнено в стиле SiLabs и на версии языка С, который понимает IDE.

Критиковать ваш код в ответ не буду, но с указателями и типами данных у вас помойка в коде. Ищите сами. Я из темы удаляюсь.

Есть разница между конструктивной критикой и "бла-бла-бла, в вашем коде не хватает комментариев, поймет только компилятор, а автор все забудет через год". Первое — приветствуется, второе — неприемлемо. А комментарии в стиле: "но с указателями и типами данных у вас помойка в коде. Ищите сами." — это вообще ни о чем. Однако, если бы вы написали что-то в стиле: "вот тут сделано неправильно потому-то и потому-то", то я сказал бы вам огромное спасибо. Если бы вы написали "в таких условиях возникает вот такая ошибка, повторять так", я бы вообще прыгал до потолка от радости. Конечно, если бы это было действительно неправильно и у меня не было бы причины сделать именно так.


  1. Валить все на конфигуратор не стоит, CubeMX и не такую еще дичь генерит, но это не значит что его использование является обязательным. А в WinAPI как раз очень разумная, не близкая мне лично, но разумная политика названия переменных;
  2. Правильно, но не в том месте в котором она вызывается. Кончится это тем, что один программист поправит функцию инициализации, а вот про все места из которых она вызывается даже не подумает. А другой программист потом будет очень рад, потратив пару часов на осознание того факта что в комментарии написано одно, а функция делает другое.
  3. Традиция, так традиция. Но мне-то какое дело до ваших традиций?
  4. Опять же, тот факт, что у вас что-то оформлено в стиле SiLabs никак не делает ваши дескрипторы более читаемыми. Хотя да, я понимаю, есть еще возможные ограничения компилятора.

А камень в ваш огород я не кидал, я просто указал на конкретные проблемы вашего стиля кода и комментирования, со ссылками на конкретные места в вашем коде, объяснил к чему это потенциально приводит.


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

UFO just landed and posted this here

По USB? А что еще с этими данными происходило? Чем еще прошивка занималась, расскажите, пожалуйста. Это интересно!

UFO just landed and posted this here
Автору однозначно зачет. Когда то сталкивался с подобной задачей, но лень победила и был просто добавлен USB-хаб. Конфигурацию, кстати, тоже часто делаю через терминал, но выделяю для этого один виртуальный порт, чтобы не возиться с перемычками.

Спасибо! Я не стал выделять конфигурацию в один порт для того, чтобы не терять в производительности. Хотя, здесь есть пространство для дебатов. Дело в том, что в stm32f103c8t6 размер USB packet buffer memory равен 512 байтам. 112 из них заняты buffer description table (для 7 endpoints), сами endpoints занимают еще 384 байта. При этом, уже даже тут пришлось несколько ужаться, и размеры BULK IN/OUT endpoints для порта 1 сделать равными 32 байтам против 64 байт у остальных портов. Это несколько ограничивает производительность первого порта. Таким образом, у нас осталось всего 16 байт, которые все и уйдут на дескриптор 8-го endpoint. Надо откуда-то взять еще хотя бы 16 байт. В принципе, можно уменьшить два из трех INTERRUPT IN endpoints до 8 байт и передавать уведомления о состоянии порта за два «захода». Но мне не захотелось так делать вот почему: конфигурационный порт нужен далеко не всем и используется относительно редко. Я не захотел выделять для него на постоянной основе ресурсы, которые могут помочь увеличить производительность при нормальной работе.


Кроме этого, при некоторых применениях, конфигурационный порт может оказаться даже вредным. Мне не хотелось бы, чтобы у конечного пользователя в системе постоянно болтался порт с помощью, которого он может легко «выстрелить себе в ногу». Поэтому я сознательно усложнил доступ к этому порту.


С другой стороны, пока я все это писал, я понял, что у меня есть аж 16 свободных байт, а текущий размер control EP0 – 8 байт. Будет лучше инвестировать эти свободные байты туда.

Спасибо! Интересный проект. Сам пользуюсь для этой цели преобразователем U-620 от ST Lab. 8 полноценных портов RS-232 на одном USB, и с внешним блоком питания.

Спасибо, не знал про U-620. Мощная штука! Но мне не подходит: мне в первую очередь требуются TTL уровни.

Очень понравилось на плиске с usb девайсом играться — 16 конечных точек в двух направлениях, хайспид и никаких дурацких ограничений. Но это, как говорится, если usb — важная часть проекта, например, аудио многоканальное.

А можно так с CDC + MSC да так чтоб под cmsis?
Оффтоп
Поставил бы плюс, но нечем

Я вряд ли буду это делать. У меня в планах выкинуть один из последовательных портов и попробовать добавить туда простенькую аудиокарту: один вход с использованием ADC, один выход с использованием таймера в режиме ШИМ. И тогда получится интерфейс для радиолюбительских трансиверов, для работы во всяких цифровых модах типа FT8. Но пока сложно сказать, когда я это сделаю.

Я хочу сделать восстановлялку данных с SD
Здорово вышло!
А может возможно пробросить напрямую, как хаб USB, к китайской звуковой? Итого 2 порта и проброшенный USB для звуковой карты, как раз для трансивера. Не уверен, что ШИМ и ADC встроенные будут работать как надо.

Собрал, адаптировал резистор 10k -> 1.5. Работает, правда при нажатии «отключить» (использую CoolTerm) виснет, пока не выдернешь USB или сброс не нажмешь. В своих поделках я дергал отдельной ногой контроллера подтянутой на D+, чтобы USB понял рестарт.

Мне интересно именно ADC и ШИМ попробовать. Может и не заработают. Посмотрим. По поводу зависания — интересно, попробую повторить. А на какой ОС вы работаете?

Исправлено в версии 2.3.3, пожалуйста, обновитесь.

Мне нужно сделать на скорую руку один адаптер. В нем нужен источник импульсов, потому решил сделать USB-UART адаптер на плате BluePill, так как у МК есть таймеры, которые могут послужить настраиваемым источником импульсов. За основу взял Ваш проект. Собрал без проблем, прошил, все работает и всё устраивает. Даже свободная нога имеется, на которую выходит первый канал второго таймера. В коде разобрался быстро. Добавил ряд команд в консоль для настройки таймера и свою структуру настроек в конфигурацию (всего два 16-битных поля). Всё читается и сохраняется без проблем, целостность конфигурации во флеш не нарушается.

Остался один вопрос. Как настроено тактирование микроконтроллера? Какие частоты заданы на системных шинах? Просто все максимально допустимые?

Да, все максимально допустимое. SYSCLK = 72 МГц. APB1(PCLK1) = 36 МГц, APB2(PCLK2) = 72 МГц.

Благодарю за ответ.

Я использую ногу PA15 для вывода сигнала таймера. Используется первый канал (CH1) таймера TIM2. И столкнулся с проблемой. Дело в том, что где-то в коде, обслуживающем USB, портится конфигурация ноги PA15. Она вроде как не используется нигде, но проблема есть. Если закомментировать строку usb_init() в main.c, то проблем нет и сигнал выводится. Таймер инициализируется самым последним. Значит портится не в функции usb_init(), а где-то в обработчиках прерываний. Пока разбираюсь, где проблема.

Используется, PA15 – это UART1 RTS (см. README.md), в device_config он тоже упоминается.

Я перекинул функцию UART1 RTS на ногу BOOT1 и освободил PA15 для использования в качестве выхода синтезатора частоты. Сигнал появился. Кстати, адаптер уже служит верой и правдой и прекрасно справляется с поставленной ему задачей. Ваш проект помог мне сэкономить время.

Sign up to leave a comment.

Articles