Arduino DUE native USB communication; SerialUSB

Hello,

a few days ago I decided to make a simple oscilloscope with my due and being the way I am, I wanted maximum sampling frequency which in turn demanded fast communication with my PC. I've been at it a while now, looking for solutions, but it turns out there isn't much documentation on this matter. Anyway, I managed to put something together and due to the lack of information on the internet I will share my project on this forum so it may help a fellow due user. Also, I will aprpeciate hints and ideas for improvments or other solutions anyone might think of or has already done so in the past.

To clarify, this is simply one way to use the due's native usb port to communicate with your pc at high speeds.

This is some simple arduino code to generate output:

uint16_t buf[256];

void setup() {
  SerialUSB.begin(9600);
  while(!SerialUSB);
  for(int i = 0; i < 256; i++)
    buf[i] = (uint16_t)i;
}

void loop() {
  SerialUSB.write((uint8_t*)buf, 512);
}

Note: the specified baud rate at SerialUSB.begin(baud) is irrelevant, you can also set it to 0. As far as I know this is because the communication will always run at maximum speed the two devices can handle. So instead of sending data constantly at a slower rate it will send the data at higher speeds in packets. I would like for someone who knows this in more detail to confirm(or not) this and write a short classification of all the parts(protocol, communication,...) at work here since I can't even name the protocol that is being used. (help)

With the arduino side complete, there are many ways to read the data from the serial port on your PC. I wanted a simple solution for this, but I couldn't get things to work with Processing, I believe this was because with SerialUSB arduino doesn't get reset when you open the port on the PC side (help!).
Nor did I manage to use python, because I had some library problems.
Next was C#, which worked, but I wanted to be able to access the code in C++ so I created a dll in C# and then failed to use it in C++.
And lastly I turned to C++ alone. Now I copied most of this code from Arduino Playground, I just changed it a bit to make it work the way I wanted it to.
Link: Arduino Playground - CPPWindows
Code:

Serial.h

#ifndef SERIALCLASS_H_INCLUDED
#define SERIALCLASS_H_INCLUDED

#define ARDUINO_WAIT_TIME 2000

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

class Serial
{
private:
	//Serial comm handler
	HANDLE hSerial;
	//Connection status
	bool connected;
	//Get various information about the connection
	COMSTAT status;
	//Keep track of last error
	DWORD errors;

public:
	//Initialize Serial communication with the given COM port
	Serial(char *portName);
	//Close the connection
	~Serial();
	//Read data in a buffer, if nbChar is greater than the
	//maximum number of bytes available, it will return only the
	//bytes available. The function return -1 when nothing could
	//be read, the number of bytes actually read.
	int ReadData(char *buffer, unsigned int nbChar, unsigned int *queue);
	//Writes data from a buffer through the Serial connection
	//return true on success.
	bool WriteData(char *buffer, unsigned int nbChar);
	//Check if we are actually connected
	bool IsConnected();


};

#endif // SERIALCLASS_H_INCLUDED

Serial.cpp

#include "Serial.h"

Serial::Serial(char *portName)
{
	//We're not yet connected
	this->connected = false;

	//Try to connect to the given port throuh CreateFile
	this->hSerial = CreateFile(portName,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	//Check if the connection was successfull
	if (this->hSerial == INVALID_HANDLE_VALUE)
	{
		//If not success full display an Error
		if (GetLastError() == ERROR_FILE_NOT_FOUND) {

			//Print Error if neccessary
			printf("ERROR: Handle was not attached. Reason: %s not available.\n", portName);

		}
		else
		{
			printf("ERROR!!!");
		}
	}
	else
	{
		//If connected we try to set the comm parameters
		DCB dcbSerialParams = { 0 };

		//Try to get the current
		if (!GetCommState(this->hSerial, &dcbSerialParams))
		{
			//If impossible, show an error
			printf("failed to get current serial parameters!");
		}
		else
		{
			//Define serial connection parameters for the arduino board
			dcbSerialParams.BaudRate = CBR_9600;
			dcbSerialParams.ByteSize = 8;
			dcbSerialParams.StopBits = ONESTOPBIT;
			dcbSerialParams.Parity = NOPARITY;
			//Setting the DTR to Control_Enable ensures that the Arduino is properly
			//reset upon establishing a connection
			dcbSerialParams.fDtrControl = DTR_CONTROL_ENABLE;

			//Set the parameters and check for their proper application
			if (!SetCommState(hSerial, &dcbSerialParams))
			{
				printf("ALERT: Could not set Serial Port parameters");
			}
			else
			{
				//If everything went fine we're connected
				this->connected = true;
				//Flush any remaining characters in the buffers 
				PurgeComm(this->hSerial, PURGE_RXCLEAR | PURGE_TXCLEAR);
				//We wait 2s as the arduino board will be reseting
				Sleep(ARDUINO_WAIT_TIME);
			}
		}
	}

}

Serial::~Serial()
{
	//Check if we are connected before trying to disconnect
	if (this->connected)
	{
		//We're no longer connected
		this->connected = false;
		//Close the serial handler
		CloseHandle(this->hSerial);
	}
}

int Serial::ReadData(char *buffer, unsigned int nbChar, unsigned int *queue)
{
	//Number of bytes we'll have read
	DWORD bytesRead;

	//Use the ClearCommError function to get status info on the Serial port
	ClearCommError(this->hSerial, &this->errors, &this->status);

	*queue = (unsigned int)status.cbInQue;

	//Check if there is something to read
	if (this->status.cbInQue>nbChar)
	{
		//Try to read the required number of chars, and return the number of read bytes on success
		if (ReadFile(this->hSerial, buffer, nbChar, &bytesRead, NULL))
		{
			return bytesRead;
		}

	}

	//If nothing has been read, or that an error was detected return 0
	return 0;

}


bool Serial::WriteData(char *buffer, unsigned int nbChar)
{
	DWORD bytesSend;

	//Try to write the buffer on the Serial port
	if (!WriteFile(this->hSerial, (void *)buffer, nbChar, &bytesSend, 0))
	{
		//In case it don't work get comm error and return false
		ClearCommError(this->hSerial, &this->errors, &this->status);

		return false;
	}
	else
		return true;
}

bool Serial::IsConnected()
{
	//Simply return the connection status
	return this->connected;
}

main.cpp

#include <stdio.h>
#include <tchar.h>
#include "Serial.h"	// Library described above
#include <string>
#include <time.h>

// application reads from the specified serial port and reports the collected data
int main(int argc, char* argv[])
{
	printf("Welcome to the serial test app!\n\n");

	Serial* SP = new Serial("COM4");    // adjust as needed

	if (SP->IsConnected())
		printf("We're connected");

	char incomingData[512] = "";			// don't forget to pre-allocate memory
	unsigned short data[256];				//printf("%s\n",incomingData);
	int dataLength = 512;
	clock_t clk = clock();
	unsigned int que, read = 0, total = 0;

	while (SP->IsConnected())
	{
		if (SP->ReadData(incomingData, dataLength, &que)) {
			for (int i = 0; i < 256; i++) {
				data[i] = incomingData[2 * i + 1] << 8 | (incomingData[2 * i] & 0xFF);
			}
			read += 512;
		}

		if (clock() - clk > CLOCKS_PER_SEC) {
			system("cls");
			total += read;
			printf("Bytes read so far: %u B\nCurrent communication speed: %u B/s\nBytes in queue: %u B\n\n", total, read, que);
			read = 0;
			clk = clock();
		}

		//Sleep(100);
	}
	return 0;
}

There are some imperfections in what I supplied, but that's because I have just made it work and I was eager to share it.

Questions and answers welcome!

Hi!!

Nice work!!

Do you think this method would allow to communicate two arduinos (Arduino DUE sender - Arduino UNO receiver)?

thanks!

Thank you for this post, it helped me get started with the same thing. I'm using LabVIEW at the receiving end. What I found is that the Due is sending serial out in 4096 chunks. This must be the default size of the Tx buffer. When I varied the receive buffer size in LabVIEW, the input always came in the same. I modified the Due code to alter the data every time it is sent so I can see each SerialUSB.write. I put a delay after each write to make it easier to see what was going on at the Rx end. The data is coming in very fast.

int x,b,i;
uint16_t buf[4][256];

void setup()
{  SerialUSB.begin(0);
   while(!SerialUSB);

   for(b=0; b<=3; b++)
      for(i = 0; i < 256; i++)
         buf[b][i] = (uint16_t)i*(b+1)*32;
}

void loop()
{  SerialUSB.write((uint8_t*)buf[b], 512);
   b=(b+1)&3;
   delay(125);
}

In LabVIEW, I found that the only serial setting I had to change (from defaults) to make it work was "Serial End Mode for Reads" or "ASRL End In". I needed to make this "None"
Attached are pictures of the LabVIEW diagram and panel.
The length ("len") shown is the length of the 16 bit array, so the string length was twice that.
I had to do a byte swap of the 16 bit numbers for them to be correct for the PC.

For those not versed in LabVIEW, a short explanation of the diagram:
A serial port connection is opened, and I am only changing one property: ASRL End In
I set the buffer size to 4096 bytes
The grey rectangle is a while loop. The loop continues until there is an error, or a stop it executed.
The serial port is polled to see if there in anything in the receive buffer; if so, then the buffer is read, the string output is type cast to a unsigned 16 bit integer array, the bytes of the 16 bit numbers are swapped, and the output is displayed on a graph (Waveform).
The panel shows the waveform. The Due is sending ramp waveforms with 4 different amplitudes.

I don't have a precise timing for the incoming signal, but when I use the LabVIEW millisecond timer to measure things, the incoming 4096 bytes doesn't even register, so it is under 1 msec.

... later ...

I just found something with SerialUSB.write(). When I send 512 bytes, it waits until there is 512*8 before sending (sends after every 8 SerialUSB.write statements), but if I send 511 bytes or less, it sends immediately. SerialUSB.flush() has no effect on this. This is what made me think that the write buffer was 4096.

panel.png

Hi TheChosenHalf,

I'm working through the issues of the native usb serial port and have come across your code. I have tried to compile it on my vs2017 express but I'm getting errors that I have no way of rectifying given my beginners status in C++. Is it possible that you could post a solution which I could compile and test against my own attempts.
thanks in advance,
Dazza000

p.s. here are the threee errors i'm getting:

Error (active) E0289 no instance of constructor "Serial::Serial" matches the argument list DUE_native_usb_serial c:\Users\crouc\OneDrive\PROJECTS\DUE_native_usb_serial\main.cpp 16

Error C2664 'Serial::Serial(const Serial &)': cannot convert argument 1 from 'const char [5]' to 'char *' DUE_native_usb_serial c:\users\crouc\onedrive\projects\due_native_usb_serial\main.cpp 16

Error C1010 unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source? DUE_native_usb_serial c:\users\crouc\onedrive\projects\due_native_usb_serial\serial.cpp 133

Working code with frequency estimation awailable on jobst.io
and check our shield! :slight_smile: