Reputation: 450
I'm having trouble understanding why my application takes so long to communicate with a device over a serial port on Windows 10. I've written two small test applications to try to see what makes it so slow. Here's the code for both of them:
''VB.NET code
Imports System.IO.Ports
Module Module1
Sub Main()
Dim port As New SerialPort("COM3", 921600, Parity.None, 8, 1)
port.Open()
port.DtrEnable = True
port.RtsEnable = True
Dim profiler As New Stopwatch
profiler.Start()
For i As Integer = 1 To 100
port.Write("1PA?" & vbCrLf)
port.ReadLine()
port.Write("TB" & vbCrLf)
port.ReadLine()
Next
profiler.Stop()
Console.WriteLine("Average: " & profiler.ElapsedMilliseconds / 100 & "ms")
Console.ReadKey()
End Sub
End Module
And:
//C++ code
#include <iostream>
#include <string>
#include "boost/asio/io_service.hpp"
#include "boost/asio/serial_port.hpp"
#include "boost/asio/read_until.hpp"
#include "boost/asio/write.hpp"
#include "boost/asio/streambuf.hpp"
#include "boost/asio/buffer.hpp"
#include "boost/thread.hpp"
#include "boost/ref.hpp"
#include "boost/lexical_cast.hpp"
using boost::asio::io_service;
using boost::asio::serial_port;
using boost::asio::streambuf;
size_t read_until(serial_port& port, streambuf& buf, const std::string& delim)
{
return boost::asio::read_until(port, buf, delim);
}
void complete(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error)
std::cout << "Error\n";
}
int main()
{
std::cout << "Starting...\n";
io_service io;
serial_port port(io, "COM3");
streambuf buf(1000);
boost::posix_time::ptime t0 = boost::posix_time::microsec_clock::local_time();
port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
port.set_option(boost::asio::serial_port_base::parity());
port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port::flow_control::hardware));
port.set_option(boost::asio::serial_port_base::baud_rate(921600));
port.set_option(boost::asio::serial_port_base::character_size(8));
for (int i = 0; i < 100; ++i)
{
boost::asio::write(port, boost::asio::buffer("1PA?\r\n", 6));
read_until(port, buf, "\r\n");
buf.consume(buf.size());
boost::asio::write(port, boost::asio::buffer("TB\r\n", 4));
read_until(port, buf, "\r\n");
buf.consume(buf.size());
}
boost::posix_time::ptime tE = boost::posix_time::microsec_clock::local_time();
std::cout << (tE-t0).total_milliseconds() << '\n';
std::cin.get();
}
The problem is that the VB.NET code reports an average of ~6ms per loop iteration (i.e. 3ms per write/read pair), while the C++ code takes over 60ms per iteration.
The rest of the project is written in C++, so I need to improve that code and can't simply use the other one. Currently, the fastest way I found is to communicate over TCP/IP with a VB.NET application that routes TCP/IP to a serial port. Strangely, this is more than twice as fast as the direct C++ implementation despite the additional steps involved.
Is there anything I'm missing, perhaps a setting in the C++ implementation? I've already tried all flow-control options, different buffer sizes, ...
Upvotes: 4
Views: 1149
Reputation: 283733
The 60 ms you are seeing for a write/read/write/read sequence corresponds pretty well to typical defaults for scheduled transfers. Here's the port configuration for an FTDI (very popular chipset for USB serial ports)
If the serial port initialization code doesn't explicitly set timeouts, what you get is the scheduled transfers every 16ms.
If you instead call SetCommTimeouts
, you can arrange for the USB device to forward the received data buffer every time there's a gap on the serial RX wire. To do this, set the ReadIntervalTimeout
to the transfer time for just a couple bytes. At 921 kbaud, each byte takes 10-11 microseconds, so the lowest possible timeout of 1 millisecond for ReadIntervalTimeout
corresponds to a gap of about 92 bytes.
However, due to the different quality-of-implementation of drivers that come with various USB devices, it's possible to run into devices that don't have hardware support for inter-character timeout. In such cases, it is probably best to disable ReadIntervalTimeout
and ReadTotalTimeoutMultiplier
, and just use ReadTotalTimeoutConstant
.
Using either of these timeout configurations, the data transfer across USB will occur more timely and your code will make progress.
However, the USB latency will still be on the order of 3 milliseconds per receive (1 while the RX line is busy, 1 ms idle to trigger the timeout, and another 1 ms to wait for the next USB timeslot). To do better than 1 message every 3 milliseconds, you need to pipeline so that multiple messages are in-flight, by replacing the stop-and-wait protocol with some sliding window scheme.
Upvotes: 7