RS485 Gateway Sketch
Back To Gateway

I originally built the gateway on an Rpi-3b+ but had problems with RS-232 Port stability (caused by reboots after power fail), so after a number of times manually restoring RS232 from Rpi to Env server, I decided to try something else.   BTW, I really liked being able to ssh to the Rpi in the shop.
RS-232 requires that each device have a dedicated UART for each host it communicats with.   I thought about a RS-232 multiplexed scheme but decided to try RS-485, a daisy chain arrangement with one master controller.   When using RS-485 each host only needs one interface for communicating with all other devices.   The RS-485 interface (MAX485) is driven by a standard UART.

Ethernet:
I'm going to need an ethernet interface to an Arduino.   I really liked the Nano ENC28J60, becasue it simplified mounting, but it's library took up too much dynamic ram and I didn't have enough for my variables!   I tested several others with W5100 and W5500 chips and they worked great and left dynamic ram for me.   The only down side of these is they have to have their own mount.   Arduino Ethernet devices run on 3.3V but they are fed from 5v and each has it's own 3 terminal regulator.  

/*H******************************************************
* ORIGINAL GATEWAY SKETCH
* EtherCard LIB FOR NANO ETHERNET SHIELD FROM RBBB_Server
********************************************************/
#include <SPI.h>
#include <Ethernet.h>                                      // NANO ETH
#include <AltSoftSerial.h>

// ********************* DEFINES *************************
#define COMRX      8                                        // RS 485 RX PIN
#define COMTX      9                                            // RS 485 TX
#define ENATX      7                                    // COM WT ENABLE PIN
#define ERR        -1
#define OK         1
#define NOTFND     0
#define ETHPIN    10                          // 10:CS, 11:SI, 12:SO, 13:SCK 
#define STRMAX    100
#define SLVWAIT   250
#define NXTPOLLTM 25                                     // POLL EVERY xx MS
#define NL        10
#define ENQ       05                                                // QUERY
#define ACK       06                                        // + ACKNOWLEDGE
#define NAK       21                                        // - ACKNOWLEDGE
#define SYN       22                                                 // SYNC
#define MAXVCTRS   4
#define COMBAUD  57600  // 9600, 14400, 19200, 28800
#define SERBAUD  9600   // 31250, 38400, 57600, 115200
#define BUFSIZ    512
typedef unsigned long ulong; 
 
// ********************* PROTOTYPES *************************
char  *zotChr( char *str, char chr );
int    fndSlv( char *str );  // RTRNS SLV#
int    rdNet( EthernetClient Clnt );
int    wtNet( EthernetClient Clnt, char *cp );
int    getNetLen( char *cp );
int    wtSlv( int slvNbr, char *str );       // STR POINTS TO MSG FOR SLAVE
int    rdSlv( int slvNbr );
int    getSLvLen( char *cp );
void   poll();

// ********************* VARIABLES *************************
AltSoftSerial Com( COMRX, COMTX );

const char *Vctrs[] = { NULL,"EN","DA","AC","DC" };
char  IoBuf[BUFSIZ];
byte  Mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x78, 0xEE };
IPAddress Ip( 192, 168, 0, 40 );                                   // NANO.0
IPAddress Dns(192, 168, 0, 2 );                                      // LIN1
IPAddress GateWay( 192, 168, 0, 2 );                              // GATEWAY
IPAddress NetMask( 255, 255, 255, 0 );                            // NETMASK
EthernetServer Srv( 4001 );                                 // CREATE SERVER
EthernetClient Clnt;
int    SlvNbr =0;
ulong  NxtPoll;

/*I*******************************************************
*  REQUEST FORM Net:   "00040000ENV\n" or "00110000AC>Barfity\n"
*  MSG FWD TO SLAVE:    "#01ENV>args\n"  "01" = slv\n#, '>' ONLY IF ARGS FOLLOW
*  MSG Rtnd FROM SLAVE: "#01TI=72.50,HI=31.00,TE=72.86,HE=31.50\n"
*  RESPONSE TO Net:    "00360000TI=72.50,HI=31.00,TE=72.86,HE=31.50\n"
*  POLLSLV:     "#ssENQ\n"     ASCII ENQ IS POLL MSG
*  POLLANS:     "#ssNAK\n" or "#ssACK>DestHostMsg\n"  (DEST HOST FMT: TBA)
*  ANNOUNCE SLAVE ON 485 NET:  "#ssSYN\n"
*  RTN: IN-BOUND MSG LEN
*********************************************************/

/*F****************************************************
*
*******************************************************/
void 
setup() 
{
    Serial.begin( SERBAUD );
    Com.begin( COMBAUD );                           // AltSoftSerial Init 
    pinMode( ETHPIN, OUTPUT );
    pinMode( ENATX, OUTPUT );
    digitalWrite( ENATX, false );               // ENABLE RS485 TRANSMISSION
    Serial.println("Init Ethernet");
    Ethernet.init( ETHPIN );                  // USE PIN 10 AS ETHERNET CSEL
    Ethernet.begin( Mac, Ip, GateWay, NetMask);       // INITIALIZE ETHERNET
    if( Ethernet.hardwareStatus() == EthernetNoHardware)
    {                                      //  ETHERNET HARDWARE NOT PRESENT
        Serial.println( "Ethernet Iface not found" );
        while( 1 ); 
    }
    if( Ethernet.linkStatus() == LinkOFF) 
        Serial.println("Ethernet cable is not connected");
    Srv.begin();
    NxtPoll = millis() + NXTPOLLTM;
    delay( 250 );
}
/*F****************************************************
*
*******************************************************/
void
loop()
{
    int    slv, len;

    if( EthernetClient Clnt = Srv.available() )
    {
      rdNet( Clnt );
        SlvNbr = fndSlv( IoBuf );
        wtSlv( SlvNbr, IoBuf );
        if( (len = rdSlv( SlvNbr )) > 0 )
         wtNet( Clnt, IoBuf );
  }
//    if( millis() >= NXTPOLLTM )
//      poll();
// while( 1 );                // DEBUG: WHOA RIGHT HERE
}
/*F****************************************************
* RECEIVE A TCP MESSAGE FROM NETWORK
*******************************************************/
int
rdNet( EthernetClient Clnt ) 
{
    int    ioNdx, hdrNdx, len, cnt;
    ulong  end;
    char   chr, buf[16];

    if( Clnt ) 
    {
        end = millis() + SLVWAIT;
        while( Clnt.connected() && (millis() < end) )     
        {                                            // HAVE AN ETHCONN
            for( cnt =ioNdx =hdrNdx =0; Clnt.available() && (millis() < end) 
                ; cnt++ ) 
            {
                chr = Clnt.read();         // READ REQ FROM Net, CHR BY CHR
                switch( cnt )
                {
                    case  0 ... 3:          // CAPTURE MSG LEN FROM ENET HDR
                        buf[hdrNdx++] = chr;
                        break;
                    case 4:                             // READ LEN COMPLETE
                        buf[4] = 0;
                        len = atoi( buf );
                        break;
                    case  5 ... 7:               // SKIP REMAINING HDR WORD
                        break;
                    case 8:
                        buf[7] =0;
                        default:
                        if( chr == NL ) 
                        {                                  // END OF REQUEST
                            IoBuf[ioNdx] = 0;           // TERMINATE RCV STR
                            return( len );
                        }                                 // END OF FOUND NL
                        IoBuf[ioNdx++] = chr;   // PLACE NEW CHAR INTO IOBUF
                }
            }                                       // END OF 'FOR AVAILABLE
        }                                        // END OF 'WHILE CONNECTED'
        return( NOTFND );
    }                       // END OF 'IF CLNT'
    return( NOTFND );
}
/*F******************************************************************
* WRITE SLV RESPONSE TO ETHERNET REQUESTOR
**********************************************************************/
int 
wtNet(  EthernetClient Clnt, char *str )      // STR POINTS TO MSG FOR SLAVE
{
    int        len;
    char       chr, *cp, hdr[16];

    cp = IoBuf;                             // SKIP SLV 485 HDR FOR RESPONSE
    len = strlen( cp );
    sprintf( hdr, "%04d0000", len );                       // FMT SLAVE HDR
    Clnt.write( hdr, 8 );
    Clnt.write( IoBuf, len );
    return( OK );
}
/*F******************************************************************
* RCV MSG FROM SLAVE
**********************************************************************/
int
rdSlv( int slvNbr )                               // *STR POINTS TO RCV BUFF
{
    int     ioNdx, adrNdx, slv, cnt, len;
    char    chr, addr[8], buf[128];
    ulong   end; 

    Com.listen();
    end = millis() + 250;                                     // SET TIMEOUT
    for( cnt =adrNdx =ioNdx =0; (millis() < end) ; )
    {                                               // READ CHARS FROM SLAVE
        if( Com.available() )
        {
            chr = Com.read();
            switch( cnt )
            {
                case 0:                               // READ SLAVE MSG MARK
                    if( chr != '#' )
                        return( NOTFND );       // NOT A VALID SLV MSG TO ME
                    cnt++;
                    break;
                case 1 ... 2:
                    addr[adrNdx++] = chr;              // READ SLAVE MSG LEN
                    cnt++;
                    break;
                default:
                    IoBuf[ioNdx++] = chr; // PUT NEW CHAR IN IOBUF, BUMP NDX
                    cnt++;
                    if( chr == NL )
                    {                                            // FOUND NL
                        IoBuf[ioNdx] =0;   // TERMINATE MSG FROM SLV, RMV NL
                        addr[2] =0;                     // TERMINATE SLV NBR
                        slv = atoi( addr );
                        if( (slv != slvNbr) || !cnt )
                            return( NOTFND );  // NOT MSG FOR ME, DISCARD IT
                        return( strlen( IoBuf ) );     // RETURN DATA LEN OK
                    }
            }             // END OF SWITCH
        }                 // END OF IF AVAIL
    }                    // END OF FOR
    return( NOTFND );                                          // TIMEOUT
}
/*F******************************************************************
* FWD REQUEST (IN IOBUF) TO SLAVE
**********************************************************************/
int 
wtSlv( int slvNbr, char *str )                // STR POINTS TO MSG FOR SLAVE
{
    char       chr, *cp, hdr[8];

    Com.listen();
    digitalWrite( ENATX, true );                 // TURN ON 485 WRITE ENABLE
    sprintf( hdr, "#%02d", slvNbr );                        // FMT SLAVE HDR
    Com.print( hdr );
    Com.print( IoBuf );
    Com.print( "\n" );                        // I DON'T LIKE PRINTLN() \r\n  
    digitalWrite( ENATX, false );               // TURN OFF 485 WRITE ENABLE
    return( OK );
}
/*F******************************************************************
* ZOT A CHAR IN A CHAR ARRAY, RTRN: &NEXT CHAR
**********************************************************************/
char*
zotChr( char *str, char chr )
{
    char      *cp;

    if( (cp = strchr( str, chr )))
        *cp++ = 0;                                // ZOT CHAR (REPL WITH ZERO)
    return( cp );
}
/*F******************************************************************
* FIND SLAVE NBR BY MATCHING MSG DEST ADDR
**********************************************************************/
int
fndSlv( char *str )
{
    int     ndx;
    char    buf[8];

    strncpy( buf, str, 2 );
    buf[2] = 0;                     // TERMINATE STRING
    for( ndx =1; ndx <= MAXVCTRS; ndx++ )
    {
        if( !strncmp( buf, Vctrs[ndx], 2 ))
            return( ndx );
    }
    return( NOTFND );
}
/*F******************************************************************
* POLL SLAVES
**********************************************************************/
int
poll( char *str )
{
    int      slv;
    char     hdr[16];
    ulong    timOt;
    
    for( slv =0; slv < MAXVCTRS ; slv++  )
    {
        sprintf( hdr, "#%02d\n", slv );
        wtSlv( slv, hdr );
    }
    NxtPoll = millis() + NXTPOLLTM;
}