Reading and Writing Registers

This example writes a setting value to a holding register, reads it to confirm the value has changed, and then reads several data values from holding registers.


PlatformIO Configuration

1; PlatformIO Project Configuration File
2;
3; Build options: build flags, source filter
4; Upload options: custom upload port, speed and extra flags
5; Library options: dependencies, extra library storages
6; Advanced options: extra scripting
7;
8; Please visit documentation for the other options and examples
9; http://docs.platformio.org/page/projectconf.html
10
11[platformio]
12description = Changing a setting and reading data from a modbus sensor
13
14[env:mayfly]
15monitor_speed = 57600
16board = mayfly
17platform = atmelavr
18framework = arduino
19lib_deps =
20 SensorModbusMaster

The Complete Code

1/** =========================================================================
2 * @example{lineno} readWriteRegister.ino
3 * @author Sara Geleskie Damiano <sdamiano@stroudcenter.org>
4 * @copyright Stroud Water Research Center
5 * @license This example is published under the BSD-3 license.
6 *
7 * @brief This example writes a setting value to a holding register, reads it to confirm
8 * the value has changed, and then reads several data values from holding registers.
9 *
10 * The register numbers in this example happen to be for an S::CAN oxy::lyser.
11 *
12 * @m_examplenavigation{example_read_write_register,}
13 * @m_footernavigation
14 * ======================================================================= */
15
16// ---------------------------------------------------------------------------
17// Include the base required libraries
18// ---------------------------------------------------------------------------
19#include <Arduino.h>
20#include <SensorModbusMaster.h>
21
22// ---------------------------------------------------------------------------
23// Set up the sensor specific information
24// ie, pin locations, addresses, calibrations and related settings
25// ---------------------------------------------------------------------------
26
27// Define the sensor's modbus address
28byte modbusAddress = 0x01; // The sensor's modbus address, or SlaveID
29long modbusBaudRate = 38400; // The baud rate the sensor uses
30
31// Define pin number variables
32const int sensorPwrPin = 10; // The pin sending power to the sensor
33const int adapterPwrPin = 22; // The pin sending power to the RS485 adapter
34const int DEREPin = 7; // The pin controlling Receive Enable and Driver Enable
35 // on the RS485 adapter, if applicable (else, -1)
36 // Setting HIGH enables the driver (arduino) to send text
37 // Setting LOW enables the receiver (sensor) to send text
38
39// Construct a Serial object for Modbus
40#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_FEATHER328P)
41// The Uno only has 1 hardware serial port, which is dedicated to comunication with the
42// computer. If using an Uno, you will be restricted to using AltSofSerial or
43// SoftwareSerial
44#include <SoftwareSerial.h>
45const int SSRxPin = 10; // Receive pin for software serial (Rx on RS485 adapter)
46const int SSTxPin = 11; // Send pin for software serial (Tx on RS485 adapter)
47#pragma message("Using Software Serial for the Uno on pins 10 and 11")
48SoftwareSerial modbusSerial(SSRxPin, SSTxPin);
49// AltSoftSerial modbusSerial;
50#elif defined ESP8266
51#pragma message("Using Software Serial for the ESP8266")
52#include <SoftwareSerial.h>
53SoftwareSerial modbusSerial;
54#elif defined(NRF52832_FEATHER) || defined(ARDUINO_NRF52840_FEATHER)
55#pragma message("Using TinyUSB for the NRF52")
56#include <Adafruit_TinyUSB.h>
57HardwareSerial& modbusSerial = Serial1;
58#elif !defined(NO_GLOBAL_SERIAL1) && !defined(STM32_CORE_VERSION)
59// This is just a assigning another name to the same port, for convienence
60// Unless it is unavailable, always prefer hardware serial.
61#pragma message("Using HarwareSerial / Serial1")
62HardwareSerial& modbusSerial = Serial1;
63#else
64// This is just a assigning another name to the same port, for convienence
65// Unless it is unavailable, always prefer hardware serial.
66#pragma message("Using HarwareSerial / Serial")
67HardwareSerial& modbusSerial = Serial;
68#endif
69
70// Construct the modbus instance
71modbusMaster modbus;
72
73// ==========================================================================
74// Main setup function
75// ==========================================================================
76void setup() {
77 // Set various pins as needed
78 if (DEREPin >= 0) { pinMode(DEREPin, OUTPUT); }
79 if (sensorPwrPin >= 0) {
80 pinMode(sensorPwrPin, OUTPUT);
81 digitalWrite(sensorPwrPin, HIGH);
82 }
83 if (adapterPwrPin >= 0) {
84 pinMode(adapterPwrPin, OUTPUT);
85 digitalWrite(adapterPwrPin, HIGH);
86 }
87
88 // Turn on the "main" serial port for debugging via USB Serial Monitor
89 Serial.begin(57600);
90
91 // Turn on your modbus serial port
92#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_FEATHER328P) || \
93 defined(ARDUINO_SAM_DUE) || not defined(SERIAL_8O1)
94 modbusSerial.begin(modbusBaudRate);
95 // NOTE: The AVR implementation of SoftwareSerial only supports 8N1
96 // The hardware serial implementation of the Due also only supports 8N1
97#elif defined(ESP8266)
98 const int SSRxPin = 13; // Receive pin for software serial (Rx on RS485 adapter)
99 const int SSTxPin = 14; // Send pin for software serial (Tx on RS485 adapter)
100 modbusSerial.begin(modbusBaudRate, SWSERIAL_8O1, SSRxPin, SSTxPin, false);
101 // NOTE: See
102 // https://github.com/plerup/espsoftwareserial/blob/40038df/src/SoftwareSerial.h#L120-L160
103 // for a list of data/parity/stop bit configurations that apply to the ESP8266's
104 // implementation of SoftwareSerial
105#else
106 modbusSerial.begin(modbusBaudRate, SERIAL_8O1);
107 // ^^ use this for 8 data bits - odd parity - 1 stop bit
108 // Serial1.begin(modbusBaudRate, SERIAL_8E1);
109 // ^^ use this for 8 data bits - even parity - 1 stop bit
110 // Serial1.begin(modbusBaudRate, SERIAL_8N2);
111 // ^^ use this for 8 data bits - no parity - 2 stop bits
112 // Serial1.begin(modbusBaudRate);
113 // ^^ use this for 8 data bits - no parity - 1 stop bits
114 // Despite being technically "non-compliant" with the modbus specifications
115 // 8N1 parity is very common.
116#endif
117
118 // Turn on debugging, if desired
119 // modbus.setDebugStream(&Serial);
120
121 // Start the modbus instance
122 modbus.begin(modbusAddress, modbusSerial, DEREPin);
123
124 // Write to a holding register
125 // In this case, we are changing the output units of a dissolved oxygen sensor
126 Serial.println("Setting DO units to ppm");
127 modbus.int16ToRegister(0x01, 1, bigEndian);
128 // Verify that the register changed
129 // 0x03 = holding register
130 // only holding registers are writeable
131 int16_t doUnitMode = modbus.int16FromRegister(0x03, 0x01, bigEndian);
132 Serial.print("Current unit mode is ");
133 Serial.println(doUnitMode);
134}
135
136// ==========================================================================
137// Main loop function
138// ==========================================================================
139void loop() {
140 // Get data values from read-only input registers (0x04)
141 // Just for show, we will do the exact same thing 2 ways
142 // All values will be read as bigEndian
143
144 // Some variables to hold results
145 uint16_t deviceStatus = 0;
146 int16_t doPPM = 0;
147 uint16_t temperature = 0;
148
149 // Method 1:
150 // Get three values one at a time from 3 different registers.
151 // This code is easier to follow, but it requires more back-and-forth between
152 // the Arduino and the sensor so it is a little "slower".
153 deviceStatus = modbus.uint16FromRegister(0x04, 0x00, bigEndian);
154 doPPM = modbus.int16FromRegister(0x04, 0x01, bigEndian);
155 temperature = modbus.uint16FromRegister(0x04, 0x02, bigEndian);
156
157 // Print results
158 Serial.print("Device Status:");
159 Serial.println(deviceStatus);
160 Serial.print("Dissolved Oxygen in ppm:");
161 Serial.println(doPPM);
162 Serial.print("Temperature in °C:");
163 Serial.println(temperature);
164 Serial.println();
165
166 // Method 2:
167 // Read all three registers at once and parse the values from the response.
168 // This is faster, especially when getting many readings, but it's trickier to
169 // write and understand the code.
170 bool success = modbus.getRegisters(0x04, 0x00, 3);
171 // ^ This gets the values and stores them in an internal "frame" with the hex values
172 // of the response
173 if (success) {
174 deviceStatus = modbus.uint16FromFrame(bigEndian, 3);
175 // ^ The first data value is at position 3 in the modbus response frame
176 // 0 = modbus address, 1 = modbus method, 2 = # registers returned, 3 = 1st
177 // value returned
178 doPPM = modbus.int16FromFrame(bigEndian, 5);
179 // ^ The next data value is at position 5 since each register occupies 2 places
180 temperature = modbus.uint16FromFrame(bigEndian, 7);
181 }
182
183 // Print results
184 Serial.print("Device Status:");
185 Serial.println(deviceStatus);
186 Serial.print("Dissolved Oxygen in ppm:");
187 Serial.println(doPPM);
188 Serial.print("Temperature in °C:");
189 Serial.println(temperature);
190 Serial.println();
191}