Stairs/module/src/lib/RS485_non_blocking.cpp

233 lines
5.8 KiB
C++

/*
RS485 protocol library - non-blocking.
Devised and written by Nick Gammon.
Date: 4 December 2012
Version: 1.0
Can send from 1 to 255 bytes from one node to another with:
* Packet start indicator (STX)
* Each data byte is doubled and inverted to check validity
* Packet end indicator (ETX)
* Packet CRC (checksum)
To allow flexibility with hardware (eg. Serial, SoftwareSerial, I2C)
you provide three "callback" functions which send or receive data. Examples are:
size_t fWrite (const byte what)
{
return Serial.write (what);
}
int fAvailable ()
{
return Serial.available ();
}
int fRead ()
{
return Serial.read ();
}
PERMISSION TO DISTRIBUTE
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
LIMITATION OF LIABILITY
The software is provided "as is", without warranty of any kind, express or implied,
including but not limited to the warranties of merchantability, fitness for a particular
purpose and noninfringement. In no event shall the authors or copyright holders be liable
for any claim, damages or other liability, whether in an action of contract,
tort or otherwise, arising from, out of or in connection with the software
or the use or other dealings in the software.
*/
#include "RS485_non_blocking.h"
// allocate the requested buffer size
void RS485::begin ()
{
data_ = (byte *) malloc (bufferSize_);
reset ();
errorCount_ = 0;
} // end of RS485::begin
// get rid of the buffer
void RS485::stop ()
{
reset ();
free (data_);
data_ = NULL;
} // end of RS485::stop
// called after an error to return to "not in a packet"
void RS485::reset ()
{
haveSTX_ = false;
available_ = false;
inputPos_ = 0;
startTime_ = 0;
} // end of RS485::reset
// calculate 8-bit CRC
byte RS485::crc8 (const byte *addr, byte len)
{
byte crc = 0;
while (len--)
{
byte inbyte = *addr++;
for (byte i = 8; i; i--)
{
byte mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
crc ^= 0x8C;
inbyte >>= 1;
} // end of for
} // end of while
return crc;
} // end of RS485::crc8
// send a byte complemented, repeated
// only values sent would be (in hex):
// 0F, 1E, 2D, 3C, 4B, 5A, 69, 78, 87, 96, A5, B4, C3, D2, E1, F0
void RS485::sendComplemented (const byte what)
{
byte c;
// first nibble
c = what >> 4;
fWriteCallback_ ((c << 4) | (c ^ 0x0F));
// second nibble
c = what & 0x0F;
fWriteCallback_ ((c << 4) | (c ^ 0x0F));
} // end of RS485::sendComplemented
// send a message of "length" bytes (max 255) to other end
// put STX at start, ETX at end, and add CRC
void RS485::sendMsg (const byte * data, const byte length)
{
// no callback? Can't send
if (fWriteCallback_ == NULL)
return;
fWriteCallback_ (STX); // STX
for (byte i = 0; i < length; i++)
sendComplemented (data [i]);
fWriteCallback_ (ETX); // ETX
sendComplemented (crc8 (data, length));
} // end of RS485::sendMsg
// called periodically from main loop to process data and
// assemble the finished packet in 'data_'
// returns true if packet received.
// You could implement a timeout by seeing if isPacketStarted() returns
// true, and if too much time has passed since getPacketStartTime() time.
bool RS485::update ()
{
// no data? can't go ahead (eg. begin() not called)
if (data_ == NULL)
return false;
// no callbacks? Can't read
if (fAvailableCallback_ == NULL || fReadCallback_ == NULL)
return false;
while (fAvailableCallback_ () > 0)
{
byte inByte = fReadCallback_ ();
switch (inByte)
{
case STX: // start of text
haveSTX_ = true;
haveETX_ = false;
inputPos_ = 0;
firstNibble_ = true;
startTime_ = millis ();
break;
case ETX: // end of text (now expect the CRC check)
haveETX_ = true;
break;
default:
// wait until packet officially starts
if (!haveSTX_)
break;
// check byte is in valid form (4 bits followed by 4 bits complemented)
if ((inByte >> 4) != ((inByte & 0x0F) ^ 0x0F) )
{
reset ();
errorCount_++;
break; // bad character
} // end if bad byte
// convert back
inByte >>= 4;
// high-order nibble?
if (firstNibble_)
{
currentByte_ = inByte;
firstNibble_ = false;
break;
} // end of first nibble
// low-order nibble
currentByte_ <<= 4;
currentByte_ |= inByte;
firstNibble_ = true;
// if we have the ETX this must be the CRC
if (haveETX_)
{
if (crc8 (data_, inputPos_) != currentByte_)
{
reset ();
errorCount_++;
break; // bad crc
} // end of bad CRC
available_ = true;
return true; // show data ready
} // end if have ETX already
// keep adding if not full
if (inputPos_ < bufferSize_)
data_ [inputPos_++] = currentByte_;
else
{
reset (); // overflow, start again
errorCount_++;
}
break;
} // end of switch
} // end of while incoming data
return false; // not ready yet
} // end of RS485::update