Сейчас распространено несколько вариантов недорогих микросхем преобразователей USB-UART, позволяющих организовать обмен данными между устройством и компьютером на основе древнего интерфейса под названием COM-порт (или RS-232).
Но в этой статье мы не будем углубляться именно в передачу данных через этот последовательный интерфейс. Мне было интересно узнать возможности дополнительных выводов, которые когда-то давно использовались для инициализации и синхронизации передачи данных. К этим выводам относятся цифровые выходы DTR и RTS и цифровые входы CTS, DSR, DCD и RI.
Для тестов я решил взять три наиболее популярных сейчас микросхемы USB-UART: CP2102, FT232 и CH340. Первые две микросхемы можно купить на алиэкспрессе на платах с нужными нам выводами. Только на модулях с FT232 очень часто стоит фейковая микросхема, которая постоянно зависает, поэтому лучше их вообще там не покупать.
![Стандартные модули USB-UART с дополнительными выводами Стандартные модули USB-UART с дополнительными выводами](https://habrastorage.org/getpro/habr/upload_files/6e3/af7/7a7/6e3af77a7156ed648311e3e25345982a.jpg)
А вот для микросхемы CH340 распространены только модули с основными выводами RX и TX, без дополнительных. Поэтому мне пришлось спаять самому такой модуль для тестов. Правда я купил микросхему CH340 с индексом «G» – в этом варианте в схеме должен быть кварц на 12МГц с конденсаторами. А вот для CH340C кварц не требуется, поэтому в будущем для своих поделок лучше брать только её.
Моя прекрасная пайка
![И это прекрасно работает И это прекрасно работает](https://habrastorage.org/getpro/habr/upload_files/312/3f1/4ca/3123f14ca13259be0accd07f09233b7e.jpg)
Как управлять дополнительными выводами UART
Примеры кода я буду показывать на C# из Visual Studio, но всё легко переносится в другие языки. Главное – найти нужные функции для управления COM-портом.
Для обращений к порту я использовал стандартный класс C# System.IO.Ports.SerialPort, который даёт всё необходимое, кроме функции считывания вывода RI. Мне этот вывод не нужен для тестов, но наверняка есть какой-то способ считать и этот вывод порта. Возможно, кто-то из экспертов напишет в комментариях, как это сделать.
Код для управления дополнительными выводами в C#
//Создаём объект для дальнейшего использования
public static SerialPort MyPort = new SerialPort();
//Далее просто список часто используемых методов и свойств (не рабочий код)
//Настройка порта:
string[] SerialPort.GetPortNames(); //Выдаёт список названий доступных COM-портов
MyPort.PortName = "COM1"; //Название нужного порта
MyPort.BaudRate = 115200; //Частота обмена данными
MyPort.Parity = Parity.None; //Чётность для проверки данных
MyPort.DataBits = 8; //Количество бит для передачи
MyPort.StopBits = StopBits.One; //Количество стоп-бит
MyPort.ReadTimeout = 200; //Максимальное время ожидания данных
MyPort.WriteTimeout = 1000; // Максимальное время отправки данных
//Инициализация порта
MyPort.Open(); //Открытие указанного порта
MyPort.Close(); //Закрытие активного порта
//Работа с данными
MyPort.Write(byte[] buffer, int offset, int count); //Запись байт в порт
MyPort.Read(byte[] buffer, int offset, int count); //Чтение байт из порта
MyPort.BytesToRead; //Количество доступных байт в приёмном буфере
MyPort.DiscardInBuffer(); //Очистка входного буфера
MyPort.DiscardOutBuffer(); //Очистка выходного буфера
//Управление дополнительными выводами:
MyPort.DtrEnable = true; //Управление выходом DTR
MyPort.RtsEnable = true; //Управление выходом RTS
bool cts_read = MyPort.CtsHolding; //Считывание входа CTS
bool dsr_read = MyPort.DsrHolding; //Считывание входа DSR
bool cd_read = MyPort.CDHolding; //Считывание входа DCD
При записи значения «true», на цифровых выходах DTR или RTS устанавливается логический 0 (нулевое значение напряжения). При записи «false», устанавливается логическая единица (3.3В или 5В, в зависимости от схемы питания микросхемы).
Аналогично при считывании цифровых входов CTS, DSR или DCD: если возвращается «true», значит, на входе логический 0. Если возвращается «false», значит, на вывод подано напряжение выше порога срабатывания.
При включении питания всех тестовых микросхем, на выходах DTR и RTS устанавливается высокий уровень напряжения. Это означает, что в них по умолчанию пишется «false». Однако в каких-то других микросхемах USB-UART инициализация может быть другая. Кроме того, время инициализации тоже может отличаться, поэтому сразу после включения питания какое-то время на выводах DTR и RTS могут быть нули, которые затем переключаются в высокий уровень.
Скорость работы цифровых выводов
К сожалению, дополнительные выводы UART работают очень медленно, если сравнивать их со скоростью передачи данных через основные выводы TX и RX.
Для оценки времени работы функций я создал программу с кнопкой на форме и прописал такой код на событие нажатия:
private void button1_Click(object sender, EventArgs e)
{
//Переключение пинов
for (int i = 0; i < 10; i++)
{
MyPort.DtrEnable = !MyPort.DtrEnable;
}
}
Этот код 10 раз инвертирует значение на выводе DTR. На осциллографе я получил следующую картину для микросхемы CP2102:
![](https://habrastorage.org/getpro/habr/upload_files/8bb/3fb/9e8/8bb3fb9e88c8ef0e9293372ec3f1b74c.png)
Как видно, длительность моментов переключения меняется, причём это происходит рандомно от запуска к запуску. Для выхода RTS картинка аналогичная, но средние значения времени работы функции вывода сильно отличаются для разных микросхем: для CP2102 оно составляет 12-20 мс, для FTDI 6-8 мс, а для CH340G – 2.5-3.5 мс.
Это значения, измеренные с помощью осциллографа. Но я также попробовал оценить время работы функции записи, используя системный таймер компьютера. Ниже код, измеряющий время работы для 1000 вызовов функции записи в порт DTR (аналогично было для RTS). Результаты записываются в текстовый файл для дальнейшего анализа.
Тестовый код для вывода в DTR
//Считывает время системного таймера (1 Tick = 100 ns)
Int64 GetTicks()
{
return DateTime.Now.Ticks;
}
//Обработка нажатия на кнопку запуска теста
private void button2_Click(object sender, EventArgs e)
{
int n = 1000;//Количество циклов опроса
string textDelta = "";//Текст для сохранения значений
for (int i = 0; i <= n; i++)
{
bool b = (i % 2 == 0);//Значение для записи в порт
Int64 start_time = GetTicks();//Запоминаем начальное время
MyPort.DtrEnable = b;//Запись в DTR
Int64 deltaTime = GetTicks() - start_time;//Вычисляем время работы
//Строим текстовую таблицу c микросекундами:
textDelta += ((double)deltaTime * 0.1).ToString("F01") + "\n";
}
//Сохраняем данные
File.WriteAllText(@"z:\WriteTimes.txt", textDelta);
}
Полученные таблицы со временем работы функций оказались немного странными. Поэтому я представил результаты некоторых измерений в виде гистограмм с 20 интервалами. Снизу шкала с миллисекундами (интервалы), а по оси Y – количество попаданий в соответствующий интервал.
Гистограммы для 1000 измерений
![Для микросхемы CP2102 Для микросхемы CP2102](https://habrastorage.org/getpro/habr/upload_files/6c9/774/54b/6c977454bc04c88fe17db809a1f294a7.png)
![Для микросхемы CH340 Для микросхемы CH340](https://habrastorage.org/getpro/habr/upload_files/c23/d9d/df2/c23d9ddf2477a74f9f4b46f4e8b5db18.png)
![Для микросхемы FT232 Для микросхемы FT232](https://habrastorage.org/getpro/habr/upload_files/091/453/cba/091453cbaa997541874feb708fbdbc75.png)
Как видно, затрачиваемое время в основном приближено к целым миллисекундам, хотя значения младших разрядов всегда рандомные. То есть, например, для CP2102 время в основном либо около 10 мс, либо 12 мс, либо 14 мс, а остальные интервалы более редкие.
Вероятно, причина таких разбросов в разной работе драйверов для этих микросхем в системе Windows. Наверняка есть привязка к программному таймеру Windows, который работает нестабильно. Возможно, где-то в драйвере используется функция Sleep(1) при ожидании ответа от микросхемы. Это объясняет тот факт, что при выполнении программы она не загружает процессор.
Не думаю, что время будет сильно меняться от компьютера к компьютеру, но вот в других операционных системах значения могут быть совсем другими. Также время может быть другим, если использовать какие-то низкоуровневые функции Windows для работы с портами.
Для оценки скорости считывания выводов я также использовал системный таймер. Ниже код для чтения входа CTS (для DSR и DCD будет аналогично).
Тестовый код для считывания CTS
private void button3_Click(object sender, EventArgs e)
{
int n = 10;//Количество циклов опроса
string textDelta = "";
string textValue = "";
for (int i = 0; i <= n; i++)
{
Int64 start_time = GetTicks();//Запоминаем начальное время
bool pinValue = MyPort.CtsHolding;//Считываем значение на входе
Int64 deltaTime = GetTicks() - start_time;//Вычисляем время работы
//Строим текстовую таблицу c микросекундами:
textDelta += ((double)deltaTime * 0.1).ToString("F01") + "\n";
//Строим таблицу с входными значениями:
if (pinValue)
{
textValue += "1\n";
}
else
{
textValue += "0\n";
}
}
//Сохраняем данные
File.WriteAllText(@"z:\ReadTimes.txt", textDelta);
File.WriteAllText(@"z:\Values.txt", textValue);
}
Тут всё аналогично в плане разброса: в основном время занимает миллисекунды, но иногда есть промежуточные значения. Однако в среднем для считывания требуется меньше времени. У меня получились такие средние значения: для CP2102 и CH340G оно составляет 2-3 мс, а для FTDI 0.001-1 мс (всех быстрее). И для других входов эти значения очень похожи.
Конечно, такая скорость работы очень плохая. Но для каких-то задач это может пригодиться.
Не забываем про безопасность
Ниже будут показаны разные обобщающие схемы для понимания основной идеи. Но при создании реального устройства всегда надо помнить о рисках работы с высокими напряжениями и токами. Если вы решите добавить в своё устройство дополнительный блок питания для включения какой-то нагрузки, то стоит также добавить гальваническую развязку, чтобы случайно не сжечь USB порт компьютера или целиком компьютер. Например, можно использовать оптроны, твёрдотельные реле или маломощные механические реле.
Также помните о том, что по умолчанию USB порт ограничивает ток потребления до 0.1А и превышение этого порога может вызвать отключение порта.
И ещё важная деталь: готовые модули USB-UART могут быть настроены как на работу с логикой 5В, так и 3.3В. В дальнейших примерах я везде использовал вариант 5В, но схемы должны работать и с логикой 3.3В.
Мигаем светодиодами
Маломощные светодиоды можно подключить прямо к выводам DTR и RTS, а при необходимости включить что-то помощнее, можно использовать транзисторы.
![Схема подключения светодиодов Схема подключения светодиодов](https://habrastorage.org/getpro/habr/upload_files/89e/b58/d0f/89eb58d0f08c55d22162fc0bf1ac2e22.png)
Думаю, тут не нужно объяснять, как включать и отключать светодиоды. Из предыдущих примеров должно быть всё понятно.
Индикаторы в виде светодиодов могут пригодиться для отображения статусов каких-то длительных процессов на компьютере. На самом деле, это очень удобно: можно отдельно запустить процесс, наблюдающий за другим процессом и включающий светодиоды в соответствии с режимами работы. Можно даже делать такую экзотическую отладку программ: оценивать работу своей программы по светодиодам гораздо интереснее, чем через отладочную консоль.
А если нужно сделать что-то типа светофора, то можно добавить микросхему двоичного дешифратора К561ИД1 (аналог CD4028B). Такая схема даст возможность отображать статус в виде свечения одного из 4 разных светодиодов (например, синий, зелёный, жёлтый, красный).
Схема с дешифратором
![](https://habrastorage.org/getpro/habr/upload_files/223/307/f71/223307f71d6e045558ccae23b0c4e3f2.png)
Правда из-за инерционности выводов DTR и RTS при их переключениях иногда будут помигивать не те светодиоды.
Вывод TX можно дополнить генератором моноимпульса на микросхеме NE555, чтобы мигать ещё одним светодиодом. То есть, можно отправлять в порт нулевой байт, чтобы зажечь светодиод на некоторое время. Так можно показывать, что регулярно выполняются какие-то действия.
Вот функция, которая выводит на TX нулевой байт, работающий как синхроимпульс:
//Выводит импульс на выход TX
void WriteTxClock()
{
MyPort.Write(new byte[1] { 0 }, 0, 1);
}
Схема генератора моноимпульса на 555
![На мой взгляд, конденсатор на 0.22 мкФ даёт оптимальную длительность На мой взгляд, конденсатор на 0.22 мкФ даёт оптимальную длительность](https://habrastorage.org/getpro/habr/upload_files/74a/602/a15/74a602a1521abdef76e918fdd38c0e38.png)
Считываем кнопки
Раз у нас есть цифровые входы CTS, DSR и DCD, то к ним можно подключить кнопки. К ним надо добавить подтягивающие резисторы и можно ещё конденсаторы для сглаживания дребезга контактов.
Схема подключения кнопок
![Конденсаторы не обязательны Конденсаторы не обязательны](https://habrastorage.org/getpro/habr/upload_files/390/18d/e42/39018de427ec9861708486392ced536d.png)
В данной схеме кнопки замыкают вывод на землю, поэтому в программе при нажатии кнопки функция считывания будет возвращать значение «true».
Для считывания кнопок в программу C# можно добавить компонент таймера System.Windows.Forms.Timer, задать ему время срабатывания 1-10 мс, а в обработке события срабатывания таймера можно считывать значения выводов CTS, DSR и DCD. Анализируя считанные значения, можно выполнять какие-то действия, например, запускать какие-то программы. Так можно сделать примитивные кнопки быстрого запуска программ.
Помимо обычных кнопок можно использовать кнопку-педаль, лазерный барьер или выход с термореле. Можно даже подключить цифровые входы к выходу какого-то генератора, чтобы считывать рандомные биты, если вам нужен простейший генератор произвольных чисел. Идей можно придумать много.
Делаем больше выходов
Если вам мало цифровых выходов, то можно добавить микросхемы последовательно-параллельных регистров 74HC595. На вход данных такого регистра можно назначить вывод DTR, на «защёлку» данных (вход LD у регистра) – вывод RTS, а в качестве импульсов синхронизации можно использовать нулевые байты данных из последовательного порта, то есть, с вывода TX. Только нужно инвертировать этот сигнал, например, с помощью транзистора.
Схема подключения регистра 74HC595
![Кстати, я использовал "цифровой транзистор", у которого резисторы уже внутри Кстати, я использовал "цифровой транзистор", у которого резисторы уже внутри](https://habrastorage.org/getpro/habr/upload_files/39a/39e/768/39a39e76822f2a566e19029f9bad5e76.png)
Причём можно последовательно подключить много регистров 74HC595. Каждая микросхема даст 8 цифровых выходов.
Правда скорость вывода данных в эти регистры будет небольшой. Если вызывать функцию записи в DTR только при смене значения бита, то быстрее всего будут выводиться байты 0x00 и 0xFF, а всех дольше – 0x55 и 0xAA (0b01010101 и 0b10101010).
Код для записи байта в регистр 74HC595
void WriteTxClock()
{
MyPort.Write(new byte[1] { 0 }, 0, 1);
}
void WriteDtr(bool data)
{
MyPort.DtrEnable = !data;
}
void WriteRts(bool data)
{
MyPort.RtsEnable = !data;
}
bool[] ConvertByteToBoolArray(byte data)
{
bool[] result = new bool[8];
for (int i = 0; i < 8; i++)
{
if ((data & (1 << i)) != 0)
{
result[i] = true;
} else
{
result[i] = false;
}
}
return result;
}
//Вывод байта в регистр 74HC595
void WriteToSerialReg(byte data)
{
bool[] bits = ConvertByteToBoolArray(data);
bool data_bit = false;
for (int i = 0; i < 8; i++)
{
if ((i == 0) | (data_bit != bits[i]))
{
WriteDtr(bits[i]);
}
data_bit = bits[i];
WriteTxClock();
}
//Load clock
WriteRts(true);
WriteRts(false);
}
Как и раньше, используя системный таймер компьютера, я оценил время работы функций. Для микросхемы CP2102 я получил среднее время записи байта (в зависимости от его значения) от 39 мс до 130 мс, для FT232 – от 20 мс до 69 мс, а для микросхемы CH340 – от 8 мс до 30 мс.
Делаем больше входов
Есть и другой тип регистров: параллельно-последовательный регистр – микросхема 74HC165. У него 8 входов, значения которых можно считывать последовательно. Такие регистры можно использовать для увеличения количества входов.
Мы можем назначить вывод RTS на «защёлку» данных, а для синхронизации также использовать вывод TX (это будет быстрее, чем формировать импульсы через DTR). Считывать данные можно любым входом, например, через CTS. Только нужно иметь ввиду, что у регистра 74HC165 вход LD («защёлка» данных) с инверсией: «защёлкивание» данных будет происходить при переходе RTS из 1 в 0.
Схема подключения регистра 74HC165
![](https://habrastorage.org/getpro/habr/upload_files/eab/b6c/9b6/eabb6c9b66470a0d4d1b2eab34b005a0.png)
Код для считывания байта из 74HC165
bool ReadCts()
{
return !MyPort.CtsHolding;
}
byte BoolArrayToByte(bool[] bits)
{
byte result = 0;
for (int i = 0; i < 8; i++)
{
if (bits[i])
{
result |= (byte)(1 << i);
}
}
return result;
}
byte ReadSerialReg()
{
bool[] bits = new bool[8];
//Load clock
WriteRts(false);
WriteRts(true);
for (int i = 0; i < 8; i++)
{
bits[i] = ReadCts();
WriteTxClock();
}
return BoolArrayToByte(bits);
}
Скорость считывания данных из регистра будет выше, чем при выводе данных, потому что, как мы выяснили ранее, чтение CTS занимает меньше времени. Для микросхемы CP2102 я получил среднее время считывания байта 45 мс, для FT232 – 14 мс, а для CH340 – 24 мс.
Аналогично, можно подключить сразу несколько микросхем 74HC165. Но раз у нас есть несколько свободных цифровых входов, то для увеличения количества регистров их можно ставить не последовательно, а параллельно, чтобы сэкономить время на количестве синхроимпульсов (хотя это будет очень небольшая экономия времени).
Больше входов и выходов
Если нужно одновременно расширить входы и выходы, то можно использовать оба типа регистров. Например, можно сделать 8 входов и 8 выходов, используя одновременно 74HC165 и 74HC595.
Схема подключения 74HC165 и 74HC595
![Второй транзистор нужен для инвертирования сигнала LD для 74HC165 Второй транзистор нужен для инвертирования сигнала LD для 74HC165](https://habrastorage.org/getpro/habr/upload_files/492/def/b0e/492defb0e33988e296e577a9400aeeeb.png)
Правда тут для считывания входов из 74HC165 нужно обязательно сначала загружать данные в 74HC595. То есть, нельзя считать входы без обновления выходов, поэтому в любом случае нужно будет тратить двойное время.
Код работы с 74HC595 и 74HC165
byte WriteAndReadSerialReg(byte data)
{
WriteToSerialReg(data);
bool[] bits = new bool[8];
for (int i = 0; i < 8; i++)
{
bits[i] = ReadCts();
WriteTxClock();
}
return BoolArrayToByte(bits);
}
Некоторые функции были показаны выше
Для микросхемы CP2102 я получил среднее время записи и чтения от 60 мс до 152 мс (в зависимости от выводимого байта), для FT232 – от 20 мс до 69 мс (тут считывание очень быстрое), а для CH340 – от 33 мс до 59 мс.
Как это выглядело в реальности
![](https://habrastorage.org/getpro/habr/upload_files/14f/4b1/18e/14f4b118e545ac67e38f87c3a06f2a39.jpg)
Делаем интерфейс I2C
Существует достаточно много устройств, которыми можно управлять через интерфейс I2C. Обычно такие устройства не требовательны к скорости обмена данными. Поэтому, имея два цифровых выхода, можно легко организовать такой интерфейс, просто добавив к выходам по транзистору с подтягивающими резисторами. При необходимости считывать данные, можно задействовать вход CTS.
Схема организации интерфейса I2C
![](https://habrastorage.org/getpro/habr/upload_files/d35/c7f/0d0/d35c7f0d03748e590669ff3ec2e91c2b.png)
Чтобы проверить работу нашего супер тормозного медленного I2C, я решил подключить к нему модуль семисегментного индикатора на 6 разрядов с драйвером TM1637.
![Светодиодный индикатор с драйвером TM1637 Светодиодный индикатор с драйвером TM1637](https://habrastorage.org/getpro/habr/upload_files/276/f00/593/276f005932ee09f639a6312f70c43b92.jpg)
Такие дисплеи продаются на алиэкспрессе, но часто там на линиях SDA и SCL стоят слишком большие конденсаторы, сглаживающие сигналы. Так что если дисплей не работает, найдите и удалите эти конденсаторы. Про эту неисправность я рассказывал в своём видео.
Для вывода данных на этот индикатор, нам нужно создать низкоуровневые функции протокола I2C, а также функцию установки яркости и вывода информации в дисплей.
Код для вывода на индикатор с драйвером TM1637
void I2C_SetClockLow()
{
MyPort.RtsEnable = false;
}
void I2C_SetClockHigh()
{
MyPort.RtsEnable = true;
}
void I2C_SetDataLow()
{
MyPort.DtrEnable = false;
}
void I2C_SetDataHigh()
{
MyPort.DtrEnable = true;
}
void I2C_Start()
{
I2C_SetClockHigh();
I2C_SetDataHigh();
I2C_SetDataLow();
}
void I2C_Stop()
{
I2C_SetClockLow();
I2C_SetDataLow();
I2C_SetClockHigh();
I2C_SetDataHigh();
}
bool I2C_ReadAck()
{
I2C_SetClockLow();
I2C_SetDataHigh();
bool ack = ReadCts();
I2C_SetClockHigh();
I2C_SetClockLow();
return ack;
}
void I2C_WriteByte(byte data)
{
bool[] bits = ConvertByteToBoolArray(data);
bool data_bit = false;
for (int i = 0; i < 8; i++)
{
I2C_SetClockLow();
if ((i == 0) | (data_bit != bits[i]))
{
if (bits[i])
{
I2C_SetDataHigh();
}
else
{
I2C_SetDataLow();
}
}
data_bit = bits[i];
I2C_SetClockHigh();
}
}
//Convert number to 7-segment code
byte NumberToSegments(int n)
{
if (n == 0) return 0x3F;//0
if (n == 1) return 0x06;//1
if (n == 2) return 0x5B;//2
if (n == 3) return 0x4F;//3
if (n == 4) return 0x66;//4
if (n == 5) return 0x6D;//5
if (n == 6) return 0x7D;//6
if (n == 7) return 0x07;//7
if (n == 8) return 0x7F;//8
if (n == 9) return 0x6F;//9
if (n == 10) return 0x77;//A
if (n == 11) return 0x7C;//B
if (n == 12) return 0x39;//C
if (n == 13) return 0x5E;//D
if (n == 14) return 0x79;//E
if (n == 15) return 0x71;//F
if (n == 16) return 0x40;//-
if (n == 17) return 0x77;//A
if (n == 18) return 0x3D;//G
if (n == 19) return 0x76;//H
if (n == 20) return 0x3C;//J
if (n == 21) return 0x73;//P
if (n == 22) return 0x38;//L
if (n == 23) return 0x6D;//S
if (n == 24) return 0x3E;//U
if (n == 25) return 0x6E;//Y
return 0x00;
}
//Send segments data into display
//d0 - low, d5 - high
void DisplayUpdate(byte d0, byte d1, byte d2, byte d3, byte d4, byte d5)
{
I2C_Start();
I2C_WriteByte(0x40);//Memory write command
I2C_ReadAck();
I2C_Stop();
I2C_Start();
I2C_WriteByte(0xc0);//Start address
I2C_ReadAck();
I2C_WriteByte(d3);
I2C_ReadAck();
I2C_WriteByte(d4);
I2C_ReadAck();
I2C_WriteByte(d5);
I2C_ReadAck();
I2C_WriteByte(d0);
I2C_ReadAck();
I2C_WriteByte(d1);
I2C_ReadAck();
I2C_WriteByte(d2);
I2C_ReadAck();
I2C_Stop();
}
// Brightness values: 0 - 8
void SetBrightness(byte brightness)
{
I2C_Start();
brightness += 0x87;
I2C_WriteByte(brightness);
I2C_ReadAck();
I2C_Stop();
}
//Send number into display
void DisplaySendNumber(int num)
{
byte dg0, dg1, dg2, dg3, dg4, dg5;
dg0 = NumberToSegments((byte)(num / 100000));
num = num % 100000;
dg1 = NumberToSegments((byte)(num / 10000));
num = num % 10000;
dg2 = NumberToSegments((byte)(num / 1000));
num = num % 1000;
dg3 = NumberToSegments((byte)(num / 100));
num = num % 100;
dg4 = NumberToSegments((byte)(num / 10));
num = num % 10;
dg5 = NumberToSegments((byte)num);
DisplayUpdate(dg5, dg4, dg3, dg2, dg1, dg0);
}
Всё прекрасно заработало, если не считать большую задержку обновления дисплея. Вызов функции SetBrightness для CP2102 занимает в среднем 348 мс, для FT232 – 193 мс, а для CH340 – 91 мс.
Функция DisplayUpdate для CP2102 требует от 2.1 с (когда все байты 0xFF или 0x00) до 2.6 с (когда все байты 0xAA). Для FT232 аналогичные задачи требуют от 1.2 с до 1.5 с, а для CH340 – от 545 мс до 725 мс.
Напомню, что это время нестабильное и отдельные вызовы могут занимать на 20-30% больше или меньше времени от указанного. Впрочем, с другими микросхемами или драйверами это время может быть кардинально другим.
Фото рабочей схемы
![При обновлении цифры заменяются последовательно и это заметно При обновлении цифры заменяются последовательно и это заметно](https://habrastorage.org/getpro/habr/upload_files/282/fb2/5f1/282fb25f15a1335a692f4672d3501b4f.jpg)
Обобщение
Как видим, дополнительные выводы преобразователя USB-UART вполне могут быть полезными для каких-то задач, хотя и с большими ограничениями. Уверен, можно значительно расширить функциональность этих устройств, если добавить больше логических микросхем, но в современном мире будет проще приделать к порту какой-то микроконтроллер. Иногда будет даже проще подключиться напрямую к USB, если микроконтроллер имеет такой интерфейс.
Кстати, если выбрать порт COM1, то скорость работы функций значительно выше. Но я не проверял этот порт с реальной схемой, так как он запрятан где-то внутри компьютера.
Весь упомянутый код для тестов схем можно скачать в архиве с проектом Visual Studio с моего сайта.
Пишите в комментариях, какие вы придумали идеи по использованию этих дополнительных выводов USB-UART. Может быть, мы вместе изобретём какой-то новенький «велосипед»?
Зачем это всё нужно было мне?
На самом деле, при изучении данной темы у меня была конкретная цель: я хотел сделать простой программатор для STM32 на базе преобразователя USB-UART, в котором выводы DTR и RTS используются для автоматического сброса микроконтроллера и активации встроенного заводского загрузчика программ. Мне нужно было понять, какие есть ограничения при работе с данными выходами. А раз я собрал информацию, то заодно решил написать про это статью.
Кстати, в какой-то степени я реализовал такой программатор: он называется NyamFlashLoader. Некоторые микроконтроллеры (в основном старые модели) он программирует. При этом я хотел сделать универсальный программатор, но, как позже выяснилось, у разных микроконтроллеров активация встроенного загрузчика происходит по-разному, а я реализовал лишь один вариант. Поэтому, на данный момент, этот проект приостановлен.
Всем спасибо за внимание!
Комментарии (12)
nikolz
11.09.2024 13:05+2А вот для микросхемы CH340 распространены только модули с основными выводами RX и TX, без дополнительных
вот здесь есть DTR
на этом есть RTS,CTS
на этом есть RTS,CTS,DTR
nanoslavic Автор
11.09.2024 13:05Спасибо. Я эти модули видел, но как-то не рассмотрел на них дополнительные выводы.
nanoslavic Автор
11.09.2024 13:05Спасибо. Я эти модули видел, но как-то не рассмотрел на них дополнительные выводы.
Jury_78
11.09.2024 13:05+1Вроде в мс FTDI есть режим Bit-banging, не проще ли через это действать?
nanoslavic Автор
11.09.2024 13:05Не уверен, что проще, вроде бы это делается через SDK от FTDI, а не через функции COM-порта. То есть, надо будет что-то ставить и изучать другие функции. Да и решение будет не универсальным. Хотя, конечно, так было бы больше возможностей. Но так то и дешёвый микроконтроллер поставить на UART не так уж сложно:)
VirtualVoid
11.09.2024 13:05+2Оригинальную FTDI, где SN не A50285BI, ещё нужно постараться найти.
Для ногодрыга бы пожалуй подошла CH341, которая известна всем по одноимённому программатору.
Astroscope
11.09.2024 13:05+2А вы знали про возможности этих дополнительных выводов USB-UART?
Да, использую их вместо отсутствующих в подавляющем большинстве компьютеров GPIO. Мой основной кейс - управление любительской радиостанцией, переключение с приема на передачу, чтение статуса шумоподавителя и тому подобные нехитрые действия, которые хорошо поддерживаются соответствующим софтом и работают по сути везде, где есть USB порты. Да, более продвинутые радиостанции принимают расширенные команды по UART (COM) и отвечают на них - позволяют устанавливать практически все параметры и отчитываться об их статусе, начиная с очевидной установки частоты и вида модуляции, но и с ними переключение прием/передача лучше осуществлять примитивным, быстрым и надежным сигналом на RTS или DTR, а не ждать, пока управляющий софт сформирует и отправит команду, а радиостанция ее распарсит и выполнит - просто сменить уровень быстрее и помехозащищеннее. В результате все это делается на одном USB-UART, проще не придумаешь.
Кому такого импровизированного GPIO мало, подумайте об USB-LPT адаптере. :)
nanoslavic Автор
11.09.2024 13:05Ничего не понятно, но выглядит круто:) Пока читал ваш комментарий, пришла идея: можно через DTR и RTS управлять электронными ключами (типа DG408 или 74HC4066), чтобы "нажимать" отдельные кнопки на плате от клавиатуры или других устройств. То есть, получается программное нажатие, но распознаваться будет как реальное от пользователя.
Flammmable
11.09.2024 13:05CH340 бывают очень разные. У CH340N, например, всего 8 выводов, 6 из которых это Vcc, GND, Tx, Rx, D+, D-.
nanoslavic Автор
11.09.2024 13:05Да, эта миниатюрная версия мне очень нравится, она прям создана для каких-то портативных устройств.
LAutour
Модули с фейковыми CH340 тоже бывают. Под маркировкой CH340G абсолюно левая по прозвонке выводов микра и бутафрский кварц, никуда не идущий. Работают подделки нормально (система видит как CH340), вот только кроме RX, TX больше ничего нет.
nanoslavic Автор
Да, возможно, и на CP2102 есть подделки. Китайцы очень умелые в этом деле:)