263 lines
4.8 KiB
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
|
|
}
|