Hi, I have built an Arduino Nano based MODBUS datalogger and I have one remaining strange bug someone might be able to shed some light on.
I am using the SimpleModbusMasterV2rev2 library, an RS485 to TTL adaptor wired to digital pins 0, 1 and 2 (with DE and RE both tied to D2) and a Hitachi hd44780 16x2 character LCD on pins 3 to 9. (there is also an SD card interface wired to the SPI inteface (D10-13), some buttons wired between A0 and +5V with series resistors, LEDs between pins A1-A3 and ground with series resistors and real time clock on the I2C interface (A4-A5), these are still connected but I have created a stripped down version of my code omitting everything except the MODBUS comms and output to the LCD). I am reading an ABB B21 electric meter and for testing am reading the least signifcant parts of the energy, voltage, current and frequency registers.
I was originally testing with a baud rate of 9600 and 8E1 Data/Parity/Stop Bits. I was reading 4 holding registers into a unsigned int array regs[], and expected them to be in regs[0] to regs[3], but when I retrieved them they are offset by one, with the result from the register defined in packet[0] returned in regs[1].. packet [2] in regs[3] and packet[3] in regs[0]
Then I started testing other baud rates and Data/Parity/Stop Bit settings and these change the array subscript the values are returned in, e.g. with 9600 and 8O1 the values are in the expected places in the array (regs[0] corresponding to packet[0] etc), with 9600 and 8N2 the result is as for 9600, 8E1
If I only have one packet defined the code always works as expected with regs[0] corresponding to packet[0] whatever the baud rate and Data/Parity/Stop Bit settings.
Here is my stripped down code. When I try interrogating different numbers of registers (let's call this n), I am changing
#define TOTAL_NO_OF_REGISTERS n
Commenting out the highest numbered modbus_construct calls as appropriate
Changing the penultimate parameter in the modbus_configure call to n
I am leaving the lcd.print commands and as one would expect they just print garbage when the array subscript exceeds the size of the array.
Further testing shows:
Values returned correctly with odd parity and 1200, 2400, 4800, 9600, 38400 or 5600 baud
Values in wrong order on odd parity with 19200 or 115200 baud, or 9600 to 115200 with even or no parity bit, no values returned at 1200, 2400 or 4800 baud if using even or no parity. Similar results with 1,3 or 4 packets (=registers) (except obviously if only one packet and register in use can't see the order problem, value is in subscript zero).
//___________________________________
//LIBRARIES
#include <LiquidCrystal.h> //LCD Library
#include <SimpleModbusMaster.h> //Modbus RTU
//DEFINITIONS to insert into code at compile time (so end up in flash memory not RAM)
//LCD Pins
#define pin_RS 8 //arduino pin wired to LCD RS
#define pin_EN 9 //arduino pin wired to LCD EN
#define pin_d4 4 //arduino pin wired to LCD d4
#define pin_d5 5 //arduino pin wired to LCD d5
#define pin_d6 6 //arduino pin wired to LCD d7
#define pin_d7 7 //arduino pin wired to LCD d8
#define pin_BL 3 //arduino pin wired to LCD backlight circuit
//Modbus
#define timeout 1000 //Maximum time for slave to respond (ms)
#define polling 2000 //Maximum scan rate of master to allow slave to return to idle (ms)
#define retry_count 10 //Maximum retries if slave returns response timeout or error
#define TxEnablePin 2 //Pin to set RS485 interface to transmit or receive (set pin high to Transmit, sets DE and RE on RS485 PCB high. DE must be high to transmit and RE low to receive)
//Misc
#define LongIntMax 4294967295 //Maximum value of a long integer, used in timer check in case value has reset to zero in the interval
#define LCD_UD_Int 900 //LCD Update Interval (ms). Should happen just over once a second so clock seconds increment nicely
//GLOBAL VARIABLES
unsigned long Prev_LCD_UD = 0; //Timer at last LCD update
#define TOTAL_NO_OF_REGISTERS 4 //Max number of registers to log, used to allocate memory, same as total packets as all packets are sized for one register
// Create an array of Packets to be configured
Packet packets[TOTAL_NO_OF_REGISTERS];
//Create an array to hold returned data in its raw form of unsigned integers
unsigned int holdingRegs[TOTAL_NO_OF_REGISTERS];
//INITIALISE DEVICES
LiquidCrystal lcd( pin_RS, pin_EN, pin_d4, pin_d5, pin_d6, pin_d7); //Initialise LCD
void setup()
{
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Initialise backlight
//BL_Timeout = Def_BL_Timeout; //Backlight timeout to default
digitalWrite(pin_BL, LOW); //Ensures pullup disabled to avoid damage due to backlight circuit bad design
pinMode(pin_BL, INPUT); //Set Backlight On
//Initialise RS485 Pin
pinMode(TxEnablePin, OUTPUT);
digitalWrite(TxEnablePin,LOW);//Receive 485
//Initialise modbus
modbus_construct(&packets[0], 6, READ_HOLDING_REGISTERS, 20483, 1, 0); //Energy
modbus_construct(&packets[1], 6, READ_HOLDING_REGISTERS, 23297, 1, 1); //Voltage
modbus_construct(&packets[2], 6, READ_HOLDING_REGISTERS, 23309, 1, 2); //Current
modbus_construct(&packets[3], 6, READ_HOLDING_REGISTERS, 23340, 1, 3); //Frequency
modbus_configure(&Serial, 9600, SERIAL_8E1, timeout, polling, retry_count, TxEnablePin, packets, 4 ,holdingRegs); //Changed from max allowable registers (TOTAL_NO_OF_REGISTERS) to actual (x)
}
void loop()
{
//Poll the MODBUS devices
modbus_update();
//Update the LCD periodically
if(Interval_Check(Prev_LCD_UD, LCD_UD_Int))
{
//Clear Display
lcd.clear(); //In case data not entirely erased by new data
//Write current values of up to 4 registers
lcd.setCursor(0,0); //LCD Column (0-15), row (0-1)
lcd.print(holdingRegs[0]);
lcd.setCursor(8,0);
lcd.print(holdingRegs[1]);
lcd.setCursor(0,1);
lcd.print(holdingRegs[2]);
lcd.setCursor(8,1);
lcd.print(holdingRegs[3]);
//Reset LCD refresh timer
Prev_LCD_UD=millis();
}
}
byte Interval_Check(unsigned long datum, unsigned long interval) //return 1 if interval has elapsed
{
unsigned long current_time = millis();
if ((current_time < datum) && (((LongIntMax - datum) + (current_time)) > interval))
{
return 1;
}
else if ((current_time < datum) && (((LongIntMax - datum) + (current_time)) <= interval))
{
return 0;
}
else if ((current_time - datum) > interval)
{
return 1;
}
else
{
return 0;
}
}
//________________________________________________________________
Any advice welcomed!
Andrew Mitchell
t.a.mitchell@ex.ac.uk