mirror of
https://github.com/RobTillaart/Arduino.git
synced 2024-10-03 18:09:02 -04:00
277 lines
5.3 KiB
C++
277 lines
5.3 KiB
C++
//
|
|
// FILE: RS485.cpp
|
|
// AUTHOR: Rob Tillaart
|
|
// DATE: 30-okt-2017
|
|
// VERSION: 0.2.4
|
|
// PURPOSE: Arduino library for RS485 modules (MAX485)
|
|
// URL: https://github.com/RobTillaart/RS485
|
|
|
|
|
|
#include "RS485.h"
|
|
|
|
|
|
// - a stream - SWserial of HW serial (preferred)
|
|
// - a pin for TX- enable line
|
|
// - slave ID, or 0 as master (or don't care)
|
|
RS485::RS485(Stream * stream, uint8_t sendPin, uint8_t deviceID)
|
|
{
|
|
_stream = stream;
|
|
_sendPin = sendPin;
|
|
_deviceID = deviceID;
|
|
|
|
pinMode(_sendPin, OUTPUT);
|
|
setRXmode(); // receiver mode
|
|
}
|
|
|
|
|
|
int RS485::available()
|
|
{
|
|
return _stream->available();
|
|
}
|
|
|
|
|
|
int RS485::read()
|
|
{
|
|
return _stream->read();
|
|
}
|
|
|
|
|
|
int RS485::peek()
|
|
{
|
|
return _stream->peek();
|
|
}
|
|
|
|
|
|
void RS485::flush()
|
|
{
|
|
_stream->flush();
|
|
}
|
|
|
|
|
|
size_t RS485::write(uint8_t c)
|
|
{
|
|
setTXmode(); // transmit mode
|
|
size_t n = _stream->write(c);
|
|
delayMicroseconds(_microsPerByte);
|
|
setRXmode(); // receiver mode
|
|
return n;
|
|
}
|
|
|
|
|
|
size_t RS485::write(char * array, uint8_t length)
|
|
{
|
|
return write((uint8_t *)array, length);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////
|
|
//
|
|
// discussion about write and yield see
|
|
// - https://github.com/RobTillaart/RS485/issues/2
|
|
//
|
|
|
|
// #define RS485_YIELD_ENABLE 1
|
|
|
|
#ifdef RS485_YIELD_ENABLE
|
|
|
|
// smallest and slightly fastest.
|
|
size_t RS485::write(uint8_t * array, uint8_t length)
|
|
{
|
|
uint8_t n = 0;
|
|
for (uint8_t i = 0; i < length; i++)
|
|
{
|
|
n += write(array[i]);
|
|
yield();
|
|
}
|
|
return n;
|
|
}
|
|
|
|
#else
|
|
|
|
// 0.2.1 version
|
|
// no yield() calls - might be blocking...
|
|
size_t RS485::write(uint8_t * array, uint8_t length)
|
|
{
|
|
setTXmode(); // transmit mode
|
|
size_t n = _stream->write(array, length);
|
|
delayMicroseconds(length * _microsPerByte);
|
|
setRXmode(); // receiver mode
|
|
return n;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void RS485::setMicrosPerByte(uint32_t baudRate)
|
|
{
|
|
// count 11 bits / byte
|
|
_microsPerByte = (11 * 1000000UL) / baudRate;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////
|
|
//
|
|
// EXPERIMENTAL - to be tested - use at own risk.
|
|
//
|
|
///////////////////////////////////////////////////////
|
|
//
|
|
// commando value meaning
|
|
//
|
|
// SOH 0x01 start of header
|
|
// STX 0x02 start of text
|
|
// ETX 0x03 end of text
|
|
// EOT 0x04 end of transmission
|
|
//
|
|
// optional
|
|
// ACK 0x06 ACKnowledge
|
|
// NAK 0x15 Not Acknowledge
|
|
// CAN 0x18 CANcel
|
|
//
|
|
///////////////////////////////////////////////////////
|
|
//
|
|
// A message has the following layout
|
|
//
|
|
// SOH start of header
|
|
// deviceID to
|
|
// deviceID sender
|
|
// length length of message
|
|
// STX start of text
|
|
// message idem
|
|
// CHECKSUM idem, message only!
|
|
// ETX end of text
|
|
// EOT end of transmission
|
|
//
|
|
|
|
void RS485::send(uint8_t receiverID, uint8_t msg[], uint8_t len)
|
|
{
|
|
uint8_t CHKSUM = 0;
|
|
setTXmode(); // transmit mode
|
|
_stream->write(SOH);
|
|
_stream->write(receiverID); // TO
|
|
_stream->write(_deviceID); // FROM
|
|
_stream->write(len); // LENGTH BODY
|
|
_stream->write(STX);
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
_stream->write(msg[i]);
|
|
CHKSUM ^= msg[i]; // Simple XOR checksum.
|
|
}
|
|
_stream->write(CHKSUM);
|
|
_stream->write(ETX);
|
|
_stream->write(EOT);
|
|
delayMicroseconds((len + 8 + 2) * _microsPerByte); // + 2 to be sure...
|
|
setRXmode();
|
|
}
|
|
|
|
|
|
// returns true if complete message is captured.
|
|
// multiple calls needed as it should be non-blocking.
|
|
bool RS485::receive(uint8_t &senderID, uint8_t msg[], uint8_t &msglen)
|
|
{
|
|
static uint8_t state = 0;
|
|
static uint8_t sender = 255; // unknown / anonymous.
|
|
static uint8_t length = 0;
|
|
static bool forMe = false;
|
|
uint8_t CHKSUM = 0;
|
|
|
|
if (_stream->available() == 0) return false;
|
|
|
|
uint8_t v = _stream->read();
|
|
// if (debug) Serial.print(v, HEX);
|
|
|
|
switch(state)
|
|
{
|
|
// waiting for new packet
|
|
case 0:
|
|
if (v == SOH)
|
|
{
|
|
_bidx = 0; // start new packet
|
|
state = 1;
|
|
}
|
|
break;
|
|
|
|
// extract receiver
|
|
case 1:
|
|
forMe = ((v == _deviceID) || (v == 255)); // PM or broadcast?
|
|
if (not forMe) state = 99;
|
|
else state = 2;
|
|
break;
|
|
|
|
// extract sender
|
|
case 2:
|
|
sender = v;
|
|
state = 3;
|
|
break;
|
|
|
|
// extract length
|
|
case 3:
|
|
msglen = v;
|
|
state = 3;
|
|
break;
|
|
|
|
// expect STX
|
|
case 4:
|
|
if (v == STX) state = 5;
|
|
else state = 99;
|
|
break;
|
|
|
|
// extract message
|
|
case 5:
|
|
if (length == 0)
|
|
{
|
|
state = 6;
|
|
break;
|
|
}
|
|
// error handling if v not ASCII ?
|
|
_buffer[_bidx++] = v;
|
|
CHKSUM ^= v;
|
|
length--;
|
|
break;
|
|
|
|
// expect checksum
|
|
case 6:
|
|
if (CHKSUM == v) state = 7;
|
|
else state = 99;
|
|
break;
|
|
|
|
// expect STX
|
|
case 7:
|
|
if (v == STX) state = 8;
|
|
else state = 99;
|
|
break;
|
|
|
|
// expect EOT
|
|
case 8:
|
|
if (v == EOT)
|
|
{
|
|
senderID = sender;
|
|
msglen = _bidx -1;
|
|
for (int i = 0; i < msglen; i++)
|
|
{
|
|
msg[i] = _buffer[i];
|
|
}
|
|
return true;
|
|
}
|
|
state = 0;
|
|
break;
|
|
|
|
// SKIP until next packet
|
|
case 99: // wait for EOT in case of error
|
|
if (v == EOT) state = 0;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE
|
|
//
|
|
|
|
|
|
// -- END OF FILE --
|
|
|