Just for curiosity, I cobbled together a quick test. I was able to send data without the Arduino resetting, and with a baudrate of 500k was able to send 32 bytes every 1ms. There is a bit of jitter sending data, there are probably ways to improve the performance with better knowledge of Windows internals.
To get the Arduino to clock out data quickly enough I wrote a direct register access version of shiftOut.
This is rough code, so use as a guide.
Arduino sketch
const byte PIN_DATA = 8;
const byte PIN_CLK = 9;
const byte PIN_SYNC = 10;
const int BUF_LEN = 32;
char buffer[BUF_LEN];
void x_shiftOut (uint8_t value)
{
for (uint8_t bit = 0; bit < 8; bit++)
{
// set DATA pin
if (value & 1)
PORTB |= (1 << 0);
else
PORTB &= ~(1 << 0);
// clock high
PORTB |= (1 << 1);
// delayMicroseconds (bitTimeUs);
value = value >> 1;
// clock low
PORTB &= ~(1 << 1);
// delayMicroseconds (bitTimeUs);
}
}
void setup()
{
Serial.begin(500000);
pinMode(PIN_DATA, OUTPUT);
pinMode(PIN_CLK, OUTPUT);
pinMode(PIN_SYNC, OUTPUT);
digitalWrite (PIN_SYNC, 1);
delay (100);
digitalWrite (PIN_SYNC, 0);
}
void loop()
{
if (Serial.available())
{
Serial.readBytes(buffer, BUF_LEN);
PORTB ^= (1 << 2);
for (int j = 0; j < BUF_LEN; j++)
x_shiftOut(buffer[j]);
}
}
Visual Studio Community 2022 (64-bit), C++ console app
// test_serial.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <windows.h>
#define UART_MAX_READ_TIMEOUT 20
#define UART_DEFAULT_READ_TIMEOUT 10
#define UART_MIN_READ_TIMEOUT 5
#define MAX_FILE_LEN 512
const int BUF_LEN = 32;
typedef enum bsl_error_codes_t {
BSL_NO_ERROR = 0x00,
ERROR_BSL_COMINIT = 0xF3,
} bsl_error_codes_t;
typedef struct
{
HANDLE hComm;
unsigned int ser_interface; // only ASC_INTERFACE supported
unsigned int device; // -d -dev 1..6
unsigned int dwBaudrate; // -b -baud 19200
char comPort[MAX_FILE_LEN]; // -p -port COM1
} parameters_t;
bool interactive_mode = false;
parameters_t g_parameters;
double ticks_per_us;
/*------------------------------------------------------------------------------
Function Name : sleep_us()
Description : High Performance Counter, to put the system to sleep less than 1 ms.
Function Called : None
Input Parameter : timeUs => Time to sleep in us.
Output Parameter: None
Return Value : None
---------------------------------------------------------------------------------*/
void sleep_us (double timeUs)
{
LARGE_INTEGER tick;
double ticks_per_us;
double start, end;
QueryPerformanceFrequency(&tick);
ticks_per_us = (double)tick.QuadPart / 1e6;
QueryPerformanceCounter(&tick);
start = (double)tick.QuadPart / ticks_per_us;
end = start;
while (end < (start + timeUs))
{
QueryPerformanceCounter(&tick);
end = (double)tick.QuadPart / ticks_per_us;
}
}
double micros(void)
{
LARGE_INTEGER tick;
QueryPerformanceCounter(&tick);
return (double)tick.QuadPart / ticks_per_us;
}
/*------------------------------------------------------------------------------
Function Name : init_uart()
Description : Responsible to initialize the UART Port (COM PORT)
Following actions are done:
-) Initialize the chosen COM PORT with the selected baudrate
-) Return the hComm handle.
Function Called : None
Input Parameter : *cPortName => Port Name (e.g: "COM1", "COM2", etc)
dwBaudrate => Baudrate
Output Parameter: *hComm => Valid Communication Handle.
Return Value : Error Code
---------------------------------------------------------------------------------*/
unsigned int init_uart(HANDLE* hComm, char* cPortName, DWORD dwBaudrate)
{
DCB BSLdcb;
COMMTIMEOUTS uartCommTimeouts = { 0 };
char portString [200];
uartCommTimeouts.ReadIntervalTimeout = 0;
uartCommTimeouts.ReadTotalTimeoutConstant = 0;
uartCommTimeouts.ReadTotalTimeoutMultiplier = UART_DEFAULT_READ_TIMEOUT;
snprintf(portString, 200, "\\\\.\\%s", cPortName);
*hComm = CreateFileA((const char *)portString,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_WRITE_THROUGH,
0);
if (*hComm == INVALID_HANDLE_VALUE)
{
return ERROR_BSL_COMINIT;
}
GetCommState(*hComm, &BSLdcb);
// Setting for the COM1 PORT
BSLdcb.BaudRate = dwBaudrate;
BSLdcb.StopBits = ONESTOPBIT;
BSLdcb.Parity = NOPARITY;
BSLdcb.fRtsControl = RTS_CONTROL_DISABLE;
BSLdcb.fDtrControl = DTR_CONTROL_DISABLE;
BSLdcb.ByteSize = 8;
BSLdcb.fOutxCtsFlow = FALSE;
BSLdcb.fOutxDsrFlow = FALSE;
BSLdcb.fOutX = FALSE;
BSLdcb.fTXContinueOnXoff = TRUE;
BSLdcb.fInX = FALSE;
//Sleep(1);
if (!SetCommState(*hComm, &BSLdcb))
{
CloseHandle(*hComm);
return ERROR_BSL_COMINIT;
}
if (!SetCommMask(*hComm, EV_RXCHAR))
{
CloseHandle(*hComm);
return ERROR_BSL_COMINIT;
}
if (!SetCommTimeouts(*hComm, &uartCommTimeouts))
{
CloseHandle(*hComm);
return ERROR_BSL_COMINIT;
}
return BSL_NO_ERROR;
}
/*------------------------------------------------------------------------------
Function Name : close_interface()
Description : Responsible to close all of the communication channel (UART or JTAG)
Function Called : None
Input Parameter : *hComm => Communication Handle.
Output Parameter: None
Return Value : Error Code
---------------------------------------------------------------------------------*/
unsigned int close_interface(HANDLE* hComm)
{
CloseHandle(*hComm);
*hComm = INVALID_HANDLE_VALUE;
return BSL_NO_ERROR;
}
void test_send(void)
{
// attempt to connect
if (init_uart(&g_parameters.hComm, &g_parameters.comPort[0], g_parameters.dwBaudrate) == BSL_NO_ERROR)
{
unsigned char chWrite[BUF_LEN];
DWORD dwNumOfBytes = 0;
for (int j = 0; j < BUF_LEN; j++)
chWrite[j] = 64 + j;
double start = micros();
while (true)
for (int j = 0; j < 5; j++)
{
WriteFile(g_parameters.hComm, &chWrite[0], BUF_LEN, &dwNumOfBytes, NULL);
while (micros() - start < 1000)
{
}
start += 1000;
//Sleep (100);
}
}
else
{
std::cout << "Error opening port";
}
}
int main()
{
std::cout << "Serial test" << std::endl;
strcpy_s (g_parameters.comPort, "COM13");
g_parameters.dwBaudrate = 500000;
LARGE_INTEGER tick;
QueryPerformanceFrequency(&tick);
ticks_per_us = (double)tick.QuadPart / 1e6;
test_send();
}
Scope trace :