Simple, tiny minimal library to implement a ModBus slave in mbed.

Files at this revision

API Documentation at this revision

Comitter:
fbcosentino
Date:
Fri Feb 22 14:31:13 2019 +0000
Commit message:
Initial commit

Changed in this revision

tinymal_modbus.cpp Show annotated file Show diff for this revision Revisions of this file
tinymal_modbus.h Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r 4f5da8a923c4 tinymal_modbus.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tinymal_modbus.cpp	Fri Feb 22 14:31:13 2019 +0000
@@ -0,0 +1,239 @@
+#include "tinymal_modbus.h"
+
+uint16_t tiny_regs[TINYMOD_NUM_REGS];
+
+uint8_t tinymod_address = 1;
+
+int tinymod_mode = TINYMOD_MODE_SLAVE;
+
+char tinymod_buf[257];
+char tinymod_resp_buf[257];
+uint32_t tiny_buf_cur = 0; // cursor to receiving buffer (and buffer length, at same time)
+
+// send ringbuffer
+uint32_t tiny_sendbuf_cur = 0; // cursor to send buffer start
+uint32_t tiny_sendbuf_len = 0; // size of send buffer
+
+bool tinymod_ready_to_process = 0; // Flag indicating when ProcessMessage should run
+
+Ticker tinymod_ticker;
+
+Serial * ser;
+DigitalOut * driver;
+
+uint32_t tinymod_cnt = 0;
+
+int _tx_completed = 0;
+
+void tinymod_RX(void) {
+    uint8_t c;
+    c = ser->getc();
+    if (tiny_buf_cur < 256) {
+       // Adds this char to buffer
+       tinymod_buf[ tiny_buf_cur ] = c;
+       tiny_buf_cur++;
+       // reset countdown to 9 (900us)
+       tinymod_cnt = 9; 
+    }
+}
+void tinymod_Send(void) {
+    if (tiny_sendbuf_cur < tiny_sendbuf_len) {
+        driver->write(1);
+        _tx_completed = 0;
+        ser->putc(tinymod_resp_buf[tiny_sendbuf_cur]);
+        tiny_sendbuf_cur++;
+    }
+}
+void tinymod_TX(void) {
+    if (tiny_sendbuf_cur < tiny_sendbuf_len) {
+        // Send one more
+        tinymod_Send();
+    }
+    else {
+        wait(0.000050);
+        driver->write(0);
+        _tx_completed = 1;
+    }
+}
+
+
+// Runs every 100us. Decrements tinymod_cnt. If reaches zero, processes buffer
+// Every time a byte is received, tinymod_cnt is set to 9 -- that is, a 900us coundown
+// It will only reach zero when there is a 900us silence in RX -- that is, end of packet
+void tinymod_tick(void) {
+    if (tinymod_cnt > 0) {
+        tinymod_cnt--;
+        if (tinymod_cnt == 0) tinymod_ready_to_process = 1; 
+    }
+}
+
+void tinymod_Init(Serial * ser_obj, DigitalOut * driver_obj) {
+    int i;
+    ser = ser_obj;
+    driver = driver_obj;
+    
+    driver->write(1);
+    wait(0.5);
+    driver->write(0);
+    
+    ser->attach(tinymod_RX, RawSerial::RxIrq);
+    ser->attach(tinymod_TX, RawSerial::TxIrq);
+    
+    tinymod_ticker.attach(&tinymod_tick, 0.0001);
+    
+    for (i=0; i<TINYMOD_NUM_REGS; i++) {
+        tiny_regs[i] = 0xFF00 | i;
+    }
+
+
+}
+
+void tinymod_Address(uint8_t addr) {
+    tinymod_address = addr;
+}
+
+uint16_t tinymod_chksum(char * buf, int buflen) {
+    uint16_t crc = 0xFFFF;
+    int i, j;
+    for (i=0; i<buflen; i++) {
+        crc ^= (uint16_t)buf[i];
+        
+        for (j=8; j>0; j--) {
+            if ((crc & 0x0001) != 0) {
+                crc >>= 1;
+                crc ^= 0xA001;
+            }
+            else crc >>= 1;
+        }
+    }
+    return crc;
+}
+
+void tinymod_ReadRegs(uint8_t * buf, uint32_t buflen) {
+    uint16_t start_addr, reg_count, val, crc;
+    int i, len;
+    
+    if (tinymod_mode == TINYMOD_MODE_SLAVE) {
+        if (buflen < 4) return;
+        start_addr = ((uint16_t)buf[0] << 8) | buf[1];
+        reg_count = ((uint16_t)buf[2] << 8) | buf[3];
+        // Do we have what is asked for?
+        if ((start_addr < TINYMOD_NUM_REGS) && ( (start_addr+reg_count) <= TINYMOD_NUM_REGS ) && (reg_count <= 125)) {
+            tinymod_resp_buf[0] = tinymod_address;
+            tinymod_resp_buf[1] = MODBUS_FUNC_READ;
+            tinymod_resp_buf[2] = reg_count*2; 
+            len = 3;
+            for (i = 0; i<reg_count; i++) {
+                val = tiny_regs[start_addr + i];
+                tinymod_resp_buf[3 + 2*i] = (val >> 8) & 0xFF;
+                tinymod_resp_buf[4 + 2*i] = (val     ) & 0xFF;
+                len += 2;
+            }
+            crc = tinymod_chksum(tinymod_resp_buf, 3 + reg_count*2);
+            tinymod_resp_buf[3 + reg_count*2] = (crc     ) & 0xFF; // CRC IS LITTLE ENDIAN
+            tinymod_resp_buf[4 + reg_count*2] = (crc >> 8) & 0xFF;
+            len += 2;
+            
+            tiny_sendbuf_cur = 0;
+            tiny_sendbuf_len = len;
+            
+            driver->write(1);
+            
+            wait(0.0019);
+
+            tinymod_Send();
+            while (_tx_completed == 0) wait(0.000010);
+            
+            wait(0.0009);
+        }
+       
+    }
+}
+void tinymod_WriteSingReg(uint8_t * buf, uint32_t buflen) {
+   // not implemented
+}
+void tinymod_WriteRegs(uint8_t * buf, uint32_t buflen) {
+    uint16_t start_addr, reg_count, byte_count, val, crc;
+    int i;
+    
+    if (tinymod_mode == TINYMOD_MODE_SLAVE) {
+        if (buflen < 5) return;
+        start_addr = ((uint16_t)buf[0] << 8) | buf[1];
+        reg_count  = ((uint16_t)buf[2] << 8) | buf[3];
+        byte_count = ((uint8_t )buf[4]);
+        if (byte_count != (reg_count*2)) return;
+        // Do we have what is asked for?
+        if ((start_addr < TINYMOD_NUM_REGS) && ( (start_addr+reg_count) <= TINYMOD_NUM_REGS ) && (reg_count <= 124)) {
+            // Write registers
+            for (i=0; i<reg_count; i++) {
+                val = ((uint16_t)buf[5 + 2*i] << 8) | (uint8_t)buf[6 + 2*i];
+                tiny_regs[start_addr + i] = val;
+            }
+            
+            // Response
+            tinymod_resp_buf[0] = tinymod_address;
+            tinymod_resp_buf[1] = MODBUS_FUNC_WRITE_MULT;
+            tinymod_resp_buf[2] = (start_addr >> 8) & 0xFF;
+            tinymod_resp_buf[3] = (start_addr     ) & 0xFF;
+            tinymod_resp_buf[4] = (reg_count >> 8) & 0xFF;
+            tinymod_resp_buf[5] = (reg_count     ) & 0xFF;
+            
+            crc = tinymod_chksum(tinymod_resp_buf, 6);
+            tinymod_resp_buf[6] = (crc     ) & 0xFF; // CRC IS LITTLE ENDIAN
+            tinymod_resp_buf[7] = (crc >> 8) & 0xFF;
+
+            tiny_sendbuf_cur = 0;
+            tiny_sendbuf_len = 8;
+
+            driver->write(1);
+            wait(0.0019);
+            
+            tinymod_Send();
+            while (_tx_completed == 0) wait(0.000010);
+            
+            wait(0.0009);
+        }
+    }    
+    
+}
+
+void tinymod_ProcessMessage(void) {
+    int dest_addr;
+    int func_num;
+    uint16_t rc_chksum, cl_chksum;
+
+    // Ignore if less than 4 chars (wee need at least address, function, checksum)
+    if (tiny_buf_cur >= 4) {
+        dest_addr = tinymod_buf[0];
+        // if the destination address for this packet is... me?
+        if (dest_addr == tinymod_address) {
+            // received checksum:
+            rc_chksum = 0x0000 | tinymod_buf[ tiny_buf_cur-1 ];
+            rc_chksum = (rc_chksum << 8) | tinymod_buf[ tiny_buf_cur-2 ];
+            // calculated chksum:
+            cl_chksum = tinymod_chksum(tinymod_buf, tiny_buf_cur-2 );
+            if (rc_chksum == cl_chksum) {
+                // We have a valid Modbus message!
+                func_num = tinymod_buf[1];
+                if      (func_num == MODBUS_FUNC_READ)          tinymod_ReadRegs(    (uint8_t *)&tinymod_buf[2], tiny_buf_cur-4) ;
+                else if (func_num == MODBUS_FUNC_WRITE_SINGLE)  tinymod_WriteSingReg((uint8_t *)&tinymod_buf[2], tiny_buf_cur-4) ;
+                else if (func_num == MODBUS_FUNC_WRITE_MULT)    tinymod_WriteRegs(   (uint8_t *)&tinymod_buf[2], tiny_buf_cur-4) ;
+            }
+            
+        }
+        else {
+            // not for me, ignore
+        }
+    }
+    
+    tiny_buf_cur = 0;
+}
+
+
+void tinymod_Check(void) {
+    if (tinymod_ready_to_process) {
+        tinymod_ProcessMessage();
+        
+        tinymod_ready_to_process = 0;
+    }
+}
\ No newline at end of file
diff -r 000000000000 -r 4f5da8a923c4 tinymal_modbus.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tinymal_modbus.h	Fri Feb 22 14:31:13 2019 +0000
@@ -0,0 +1,89 @@
+/** @file tinymal_modbus.h
+ *  Library to implement a ModBus slave in mbed. 
+ *  @note Currently supports 38400 baud rate only.
+ * 
+ *  @author Fernando Cosentino
+ *  os.mbed.com/users/fbcosentino
+ *
+ *  Example:
+ *  @code
+ *  #include "mbed.h"
+ *  #include "tinymal_modbus.h"
+ *
+ *  // pins are for LPC11U35
+ *  Serial rs485(P0_19, P0_18);  // a serial port object
+ *  DigitalOut driver_pin(P0_2); // direction driver pin
+ *  DigitalOut coil1(P0_20);     // a coil - this is the application
+ *
+ *  int main() {
+ *      rs485.baud(38400);
+ *      tinymod_Init(&rs485, &driver_pin);
+ *      tinymod_Address(10);
+ *
+ *      // tiny_regs is the array containing the modbus registers
+ *      tiny_regs[0] = 0; // Sets an initial value to register 0
+ *
+ *      while(1) {
+ *          // call this periodically to process the messages
+ *          tinymod_Check();
+ *
+ *          // make the coil reflect the internal modbus register
+ *          if (tiny_regs[0] > 0) {
+ *              coil1 = 1;
+ *          }
+ *          else {
+ *              coil1 = 0;
+ *          }
+ *      } 
+ *  }
+ *  @endcode
+ */
+
+#include "mbed.h"
+
+#define TINYMOD_NUM_REGS    16
+
+#define TINYMOD_MODE_SLAVE   0
+#define TINYMOD_MODE_MASTER  1
+
+#define MODBUS_FUNC_READ            3
+#define MODBUS_FUNC_WRITE_SINGLE    6
+#define MODBUS_FUNC_WRITE_MULT      16
+
+
+extern uint8_t tinymod_address;
+extern int tinymod_mode;
+
+
+/**
+ *  Array storing the register values - your application will work with this.
+ */
+extern uint16_t tiny_regs[TINYMOD_NUM_REGS];
+
+
+/**
+ *  Initialises the internal Modbus receiver based on the specified serial port
+ *  direction driver objects. You must initialise those objects yourself before
+ *  instancing the tinymal modbus object.
+ *
+ *  @param ser_obj Serial instance of the Serial class.
+ *  @param driver_obj Digital output pin instance of the DigitalOut class
+ */
+void tinymod_Init(Serial * ser_obj, DigitalOut * driver_obj);
+
+/**
+ *  Sets the listening address for this device. Valid addresses are in the
+ *  range of 0 - 247 decimal (0x00 - 0xF7).
+ *
+ *  @param addr Address this device should respond to (0 - 247)
+ */
+void tinymod_Address(uint8_t addr);
+
+/**
+ *  Internally processes any received messages - you must call this as often
+ *  as possible (ideally once per main loop if your main loop is short).
+ *  Messages are not automatically processed to keep interrupt handling to the
+ *  bare minimum, so you have the freedom to decide when you are going to spend
+ *  processor time processing the message via this function call.
+ */
+void tinymod_Check(void);
\ No newline at end of file