在C#中使用SerialPort通过USB(PC / Arduino)的通信速度似乎"clip"



准备一个冗长的解释-对不起。

我正在使用Visual Studio 2019在C#中构建一个应用程序,该应用程序将与一些Arduino MEGA 2560 R3设备进行通信。连接是通过USB串行(如果我没有错的话,这是USB CDC,对吧?)。

通信在各种各样的障碍物上运行良好,甚至高达2000000。

为了确保我的Arduino可以";跟随";由我的PC发送的命令速率,我使用ACK协议,这就像我的Arduino用序列"确认"每个命令一样简单;A";。

为了对通信进行压力测试,我让应用程序发送";0123456789";命令尽可能快;A";在每个命令发送之后。这意味着发送11个字符,接收2个字符,总共13个字符。

这就是神秘的开始

从38400波特开始,Tx/Rx周期似乎以每秒244的速度缩短。如果我将波特率增加到2000000,它将保持在每秒244个Tx/Rx周期这里发生了什么事

以下是PC代码,这是一个用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 & 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;

UDR0 = 'n';
while((UCSR0A & 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;
}
bCommandRx = false;
}
}

我已经可以保证Arduino代码完美工作,因为我用2个Arduino的背靠背测试了它,以排除它们导致剪辑的可能性。如果你想阅读完整的故事,你可以在Arduino论坛上找到:

https://forum.arduino.cc/index.php?topic=719361.0

如果我用2个背靠背的Arduino来测量通信速度,那么它以2000000波特的速度达到每秒6060个Tx/Rx集(你必须计算一些开销,1000个命令的开销约为100毫秒-你可以在Arduino论坛上阅读完整的报告)。

但使用我的控制台应用程序,我每秒最多可获得244个Tx/Rx集。除了(这让它变得更奇怪),如果我构建";A";在我的Arduino中的ISR路由中,那么我得到的正是每秒488个Tx/Rx集的两倍:

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 & 1<<TXC0)==0);
UCSR0A |= 1<<TXC0;

UDR0 = 'n';
while((UCSR0A & 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";在loop()或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集,这稍微慢一些,但非常接近。当然可能会有一些";转换滞后";,从而在串行和USB之间产生一些不同步,从而丢失一些USB帧。将";A";loop()中的答案(而不是ISR中的答案)只给了我一半(244)的命令。我认为,将答案放入ISR中使其反应如此之快,以至于数据仍然可以";背负式";在下一帧。如果我把答案放在loop()中,它就错过了火车,并占用了后来的1毫秒帧。不管怎样,这些只是暂时的猜测,所以我当然可能完全错了

更新:

我只是尝试在C#程序中使用Kernel32.CreateFile、Kernel32.WriteFile和Kernel32.ReadFile处理一些较低级别的文件,但得到了完全相同的结果,即每秒244个Tx/Rx集。甚至试着玩计时(COMMTIMEOUTS),但什么都没有改变。

我还尝试过发送和接收更大的字符串,似乎如果Tx和Rx部分都使用72+"\n",它会保持稳定。使用更多的字符开始每秒丢弃Tx/Rx集,直到我对Tx和Rx部分都使用76+'\n'。然后它再次保持稳定在每秒162个Tx/Rx集。

对于我的应用程序来说,每秒244个Tx/Rx集已经足够了,但我仍然很想了解这个限制是从哪里来的。

这是一个旧线程,但我还是要评论一下。我也遇到过同样的问题。你可以看到消息爆裂,然后保持,然后爆裂等。这个问题在arduino方面对我来说已经解决了,但由于我不使用Mega,而是使用Teensy(使用Teensyduino,arduino的扩展),这个解决方案可能不适用于你。无论如何,SerialUSB.send_now()为我解决了这个问题。我不知道这个函数在底层做什么,但它可能是一个起点。

最新更新