/* 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 long _pulse_len = 616; const long _break = _pulse_len * 20; boolean _line = LOW; // current state of the transmission line long _last_flip = 0; // timestamp since last flip of line state 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 long _diff = 0; // time difference between last flips long _now = 0; // current time long _count = 0; // helper for bit iteration void interrupt485(){ _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 (_recv_msg[_pos] == 0x00){ // received byte is string terminator _avail = true; // notify about complete transmission // TODO: prevent writing out of buffer boundaries! } _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 attachInterrupt(digitalPinToInterrupt(_RO),interrupt485,CHANGE); _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; }