Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNS: Serial Communication #120

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
49 changes: 49 additions & 0 deletions arduino/main.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <SoftwareSerial.h>
#include <SerialProtocol.h>
#include <StreamSerialProtocol.h>
#include <ArduinoSerialProtocol.h>

int IRSensor1 = 2;
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems brittle if we add/remove the number of IR sensors and wheel encoders? Can we not maintain a map of IR sensors and wheel encoders and loop through?

Copy link
Member

@mifrandir mifrandir Apr 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. We are only ever going to have four IR sensors. Let's not make it more complicated than it needs to be.

Looking at it again, we should not be using a map, I stand by that. However, I don't see why we don't have something like

#define kNumWheelEncoders 4
const int kWheelEncoders[kNumWheelEncoders] = { /* ... */};

Sorry, I was thinking in terms of C++.

Of course, this also raises the question of how we are going to do testing when we require all four to be present.

int IRSensor2 = 3;
int IRSensor3 = 4;
int IRSensor4 = 5;

// The payload that will be sent to the other device
struct Payload {
int16_t wheelEncoderA;
int16_t wheelEncoderB;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wheel_encoder_a

int16_t wheelEncoderC;
int16_t wheelEncoderD;
} payload;

ArduinoSerialProtocol protocol(&Serial, (uint8_t*)&payload, sizeof(payload));
uint8_t receiveState;

void setup()
{
Serial.begin(9600);

payload.wheelEncoderA = 0;
payload.wheelEncoderB = 0;
payload.wheelEncoderC = 0;
payload.wheelEncoderD = 0;
}

void loop()
{
if(digitalRead(IRSensor1)){
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
++payload.wheelEncoderA;
}
if(digitalRead(IRSensor2)){
++payload.wheelEncoderB;
}
if(digitalRead(IRSensor3)){
++payload.wheelEncoderC;
}
if(digitalRead(IRSensor4)){
++payload.wheelEncoderD;
}

protocol.send();
}

138 changes: 138 additions & 0 deletions src/utils/io/serial.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#include "serial.hpp"

#include <fcntl.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#define HEADER 'S'
#define BYTES_TO_PAYLOAD 2
mifrandir marked this conversation as resolved.
Show resolved Hide resolved

/* Message format:
Header (1 byte) - Header Byte
DataLength (1 byte) - The size of the payload
Data (DataLength bytes) - Actual payload
Checksum (1 byte) - Message integrity check

Total message size = (payload size + 3) bytes;
*/

namespace hyped::utils::io {

SerialProtocol::SerialProtocol(int serial, uint8_t *payload, uint8_t payloadSize)
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
{
setSerial(serial);
this->payload = payload;
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
this->payloadSize = payloadSize;
inputBuffer = (uint8_t *)malloc(payloadSize);
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
}

void SerialProtocol::setSerial(int serial)
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
{
this->serial = serial;
};

uint8_t SerialProtocol::send()
{
if (!serialAvailable()) {
// There is no place to send the data
return ProtocolState::NO_SERIAL;
}

uint8_t checksum = payloadSize;

// Write the HEADER
sendData(HEADER);
sendData(payloadSize);

// Write the payload
for (int i = 0; i < payloadSize; i++) {
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
checksum ^= *(payload + i);
sendData(*(payload + i));
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
}

// Complete with the checksum
sendData(checksum);

return ProtocolState::SUCCESS;
}

// This method is blocking while there is data in the serial buffer
uint8_t SerialProtocol::receive()
{
uint8_t data;

if (!serialAvailable()) {
// Cannot get any data
return ProtocolState::NO_SERIAL;
}

while (true) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While sys_.is_running() please. We don't want this to stop us from shutting the application down.

data = readData();
mifrandir marked this conversation as resolved.
Show resolved Hide resolved

if (data == -1) {
return bytesRead > 0 ? ProtocolState::WAITING_FOR_DATA : ProtocolState::NO_DATA;
kshxtij marked this conversation as resolved.
Show resolved Hide resolved
}

if (bytesRead == 0) {
// Look for HEADER
if (data != HEADER) { return ProtocolState::NO_DATA; }

++bytesRead;
continue;
}

if (bytesRead == 1) {
// Look for payload size
if (data != payloadSize) {
bytesRead = 0;
return ProtocolState::INVALID_SIZE;
}

// The checksum starts with the payload size
actualChecksum = data;
++bytesRead;
continue;
}

// The > check ensures that we don't overflow and
// that we discard bad messages
if (bytesRead >= (payloadSize + BYTES_TO_PAYLOAD)) {
// We are done with the message. Regardless of the outcome
// only a new message can come next
bytesRead = 0;

// Before we can be fully done, we need the
// checksum to match
if (actualChecksum != data) { return ProtocolState::INVALID_CHECKSUM; }

memcpy(payload, inputBuffer, payloadSize);
return ProtocolState::SUCCESS;
}

inputBuffer[bytesRead - BYTES_TO_PAYLOAD] = data;
actualChecksum ^= data;

++bytesRead;
}
}

bool SerialProtocol::serialAvailable()
{
return serial != -1;
}

void SerialProtocol::sendData(uint8_t data)
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
{
write(serial, &data, 1);
}

uint8_t SerialProtocol::readData()
{
uint8_t data[1];
int cnt = read(serial, (void *)data, 1);
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
if (cnt == 0) { return -1; }

return data[0];
}
}; // namespace hyped::utils::io
61 changes: 61 additions & 0 deletions src/utils/io/serial.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <stdlib.h>
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
#include <string.h>
#include <stdint.h>

namespace hyped::utils::io {

struct ProtocolState {
enum Enum
mifrandir marked this conversation as resolved.
Show resolved Hide resolved
{
// The serial object was not set
NO_SERIAL = 0,

// The operation succeeded
SUCCESS = 1,

// There is not (enough) data to process
NO_DATA = 2,

// The object is being received but the buffer doesn't have all the data
WAITING_FOR_DATA = 3,

// The size of the received payload doesn't match the expected size
INVALID_SIZE = 4,

// The object was received but it is not the same as one sent
INVALID_CHECKSUM = 5
};
};

class SerialProtocol {
public:
SerialProtocol(int, uint8_t*, uint8_t);

// Sends the current payload

// Returns a ProtocolState enum value
uint8_t send();

// Tries to receive the payload from the
// current available data
// Will replace the payload if the receive succeeds

// Returns a ProtocolState enum value
uint8_t receive();
mifrandir marked this conversation as resolved.
Show resolved Hide resolved

void setSerial(int);
bool serialAvailable();
void sendData(uint8_t);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give the arguments names for readability.

uint8_t readData();

private:
int serial;
uint8_t* payload;
uint8_t payloadSize;

uint8_t* inputBuffer;
uint8_t bytesRead;

uint8_t actualChecksum;
};
};