ElcanoSerial

From Elcano Project Wiki
Jump to navigation Jump to search


OBSOLETE

UART Serial communication has been replaced by CAN bus serial communication.

Tyler Folsom May 30, 2020


Abstract

The library is meant to send a packet of data from one Arduino in the system to the next over serial connections.

When a connection is normally made, the Arduino must block until it has finished reading a continuous block of text. However, as these are nearly-real time embedded systems, we cannot do this, and it must read the data asynchronously.

The way that we chose to do this was with a state machine that can update one character at a time without loosing track of where it is in the processing stage. As a result, the data is updated in such a way that no blocking is required between reads, and if a batch of data is not fully read at the right time, the device can come back and keep going later.

The ParseState data structure contains a pointer to the resulting data (where it places information it has read from the input), and a flag that tells the state machine what part of the structure it is currently at.

The Arduinos are arranged into a ring, so that all data will eventually reach its target, as the Arduinos pass data they don't need back into the ring.

Data Format

A message shall always begin with 1 header character ('D', 'S', 'G', or 'X').

The message body shall consist of 0 or more flags, up to a maximum of 7 (which is the number of distinct attribute types). No single attribute type can be used for 2 or more flags in 1 message.

Flags shall consist of the left curly bracket ('{'), followed in order by an attribute type character ('n', 's', 'a', 'o', 'b', 'r', 'p'), and by 1 value (in the case of 'n', 's', 'a', 'o', 'b', 'r') or by 2 values (in the case of 'p'). The order of given flags is not significant. For the number, sensor, angle, bearing, and probability attribute types, the value is a single decimal number composed of, optionally, an ASCII hyphen-minus character ('-') (for negative integers), followed by the ASCII characters '0' through '9'. For the "Position" attribute type, the values are 2 such decimal numbers separated by a comma (',').

Each message type uses a subset of the available attribute types. Any flag that contains an attribute that is incompatible with the given message type will be ignored.

The message ends with an 8-bit SMBUS CRC checksum, followed by a newline ('\n') character. Other space characters have no significance to the message.

Header Types

*Header Type* *Character* *Purpose* *Used Attribute Types*
Drive 'D' Sends data to C2 Speed, Angle
Sensor 'S' Sends data to C3 and C6 Speed, Angle, Obstacle, Bearing, Position
Goal 'G' Communicates the location of an object to C4 Number, Bearing, Probability, Position
Segment 'X' Communicates part of the navigation path to C3 Number, Speed, Bearing, Position

Attribute Types

*Attribute Type* *Character*
Number 'n'
Speed 's'
Angle 'a'
Obstacle 'o'
Bearing 'b'
Probability 'r'
Position 'p'

Message Examples

(Without checksums)

D{s 300}{a 0}\n

In the above example, 'D' is the header, followed by a body which contains the flags '{s 300}' and '{a 0}'.

S {s 256} {b45} {a30} {p 0, 0} {r 56} \n

In the above example, the spaces and the order of the flags do not matter. Because the Sensor message type does not use the Probability attribute type, the Probability flag is ignored.

X\n

The above example is a valid message, but it is useless.

Parsing Expression Grammar

Note: This section does not take into account space characters or the checksum.

A PEG for the data that gets passed around is as follows:

('D' / 'S' / 'G' / 'X') ('{' ('n' / 's' / 'a' / 'o' / 'b' / 'r') '-'? [0-9]+ '}')+
\--------header-------/ \-------------------------body---------------------------/
\------------attribute------------/ \value(s)-/

The various components of the string have been labeled for your convenience.

This PEG does not include the Position attribute type ('p'), which sends a pair of numbers. Replace the body section of the PEG with this:

'{' 'p' '-'? [0-9]+ ',' '-'? [0-9]+ '}'

Any spaces sent are ignored by the parser. Given all of the above, a complete PEG would look like the following:

Message = ('D' / 'S' / 'G' / 'X') ('{' (
('n' / 's' / 'a' / 'o' / 'b' / 'r') '-'? [0-9]+
/ 'p' '-'? [0-9]+ ',' '-'? [0-9]+
) '}')+

This may be of use to you if you need to re-implement this in the future.

Code Skeleton

Here is some example code, which you can use as a skeleton for writing your own stuff.

#include <ElcanoSerial.h>

elcano::ParseState ps;
elcano::SerialData dt;

void setup() {
Serial1.begin(elcano::baudrate);
Serial2.begin(elcano::baudrate);

ps.dt  = &dt;
ps.input = &Serial1;
ps.output = &Serial2;
ps.capture = elcano::MsgType::DESIRED_TYPE;
dt.clear();

// Any other initialization code goes here
}

void loop1() {
// Update code here that depends on having received a data set
}

void loop2() {
// Update code here that does not depend on having received a data set
}

void loop() {
elcano::ParseStateError r = ps.update();
if (r == elcano::ParseStateError::success) {
loop1();
}

loop2();
}

Replace DESIRED_TYPE with drive, sensor, goal, or seg depending on your component. The specific serial port used by your component may vary, so you may have to change Serial1 and Serial2 around.

The error handling code in the skeleton is enough to get you started, but may be a little bit lacking if you want more fine grained control, especially if you are debugging. That can be accomplished with a switch statement in the loop function:

void loop() {
elcano::ParseStateError r = ps.update();
switch (r) {
case elcano::ParseStateError::success:
loop1();
break;
<other cases go here>
}

loop2();
}

Limitations

The Arduino has a hard limit of 32 characters which it can store at a time from the serial port. It can also only write to the serial far faster than it can read and parse. This combined leads to a situation where if you write too quickly, you will overwhelm the reader and get garbage results. This can be countered in 2 ways:

  1. Put a wait between writes. Hold on, I hear you say! Isn't the point of the asynchronous system that no blocking is required? The difference between this and naive blocking serial usage is that this blocks on write, while the other blocks on read, and this is purely due to the memory constraints on the Arduinos.
  2. Only write to the message loop after you have just recieved from the message loop. Based on our usage example above this, you would only write in loop1 and not in loop2. This ensures that nothing gets written until something is received, which forces all delay necessary to prevent a problem.

If at some point in the future, the hardware gets updated to something without the hard Arduino requirements, then feel free to experiment with removing these limitations.

External Links

!ElcanoSerial: https://github.com/elcano/elcano/tree/master/libraries/ElcanoSerial

!FastCRC: https://github.com/FrankBoesing/FastCRC

-- Main.JohnsonB - 2017-06-30