News:

To visit MidNite Solar click this link www.midnitesolar.com

Main Menu

rs232 modbus to Classic

Started by dgd, February 05, 2015, 03:48:23 AM

Previous topic - Next topic

dgd

Some info on connecting an UNO to a Classic via rs232 modbus
Using a low cost UNO, rs232 shield and LCD 16by2 disply shield (with small keyboard) all up cost under $25, the only part that had to be made was a cable rj11 to DB9 plug. Connected to Classic middle rj11 socket MNGP/SLAVE.
The software library Simplemodbusmaster was used. The example program (sketch) provided with the lib is well detailed and and excellent explantionof how it all works. It supports the most common Modbus commands
Only a few software changes were made to get it working with a Classic.
I initially only wanted to read the four registers 4114, battery voltage, to 4117 (see Midnite Modbus register list)
So the only changes made were:

#define baud 19200
#define timeout 1000
#define polling 200 // the scan rate

// array for register values read from Classic
unsigned int readRegs[5];

..in the setup()
  modbus_construct(packet1, 10, READ_HOLDING_REGISTERS, 4114, 4, readRegs);
  modbus_configure(&Serial, baud, SERIAL_8N1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);

and that was all  :)

..in loop()
the  modbus_update()
does the communications in the background.
This command is repeated in loop(), I inserted a delay(200) to let the LCD update at a readable rate

I can provide the full sketch with LCD  displaying of the modbus values if anyone wants it.

dgd



Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

cabinrob

This sounds almost exactly like the project I was going to start planning!
I would love to get a copy of your sketch!
Have you considered an Ethernet shield to enable web access?
rob

mtdoc

Quote from: dgd on February 05, 2015, 03:48:23 AM

I can provide the full sketch with LCD  displaying of the modbus values if anyone wants it.


Yes, please!
Array 1: Sanyo HIT225 X 8 on Wattsun tracker. Array 2: Evergreen ES-E-225 X 12 on shed roof. Midnite e-panel with Outback GVFX3648, FNDC and Classic 150 X 2. 436 AH AGMs. Honda eu2000i X 2.

dgd

#3
Ok, a couple of days playing with this and it's getting nicely complicated and more useful  8)
Reading via rs232 modbus 20 registers from 4114 battery voltage up to the one that has FET temp
The 2 line by 16 char blue lcd now has four 2-line pages scrollable up and down using the up and down keypad buttons.
Showing charging state, battery volts and amps, watts and Kwhrs, PV volts and PV max volts, pcb and FET temps.

This is using the normal size UNO, rs232 shield and 1602 blue lcd with 5 key keypad shield.
I have a few Lites out at different sites so I'm intending to nicely box up these parts with a dc to dc converter to power it although it seems the 9volts available on the Middle serial rj12 in the Classic could power it. Then get one on each lite.
I had considered upgrading the Lites with MNGPs but the sites with classics have more support problems because locals want to fiddle with buttons and mngp display.
There is nothing worse than a 6 hour round trip to find some idiot has unintentionally turned off the charging mode.
At least with this UNO display there is no fatal keystrokes, even pressing the rest button only resets the UNO  :)
Dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

dgd

#4
Sketch below, sorry I could not get a server without paying $ or giving away all my personal details, to upload this too.
It seems to fit in one posting.
Its more documentation than code but is a work in progress.
Anyhow it works for me, despite lack of printf (formatted print) and all those tiresome print instructions
Remember you will need to google SimpleModbusMaster and load the library to your PC that you do Arduino development on.

dgd


#include <LiquidCrystal.h>

#include <SimpleModbusMaster.h>

// Connect Uno  via rs232 shield to Midnite Classic's rj11 MNGP/Slave rs232 port.
// uses Modbus RTU to retrieve modbus registers. UNO is master, Classic is slave.
// David Dix Jan 2015, dgd@kc.net.nz

/*
   SimpleModbusMaster allows you to communicate
   to any slave using the Modbus RTU protocol.
 
   To communicate with a slave you need to create a packet that will contain
   all the information required to communicate to the slave.
   Information counters are implemented for further diagnostic.
   These are variables already implemented in a packet.
   You can set and clear these variables as needed.
   
   The following modbus information counters are implemented:   
   
   requests - contains the total requests to a slave
   successful_requests - contains the total successful requests
   failed_requests - general frame errors, checksum failures and buffer failures
   retries - contains the number of retries
   exception_errors - contains the specific modbus exception response count
   These are normally illegal function, illegal address, illegal data value
   or a miscellaneous error response.
 
   And finally there is a variable called "connection" that
   at any given moment contains the current connection
   status of the packet. If true then the connection is
   active. If false then communication will be stopped
   on this packet until the programmer sets the connection
   variable to true explicitly. The reason for this is
   because of the time out involved in modbus communication.
   Each faulty slave that's not communicating will slow down
   communication on the line with the time out value. E.g.
   Using a time out of 1500ms, if you have 10 slaves and 9 of them
   stops communicating the latency burden placed on communication
   will be 1500ms * 9 = 13,5 seconds! 
   Communication will automatically be stopped after the retry count expires
   on each specific packet.
 
   All the error checking, updating and communication multitasking
   takes place in the background.
 
   In general to communicate with to a slave using modbus
   RTU you will request information using the specific
   slave id, the function request, the starting address
   and lastly the data to request.
   Function 1, 2, 3, 4, 15 & 16 are supported. In addition to
   this broadcasting (id = 0) is supported for function 15 & 16.

   Constants are provided for:
   Function 1  - READ_COIL_STATUS
   Function 2  - READ_INPUT_STATUS
   Function 3  - READ_HOLDING_REGISTERS
   Function 4  - READ_INPUT_REGISTERS
   Function 15 - FORCE_MULTIPLE_COILS
   Function 16 - PRESET_MULTIPLE_REGISTERS
   
   Note: 
   The Arduino serial ring buffer is 128 bytes or 64 registers.
   Most of the time you will connect the Arduino using a MAX485 or similar.

   In a function 3 or 4 request the master will attempt to read from a
   slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES
   and two BYTES CRC the master can only request 122 bytes or 61 registers.

   In a function 16 request the master will attempt to write to a
   slave and since 9 bytes is already used for ID, FUNCTION, ADDRESS,
   NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write
   118 bytes or 59 registers.
   
   Note:
   Using a USB to Serial converter the maximum bytes you can send is
   limited to its internal buffer which differs between manufactures.

   Since it is assumed that you will mostly use the Arduino to connect without
   using a USB to Serial converter the internal buffer is set the same as the
   Arduino Serial ring buffer which is 128 bytes.
   
   The example will use packet1 to read a register from address 0 (the adc ch0 value)
   from the arduino slave. It will then use this value to adjust the brightness
   of an led on pin 9 using PWM.
   It will then use packet2 to write a register (its own adc ch0 value) to address 1
   on the arduino slave adjusting the brightness of an led on pin 9 using PWM.
*/

// number of two line LCD display pages
#define HIGH_PAGE 3

//////////////////// Port information ///////////////////
#define baud 19200
#define timeout 1000
#define polling 200 // the scan rate

// If the packets internal retry register matches
// the set retry count then communication is stopped
// on that packet. To re-enable the packet you must
// set the "connection" variable to true.
#define retry_count 10

// used to toggle the receive/transmit pin on the driver
#define TxEnablePin 2

#define LED 9

// This is the easiest way to create new packets
// Add as many as you want. TOTAL_NO_OF_PACKETS
// is automatically updated.
enum
{
  PACKET1,
  TOTAL_NO_OF_PACKETS // leave this last entry
};

// Create an array of Packets to be configured
Packet packets[TOTAL_NO_OF_PACKETS];


// Create a packetPointer to access each packet
// individually. This is not required you can access
// the array explicitly. E.g. packets[PACKET1].id = 2;
// This does become tedious though...
packetPointer packet1 = &packets[PACKET1];
// packetPointer packet2 = &packets[PACKET2];

// Data read from the arduino slave will be stored in this array
// if the array is initialized to the packet.
unsigned int readRegs[21];

// Data to be written to the arduino slave
// unsigned int writeRegs[1];

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int page=0;  // display page on LCD
// define some values used by the panel and buttons
int lcd_key     = 0;
int adc_key_in  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

void setup()
{
  lcd.begin (16,2);
 
  // setup LCD display
 
  // The modbus packet constructor function will initialize
  // the individual packet with the assigned parameters. You can always do this
  // explicitly by using struct pointers. The first parameter is the address of the
  // packet in question. It is effectively the "this" parameter in Java that points to
  // the address of the passed object. It has the following form:
  // modbus_construct(packet, id, function, address, data, register array)
 
  // For functions 1 & 2 data is the number of points
  // For functions 3, 4 & 16 data is the number of registers
  // For function 15 data is the number of coils
 
  // read 20 register starting at address 4114 
  modbus_construct(packet1, 10, READ_HOLDING_REGISTERS, 4114, 20, readRegs);
 
  // write 1 register starting at address 1 
//  modbus_construct(packet2, 2, PRESET_MULTIPLE_REGISTERS, 1, 1, writeRegs);
 
  // P.S. the register array entries above can be different arrays
 
  /* Initialize communication settings:
     parameters(HardwareSerial* SerialPort,
long baud,
unsigned char byteFormat,
unsigned int timeout,
unsigned int polling,
unsigned char retry_count,
unsigned char TxEnablePin,
Packet* packets,
unsigned int total_no_of_packets);

     Valid modbus byte formats are:
     SERIAL_8N2: 1 start bit, 8 data bits, 2 stop bits
     SERIAL_8E1: 1 start bit, 8 data bits, 1 Even parity bit, 1 stop bit
     SERIAL_8O1: 1 start bit, 8 data bits, 1 Odd parity bit, 1 stop bit
     
     You can obviously use SERIAL_8N1 but this does not adhere to the
     Modbus specifications. That said, I have tested the SERIAL_8N1 option
     on various commercial masters and slaves that were suppose to adhere
     to this specification and was always able to communicate... Go figure.
     
     These are already defined in the Arduino global name space.
  */
  modbus_configure(&Serial, baud, SERIAL_8N1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);
 
// pinMode(LED, OUTPUT);
}

void loop()
{
 
  float batt_volts, in_volts, batt_amps, kwh, high_in_volts;
  int state, watts;
  float fet_temp, pcb_temp;

  // read the Classic's modbus registers
  modbus_update();
 
// writeRegs[0] = analogRead(A0); // update data to be written to arduino slave
 
// analogWrite(LED, readRegs[0]>>2); // constrain adc value from the arduino slave to 255
 
  /* You can check or alter the internal counters of a specific packet like this:
     packet1->requests;
     packet1->successful_requests;
     packet1->failed_requests;
     packet1->exception_errors;
     packet2->requests;
     packet2->successful_requests;
     packet2->failed_requests;
     packet2->exception_errors;
  */
 
  // make Classic register values ready for LCD
     batt_volts = readRegs[0];
     batt_volts /=10;
     in_volts = readRegs[1];
     in_volts /=10;
     batt_amps = readRegs[2];
     batt_amps /=10;
     kwh= readRegs[3];
     kwh /=10;
     watts = readRegs[4];
     state = (unsigned int)readRegs[5] >> 8;  // high byte contains charge state code
     high_in_volts = readRegs[8];
     high_in_volts /=10;
     pcb_temp = readRegs[18];
     pcb_temp /=10;
     fet_temp = readRegs[19];
     fet_temp /=10;

     // top line of lcd
     lcd.setCursor(0,0);
     if (page==0)         // first page of 2 lines on LCD displays Clssic state on top
     {                    // line and battery voltage and current on second line
      switch (state) {
       case 0:
          lcd.print ("Resting  ");
          break;
       case 3:
          lcd.print ("Absorb   ");
          break;
       case 4:
          lcd.print ("BulkMppt ");
         break;
       case 5:
          lcd.print ("Float    ");
          break;
       case 6:
          lcd.print ("FloatMppt");
          break;
       case 7:
          lcd.print ("Equalize ");
          break;
       case 10:
          lcd.print ("HyperVoc ");
          break;
       case 18:
          lcd.print ("EqMppt   ");
          break;
      }
     

     // second line of page 0
     
     lcd.setCursor(0,1);
     lcd.print("Batt ");
     lcd.print(batt_volts);
     lcd.setCursor(9,1);
     lcd.print("v ");
     lcd.print(batt_amps);
     if (batt_amps < 10)  // dont want that extra zero
     {
       lcd.setCursor(14,1);
       lcd.print ("a ");
     }
     else
     {
        lcd.setCursor(15,1);
        lcd.print ("a");
     }
   } // end of page 0
   
   if (page==1)           // second page of two LCD lines shows Watts on first line and
   {                      // Energy Kw/hrs on second line
     lcd.setCursor(0,0);
     lcd.print ("Watts ");
     lcd.print(watts);

     lcd.setCursor(0,1);
     lcd.print("KwHrs ");
     lcd.print(kwh);
   } // end of page 1
   
   if (page == 2)         // third page of two LCD lines shows PV input voltage on first line
   {                      // and PV in max voltage on second lione   
     lcd.setCursor(0,0);
     lcd.print("PV input ");
     lcd.print(in_volts);
     if (in_volts > 99.9)
     lcd.setCursor(14,0);
     else
     lcd.setCursor(13,0);
     lcd.print("v ");
     
     lcd.setCursor(0,1);
     lcd.print("PV max   ");
     lcd.print(high_in_volts);
     if (high_in_volts > 99.9)
     {
       lcd.setCursor(14,1);
       lcd.print ("v ");
     }
     else
     {
       lcd.setCursor(13,1);
       lcd.print("v  ");
     }
   }  // end page 2
   
   if (page == 3)         // fourth page of two LCD lines has PCB temp on top line and FET
   {                      // temp on second line
     lcd.setCursor(0,0);
     lcd.print("Temps  PCB ");
     lcd.print (pcb_temp);   
     lcd.setCursor(0,1);
     lcd.print("       FET ");
     lcd.print (fet_temp);

   }   // end page 3
   
// check if keypad button pressed - presently only looking for UP and DOWN keys to scroll
// up/down beween disply pages, further pages on way...

lcd_key = read_LCD_buttons();  // read the buttons

switch (lcd_key)               // depending on which button was pushed, we perform an action
{
   case btnRIGHT:
     {
     break;
     }
   case btnLEFT:
     {
     break;
     }
   case btnUP:
     {
     if (page == 0) page = HIGH_PAGE;
     else page -= 1;
     lcd.clear();
     break;
     }
   case btnDOWN:
     {
     if (page >= HIGH_PAGE) page=0;
     else page += 1;
     lcd.clear();
     break;
     }
   case btnSELECT:
     {
     break;
     }
     case btnNONE:
     {
     break;
     }
}


// delay to stop LCD flickering, refreshes display 300ms
  delay(300);
 
}

// read the buttons
int read_LCD_buttons()
{
adc_key_in = analogRead(0);      // read the value from the sensor
// my buttons when read are centered at these valies: 0, 144, 329, 504, 741
// we add approx 50 to those values and check to see if we are close
if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
// For V1.1 us this threshold
if (adc_key_in < 50)   return btnRIGHT; 
if (adc_key_in < 250)  return btnUP;
if (adc_key_in < 450)  return btnDOWN;
if (adc_key_in < 650)  return btnLEFT;
if (adc_key_in < 850)  return btnSELECT; 

// For V1.0 comment the other threshold and use the one below:
/*
if (adc_key_in < 50)   return btnRIGHT; 
if (adc_key_in < 195)  return btnUP;
if (adc_key_in < 380)  return btnDOWN;
if (adc_key_in < 555)  return btnLEFT;
if (adc_key_in < 790)  return btnSELECT;   
*/
return btnNONE;  // when all others fail, return this...
}

Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

dgd

At one of my Classic Lite locations I need to control a vent fan, a night light and I would like to have an additional small cooling fan that directs airflow around the back of aluminum plate that has the Classic and another item surface mounted on it. The plate is on 1 inch standoffs attached to a cement block wall.
The Lite has a WbJr so only AUX1 is left

Now with the UNO attached there are many available digital and analogue control outputs. So one update I plan to make is connect some of the UNO digital outputs to this 8 port relay control board.
It uses Omron 240v 50/60Hz 2amp relays as I want to use two CFL night lights, AC vent fan and AC boxer cooling fan.

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

mtdoc

#6
Most excellent dgd. Thank you!

I happen to have a few 'duinos sitting around and also the same LCD and RS232 shields you used.

I've been working on other non PV system related electronics projects but  have been meaning to get back to some things useful to my PV system.  This is just the ticket to get me back to it. 
Array 1: Sanyo HIT225 X 8 on Wattsun tracker. Array 2: Evergreen ES-E-225 X 12 on shed roof. Midnite e-panel with Outback GVFX3648, FNDC and Classic 150 X 2. 436 AH AGMs. Honda eu2000i X 2.

dgd

Quote from: cabinrob on February 05, 2015, 04:57:36 PM
This sounds almost exactly like the project I was going to start planning!
I would love to get a copy of your sketch!
Have you considered an Ethernet shield to enable web access?
rob

I'm working on this now using a W5100 based ethernet shield with micro SD card storage.  I can get the web server working and taking data via rs232 modbus from Classic.
Or more exactly, I'm storing a log of data from the Classic on an SD card. The only web page I'm playing with now is a text type lookalike of an early MN designed page that showed a page of about 20 entries in a table of voltages, amps,kwhr, flags etc (node 10 page I think it was called).  Each line of data was on a 10 minute interval.
MN did not use this text table display page when MyMidnite was released.

It does not appear to be that difficult to store a graphical web page frame on SD then dynamically serve a page with Classic running values in it. I'm thinking of a 'skin' type design that refreshed often enough to be useful.

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

dgd

Mtdoc,
I hope you can copy and paste the sketch.
Let me know if you get it going and of course any code improvement changes/additions would be very welcome  :)
dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

mtdoc

Yes, I'll let you know. It will be a few weeks before I can really dig in to this. I'll need to make the cable. If I get it working well, I plan to interface it with LabView using the LabView Interface For Arduino toolkit and get a GUI going on my system monitoring computer.  I've wanted to do this for a while but hadn't had the time to learn Modbus so that I could brew my own solution. It looks like this will let me bypass that step and get my lazy butt going on it!

Thanks again dgd!
Array 1: Sanyo HIT225 X 8 on Wattsun tracker. Array 2: Evergreen ES-E-225 X 12 on shed roof. Midnite e-panel with Outback GVFX3648, FNDC and Classic 150 X 2. 436 AH AGMs. Honda eu2000i X 2.

cabinrob

dgd,

Thanks for posting the sketch.
Let us know how the Ethernet connection turns out.
You're many steps ahead of me - given that I haven't even taken delivery of my Classic yet - but hopefully I'll be able to contribute once I've got some hardware on my bench.
What I don't get, is why Midnite Solar isn't doing something similar?  They could even sell an Arduino based system in a nice box + some software and we would be all good to go.

xsnrg

dgd, nice work!  It is a lot of fun seeing what people can think to do with all the "little boards that can" that are floating around now.
3x 250w Renogy RNG-250D
1x MidNite KID w/WBjr and MNBTS
1x 12v 100Ah el cheapo deep cycle
1x 300w PST-300-12 Samlex pure sine
http://www.howardweb.org/weather/solar/index.html

dgd

Quote from: xsnrg on February 06, 2015, 08:34:17 PM
dgd, nice work!  It is a lot of fun seeing what people can think to do with all the "little boards that can" that are floating around now.

Yes it is, I must admit I'm still surprised by how versatile and capable the arduino stuff is.
This little project is so dead easy that anyone could do it (and should!), the only issue is making or have made a suitable cable.
The three cards are cheap on EBay and simply plug together.
ALthough I intended it for a Classic Lite it will also be useful with a normal Classic to at least provide many more AUX outputs.
The Arduino UNO even has several PWM outputs so will be capable of PWM controlling an SSR for water heating (or similar)

I can think of many more uses for this with the Classic.
The grail will definitely be a proper web server for the Classic, that should not be too far away.

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

dgd

#13
a sketch update
to power off the LCD backlight after no keypad activity for about 1 minute. Make LCDOFF_TIME larger for a longer delay
Any key pressed activates backlight
dgd

---
a new define
#define LCDOFF_TIME 200
and two new global variables
int lcdoff_counter = 0;
int lcdoff = 0;

code to insert after the line with
lcd_key = read_LCD_buttons();  // read the buttons
in the loop() routine

// lcd backlight on/off control timeout

if (lcd_key == btnNONE)
{
   if (lcdoff_counter++ >= LCDOFF_TIME)
   {
     pinMode(10, OUTPUT);
     digitalWrite(10,LOW);   // pin 10 in output low turns off lcd backlight
     lcdoff=1;
     lcdoff_counter--;
   }
}
else
{
   lcdoff_counter = 0;
   if (lcdoff)
   {
      pinMode(10, INPUT_PULLUP);  // pin 10 input pulled high turns on backlight
      lcd_key = btnNONE;               // so keypress only activates backlight
    }
   lcdoff = 0;
}

Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

mtdoc

Hi dgd,

I had a few free moments last night so I  plugged your code into the Arduino IDE last night - but it wouldn't compile. It looks like there's an issue with your  packet array and pointer type definitions. I'm a C newbie so it would take me some time to figure out how to best to fix it - but since it's your code, I thought I'd throw it your way first.  Or maybe I goofed  with my cut and paste?  Have you tried to compile the code exactly as you posted it here?

Thanks for your work on this!
Array 1: Sanyo HIT225 X 8 on Wattsun tracker. Array 2: Evergreen ES-E-225 X 12 on shed roof. Midnite e-panel with Outback GVFX3648, FNDC and Classic 150 X 2. 436 AH AGMs. Honda eu2000i X 2.