I have put something together but I’m in two minds about posting the whole thing as I’m afraid it will probably just confuse things even more. However, I will post an excerpt of the receive code and try and explain how it works.
Working with serial ports on an Auduino is a bit of a chore. The standard board only has one serial port so a choice has to be made to either use it for communications or debugging purposes as a console. The softwareSerial library is ok for simple tasks but suffers from a couple of issues.
- When transmitting, the receiver is disabled. In fact all interrupts are disabled
- As it uses “bit banging”, using high baud rates only marginally works, I would avoid 115200 even though it is supported.
To be honest, I would keep the baud rate at 38400. This will allow enough bandwidth for the type of communications you want to do but be low enough that any errors in the bit banging timing become insignificant. 38400 is a very common communication rate for the type of equipment I often work with and is pretty much a standard in the broadcast industry for equipment control.
The problem of not being able to transmit and receive at the same time can be avoided by the use of the master/slave protocol I suggested earlier.
Transmitting data is easy, receiving data is harder. When receiving data you have to account for corrupted messages, partial messages, cables being connected and disconnected and possible parity and framing errors. I prefer binary messages but the following technique will work just as well with ASCII messages.
So first of all we need somewhere to store the received information. Although a simple array will work, this example uses a union of both an array and a structure. A structure is a way of grouping several variables into one object. A union is a way of accessing the same region of memory in two (or more) different ways. This is very convenient when, for example, you want to be receiving a stream of bytes and placing them in a continuous array but afterwards access them by name. Here is a simple union that I will use for receiving data.
// Storage for the vex communications data
#define VEXDATAOFFSET 4
#define VEX_DATA_BUFFER_SIZE 16
typedef union _vexdata {
struct {
unsigned char header_aa;
unsigned char header_55;
unsigned char message_type;
unsigned char datalen;
unsigned char analog_0h;
unsigned char analog_0l;
unsigned char analog_1h;
unsigned char analog_1l;
unsigned char digital_0;
unsigned char digital_1;
unsigned char checksum;
} data;
unsigned char buffer[VEX_DATA_BUFFER_SIZE];
} vexdata;
The union has two parts. The second part is an unsigned char array.
unsigned char buffer[VEX_DATA_BUFFER_SIZE];
VEX_DATA_BUFFER_SIZE is defined to be 16, slightly larger than the structure that is the first part of the union. The first part of the union is a structure.
struct {
unsigned char header_aa;
unsigned char header_55;
unsigned char message_type;
unsigned char datalen;
unsigned char analog_0h;
unsigned char analog_0l;
unsigned char analog_1h;
unsigned char analog_1l;
unsigned char digital_0;
unsigned char digital_1;
unsigned char checksum;
} data;
By using “typedef” in front of the union description I have defined a new data type, this can be used in the same way as “int” or “double”, to declare storage you do the following.
vexdata MyVexDataRx;
This creates a new variable called “MyVexDataRx” which has the type “vexdata”. As an example, this is how you would access the first member in the array.
MyVexDataRx.buffer[0]
or
MyVexDataRx.data.header_aa;
The contents of “vexdata” will change depending on your chosen communications protocol, the structure above has variables that work with the protocol for my demo code, yours may be different. In this case the structure members have the following meanings.
header_aa
fixed value of 0xAA used to detect the start of a message.
header_55
fixed value of 0x55 used to detect the start of a message
message_type
One byte that tells me what information is in the message, not really used in the demo code.
datalen
The number of bytes in the remainder of the message.
analog_0h, analog_0l, analog_1h, analog_1l, digital_0, digital_1
The actual data for my message, the demo sent two 16 bit analog values and two 8 bit digital values. Each 16 bit analog value was split into two parts for transmission, the top 8 bits (high byte) and the lower 8 bits (lower byte).
checksum
A byte that allows the packet to be checked for simple communication errors.
As I said, transmission is easy, fill in the data structure and then send each byte using a loop that accesses each byte in the array.
Receiving is harder, I normally use a state machine to do this. A state machine allows us to keep track of where we are in receiving the message. To start off, you need to detect the header, when that has been done read the message type, next the data length etc. At each stage of receiving the message error checking can be done if, for example, the second byte of the header is wrong then the state machine is reset and you start over by waiting for the first byte of the header. Anyway, here is the receive state machine for the demo code.
/*-----------------------------------------------------------------------------*/
/* */
/* Message receive task */
/* */
/*-----------------------------------------------------------------------------*/
void
serialRxTask()
{
static int serialRxState = 0;
static int serialDataReceived = 0;
static long timeout = 0;
int c;
// check for a received character
if( (c = serialRxChar()) < 0 )
{
// no received char
// Check for inter char timeout
if( CheckTimeout(&timeout) == 1 )
serialRxState = 0;
// done
return;
}
// A new character has been received so process it.
// Start inter char timeout
StartTimeout( &timeout );
// Where are we in the message parsing process ?
switch( serialRxState )
{
case 0:
// look for first header byte
if( c == MyVexDataRx.data.header_aa )
serialRxState++;
else
StopTimeout(&timeout);
break;
case 1:
// look for second header byte
if( c == MyVexDataRx.data.header_55 )
serialRxState++;
else
{
// Bad message
StopTimeout(&timeout);
serialRxState = 0;
}
break;
case 2:
// We have a good header so next is message type
MyVexDataRx.data.message_type = c;
serialRxState++;
break;
case 3:
// next is data length byte
MyVexDataRx.data.datalen = c;
serialDataReceived = 0;
serialRxState++;
break;
case 4:
// receive the data packet
MyVexDataRx.buffer VEXDATAOFFSET + serialDataReceived ] = c;
if( ++serialDataReceived == MyVexDataRx.data.datalen )
serialRxState++;
// check for buffer overflow
if( serialDataReceived >= (VEX_DATA_BUFFER_SIZE-VEXDATAOFFSET) )
{
// Error
StopTimeout(&timeout);
serialRxState = 0;
}
break;
case 5:
// Received checksum byte
// stop timeout
StopTimeout( &timeout );
// calculate received checksum
VexDataChecksum( &MyVexDataRx );
// compare checksums
if( MyVexDataRx.data.checksum == c )
{
// Good data
#ifdef DEBUG
VexDataPrint (&MyVexDataRx);
#endif
// Decode the message
serialRxDecode();
}
else
Serial.write("bad data\n");
// done
serialRxState = 0;
break;
default:
break;
}
}
Yes, it looks long and complicated see the next post for a brief explanation of what is happening more or less.
(hit the 10000 char limit
)