Comments 44
Для тестов я решил взять три наиболее популярных сейчас микросхемы USB-UART: CP2102, FT232 и CH340. Первые две микросхемы можно купить на алиэкспрессе на платах с нужными нам выводами. Только на модулях с FT232 очень часто стоит фейковая микросхема, которая постоянно зависает, поэтому лучше их вообще там не покупать.
Модули с фейковыми CH340 тоже бывают. Под маркировкой CH340G абсолюно левая по прозвонке выводов микра и бутафрский кварц, никуда не идущий. Работают подделки нормально (система видит как CH340), вот только кроме RX, TX больше ничего нет.
А вот для микросхемы CH340 распространены только модули с основными выводами RX и TX, без дополнительных
вот здесь есть DTR
на этом есть RTS,CTS
на этом есть RTS,CTS,DTR
Вроде в мс FTDI есть режим Bit-banging, не проще ли через это действать?
Не уверен, что проще, вроде бы это делается через SDK от FTDI, а не через функции COM-порта. То есть, надо будет что-то ставить и изучать другие функции. Да и решение будет не универсальным. Хотя, конечно, так было бы больше возможностей. Но так то и дешёвый микроконтроллер поставить на UART не так уж сложно:)
Для ftdi вариантов не мало, например библиотека и на python.
А еще можно зайти на сайт производителя и найти библиотеки даже под visualBasic. там есть уже непросто ногодрыг, а сразу еще реализованы интерфейсы i2C/SPI/jtag. Что и используют производители огромного числа программаторов/отладчиков на базе микросхемы FT2232H для огромного числа микросхем.
Аналогичные штуки есть и в серии usb интерфейсов CP и CH.
конечно придется чему то подучиться но и работает это все заметно быстрее.
Оригинальную FTDI, где SN не A50285BI, ещё нужно постараться найти.
Для ногодрыга бы пожалуй подошла CH341, которая известна всем по одноимённому программатору.
Как у автора FT232 у меня вполне работает.
У меня есть два модуля FT232 с аликспресса и оба работают некоторое время, потом подвисают на полсекунды и заново начинают работать. Причём модули куплены в разное время. Для статьи я нашёл совсем другой модуль FT232, купленный очень давно, наверное лет 5 назад:) Он хорошо работает.
А вы знали про возможности этих дополнительных выводов USB-UART?
Да, использую их вместо отсутствующих в подавляющем большинстве компьютеров GPIO. Мой основной кейс - управление любительской радиостанцией, переключение с приема на передачу, чтение статуса шумоподавителя и тому подобные нехитрые действия, которые хорошо поддерживаются соответствующим софтом и работают по сути везде, где есть USB порты. Да, более продвинутые радиостанции принимают расширенные команды по UART (COM) и отвечают на них - позволяют устанавливать практически все параметры и отчитываться об их статусе, начиная с очевидной установки частоты и вида модуляции, но и с ними переключение прием/передача лучше осуществлять примитивным, быстрым и надежным сигналом на RTS или DTR, а не ждать, пока управляющий софт сформирует и отправит команду, а радиостанция ее распарсит и выполнит - просто сменить уровень быстрее и помехозащищеннее. В результате все это делается на одном USB-UART, проще не придумаешь.
Кому такого импровизированного GPIO мало, подумайте об USB-LPT адаптере. :)
Ничего не понятно, но выглядит круто:) Пока читал ваш комментарий, пришла идея: можно через DTR и RTS управлять электронными ключами (типа DG408 или 74HC4066), чтобы "нажимать" отдельные кнопки на плате от клавиатуры или других устройств. То есть, получается программное нажатие, но распознаваться будет как реальное от пользователя.
А что если для этого использовать лампочки с клавиатуры. И usb дополнительный занимать не надо.
CH340 бывают очень разные. У CH340N, например, всего 8 выводов, 6 из которых это Vcc, GND, Tx, Rx, D+, D-.
Да, эта миниатюрная версия мне очень нравится, она прям создана для каких-то портативных устройств.
В плане миниатюрности CH340N всё же не самый маленький мост :)
Он занимает 5х6мм.
А, например, CP2102N-A02-GQFN20 занимает 3х3мм.
Но миниатюризация чипа с определённого момента начинает быть не вполне осмысленной - разъём-то не уменьшить ниже определённого уровня.
Я тут, кстати, накидал небольшой обзор всяких разных мостов, чтобы оценивать, скажем так, тенденции и масштабы.
Мне больше нравится cp2104, шустрая, стабильная, есть отдельные GPIO с управлением, правда, через их API, можно зашить конфиг с нужным VID/PID и настройками GPIO (правда, только два раза)
Благодарю!
Очень хорошо структурированная статья, более того, ценная не только кодом и использованием сейчас, после COM-мышей, данных выводов, но и различными примерами аппаратного окружения, в одной статье.
О всех этих микросхемах вроде помнишь, а когда доходит до тела-дела, вечно начинаешь тупить и искать конкретные применения.
Ну а код можно написать любой, благо что при наличии офисных пакетов почти на всех компьютерах, питон и Васик (Оба в MS и в Либре Офисах всегда в наличии) всегда при нас, даже в урезаной офисной машинке ;).
Да, способ этот известен с древних времен. В некоторых компьютерах 80-ых, в целях экономии, чтобы не ставить лишние корпуса i8255, использовали редко используемые линии полного RS-232 (типа Ring Indicator) как линии последовательного ввода-вывода.
Мне вспомнилась статья, где неиспользованные линии RS-232 использовались для питания преобразователя RS-232 <-> UART.
По поводу
Полученные таблицы со временем работы функций оказались немного странными.
в C# использовать DateTime.Now
для оценки времени не самая удачная идея. Можно посмотреть, например, здесь - https://habr.com/ru/companies/tbank/articles/454058/ или тут - https://ru.stackoverflow.com/questions/758708/Как-точно-измерить-время-выполнения-операции-С
Судя по описанию функции DateTime.Now.Ticks
, она выдаёт таймкод с шагом 100 нс. Это очень даже точно для моих задач:) Я её немного протестировал вместе с осциллографом и вроде бы она вполне точно показывает отсчёты как для измерения времени, так и для организации задержки в цикле.
Если бы функции COM-порта работали значительно быстрее, тогда, действительно, лучше было бы использовать специальные методы. Но тут я уже знал, что функции работают медленно (так как видел на осциллографе задержки в миллисекунды), поэтому такого таймера вполне достаточно.
"DateTime.Now.Ticks основана на функции WinAPI GetSystemTimeAsFileTime(). Она выражается в сотых наносекунды. Фактическая точность DateTime.Ticks зависит от системы. В Windows XP приращение системных часов составляет около 15,6 мс. В Windows 7 его точность составляет 1 мс. Однако при использовании схемы энергосбережения (обычно на ноутбуках) оно может уменьшиться до 15,6 мс. На Windows 10 и 11 я не в курсе." - взято из 1го ответа отсюда https://stackoverflow.com/questions/243351/environment-tickcount-vs-datetime-now
в комментариях кто-то добавил что в windows 10 точность у него составила чуть более 1мс
P.S Замерил у себя на работе (на windows 10, core i3-10100, .Net 8). Разница двух подряд идущих считываний тиков (в цикле считывал два раза подряд тики и выводил на экран разницу) давало от 0,1мс до 4мс с лишним, иногда выбросы были больше ( у меня было 18 мс)
Да, похоже, вы правы, хотя у меня код:
Int64 start_time1 = DateTime.Now.Ticks;
Int64 start_time2 = DateTime.Now.Ticks;
label1.Text = (start_time2 - start_time1).ToString();
Всегда показывает ноль:) Но если добавить какой-то большой цикл с делением между считываниями, то, действительно, время почти всегда кратно 1 мс, хоть и не ровно.
Попробовал такой вариант:
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
bool b = MyPort.CtsHolding;
stopWatch.Stop();
label1.Text = ((double)stopWatch.ElapsedTicks / (double)Stopwatch.Frequency).ToString();
label2.Text = b.ToString();
Вроде бы это должно показывать время в секундах. Как результат - примерно то же самое: в среднем функция выполняется 2 мс для CP2102. То есть, вы правы насчёт точности DateTime.Now.Ticks
, но в итоге результаты те же и со Stopwatch();
Попробовал и другие функции - тоже результат совпадает и так же значения постоянно прыгают с шагом примерно в мс.
То есть, функции реально медленные и привязаны к мс, поэтому особенно ничего не изменилось при переходе на более точный метод. Или я что-то не так сделал и тут:)
попробуйте из примера отсюда - https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch.frequency?view=net-8.0#system-diagnostics-stopwatch-frequency - вычислить разрешение вашего таймера, если оно меньше микросекунды, таково свойство CP2102, если больше - значит измерения нужно делать на другом компьютере
гляньте еще тут - https://ru.stackoverflow.com/questions/758708/Как-точно-измерить-время-выполнения-операции-С - чтобы понять проблематику
P.S Ну и C# не очень хороший инструмент для коротких измерений, слишком многое нужно учитывать
Да, спасибо, вроде я всё это уже изучил и понял проблему. Свойство Stopwatch.Frequency возвращает true, значит, у меня таймер высокого разрешения. И вышеприведённый код я уже проверил на измерении времени выполнения расчётов в цикле - он показывает микросекунды. Например, код
Stopwatch stopWatch = new Stopwatch();
double f = 0;
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
f += (double)i / 3;
}
stopWatch.Stop();
label1.Text = ((double)stopWatch.ElapsedTicks / (double)Stopwatch.Frequency).ToString();
label2.Text = f.ToString();
всегда выдаёт примерно 5E-05 . То есть, это 50 мкс. Если менять количество циклов, то время тоже меняется через микросекунды, а не миллисекунды, как с функциями COM-порта. То есть, это реально более точный способ, но именно для задачи в статье ничего особо не изменилось.
RTS/CTS не "когда-то давно", а в любой момент могут быть использованы для приостановки потока по uart'у, если вдруг имбеднутый девайс не вывозит.
DTR можно заводить (через джампер есс-но или нормально невпаянный резистор) на сброс девайса, тогда распальцовка в терминалке позволяет удобно девайс сбрасывать при отладке.
Вы имеете ввиду, что микросхемы USB-UART автоматически перестанут передавать данные, если на CTS будет 0? Что-то мне кажется, это не работает, иначе нужно было бы обязательно подтягивать эти входы к питанию. Если это самому можно отслеживать в своей передающей программе, тогда это неплохой вариант использования. Правда можно просто пореже передавать данные, чтобы не переполнять буфер устройства.
Насчёт ресета девайса: возможно, можно придумать какую-то хитрую логическую схему, чтобы устройство не сбрасывалось случайно при подключении и отключении USB. Например, можно выводить через DTR и RTS специальные комбинации для ресета устройства и активации бутлоадера при необходимости обновить прошивку. Я это продумывал для программатора STM32, но это достаточно сильное усложнение и проще заставить пользователя переключателем выбирать режим "работа" или "прошивка". Хотя для каких-то проектов это может быть актуально.
Есть предположение, что реализация bit-bang на Си покажет меньшие задержки.
Скорее всего намного быстрее, но это же будет не универсальное решение. То есть, такой режим используется только в API драйвера, который у каждой фирмы свой. Тогда программу надо затачивать на конкретную микросхему. В этом плане лучше будет использовать специализированные версии типа CP2104 и CH341, у которых есть дополнительные GPIO. Статья же про стандартный COM порт и его выводы:)
Очень познавательно, спасибо!
По поводу кода я добавлю:
System.Windows.Forms.Timer
может иметь погрешность 55 мс в силу своей однопоточной природы. В вашем примере нет нескольких потоков, но в целом, для точного таймера с потоками используйтеSystem.Timers.Timer
https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer?view=net-8.0&viewFallbackFrom=windowsdesktop-8.0Пожалуйста не объединяйте строки с помощью
string
+string
, это сжирает память и неэффективно.
Внутриstring
— обычный массивchar
, когда вы соединяете строки, фактически делаете это:public char[] PseudoStringConcat(char[] firstString, char[] secondString) { char[] result = new char[firstString.Length + secondString.Length]; int i = 0; foreach (var c in firstString) { result[i] = c; i++; } foreach (var c in secondString) { result[i] = c; i++; } return result; }
Для объединения строк служитStringBuilder
https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-text-stringbuilder
На самом деле, при изучении данной темы у меня была конкретная цель: я хотел сделать простой программатор для STM32 на базе преобразователя USB-UART, в котором выводы DTR и RTS используются для автоматического сброса микроконтроллера и активации встроенного заводского загрузчика программ.
Повторить функциональность stm32flash что ли? Только использование RTS/DTR для Reset/Boot0 не очень удобно. Когда утилита прошивки завершает работу, операционка может вернуть RTS/DTR в неудобное состояние. Например, оставить Reset на земле.
Впрочем, я такое тоже делал - для контроллеров WCH. Они через stm32flash программироваться не умеют, пришлось писать свою утилиту. И там тоже предусмотрел RTS/DTR.
Но удобнее всего оказалась самодельная платка на stm32f103 (по мотивам вот этой моей статьи), отображающаяся в системе как два COM-порта /dev/tty_STFLASH_0
и /dev/tty_DBG_0
. Через первый можно прошивать, а через второй - отлаживать. Причем если на STFLASH выставить скорость 50 бод, можно управлять тремя выводами: байт 'b' отвечает за линию boot0, 'r' - reset, 'u' - реле, разрывающее линии D+/D- USB (не пригодилось). Ну и 'z' возвращает все в дефолтное состояние. Такое нестандартное управление гарантирует, что никто посторонний этими линиями управлять не будет. Ну а разделение порта программирования и порта отладки - что не надо каждый раз перезапускать screen.
Использование выводов DTR, RTS и CTS от UART для своих делишек