Library to interact with RS485 serial.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

213 lines
6.5 KiB

/*
Library for Software Serial over RS485 connection
Created by Stepan Richter, November 2023
*/
#include "Arduino.h"
#include "SoftRS485.h"
//#define SEND
#define MSG_LEN 64
// Pins
int _RO, _RE, _DE, _DI;
const unsigned long _pulse_len = 1000;
const unsigned long _break = _pulse_len * 20;
unsigned long _last_flip = 0; // timestamp of last flip of line state
boolean _line = LOW; // current state of the transmission line
boolean _start = false; // indicates whether a start bit shall be ignored
// Receive vars
byte _recv_msg[MSG_LEN]; // message buffer
boolean _avail = false; // indicates, wheter a message has been received
int _pos = 0; // character position within the buffer
int _idx = 0; // bit position within the current character
// variables, that could be local, but are stored globally for speed optimization
unsigned long _diff = 0; // time difference between last flips
unsigned long _now = 0; // current time
unsigned long _count = 0; // helper for bit iteration
#if defined CONTROLLINO_MAXI
ISR(PCINT1_vect){
#else
void interrupt485(){
#endif
_line = !digitalRead(_RO); // HIGH level = logical zero and vice versa
_now = micros(); // get current time
_diff = _now - _last_flip; // duration of the _previous_ bit
_last_flip = _now; // store time of the current flip
#ifdef RECV
Serial.print(!_line);
Serial.print("-(");
Serial.print(_diff);
Serial.print("µs)→");
Serial.print(_line);
Serial.print(" ");
#endif
if (_diff > _break){ // long break means we are starting with a new transmission
_start = true; // first bit will be ignored on the next edge
_pos = 0; // start writing at the beginning of the message buffer
_idx = 0;
#ifdef RECV
Serial.println("==RESET==");
#endif
} else {
_count = _pulse_len>>1; // bit lengths may vary in certain bounds. this makes sure we always are in the bounds
while (_count < _diff){ // iterate though each bit
_count += _pulse_len;
if (_idx){ // not the first bit
_recv_msg[_pos] |= !_line << _idx; // set the bit of the current character
} else { // first bit
_recv_msg[_pos] = !_line; // reset all bits, set the first bit of the current character
}
if (_start){ // current bit is start bit
_start = false; // ignore by not increasing the bit index
} else { // current bit is not a start bit
_idx++; // advance to the next bit
}
#ifdef RECV
Serial.print(_line);
#endif
if (_idx == 8){ // full byte received
if (_pos == MSG_LEN || _recv_msg[_pos] == 0x00){ // received byte is string terminator
_avail = true; // notify about complete transmission
break;
}
_pos++; // advance to nect character of buffer
_idx=0; // start with first bit of that character
}
}
#ifdef RECV
Serial.println();
#endif
}
}
void init485(int RO, int nRE, int DE, int DI){
_RO=RO; // save the pin numbers
_RE=nRE;
_DE=DE;
_DI=DI;
_avail = false;
#if defined SEND || defined RECV
Serial.begin(115200);
Serial.print(F("RO = "));
Serial.println(_RO);
Serial.print(F("^RE = "));
Serial.println(_RE);
Serial.print(F("DE = "));
Serial.println(_DE);
Serial.print(F("DI = "));
Serial.println(_DI);
#endif
#ifdef RECV
Serial.println("RECV enabled");
#endif
#ifdef SEND
Serial.println("SEND enabled");
#endif
pinMode(_RO,INPUT); // we are reading on line _RO
pinMode(_RE,OUTPUT);
digitalWrite(_RE,LOW); // enable reading
pinMode(_DE,OUTPUT);
digitalWrite(_DE,LOW); // disable writing
pinMode(_DI,OUTPUT);
digitalWrite(_DI,LOW); // output line = LOW
#if defined CONTROLLINO_MAXI
Serial.println("Setting up for Controllino MAXI!");
// https://forum.arduino.cc/t/enable-interrupt/259014/2
// pin change interrupt enable (PCIE) port 1
// in pin change interrupt control register (PCICR)
PCICR |= (1<<PCIE1);
// enable bit for pin change interrupt 9 (PCINT9)
// in pin change mask 1
PCMSK1 |= (1<<PCINT9);
#else
attachInterrupt(digitalPinToInterrupt(_RO),interrupt485,CHANGE);
#endif
_last_flip = micros();
}
boolean available485(){
return _avail;
}
String get485message(){
_avail = false;
return String((char*)_recv_msg);
}
boolean writeBit(boolean bit){
digitalWrite(_DI,!bit);
delayMicroseconds(5);
if (_line != bit){ // collision!
digitalWrite(_DE,LOW); // abort transmission immediately
#ifdef SEND
Serial.println(F("Collision!"));
#endif
return false; // notify caller
}
delayMicroseconds(_pulse_len-5);
return true;
}
boolean writeByte(byte b){
return writeBit(b & 0x01)
&& writeBit(b & 0x02)
&& writeBit(b & 0x04)
&& writeBit(b & 0x08)
&& writeBit(b & 0x10)
&& writeBit(b & 0x20)
&& writeBit(b & 0x40)
&& writeBit(b & 0x80);
}
boolean send485(char message[]){
#ifdef SEND
Serial.print(F("preparing to send "));
Serial.print(message);
Serial.print(F(": line state = "));
Serial.print(_line);
Serial.print(F(" / last_flip = "));
Serial.print(_last_flip);
Serial.print(F(" / micros = "));
Serial.println(micros());
#endif
// if the line is busy: wait for it to get free
while (_line || (micros() - _last_flip) < _break) {
delayMicroseconds(_break);
}
#ifdef SEND
Serial.println(F("enabling driver…"));
#endif
digitalWrite(_DI,LOW);
digitalWrite(_DE,HIGH); // enable driver
int pos = -1; // increment pos prior to sending char!
boolean sync = writeBit(0);
do {
pos++; // incrementation at beginning of loop in order to allow post-loop condition!
sync = sync && writeByte(message[pos]);
} while (sync && message[pos]);
digitalWrite(_DI,LOW);
digitalWrite(_DE,LOW); // disable driver
#ifdef SEND
Serial.println(F("disabled driver…"));
#endif
return sync;
}