Stairs/module/src/lib/TinyRS485.c

263 lines
4.8 KiB
C

/*
* Stairs lighting
* Copyright 2017 (c) Mark van Renswoude
*
* https://git.x2software.net/pub/Stairs
*
* Straight up port of Nick Gammon's non-blocking
* RS485 library to C and removing the Arduino
* framework dependency.
*
* Source:
* https://www.gammon.com.au/forum/?id=11428
* Devised and written by Nick Gammon.
* Date: 4 December 2012
* Version: 1.0
*/
#include "TinyRS485.h"
#include "TinyMillis.h"
#include <stdlib.h>
ReadCallback doRead;
AvailableCallback doAvailable;
WriteCallback doWrite;
// where we save incoming stuff
uint8_t* data;
// how much data is in the buffer
uint8_t bufferSize;
// this is true once we have valid data in buf
bool available;
// an STX (start of text) signals a packet start
bool haveSTX;
// count of errors
uint16_t errorCount;
// variables below are set when we get an STX
bool haveETX;
uint8_t inputPos;
uint8_t currentByte;
bool firstNibble;
uint16_t startTime;
// helper private functions
uint8_t crc8 (uint8_t* addr, uint8_t len)
{
uint8_t crc = 0;
while (len--)
{
uint8_t inbyte = *addr++;
for (uint8_t i = 8; i; i--)
{
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
// 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 sendComplemented(uint8_t what)
{
uint8_t c;
// first nibble
c = what >> 4;
doWrite((c << 4) | (c ^ 0x0F));
// second nibble
c = what & 0x0F;
doWrite((c << 4) | (c ^ 0x0F));
}
void rs485_begin(ReadCallback readCallback,
AvailableCallback availableCallback,
WriteCallback writeCallback,
uint8_t bufferSize)
{
doRead = readCallback;
doAvailable = availableCallback;
doWrite = writeCallback;
data = (uint8_t*)malloc(bufferSize);
rs485_reset();
errorCount = 0;
}
void rs485_stop(void)
{
rs485_reset();
free(data);
data = NULL;
}
void rs485_reset(void)
{
haveSTX = false;
available = false;
inputPos = 0;
startTime = 0;
}
// send a message of "length" bytes (max 255) to other end
// put STX at start, ETX at end, and add CRC
void rs485_sendMsg(uint8_t* data, uint8_t length)
{
doWrite(STX); // STX
for (uint8_t i = 0; i < length; i++)
sendComplemented(data[i]);
doWrite(ETX); // ETX
sendComplemented(crc8(data, length));
}
bool rs485_available(void)
{
return available;
}
uint8_t* rs485_getData(void)
{
return data;
}
uint8_t rs485_getLength(void)
{
return inputPos;
}
uint16_t rs485_getErrorCount(void)
{
return errorCount;
}
uint16_t rs485_getPacketStartTime(void)
{
return startTime;
}
bool rs485_isPacketStarted(void)
{
return haveSTX;
}
// 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(void)
{
// no data? can't go ahead (eg. begin() not called)
if (data == NULL)
return false;
// no callbacks? Can't read
if (doAvailable == NULL || doRead == NULL)
return false;
while (doAvailable() > 0)
{
uint8_t inByte = doRead();
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))
{
rs485_reset();
errorCount++;
break; // bad character
}
// convert back
inByte >>= 4;
// high-order nibble?
if (firstNibble)
{
currentByte = inByte;
firstNibble = false;
break;
}
// 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)
{
rs485_reset();
errorCount++;
break; // bad crc
}
available = true;
return true; // show data ready
}
// keep adding if not full
if (inputPos < bufferSize)
data[inputPos++] = currentByte;
else
{
rs485_reset(); // overflow, start again
errorCount++;
}
break;
}
}
return false; // not ready yet
}