Сейчас распространено несколько вариантов недорогих микросхем преобразователей USB-UART, позволяющих организовать обмен данными между устройством и компьютером на основе древнего интерфейса под названием COM-порт (или RS-232).
Но в этой статье мы не будем углубляться именно в передачу данных через этот последовательный интерфейс. Мне было интересно узнать возможности дополнительных выводов, которые когда-то давно использовались для инициализации и синхронизации передачи данных. К этим выводам относятся цифровые выходы DTR и RTS и цифровые входы CTS, DSR, DCD и RI.
Для тестов я решил взять три наиболее популярных сейчас микросхемы USB-UART: CP2102, FT232 и CH340. Первые две микросхемы можно купить на алиэкспрессе на платах с нужными нам выводами. Только на модулях с FT232 очень часто стоит фейковая микросхема, которая постоянно зависает, поэтому лучше их вообще там не покупать.

А вот для микросхемы CH340 распространены только модули с основными выводами RX и TX, без дополнительных. Поэтому мне пришлось спаять самому такой модуль для тестов. Правда я купил микросхему CH340 с индексом «G» – в этом варианте в схеме должен быть кварц на 12МГц с конденсаторами. А вот для CH340C кварц не требуется, поэтому в будущем для своих поделок лучше брать только её.
Моя пайка под спойлером (названия спойлеров слетают, поддержка исправляет глюк, поэтому я пока просто добавлю текст рядом)
Скрытый текст

Как управлять дополнительными выводами 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:

Как видно, длительность моментов переключения меняется, причём это происходит рандомно от запуска к запуску. Для выхода 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);
}
Update: В комментариях подсказали, что вместо DateTime.Now.Ticks лучше использовать класс Stopwatch для повышения точности. Но с ним результаты примерно те же, так как функции обращения к выводам COM-порта реально медленные.
Полученные таблицы со временем работы функций оказались немного странными. Поэтому я представил результаты некоторых измерений в виде гистограмм с 20 интервалами. Снизу шкала с миллисекундами (интервалы), а по оси Y – количество попаданий в соответствующий интервал.
Гистограммы для 1000 измерений
Скрытый текст



Как видно, затрачиваемое время в основном приближено к целым миллисекундам, хотя значения младших разрядов всегда рандомные. То есть, например, для 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, а при необходимости включить что-то помощнее, можно использовать транзисторы.

Думаю, тут не нужно объяснять, как включать и отключать светодиоды. Из предыдущих примеров должно быть всё понятно.
Индикаторы в виде светодиодов могут пригодиться для отображения статусов каких-то длительных процессов на компьютере. На самом деле, это очень удобно: можно отдельно запустить процесс, наблюдающий за другим процессом и включающий светодиоды в соответствии с режимами работы. Можно даже делать такую экзотическую отладку программ: оценивать работу своей программы по светодиодам гораздо интереснее, чем через отладочную консоль.
А если нужно сделать что-то типа светофора, то можно добавить микросхему двоичного дешифратора К561ИД1 (аналог CD4028B). Такая схема даст возможность отображать статус в виде свечения одного из 4 разных светодиодов (например, синий, зелёный, жёлтый, красный).
Схема с дешифратором
Скрытый текст

Правда из-за инерционности выводов DTR и RTS при их переключениях иногда будут помигивать не те светодиоды.
Вывод TX можно дополнить генератором моноимпульса на микросхеме NE555, чтобы мигать ещё одним светодиодом. То есть, можно отправлять в порт нулевой байт, чтобы зажечь светодиод на некоторое время. Так можно показывать, что регулярно выполняются какие-то действия.
Вот функция, которая выводит на TX нулевой байт, работающий как синхроимпульс:
//Выводит импульс на выход TX
void WriteTxClock()
{
MyPort.Write(new byte[1] { 0 }, 0, 1);
}
Схема генератора моноимпульса на 555
Скрытый текст

Считываем кнопки
Раз у нас есть цифровые входы CTS, DSR и DCD, то к ним можно подключить кнопки. К ним надо добавить подтягивающие резисторы и можно ещё конденсаторы для сглаживания дребезга контактов.
Схема подключения кнопок
Скрытый текст

В данной схеме кнопки замыкают вывод на землю, поэтому в программе при нажатии кнопки функция считывания будет возвращать значение «true».
Для считывания кнопок в программу C# можно добавить компонент таймера System.Windows.Forms.Timer, задать ему время срабатывания 1-10 мс, а в обработке события срабатывания таймера можно считывать значения выводов CTS, DSR и DCD. Анализируя считанные значения, можно выполнять какие-то действия, например, запускать какие-то программы. Так можно сделать примитивные кнопки быстрого запуска программ.
Помимо обычных кнопок можно использовать кнопку-педаль, лазерный барьер или выход с термореле. Можно даже подключить цифровые входы к выходу какого-то генератора, чтобы считывать рандомные биты, если вам нужен простейший генератор произвольных чисел. Идей можно придумать много.
Делаем больше выходов
Если вам мало цифровых выходов, то можно добавить микросхемы последовательно-параллельных регистров 74HC595. На вход данных такого регистра можно назначить вывод DTR, на «защёлку» данных (вход LD у регистра) – вывод RTS, а в качестве импульсов синхронизации можно использовать нулевые байты данных из последовательного порта, то есть, с вывода TX. Только нужно инвертировать этот сигнал, например, с помощью транзистора.
Схема подключения регистра 74HC595
Скрытый текст

Причём можно последовательно подключить много регистров 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
Скрытый текст

Код для считывания байта из 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
Скрытый текст

Правда тут для считывания входов из 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 мс.
Как это выглядело в реальности
Скрытый текст

Делаем интерфейс I2C
Существует достаточно много устройств, которыми можно управлять через интерфейс I2C. Обычно такие устройства не требовательны к скорости обмена данными. Поэтому, имея два цифровых выхода, можно легко организовать такой интерфейс, просто добавив к выходам по транзистору с подтягивающими резисторами. При необходимости считывать данные, можно задействовать вход CTS.
Схема организации интерфейса I2C
Скрытый текст

Чтобы проверить работу нашего супер тормозного медленного I2C, я решил подключить к нему модуль семисегментного индикатора на 6 разрядов с драйвером TM1637.

Такие дисплеи продаются на алиэкспрессе, но часто там на линиях 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% больше или меньше времени от указанного. Впрочем, с другими микросхемами или драйверами это время может быть кардинально другим.
Фото рабочей схемы
Скрытый текст

Обобщение
Как видим, дополнительные выводы преобразователя USB-UART вполне могут быть полезными для каких-то задач, хотя и с большими ограничениями. Уверен, можно значительно расширить функциональность этих устройств, если добавить больше логических микросхем, но в современном мире будет проще приделать к порту какой-то микроконтроллер. Иногда будет даже проще подключиться напрямую к USB, если микроконтроллер имеет такой интерфейс.
Кстати, если выбрать порт COM1, то скорость работы функций значительно выше. Но я не проверял этот порт с реальной схемой, так как он запрятан где-то внутри компьютера.
Весь упомянутый код для тестов схем можно скачать в архиве с проектом Visual Studio с моего сайта.
Пишите в комментариях, какие вы придумали идеи по использованию этих дополнительных выводов USB-UART. Может быть, мы вместе изобретём какой-то новенький «велосипед»?
Update: Подписчик в Телеграм прислал свой экспериментальный код на Си для подключения LCD1602 через дополнительные выводы COM-порта: ссылка на GitHub.
Зачем это всё нужно было мне?
На самом деле, при изучении данной темы у меня была конкретная цель: я хотел сделать простой программатор для STM32 на базе преобразователя USB-UART, в котором выводы DTR и RTS используются для автоматического сброса микроконтроллера и активации встроенного заводского загрузчика программ. Мне нужно было понять, какие есть ограничения при работе с данными выходами. А раз я собрал информацию, то заодно решил написать про это статью.
Кстати, в какой-то степени я реализовал такой программатор: он называется NyamFlashLoader. Некоторые микроконтроллеры (в основном старые модели) он программирует. При этом я хотел сделать универсальный программатор, но, как позже выяснилось, у разных микроконтроллеров активация встроенного загрузчика происходит по-разному, а я реализовал лишь один вариант. Поэтому, на данный момент, этот проект приостановлен.
Всем спасибо за внимание!