#c# #arduino #virtual-serial-port
#c# #arduino #виртуальный последовательный порт
Вопрос:
Приготовьтесь к пространному объяснению — извините.
Я создаю приложение на C # с использованием Visual Studio 2019, которое будет взаимодействовать с некоторыми устройствами Arduino MEGA 2560 R3. Соединение осуществляется через последовательный USB (если я не ошибаюсь, это USB CDC, верно?).
Связь работает нормально при всех скоростях передачи данных, даже до 2000000.
Чтобы убедиться, что мой Arduino может «следовать» скорости команд, отправляемых моим компьютером, я использую ACK-протокол, который так же прост, как и мой Arduino, подтверждающий каждую команду последовательностью «A n».
В попытке провести стресс-тестирование связи я позволяю приложению отправлять команды «0123456789 n» как можно быстрее, ожидая «A n» после каждой отправленной команды. Это означает отправку 11 символов и получение 2 символов, в общей сложности 13 символов.
И вот тут начинается загадка.
Начиная с 38400 бод, цикл передачи / передачи данных, похоже, обрезается со скоростью 244 в секунду. Если я увеличу скорость передачи данных до 2000000, она останется на уровне 244 циклов передачи / передачи данных в секунду. Что здесь происходит!
Ниже приведен код ПК, который представляет собой простое консольное приложение, написанное в Visual Studio 2019:
using System;
using System.Diagnostics;
using System.IO.Ports;
namespace BasicComTest
{
class Program
{
public static SerialPort _serialPort;
static void Main(string[] args)
{
_serialPort = new SerialPort();
_serialPort.BaudRate = 115200;
_serialPort.PortName = "COM4";
_serialPort.Handshake = Handshake.None;
_serialPort.NewLine = "n";
_serialPort.ReadTimeout = 2000;
_serialPort.WriteTimeout = 2000;
_serialPort.Open();
_serialPort.WriteLine("");
Stopwatch sw = new Stopwatch();
sw.Start();
ulong count = 0;
while (!Console.KeyAvailable)
{
_serialPort.WriteLine($"0123456789");
string ack = _serialPort.ReadLine();
count ;
if (count == 1000)
{
sw.Stop();
Debug.WriteLine($"1000 commands in {sw.ElapsedMilliseconds} - {1000000/sw.ElapsedMilliseconds} commands/sec");
count = 0;
sw.Restart();
}
}
_serialPort.Close();
}
}
}
И ниже приведен простой эскиз Arduino (я использую процедуру ISR в попытке увеличить скорость):
volatile uint8_t rcvd = 0;
String sCommandRx;
String sCommand;
bool bCommandRx;
ISR(USART0_RX_vect)
{
// received data
rcvd = UDR0;
if (rcvd == 'n')
{
sCommandRx = sCommand;
bCommandRx = true;
sCommand = "";
}
else
sCommand = (char)rcvd;
}
void setup()
{
UBRR0H = 0;
UBRR0L = 0;
// USART initialization with 16MHz system clock, 8-none-1, RX interrupt
UCSR0A = 1<<U2X0 | 0<<MPCM0;
UCSR0B = 1<<RXCIE0 | 0<<TXCIE0 | 0<<UDRIE0 | 1<<RXEN0 | 1<<TXEN0 | 0<<UCSZ02;
UCSR0C = 0<<UMSEL01 | 0<<UMSEL00 | 0<<UPM01 | 0<<UPM00 | 0<<USBS0 | 1<<UCSZ01 | 1<<UCSZ00 | 0<<UCPOL0;
sCommand = "";
}
void loop()
{
if (bCommandRx)
{
if (sCommandRx == "0123456789")
{
UDR0 = 'A';
while((UCSR0A amp; 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;
UDR0 = 'n';
while((UCSR0A amp; 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;
}
bCommandRx = false;
}
}
I can already guarantee that the Arduino code works perfect, because I tested it with 2 Arduino’s back to back to rule out that they are causing the clipping. If you want to read the full story, you will find it on the Arduino forum here:
https://forum.arduino.cc/index.php?topic=719361.0
If I measure the communication speed with the 2 back-to-back Arduino’s, then it goes as fast as 6060 Tx/Rx sets per second at 2000000 baud (you have to count some overhead, which is measured at about 100 msec for 1000 commands — you can read the full report in the Arduino forum).
But using my console application, I get maximum 244 Tx/Rx sets per second. Except (and this makes it even weirder), if I build the «An» in the ISR routing in my Arduino, then I’m getting exactly the double of 488 Tx/Rx sets per second:
volatile uint8_t rcvd = 0;
ISR(USART0_RX_vect)
{
// received data
rcvd = UDR0;
// reply with "An" after receiving "n"
if(rcvd == 'n')
{
UDR0 = 'A';
while((UCSR0A amp; 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;
UDR0 = 'n';
while((UCSR0A amp; 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;
}
}
void setup()
{
// set baud rate (500 Kbps)
UBRR0H = 0;
UBRR0L = 3;
// USART initialization with 16MHz system clock, 8-none-1, RX interrupt
UCSR0A = 1<<U2X0 | 0<<MPCM0;
UCSR0B = 1<<RXCIE0 | 0<<TXCIE0 | 0<<UDRIE0 | 1<<RXEN0 | 1<<TXEN0 | 0<<UCSZ02;
UCSR0C = 0<<UMSEL01 | 0<<UMSEL00 | 0<<UPM01 | 0<<UPM00 | 0<<USBS0 | 1<<UCSZ01 | 1<<UCSZ00 | 0<<UCPOL0;
}
void loop(){
}
Но независимо от того, помещаю ли я передачу «A n» в цикл () или в ISR, результаты последовательного Arduino остаются неизменными.
Извините за это очень длинное и подробное описание. Но мой вопрос, очевидно, очень прост. В чем причина этого отсечения?
У меня есть теория. Хотя я прочитал много «плохих комментариев» о SerialPort, я думаю, что причина должна быть найдена в том факте, что это происходит через USB (Arduino работает с USB 2.0). Я объяснил теорию на форуме Arduino (я даже немного исправил свое объяснение ниже):
Если я не ошибаюсь, устройства USB 2.0 HID (и я предполагаю, что CDC такой же) имеют частоту опроса 1 мс. Я работал с некоторыми микроконтроллерами PIC и создал свой собственный USB-стек на своем ПК для связи с ним, и у меня был некоторый предел в 1000 команд / сек или 500 наборов Tx / Rx в секунду. Как вы можете прочитать выше, самые быстрые результаты, которые я мог получить с SerialPort, составляли 488 наборов Tx / Rx в секунду, что немного медленнее, но довольно близко. Конечно, может быть некоторое «отставание от преобразования», что приводит к некоторой рассинхронизации между serial и USB, что приводит к отсутствию некоторых кадров USB. Ввод ответа «A n» в цикл () (не в ISR) дал мне только половину (244) команд. Я думаю, что ввод ответа в ISR заставил его реагировать так быстро, что данные все равно могли «вернуться» в следующий кадр. Если я помещу ответ в цикл (), он пропустил поезд и взял несколько более поздних кадров 1 мсек. В любом случае, на данный момент это просто ДИКИЕ ДОГАДКИ, поэтому я, конечно, могу быть СОВЕРШЕННО неправ.
Обновить:
Просто попытался поработать с некоторой обработкой файлов более низкого уровня в моей программе на C #, используя Kernel32.CreateFile, Kernel32.WriteFile и Kernel32.ReadFile, но получил точно такие же результаты, что означает 244 набора Tx / Rx в секунду. Даже пытался поиграть с таймингами (COMMTIMEOUTS), но ничего не изменилось.
Я также пробовал отправлять и получать большие строки символов, и кажется, что если я использую до 72 ‘ n’ как для передачи, так и для передачи данных, она остается стабильной. Использование большего количества символов приводит к удалению наборов Tx / Rx в секунду, пока я не использую 76 ‘ n’ как для передачи, так и для передачи. Затем он снова остается стабильным при 162 наборах Tx / Rx в секунду.
Для моего приложения 244 набора Tx / Rx в секунду достаточно хороши, но все же мне любопытно понять, откуда это ограничение.
Комментарии:
1. Просто попробовал, добавив _serialPort.BaseStream. Flush () после отправки моей строки, но безуспешно. Все еще на уровне 244 наборов Tx / Rx в секунду. Что также странно, так это то, что если я отправлю 70 символов вместо 10 ( ‘n’), я получу точно такие же результаты. Так что действительно кажется, что где-то система ожидает заполнения буфера, а затем решает передать после определенного тайм-аута. Разве это кому-то не напоминает?
2. Вы могли бы попробовать а) отправлять команды большими (r) блоками с одним ack для нескольких команд или б) реализовать полный дуплекс (отправка команд последовательно в одном потоке при асинхронном чтении ack в другом
3. Исходное приложение использовало async / await (TAP), в котором у меня был поток отправки и поток получения. Конечно, предполагается, что мое приложение ожидает, пока не будет получено подтверждение, чтобы отправить другую команду, но я построил это полностью асинхронно, используя отдельные потоки. Я получаю точно такие же результаты — 244 набора Tx / Rx в секунду. В любом случае, сегодня я собираюсь попробовать использовать stream, избавившись от SerialPort. Просто как вопрос эксперимента.
4. Похоже, никто не может дать ответ? Или мое объяснение слишком длинное? 🙂
Ответ №1:
Это старая тема, но я все равно комментирую. Я столкнулся с той же проблемой. Вы видите, что сообщения разрываются, затем удерживаются, затем разрываются и т.д. Проблема была решена на стороне arduino для меня, но поскольку я использую не Mega, а Teensy (с Teensyduino, расширением для Arduino), это решение, возможно, недоступно для вас. В любом случае, SerialUSB.send_now() решил проблему для меня. Я понятия не имею, что эта функция делает на низком уровне, но, возможно, это может быть отправной точкой.