├── Collection_of_manuals ├── Eastron SDM120C Protocol.pdf ├── Eastron SDM220_Protocol_V1.1.pdf ├── Eastron SDM630DC_Protocol.pdf ├── Eastron-SDM630-DC-user-manual-2013.pdf ├── Eastron.SDM320M.user.manual.2014.V2.0.pdf ├── M98174001-01 CIRCUTOR CVM-MINI.pdf ├── Modbus_over_serial_line_V1_02.pdf ├── SDM 120C_Manual.pdf ├── eastron.sdm220.pdf └── old_SDM120C.pdf ├── ES-Manual-de-usuario.md ├── LICENSE.md ├── Old_versions ├── Modbus-Energy-Monitor-Arduino │ ├── Modbus-Energy-Monitor-Arduino.ino │ ├── ModbusSensor.cpp │ └── ModbusSensor.h └── configSDM120 │ ├── SimpleModbusMasterSDM120.cpp │ ├── SimpleModbusMasterSDM120.h │ └── configSDM120.ino ├── README.md ├── Stable version ├── Modbus-Energy-Monitor-Arduino │ ├── Modbus-Energy-Monitor-Arduino.ino │ ├── ModbusSensor.cpp │ ├── ModbusSensor.h │ ├── Monitor Serial debug.txt │ └── SDMdefines.h └── Modbus-Energy-Monitor-max-min │ ├── Modbus-Energy-Monitor-max-min.ino │ ├── ModbusSensor.cpp │ └── ModbusSensor.h └── dev version ├── Modbus-Energy-Monitor-Arduino ├── Modbus-Energy-Monitor-Arduino.ino ├── ModbusSensor.cpp ├── ModbusSensor.h ├── Monitor Serial debug.txt ├── SDMdefines.h └── keywords.txt ├── Modbus-Energy-Monitor-Arduino054 ├── Modbus-Energy-Monitor-Arduino054.ino ├── ModbusSensor.cpp ├── ModbusSensor.h ├── Monitor Serial debug.txt └── SDMdefines.h ├── SDM120configure └── SDM120configure.ino └── esp8266modbusEnergyMonitor ├── ModbusSensor.cpp ├── ModbusSensor.h ├── NODEMCU_DEVKIT_SCH.png ├── SDMdefines.h ├── esp8266-sdm120c-Aledav.jpg └── esp8266modbusEnergyMonitor.ino /Collection_of_manuals/Eastron SDM120C Protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/Eastron SDM120C Protocol.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/Eastron SDM220_Protocol_V1.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/Eastron SDM220_Protocol_V1.1.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/Eastron SDM630DC_Protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/Eastron SDM630DC_Protocol.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/Eastron-SDM630-DC-user-manual-2013.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/Eastron-SDM630-DC-user-manual-2013.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/Eastron.SDM320M.user.manual.2014.V2.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/Eastron.SDM320M.user.manual.2014.V2.0.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/M98174001-01 CIRCUTOR CVM-MINI.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/M98174001-01 CIRCUTOR CVM-MINI.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/Modbus_over_serial_line_V1_02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/Modbus_over_serial_line_V1_02.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/SDM 120C_Manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/SDM 120C_Manual.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/eastron.sdm220.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/eastron.sdm220.pdf -------------------------------------------------------------------------------- /Collection_of_manuals/old_SDM120C.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/Collection_of_manuals/old_SDM120C.pdf -------------------------------------------------------------------------------- /ES-Manual-de-usuario.md: -------------------------------------------------------------------------------- 1 | #MANUAL DE USUARIO 2 | 3 | ##**Alcance de la librería** 4 | 5 | Permitir la comunicación de un Arduino o ESP8266 en modo máster con dispositivos esclavos empleando un canal RS485 half-duplex. 6 | Los esclavos son del tipo monitores de energía, en particular EASTRON modelos SDM120, SDM220, SDM320, SDM530M Y SDM630. 7 | No utiliza interrupciones, excepto las empleadas por el puerto serie y la UART. El proceso de comunicación permite utilizar la librería con otros procesos en paralelo sin latencia. 8 | 9 | Las funciones implementadas son: 10 | - 0x03 READ HOLDING REGISTERS 11 | - 0x04 READ INPUT REGISTERS 12 | - 0x10 PRESET MULTIPLES REGISTERS 13 | 14 | ##**Conexiones.** 15 | 16 | (incluir esquema con MAX485 y MAX3485) 17 | 18 | ##Limitaciones. 19 | - Las propias del MAX485, máximo 32 dispositivos esclavos. 20 | - Se utiliza en exclusiva un puerto serie. El MEGA dispone de tres puertos serie. 21 | El UNO, Leonardo, etc, dispone de un único puerto serie conectado al puerto USB, por lo que no se pueden emplear simultáneamente. 22 | No se ha realizado la adaptación a SoftwareSerial, pero parece factible. 23 | - El ESP8266 dispone de dos puertos serie, uno de ellos ``Serial1`` solo de salida. Ver apartado de ESP8266. 24 | - Se ha limitado el buffer RX a 32 bytes, lo que permite 6 registros de 4 bytes más 5 de la trama. En caso de requerir más modificar el archivo de cabecera. 25 | 26 | No se ha implementado las funciones de lectura y escritura de 'coils' 27 | 28 | ##Archivos de la librería 29 | 30 | La librería ModbusSensor se compone de los siguientes archivos: 31 | - ``ModbusSensor.h`` archivo de cabecera con las definiciones de las clases, 32 | - ``ModbusSensor.cpp`` archivo de métodos y rutinas, 33 | - ``SDMdefines.h`` archivo de definiciones de los parámetros de los diferentes equipos modbus implementados. para los medidores de energía de la marca EASTRON, modelos SDM120, SDM220, SDM320, SDM530M Y SDM630. 34 | 35 | ##Objetos y métodos 36 | 37 | ###modbusMaster 38 | 39 | La clase ``modbusmaster`` tiene una única instancia, llamada ``MBSerial``. Se encarga de gestionar el canal serie al que está conectado un chip tipo MAX485. 40 | Se distingue de otros gestores de canal serie por incluir la gestión de listas de objetos *modbusSensor*, 41 | a los que llama al principio de cada periodo de muestreo ``POLL_INTERVAL``. Los estados de envío y recepcion de mensajes se ajustan a lo especificado en el protocolo Modbus RTU actuando como *master* del canal, 42 | gestionando los tiempos de espera. 43 | 44 | **Métodos:** 45 | 46 | * ``MBSerial.config()`` configura los parámetros para realizar el proceso de peticiones. Obligatorio invocarlo al inicio del programa 47 | * ``MBserial.begin()`` inicia el canal serie. Obligatorio invocarlo antes de enviar peticiones. 48 | * ``MBSerial.end()`` detiene el canal serie. 49 | * ``MBSerial.connect()`` conecta un objeto modbusSensor a la lista. Se realiza por defecto al construirse el objeto modbusSensor 50 | * ``MBSerial.disconnect()`` desconecta un objeto modbusSensor de la lista. Se realiza por defecto al destruirse el objeto modbusSensor. 51 | * ``MBSerial.sendRequest()`` inicia el proceso de petición de registros. Debe 52 | * ``MBSerial.available()`` realiza el proceso de la Máquina de Estados Finitos, devuelve ``true`` cuando se ha completado la lista de peticiones. 53 | En el resto de estados devuelve ``false``. Al no tener interrupcion para activarse, se debe incluir dentro de un bucle. 54 | 55 | Para procesos en bucle: 56 | ```c++ 57 | void loop() { 58 | ... 59 | if (currentMillis - previousMillis < POLL_INTERVAL) 60 | if( MBSerial.sendRequest()) 61 | previousMillis = currentMillis; 62 | 63 | if (MBSerial.available()) { 64 | energy = enrg.read(); 65 | } 66 | ... 67 | } 68 | ``` 69 | En caso de un proceso secuencial, en el que se requiera bloquear el proceso hasta obtener la respuesta: 70 | ```c++ 71 | modbusSensor id(idNumber, DEVICE_ID, HOLD_VALUE, sizeof(float), READ_HOLDING_REGISTERS); 72 | while (!MBSerial.sendRequest()) {} 73 | while (!MBSerial.available()) {} 74 | Serial.print("Meter Id: ")); 75 | Serial.println(id.read(), 0); 76 | ``` 77 | 78 | ###*modbusSensor* 79 | 80 | El objeto ``modbusSensor`` está pensado para contener un valor o grupo de valores de un dispositivo esclavo modbus. 81 | Dentro de cada instancia modbusSensor el valor o valores se almacena como una secuencia de bytes que refleja el contenido de los registros del esclavo, además del *frame* de petición. 82 | 83 | Los dispositivos Eastron SDM almacenan los datos como valores ``float`` según IEEE 754, ocupan 4 bytes en 2 registros consecutivos. 84 | Otros valores están como valores BCD o hexadecimal. En todos ellos el orden de los bytes (*endianess*) es inverso al del Arduino. 85 | Por este motivo la transferencia de bytes se realiza en orden inverso. En caso de implementar un dispositivo con el mismo orden de bytes, 86 | el usuario debera implementar su propia gestión de bytes para re-inverir el orden. 87 | 88 | **Métodos:** 89 | 90 | - Constructor, con los datos para construir la trama de petición. Solo funciones 0x03 y 0x04. Se conecta a la lista de ``MBSerial`` en orden de creación. 91 | - Destructor, libera la memoria dinámica y desconecta el objeto de ``MBSerial``. 92 | - ``connect()`` conecta el objeto a ``MBSerial``. Ya está incluido en el constructor, no es necesario llamarlo explícitamente. 93 | - ``disconnect()`` desconecta el objeto de ``MBSerial``. Ya está incluido en el destructor, no es necesario llamarlo explícitamente 94 | - ``preset()`` solo objetos con función 0x03. Recontruye la trama para enviar una petición 0x10 PRESET MULTIPLE REGISTERS con el valor del argumento. 95 | El tamaño del argumento debe coincidir con el de creación. no hace comprobación de coincidencia de tipos. 96 | - ``read()`` lee el valor del registro. En caso de que la última petición no haya sido respondida (dispositivo sin corriente) devuelve el valor configurado (último leido, cero o uno). 97 | - ``getStatus()`` devuelve el ``_status`` del objeto. La lista de posible valores se encuentra en el archivo de cabecera. 98 | - ``printStatus()`` imprime un mensaje por el puerto ``Serial`` según el valor de ``_status``. 99 | 100 | **Sketchs de ejemplo** 101 | 102 | ``modbus-Energy-Monitor-Arduino.ino`` Ejemplo para leer valores y parámetros en intervalos periódicos de tiempo, con salida por el puerto *Serial*. 103 | 104 | ``modbus-Energy-max-min.ino`` Ejemplo para leer valores en intervalos cortos, calcular máximos y mínimos, y en intervalo largo con salida por el puerto ``Serial``. 105 | 106 | ``SDM120configure.ino`` Sketch para leer y cambiar los parámetros de un SDM120. Necesario para cambiar valores por defecto como *baud-rate*, o para poner más de uno dentro del mismo canal modbus. 107 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Old_versions/Modbus-Energy-Monitor-Arduino/Modbus-Energy-Monitor-Arduino.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusEnergyMonitor example 3 | An example to collect data from a Modbus energy monitor using ModbusSensor class 4 | to datalogger, include a RTC DS3231 and a SD card 5 | version 0.3 BETA 22/12/2015 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | **********************************************************************/ 11 | /* 12 | 13 | */ 14 | #define SERIAL_OUTPUT 1 15 | 16 | #if SERIAL_OUTPUT 17 | # define SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 18 | # define SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 19 | # define SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 20 | #else 21 | # define SERIAL_BEGIN(...) 22 | # define SERIAL_PRINT(...) 23 | # define SERIAL_PRINTLN(...) 24 | #endif 25 | 26 | #include "ModbusSensor.h" 27 | 28 | #define MB_SERIAL_PORT &Serial1 // Arduino has only one serial port, Mega has 3 serial ports. 29 | // if use Serial 0, remember disconect Tx (pin0) when upload sketch, then re-conect 30 | #define MB_BAUDRATE 2400 // b 2400 31 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 32 | #define TxEnablePin 17 33 | 34 | #define ID_1 1 // id 001 modbus id of the energy monitor 35 | #define REFRESH_INTERVAL 5000 // refresh time, 5 SECONDS 36 | #define WRITE_INTERVAL 20000UL // values send to serial port, 1 minute ( 60 * 1000) 37 | #define KWH_2_WS 36000000 38 | 39 | // Direcciones registros de datos solo lectura. Valores tipo float. 40 | // Utilizar funcion 04 lectura, numero de registros 16-bits 2. 41 | 42 | #define VOL_ADR 0x0000 // VOLTAJE. 43 | #define CUR_ADR 0x0006 // CORRIENTE. 44 | #define POW_ADR 0x000C // POTENCIA ACTIVA. 45 | #define APO_ADR 0x0012 // Potencia Aparente. 46 | #define PFA_ADR 0x001E // Factor de potencia. 47 | #define FRE_ADR 0x0046 // Frecuencia. 48 | #define PEN_ADR 0x0048 // ENERGIA IMPORTADA KWH 49 | #define REN_ADR 0x004A // Energia exportada. 50 | #define TEN_ADR 0x0156 // Energia activa Total. 51 | #define TRE_ADR 0x0158 // Energia reactiva Total. 52 | 53 | // multiplication factor, store value as an integer 54 | #define VOL_FAC 10 55 | #define CUR_FAC 100 56 | #define POW_FAC 10 57 | #define PFA_FAC 100 58 | #define FRE_FAC 10 59 | #define ENE_FAC 100 60 | 61 | 62 | modbusMaster MBserial(MB_SERIAL_PORT, TxEnablePin); // instance to collect data using Modbus protocol over RS485 63 | 64 | //variables to poll, process and send values 65 | modbusSensor volt(&MBserial, ID_1, VOL_ADR, CHANGE_TO_ZERO); 66 | modbusSensor curr(&MBserial, ID_1, CUR_ADR, CHANGE_TO_ZERO); 67 | modbusSensor pwr(&MBserial, ID_1, POW_ADR, CHANGE_TO_ZERO); 68 | modbusSensor enrg(&MBserial, ID_1, PEN_ADR, HOLD_VALUE); 69 | modbusSensor freq(&MBserial, ID_1, FRE_ADR, CHANGE_TO_ZERO); 70 | modbusSensor aPwr(&MBserial, ID_1, APO_ADR, CHANGE_TO_ZERO); 71 | modbusSensor pwrFact(&MBserial, ID_1, PFA_ADR, CHANGE_TO_ONE); 72 | 73 | uint16_t voltage, maxVoltage, minVoltage; // integer, factor x10 74 | uint16_t current, maxCurrent, minCurrent; // integer, factor x100 75 | uint16_t power, maxPower, minPower; // integer, factor x10 76 | uint16_t lastEnergy, energy, avgPower; // integer, factor x100 77 | uint16_t frequency, maxFreq, minFreq; // integer, factor x100 78 | uint16_t aPower, maxApower, minApower; // integer, factor x10 79 | uint16_t powerFactor, maxPF, minPF; // integer, factor x100 80 | 81 | unsigned long previousMillis = 0; 82 | unsigned long currentMillis = 0; 83 | boolean firstData; 84 | 85 | void setup() { 86 | SERIAL_BEGIN(9600); 87 | MBserial.begin(MB_BAUDRATE, MB_BYTEFORMAT, REFRESH_INTERVAL); 88 | delay(95); 89 | SERIAL_PRINTLN("time(s), maxVolt(V), minVolt(V), maxCurr(A) minCurr(A), maxPower(W), minPower(W), maxApPower(VA), minApPower(VA), maxFreq(Hz), minFreq(Hz), AvgPower (W), Energy(Kwh)"); 90 | 91 | firstData = false; 92 | power = 0; 93 | maxPower = 0; // in case it has been recorded, use it 94 | minPower = 0; 95 | lastEnergy = 0; // in case it has been recorded, use it 96 | energy = lastEnergy; 97 | } 98 | 99 | void loop() { 100 | sei(); 101 | if (MBserial.available()) { 102 | voltage = volt.read(VOL_FAC); 103 | current = curr.read(CUR_FAC); 104 | power = pwr.read(POW_FAC); 105 | aPower = aPwr.read(POW_FAC); 106 | frequency = freq.read(FRE_FAC); 107 | energy = enrg.read(ENE_FAC); 108 | 109 | if (!firstData) { 110 | if (maxVoltage < voltage) maxVoltage = voltage; 111 | if (minVoltage > voltage) minVoltage = voltage; 112 | if (maxCurrent < current) maxCurrent = current; 113 | if (minCurrent > current) minCurrent = current; 114 | if (maxPower < power) maxPower = power; 115 | if (minPower > power) minPower = power; 116 | if (maxApower < aPower) maxApower = aPower; 117 | if (minApower > aPower) minApower = aPower; 118 | if (maxFreq < frequency) maxFreq = frequency; 119 | if (minFreq > frequency) minFreq = frequency; 120 | if (maxPower < power) maxPower = power; 121 | if (minPower > power) minPower = power; 122 | } 123 | else { 124 | maxVoltage = voltage; 125 | minVoltage = voltage; 126 | maxCurrent = current; 127 | minCurrent = current; 128 | maxPower = power; 129 | minPower = power; 130 | maxApower = aPower; 131 | minApower = aPower; 132 | maxFreq = frequency; 133 | minFreq = frequency; 134 | firstData = false; 135 | } 136 | } 137 | 138 | currentMillis = millis(); 139 | if (currentMillis - previousMillis >= WRITE_INTERVAL) { 140 | previousMillis = currentMillis; 141 | avgPower = (energy - lastEnergy) * KWH_2_WS / (WRITE_INTERVAL / 1000); //average power KWh/s to W 142 | lastEnergy = energy; 143 | firstData = true; 144 | 145 | SERIAL_PRINT(currentMillis / 1000); 146 | SERIAL_PRINT(","); 147 | SERIAL_PRINT((float)maxVoltage / VOL_FAC, 1); 148 | SERIAL_PRINT(","); 149 | SERIAL_PRINT((float)minVoltage / VOL_FAC, 1); 150 | SERIAL_PRINT(","); 151 | SERIAL_PRINT((float)maxCurrent / CUR_FAC, 2); 152 | SERIAL_PRINT(","); 153 | SERIAL_PRINT((float)minCurrent / CUR_FAC, 2); 154 | SERIAL_PRINT(","); 155 | SERIAL_PRINT((float)maxPower / POW_FAC, 2); 156 | SERIAL_PRINT(","); 157 | SERIAL_PRINT((float)minPower / POW_FAC, 2); 158 | SERIAL_PRINT(","); 159 | SERIAL_PRINT((float)maxApower / POW_FAC, 2); 160 | SERIAL_PRINT(","); 161 | SERIAL_PRINT((float)minApower / POW_FAC, 2); 162 | SERIAL_PRINT(","); 163 | SERIAL_PRINT((float)maxFreq / FRE_FAC, 2); 164 | SERIAL_PRINT(","); 165 | SERIAL_PRINT((float)minFreq / FRE_FAC, 2); 166 | SERIAL_PRINT(","); 167 | SERIAL_PRINT((float)avgPower / ENE_FAC, 2); 168 | SERIAL_PRINT(","); 169 | SERIAL_PRINTLN((float)energy / ENE_FAC, 2); 170 | 171 | } 172 | } 173 | 174 | 175 | -------------------------------------------------------------------------------- /Old_versions/Modbus-Energy-Monitor-Arduino/ModbusSensor.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ModbusSensor class 3 | A class to collect data from a Modbus energy monitor model SDM120 and family 4 | 5 | version 0.3 BETA 22/12/2015 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | *******************************************************************************/ 11 | //------------------------------------------------------------------------------ 12 | 13 | #define MODBUS_SERIAL_OUTPUT //Verbose MODBUS messages and timing 14 | 15 | #ifdef MODBUS_SERIAL_OUTPUT 16 | #define MODBUS_SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 17 | #define MODBUS_SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 18 | #define MODBUS_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 19 | #else 20 | #define MODBUS_SERIAL_BEGIN(...) 21 | #define MODBUS_SERIAL_PRINT(...) 22 | #define MODBUS_SERIAL_PRINTLN(...) 23 | #endif 24 | 25 | #include "ModbusSensor.h" 26 | 27 | // Finite state machine status 28 | #define STOP 0 29 | #define SEND 1 30 | #define SENDING 2 31 | #define RECEIVING 3 32 | #define IDLE 4 33 | #define WAITING_NEXT_POLL 5 34 | 35 | #define READ_COIL_STATUS 0x01 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 36 | #define READ_INPUT_STATUS 0x02 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 37 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary contents of holding registers (4X references) in the slave. 38 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 39 | #define FORCE_MULTIPLE_COILS 0x0F // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 40 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 41 | 42 | #define MB_VALID_DATA 0x00 43 | #define MB_INVALID_ID 0xE0 44 | #define MB_INVALID_FC 0xE1 45 | #define MB_TIMEOUT 0xE2 46 | #define MB_INVALID_CRC 0xE3 47 | #define MB_INVALID_BUFF 0xE4 48 | #define MB_ILLEGAL_FC 0x01 49 | #define MB_ILLEGAL_ADR 0x02 50 | #define MB_ILLEGAL_DATA 0x03 51 | #define MB_SLAVE_FAIL 0x04 52 | #define MB_EXCEPTION 0x05 53 | 54 | // when _status is diferent to MB_VALID_DATA change it to zero or hold last valid value? 55 | //#define CHANGE_TO_ZERO 0x00 56 | //#define CHANGE_TO_ONE 0x01 57 | //#define HOLD_VALUE 0xFF 58 | 59 | 60 | uint16_t calculateCRC(uint8_t *array, uint8_t num) { 61 | uint16_t temp, flag; 62 | temp = 0xFFFF; 63 | for (uint8_t i = 0; i < num; i++) { 64 | temp = temp ^ array[i]; 65 | for (uint8_t j = 8; j; j--) { 66 | flag = temp & 0x0001; 67 | temp >>= 1; 68 | if (flag) 69 | temp ^= 0xA001; 70 | } 71 | } 72 | return temp; 73 | } 74 | 75 | // Constructor 76 | modbusSensor::modbusSensor(modbusMaster * mbm, uint8_t id, uint16_t adr, uint8_t hold) { 77 | _frame[0] = id; 78 | _frame[1] = READ_INPUT_REGISTERS; 79 | _frame[2] = adr >> 8; 80 | _frame[3] = adr & 0x00FF; 81 | _frame[4] = 0x00; 82 | _frame[5] = 0x02; 83 | uint16_t crc = calculateCRC(_frame, 6); 84 | _frame[6] = crc & 0x00FF; 85 | _frame[7] = crc >> 8; 86 | _status = MB_TIMEOUT; 87 | _hold = hold; 88 | _value.f = 0.0; 89 | (*mbm).connect(this); 90 | } 91 | /* 92 | // Constructor 93 | modbusSensor::modbusSensor(uint8_t id, uint16_t adr, uint8_t hold) { 94 | _frame[0] = id; 95 | _frame[1] = READ_INPUT_REGISTERS; 96 | _frame[2] = adr >> 8; 97 | _frame[3] = adr & 0x00FF; 98 | _frame[4] = 0x00; 99 | _frame[5] = 0x02; 100 | uint16_t crc = calculateCRC(_frame, 6); 101 | _frame[6] = crc & 0x00FF; 102 | _frame[7] = crc >> 8; 103 | _status = MB_TIMEOUT; 104 | _hold = hold; 105 | _value.f = 0.0; 106 | MBSerial.connect(this); 107 | }*/ 108 | 109 | 110 | 111 | // read value in defined units 112 | float modbusSensor::read() { 113 | if (_status == MB_TIMEOUT) 114 | switch (_hold) { 115 | case CHANGE_TO_ZERO: return 0.0; 116 | case CHANGE_TO_ONE: return 1.0; 117 | case HOLD_VALUE: return _value.f; 118 | } 119 | return _value.f; 120 | } 121 | 122 | // read value as a integer multiplied by factor 123 | uint16_t modbusSensor::read(uint16_t factor) { 124 | if (_status == MB_TIMEOUT) 125 | switch (_hold) { 126 | case CHANGE_TO_ZERO: return (uint16_t) 0; 127 | case CHANGE_TO_ONE: return (uint16_t) factor; 128 | case HOLD_VALUE: return (uint16_t)(_value.f * factor); 129 | } 130 | return (uint16_t)(_value.f * factor); 131 | } 132 | // get status of the value 133 | inline uint8_t modbusSensor::getStatus() { 134 | return _status; 135 | } 136 | 137 | // write sensor value 138 | inline void modbusSensor::write(float value) { 139 | _value.f = value; 140 | } 141 | 142 | // put new status 143 | inline uint8_t modbusSensor::putStatus(uint8_t status) { 144 | _status = status; 145 | return _status; 146 | } 147 | 148 | // get pointer to _poll frame 149 | inline uint8_t *modbusSensor::getFramePtr() { 150 | return _frame; 151 | } 152 | 153 | //---------------------------------------------------------------------------------------// 154 | //---------------------------------------------------------------------------------------// 155 | 156 | //constructor 157 | modbusMaster::modbusMaster(HardwareSerial * hwSerial, uint8_t TxEnPin) { 158 | _state = STOP; 159 | _TxEnablePin = TxEnPin; 160 | pinMode(_TxEnablePin, OUTPUT); 161 | _MBSerial = hwSerial; 162 | _totalSensors = 0; 163 | for (uint8_t i = 0; i < MAX_SENSORS; i++) 164 | _mbSensorsPtr[i] = 0; 165 | } 166 | 167 | //------------------------------------------------------------------------------ 168 | // Connect a modbusSensor to the modbusMaster array of queries 169 | void modbusMaster::connect(modbusSensor * mbSensor) { 170 | if (_totalSensors < MAX_SENSORS) { 171 | _mbSensorsPtr[_totalSensors] = mbSensor; 172 | _totalSensors++; 173 | } 174 | return; 175 | } 176 | 177 | //------------------------------------------------------------------------------ 178 | // Disconnect a modbusSensor to the modbusMaster array of queries 179 | void modbusMaster::disconnect(modbusSensor * mbSensor) { 180 | uint8_t i, j; 181 | for (i = 0; i < _totalSensors; i++) { 182 | if (_mbSensorsPtr[i] == mbSensor) { 183 | for (j = i; j < _totalSensors - 1; j++) { 184 | _mbSensorsPtr[j] = _mbSensorsPtr[j + 1]; 185 | } 186 | _totalSensors--; 187 | _mbSensorsPtr[_totalSensors] = 0; 188 | } 189 | } 190 | } 191 | 192 | //------------------------------------------------------------------------------ 193 | // begin communication using ModBus protocol over RS485 194 | void modbusMaster::begin(uint16_t baudrate, uint8_t byteFormat, uint16_t pollInterval) { 195 | _pollInterval = pollInterval - 1; 196 | if (baudrate > 19200) 197 | _T2_5 = 1250; 198 | //_T3_5 = 1750; _T1_5 = 750; 199 | else 200 | _T2_5 = 27500000 / baudrate; // 2400 bauds --> 11458 us; 9600 bauds --> 2864 us 201 | //_T3_5 = 38500000 / baudrate; // number of bits 11 * 3.5 = 202 | //_T1_5 = 16500000 / baudrate; // 1T * 1.5 = T1.5 203 | (*_MBSerial).begin(baudrate, byteFormat); 204 | _state = SEND; 205 | digitalWrite(_TxEnablePin, LOW); 206 | } 207 | 208 | //------------------------------------------------------------------------------ 209 | // end communication over serial port 210 | inline void modbusMaster::end() { 211 | _state = STOP; 212 | (*_MBSerial).end(); 213 | } 214 | 215 | //------------------------------------------------------------------------------ 216 | // Finite State Machine core, 217 | boolean modbusMaster::available() { 218 | static uint8_t indexSensor = 0; // index of arrray of sensors 219 | static uint8_t frameSize; // size of the answer frame 220 | static uint32_t tMicros; // time to check between characters in a frame 221 | static uint32_t nowMillis = millis(); 222 | static uint32_t lastPollMillis = nowMillis; // time to check poll interval 223 | static uint32_t sendMillis = nowMillis; // time to check timeout interval 224 | static uint32_t receiveMillis = nowMillis; // time to check waiting interval 225 | 226 | switch (_state) { 227 | //----------------------------------------------------------------------------- 228 | case SEND: 229 | 230 | if (indexSensor < _totalSensors) { 231 | _mbSensorPtr = _mbSensorsPtr[indexSensor]; 232 | _framePtr = (*_mbSensorPtr).getFramePtr(); 233 | digitalWrite(_TxEnablePin, HIGH); 234 | sendFrame(); 235 | 236 | _state = SENDING; 237 | return false; 238 | } 239 | else { 240 | indexSensor = 0; 241 | _state = WAITING_NEXT_POLL; 242 | return true; 243 | } 244 | 245 | //----------------------------------------------------------------------------- 246 | case SENDING: 247 | 248 | if ((*_MBSerial).availableForWrite() == SERIAL_TX_BUFFER_SIZE - 1) { //TX buffer empty 249 | delayMicroseconds(_T2_5); // time to be sure last byte sended 250 | while ((*_MBSerial).available()) (*_MBSerial).read(); // clean RX buffer 251 | digitalWrite(_TxEnablePin, LOW); 252 | sendMillis = millis(); //starts slave's timeOut 253 | _state = RECEIVING; 254 | frameSize = 0; 255 | } 256 | return false; 257 | 258 | //----------------------------------------------------------------------------- 259 | case RECEIVING: 260 | 261 | if (!(*_MBSerial).available()) { 262 | if (millis() - sendMillis > TIMEOUT) { 263 | (*_mbSensorPtr).putStatus(MB_TIMEOUT); 264 | indexSensor++; 265 | _state = SEND; 266 | } 267 | return false; 268 | } 269 | 270 | if ((*_MBSerial).available() > frameSize) { 271 | frameSize++; 272 | tMicros = micros(); 273 | } 274 | else { 275 | if (micros() - tMicros > _T2_5) { 276 | readBuffer(frameSize); 277 | MODBUS_SERIAL_PRINTLN((*_mbSensorPtr).getStatus(), HEX); 278 | indexSensor++; 279 | receiveMillis = millis(); //starts waiting interval to next request 280 | _state = IDLE; 281 | } 282 | } 283 | return false; 284 | 285 | //----------------------------------------------------------------------------- 286 | case IDLE: 287 | if (millis() - receiveMillis > WAITING_INTERVAL) 288 | _state = SEND; 289 | return false; 290 | 291 | //----------------------------------------------------------------------------- 292 | case WAITING_NEXT_POLL: 293 | nowMillis = millis(); 294 | if ((nowMillis - lastPollMillis) > _pollInterval) { 295 | lastPollMillis = nowMillis; 296 | _state = SEND; 297 | } 298 | return false; 299 | 300 | //----------------------------------------------------------------------------- 301 | case STOP: // do nothing 302 | 303 | return false; 304 | 305 | } 306 | } 307 | 308 | //----------------------------------------------------------------------------- 309 | inline void modbusMaster::readBuffer(uint8_t frameSize) { 310 | uint8_t index = 0; 311 | // boolean ovfFlag = false; 312 | MODBUS_SERIAL_PRINT(millis()); 313 | MODBUS_SERIAL_PRINT(" SLAVE:"); 314 | for (index = 0; index < frameSize; index++) { 315 | _buffer[index] = (*_MBSerial).read(); 316 | #ifdef MODBUS_SERIAL_OUTPUT 317 | if (_buffer[index] < 0x10) 318 | Serial.print(F(" 0")); 319 | else 320 | Serial.print(F(" ")); 321 | Serial.print(_buffer[index], HEX); 322 | #endif 323 | } 324 | MODBUS_SERIAL_PRINT(" "); 325 | MODBUS_SERIAL_PRINTLN(millis()); 326 | 327 | // The minimum buffer size from a slave can be an exception response of 5 bytes. 328 | // If the buffer was partially filled set a frame_error. 329 | if (frameSize < 5) { 330 | (*_mbSensorPtr).putStatus(MB_SLAVE_FAIL); 331 | return; 332 | } 333 | 334 | if (_buffer[0] != _framePtr[0]) { 335 | (*_mbSensorPtr).putStatus(MB_INVALID_ID); 336 | return; 337 | } 338 | 339 | uint16_t crc = calculateCRC(_buffer, index - 2); 340 | if (_buffer[frameSize - 1] != crc >> 8 && _buffer[frameSize - 2] != crc & 0x00FF) { 341 | (*_mbSensorPtr).putStatus(MB_INVALID_CRC); 342 | return; 343 | } 344 | if (_buffer[1] & 0x80 == 0x80) { 345 | (*_mbSensorPtr).putStatus(_buffer[2]); // see exception codes in define area 346 | return; 347 | } 348 | 349 | if (_buffer[1] != _framePtr[1]) { 350 | (*_mbSensorPtr).putStatus(MB_INVALID_FC); 351 | return; 352 | } 353 | 354 | switch (_buffer[1]) { 355 | case READ_INPUT_REGISTERS: 356 | if (_buffer[2] == 4) { 357 | dataFloat temp; 358 | temp.array[3] = _buffer[3]; 359 | temp.array[2] = _buffer[4]; 360 | temp.array[1] = _buffer[5]; 361 | temp.array[0] = _buffer[6]; 362 | MODBUS_SERIAL_PRINTLN(temp.f); 363 | (*_mbSensorPtr).write(temp.f); 364 | (*_mbSensorPtr).putStatus(MB_VALID_DATA); 365 | return; 366 | } 367 | else { 368 | (*_mbSensorPtr).putStatus(MB_ILLEGAL_DATA); 369 | return; 370 | } 371 | default: 372 | (*_mbSensorPtr).putStatus(MB_INVALID_FC); 373 | return; 374 | } 375 | } 376 | 377 | //----------------------------------------------------------------------------- 378 | // 379 | inline void modbusMaster::sendFrame() { 380 | MODBUS_SERIAL_PRINT(millis()); 381 | MODBUS_SERIAL_PRINT(F(" MASTER:")); 382 | 383 | (*_MBSerial).write(_framePtr, 8); 384 | 385 | #ifdef MODBUS_SERIAL_OUTPUT 386 | for (uint8_t i = 0; i < 8; i++) { 387 | if (_framePtr[i] < 0x10) 388 | Serial.print(F(" 0")); 389 | else 390 | Serial.print(F(" ")); 391 | Serial.print(_framePtr[i], HEX); 392 | } 393 | #endif 394 | MODBUS_SERIAL_PRINT(" "); 395 | MODBUS_SERIAL_PRINTLN(millis()); 396 | 397 | } 398 | 399 | -------------------------------------------------------------------------------- /Old_versions/Modbus-Energy-Monitor-Arduino/ModbusSensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.h 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and family. 5 | 6 | version 0.3 BETA 22/12/2015 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | 13 | #ifndef ModbusSensor_h 14 | #define ModbusSensor_h 15 | 16 | #include "Arduino.h" 17 | #define MAX_SENSORS 16 18 | // The maximum number of bytes in a modbus packet is 256 bytes. 19 | // The serial buffer limits this to 128 bytes. 20 | // We can reduce it to maximum data sending by a slave SMD120 9 bytes, SMD 630 is 85 bytes 21 | // Three phase meters are 3 values, 6 registers and 12 bytes, plus 5 frame bytes, total 17 22 | #define BUFFER_SIZE 32 23 | #define TIMEOUT 110 // time to fail a request 24 | #define WAITING_INTERVAL 40 // time required by SDM120 to be prepared to receive a new request 25 | 26 | // What happens when _status is diferent to MB_VALID_DATA? 27 | #define CHANGE_TO_ZERO 0x00 28 | #define CHANGE_TO_ONE 0x01 29 | #define HOLD_VALUE 0xFF 30 | 31 | class modbusMaster; 32 | 33 | union dataFloat { 34 | float f; 35 | uint8_t array[4]; 36 | }; 37 | 38 | //------------------------------------------------------------------------------ 39 | class modbusSensor { 40 | public: 41 | // Constructor 42 | modbusSensor(modbusMaster *mbm, uint8_t id, uint16_t adr, uint8_t hold); 43 | 44 | // Constructor 45 | // modbusSensor(uint8_t id, uint16_t adr, uint8_t hold); 46 | 47 | // read value in defined units 48 | float read(); 49 | 50 | // read value as a integer multiplied by factor 51 | uint16_t read(uint16_t factor); 52 | 53 | // get status of the value 54 | inline uint8_t getStatus(); 55 | 56 | // write sensor value 57 | inline void write(float value); 58 | 59 | // change status, return new status 60 | inline uint8_t putStatus(uint8_t status); 61 | 62 | // get pointer to _poll frame 63 | inline uint8_t *getFramePtr(); 64 | 65 | private: 66 | uint8_t _frame[8]; 67 | dataFloat _value; 68 | uint8_t _status; 69 | uint8_t _hold; 70 | }; 71 | 72 | //------------------------------------------------------------------------------ 73 | class modbusMaster { 74 | public: 75 | //constructor 76 | modbusMaster(HardwareSerial *mbSerial, uint8_t TxEnPin); 77 | 78 | // Connect a modbusSensor to modbusMaster array of queries 79 | void connect(modbusSensor *mbSensor); 80 | 81 | // Disconnect a modbusSensor to modbusMaster array of queries 82 | void disconnect(modbusSensor *mbSensor); 83 | 84 | // begin communication using ModBus protocol over RS485 85 | void begin(uint16_t baudrate, uint8_t byteFormat, uint16_t pollInterval); 86 | 87 | // end communication over serial port 88 | void end(); 89 | 90 | // Finite State Machine core, process FSM and check if the array of sensors has been requested 91 | boolean available(); 92 | 93 | private: 94 | inline void sendFrame(); 95 | inline void readBuffer(uint8_t frameSize); 96 | uint8_t _state; // Modbus FSM status (SENDING, RECEIVING, STANDBY, WAINTING_NEXT_POLL) 97 | uint8_t _TxEnablePin; // pin to enable transmision in MAX485 98 | uint8_t _totalSensors; // constant, max number of sensors to poll 99 | uint16_t _pollInterval; // constant, time between polling same data 100 | uint32_t _T2_5; // time between characters in a frame, in microseconds 101 | uint8_t _buffer[BUFFER_SIZE]; // buffer to process rececived frame 102 | // uint8_t _availableSensors; // number of refreshed sensors, decrement when read, increment when write 103 | // uint16_t _availableSensorsFlag; // array of flags to register new available sensor values 104 | HardwareSerial *_MBSerial; 105 | modbusSensor *_mbSensorsPtr[MAX_SENSORS]; // array of modbusSensor's pointers 106 | modbusSensor *_mbSensorPtr; 107 | uint8_t *_framePtr; 108 | }; 109 | extern modbusMaster MBSerial; 110 | 111 | #endif 112 | 113 | -------------------------------------------------------------------------------- /Old_versions/configSDM120/SimpleModbusMasterSDM120.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_MODBUS_MASTER_SDM120 2 | #define SIMPLE_MODBUS_MASTER_SDM120 3 | 4 | // SimpleModbusMasterV12 modificada para powerMeter SDM120C 5 | 6 | /* 7 | SimpleModbusMaster allows you to communicate 8 | to any slave using the Modbus RTU protocol. 9 | 10 | To communicate with a slave you need to create a packet that will contain 11 | all the information required to communicate to the slave. 12 | Information counters are implemented for further diagnostic. 13 | These are variables already implemented in a packet. 14 | You can set and clear these variables as needed. 15 | 16 | The following modbus information counters are implemented: 17 | 18 | requests - contains the total requests to a slave 19 | successful_requests - contains the total successful requests 20 | failed_requests - general frame errors, checksum failures and buffer failures 21 | retries - contains the number of retries 22 | exception_errors - contains the specific modbus exception response count 23 | These are normally illegal function, illegal address, illegal data value 24 | or a miscellaneous error response. 25 | 26 | And finally there is a variable called "connection" that 27 | at any given moment contains the current connection 28 | status of the packet. If true then the connection is 29 | active. If false then communication will be stopped 30 | on this packet until the programmer sets the connection 31 | variable to true explicitly. The reason for this is 32 | because of the time out involved in modbus communication. 33 | Each faulty slave that's not communicating will slow down 34 | communication on the line with the time out value. E.g. 35 | Using a time out of 1500ms, if you have 10 slaves and 9 of them 36 | stops communicating the latency burden placed on communication 37 | will be 1500ms * 9 = 13,5 seconds! 38 | Communication will automatically be stopped after the retry count expires 39 | on each specific packet. 40 | 41 | All the error checking, updating and communication multitasking 42 | takes place in the background. 43 | 44 | In general to communicate with to a slave using modbus 45 | RTU you will request information using the specific 46 | slave id, the function request, the starting address 47 | and lastly the data to request. 48 | Function 1, 2, 3, 4, 15 & 16 are supported. In addition to 49 | this broadcasting (id = 0) is supported for function 15 & 16. 50 | 51 | Constants are provided for: 52 | Function 1 - READ_COIL_STATUS 53 | Function 2 - READ_INPUT_STATUS 54 | Function 3 - READ_HOLDING_REGISTERS 55 | Function 4 - READ_INPUT_REGISTERS 56 | Function 15 - FORCE_MULTIPLE_COILS 57 | Function 16 - PRESET_MULTIPLE_REGISTERS 58 | 59 | Note: 60 | The Arduino serial ring buffer is 64 bytes or 32 registers. 61 | Most of the time you will connect the Arduino using a MAX485 or similar. 62 | 63 | In a function 3 or 4 request the master will attempt to read from a 64 | slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES 65 | and two BYTES CRC the master can only request 58 bytes or 29 registers. 66 | 67 | In a function 16 request the master will attempt to write to a 68 | slave and since 9 bytes is already used for ID, FUNCTION, ADDRESS, 69 | NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write 70 | 54 bytes or 27 registers. 71 | 72 | Note: 73 | Using a USB to Serial converter the maximum bytes you can send is 74 | limited to its internal buffer which differs between manufactures. 75 | 76 | Since it is assumed that you will mostly use the Arduino to connect without 77 | using a USB to Serial converter the internal buffer is set the same as the 78 | Arduino Serial ring buffer which is 64 bytes. 79 | */ 80 | 81 | #include "Arduino.h" 82 | 83 | #define READ_COIL_STATUS 1 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 84 | #define READ_INPUT_STATUS 2 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 85 | #define READ_HOLDING_REGISTERS 3 // Reads the binary contents of holding registers (4X references) in the slave. 86 | #define READ_INPUT_REGISTERS 4 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 87 | #define FORCE_MULTIPLE_COILS 15 // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 88 | #define PRESET_MULTIPLE_REGISTERS 16 // Presets values into a sequence of holding registers (4X references). 89 | 90 | #define MB_SUCCESS 0x00 91 | #define MB_INVALID_ID 0xE0 92 | #define MB_INVALID_FC 0xE1 93 | #define MB_TIMEOUT 0xE2 94 | #define MB_INVALID_CRC 0xE3 95 | #define MB_INVALID_BUFF 0xE4 96 | #define MB_ILLEGAL_FC 0x01 97 | #define MB_ILLEGAL_ADR 0x02 98 | #define MB_ILLEGAL_DATA 0x03 99 | #define MB_SLAVE_FAIL 0x04 100 | 101 | 102 | typedef struct 103 | { 104 | // specific packet info 105 | unsigned char id; 106 | unsigned char function; 107 | unsigned int address; 108 | // For functions 1 & 2 data is the number of points 109 | // For functions 3, 4 & 16 data is the number of registers 110 | // For function 15 data is the number of coils 111 | unsigned int data; 112 | unsigned int* register_array; 113 | 114 | // modbus information counters 115 | unsigned int requests; 116 | unsigned int successful_requests; 117 | unsigned int failed_requests; 118 | unsigned int exception_errors; 119 | unsigned int retries; 120 | // connection status of packet 121 | unsigned char connection; 122 | 123 | 124 | boolean is_SDM120; 125 | } Packet; 126 | 127 | typedef Packet* packetPointer; 128 | 129 | // function definitions 130 | void modbus_update(); 131 | 132 | void modbus_construct_SDM120(Packet *_packet, 133 | unsigned char id, 134 | unsigned int address, 135 | unsigned int* register_array); 136 | 137 | void modbus_construct(Packet *_packet, 138 | unsigned char id, 139 | unsigned char function, 140 | unsigned int address, 141 | unsigned int data, 142 | unsigned int* register_array); 143 | 144 | void modbus_configure(HardwareSerial* SerialPort, 145 | long baud, 146 | unsigned char byteFormat, 147 | unsigned int _timeout, 148 | unsigned int _polling, 149 | unsigned char _retry_count, 150 | unsigned char _TxEnablePin, 151 | Packet* _packets, 152 | unsigned int _total_no_of_packets); 153 | 154 | boolean processRequest(packetPointer pPacket); 155 | unsigned char processStatus(unsigned char status); 156 | 157 | #endif 158 | -------------------------------------------------------------------------------- /Old_versions/configSDM120/configSDM120.ino: -------------------------------------------------------------------------------- 1 | /* SDM120C Modbus RTU Protocol con Arduino. 2 | * version 0.0.1 by @peninquen 3 | * based on version 0.0.4 by @cosmopaco. 4 | * 5 | * Sketch para modificar los parametros de comunicación del dispositivo: 6 | * Poner el SDM120 en modo -SET- pulsando durante 3 segundos en el pulsador. 7 | * Introducir los nuevos datos por el terminal serial 8 | * En caso de introducir un valor no válido, no realiza la acción y salta al siguiente parámetro 9 | * Algunos parametros requieren reiniciar el SMD120 para tener efecto, eperar a completar el programa para reiniciar... 10 | * Materiales 11 | * Arduino Mega 12 | * Modulo RS485 13 | * Conexiones (Configuracion por defecto) Puerto serial 1 14 | * Arduino pin Modulo RS485 pin 15 | * 19 RO (receive out) 16 | * 18 DI (data in) 17 | * 17 DE/RE (data enable/receive enable). 18 | * 19 | */ 20 | # define MODBUS_SERIAL_OUTPUT 1 // verbose 21 | #include "SimpleModbusMasterSDM120.h" 22 | 23 | // Direcciones registros de datos de configuración de lectura y escritura, valores tipo float. 24 | // Utilizar funcion 03 para lectura, función 16 para escritura, 2 registros, número de bytes 4. 25 | 26 | #define ID_ADR 0X0014 // meter id (1-247). 27 | #define BAUD_ADR 0X001C // Baud rate (0:2400 1:4800 2:9600 5:1200) 28 | #define TURN_ADR 0XF900 // Tiempo entre pantallas (0-30s) 29 | #define PULSE1_ADR 0XF910 // Pulsos/Kwh (0:1000, 1:100, 2:10 3:1 pulso/Kwh) 30 | #define MODE_ADR 0XF920 // Modo medida energía (1-3) 31 | #define PULSE1_MODE_ADR 0XF930 // Modo salida pulsos a led (0:imp+exp, 1:imp, 2:exp) 32 | #define PARITY_ADR 0XF930 // Parity biy (0:none, 1:even, 2:odd) 33 | // 34 | 35 | /* 36 | Constants are provided for: 37 | Function 1 - READ_COIL_STATUS 38 | Function 2 - READ_INPUT_STATUS 39 | Function 3 - READ_HOLDING_REGISTERS 40 | Function 4 - READ_INPUT_REGISTERS 41 | Function 15 - FORCE_MULTIPLE_COILS 42 | Function 16 - PRESET_MULTIPLE_REGISTERS 43 | Valid modbus byte formats are: 44 | SERIAL_8N2: 1 start bit, 8 data bits, 2 stop bits 45 | SERIAL_8E1: 1 start bit, 8 data bits, 1 Even parity bit, 1 stop bit 46 | SERIAL_8O1: 1 start bit, 8 data bits, 1 Odd parity bit, 1 stop bit 47 | */ 48 | // Verificar que realmente tiene estos parametros antes de empezar. 49 | // Pulsar el botón para pasar por las pantallas hasta las de configuración. 50 | unsigned int idNumber = 1; // Id 001 51 | #define SDM120C_BAUDRATE 2400 // b 2400 52 | #define SDM120C_BYTEFORMAT SERIAL_8N2 // Prty n 53 | // 54 | 55 | #define TIMEOUT 200 // tiempo hasta dar por fallida la petición 56 | #define POLLING 2000 // tiempo entre toma de datos, no se usa, solo una petición por parámetro. 57 | #define RETRYCOUNT 2 // numero de reintentos fallidos hasta 'connection' false que desactiva el packet 58 | // para volver poner 'connection' a true. 59 | #define TXENPIN 17 // Pin cambio recibir/transmite para el driver RS485 60 | 61 | 62 | // This is the easiest way to create new packets 63 | // Add as many as you want. TOTAL_NO_OF_PACKETS 64 | // is automatically updated. 65 | enum 66 | { 67 | PACKET1, 68 | TOTAL_NO_OF_PACKETS // leave this last entry 69 | }; 70 | 71 | // Create an array of Packets to be configured 72 | Packet packets[TOTAL_NO_OF_PACKETS]; 73 | 74 | // Create a packetPointer to access each packet 75 | // individually. This is not required you can access 76 | // the array explicitly. E.g. packets[PACKET1].id = 2; 77 | // This does become tedious though... 78 | packetPointer parameterPacket = &packets[PACKET1]; 79 | 80 | 81 | // Union 82 | union datas { 83 | byte b[4]; // modo pruebas, en principio no se usa 84 | float F; 85 | unsigned int Array[2]; 86 | } parameter; 87 | unsigned int newParameter; 88 | 89 | 90 | void setup() { 91 | //Iniciamos puerto serial"0" Arduino Mega para entrada/salida de datos. 92 | Serial.begin(9600); 93 | delay(1000); 94 | Serial.println(F("Config SDM120-Modbus")); 95 | // Iniciamos comunicación modbus SERIAL1 Arduino Mega. 96 | modbus_configure(&Serial1, SDM120C_BAUDRATE, SDM120C_BYTEFORMAT, TIMEOUT, POLLING, RETRYCOUNT, TXENPIN, packets, TOTAL_NO_OF_PACKETS); 97 | Serial.println(F("Long press button to enter -SET- mode")); 98 | 99 | Serial.println(F("Enter expected Id:")); 100 | while (!Serial.available()) {} 101 | idNumber = Serial.parseInt(); 102 | 103 | //Procesa el valor de registro id 104 | modbus_construct(parameterPacket, idNumber, READ_HOLDING_REGISTERS, ID_ADR, 2, parameter.Array); 105 | if (!processRequest(parameterPacket)) return; 106 | Serial.print(F("Meter Id: ")); Serial.println(parameter.F, 0); 107 | Serial.print(F("New Id: ")); 108 | while (!Serial.available()) {} 109 | newParameter = Serial.parseInt(); 110 | if (newParameter >= 1 && newParameter <= 247) { 111 | parameter.F = (float)newParameter; 112 | Serial.println(parameter.F, 0); 113 | modbus_construct(parameterPacket, idNumber, PRESET_MULTIPLE_REGISTERS, ID_ADR, 2, parameter.Array); 114 | if (!processRequest(parameterPacket)) return; 115 | Serial.println(F(" done")); 116 | idNumber = newParameter; // actuliza el id para las siguientes peticiones 117 | } 118 | else Serial.println(F("Skip")); 119 | 120 | //Procesa el valor de registro baud rate 121 | modbus_construct(parameterPacket, idNumber, READ_HOLDING_REGISTERS, BAUD_ADR, 2, parameter.Array); 122 | if (!processRequest(parameterPacket)) return; 123 | Serial.print(F("Baud rate (0:2400 1:4800 2:9600 5:1200): ")); Serial.println(parameter.F, 0); 124 | Serial.print(F("New baud rate: ")); 125 | while (!Serial.available()) {} 126 | newParameter = Serial.parseInt(); 127 | switch (newParameter) { 128 | case 0: 129 | case 1: 130 | case 2: 131 | case 5: 132 | parameter.F = (float)newParameter; 133 | Serial.println(parameter.F, 0); 134 | modbus_construct(parameterPacket, idNumber, PRESET_MULTIPLE_REGISTERS, BAUD_ADR, 2, parameter.Array); 135 | if (!processRequest(parameterPacket)) return; 136 | Serial.println(F(" done")); 137 | break; 138 | default: Serial.println(F("Skip")); 139 | } 140 | 141 | //Procesa el valor de registro 'tiempo entre pantallas' 142 | modbus_construct(parameterPacket, idNumber, READ_HOLDING_REGISTERS, TURN_ADR, 1, parameter.Array); 143 | if (!processRequest(parameterPacket)) return; 144 | Serial.print(F("Time of display in turns (0 - 30 seconds): ")); Serial.print(parameter.Array[0], HEX); 145 | Serial.print(F("New time: ")); 146 | while (!Serial.available()) {} 147 | newParameter = Serial.parseInt(); 148 | if (newParameter >= 0 && newParameter <= 30) { 149 | parameter.Array[0] = newParameter % 10 | (newParameter / 10 << 4); //convert to BCD 150 | Serial.println(newParameter); 151 | modbus_construct(parameterPacket, idNumber, PRESET_MULTIPLE_REGISTERS, TURN_ADR, 1, parameter.Array); 152 | if (!processRequest(parameterPacket)) return; 153 | Serial.println(F(" done")); 154 | } 155 | else Serial.println(F("Skip")); 156 | 157 | //Procesa el valor de registro 'salida pulso 1' 158 | modbus_construct(parameterPacket, idNumber, READ_HOLDING_REGISTERS, PULSE1_ADR, 1, parameter.Array); 159 | if (!processRequest(parameterPacket)) return; 160 | Serial.print(F("Pulse 1 output (0:1000, 1:100, 2:10 3:1 imp/Kwh): ")); Serial.println(parameter.Array[0], HEX); 161 | Serial.print(F("New pulse 1 output value: ")); 162 | while (!Serial.available()) {} 163 | newParameter = Serial.parseInt(); 164 | if (newParameter >= 0 && newParameter <= 3) { 165 | parameter.Array[0] = newParameter; 166 | Serial.println(parameter.Array[0], HEX); 167 | modbus_construct(parameterPacket, idNumber, PRESET_MULTIPLE_REGISTERS, PULSE1_ADR, 1, parameter.Array); 168 | if (!processRequest(parameterPacket)) return; 169 | Serial.println(F(" done")); 170 | } 171 | else Serial.println(F("Skip")); 172 | 173 | //Procesa el valor de registro 'modo medida de energía' 174 | modbus_construct(parameterPacket, idNumber, READ_HOLDING_REGISTERS, MODE_ADR, 1, parameter.Array); 175 | if (!processRequest(parameterPacket)) return; 176 | Serial.print(F("Measurement mode (0:mode 1, 1:mode 2, 2:mode 3): ")); Serial.println(parameter.Array[0], HEX); 177 | Serial.print(F("New Measure mode: ")); 178 | while (!Serial.available()) {} 179 | newParameter = Serial.parseInt(); 180 | if (newParameter >= 0 && newParameter <= 2) { 181 | parameter.Array[0] = newParameter; 182 | Serial.println(parameter.Array[0], HEX); 183 | modbus_construct(parameterPacket, idNumber, PRESET_MULTIPLE_REGISTERS, MODE_ADR, 1, parameter.Array); 184 | if (!processRequest(parameterPacket)) return; 185 | Serial.println(F(" done")); 186 | } 187 | else Serial.println(F("Skip")); 188 | 189 | //Procesa el valor de registro 'modo salida pulso 1' 190 | modbus_construct(parameterPacket, idNumber, READ_HOLDING_REGISTERS, PULSE1_MODE_ADR, 1, parameter.Array); 191 | if (!processRequest(parameterPacket)) return; 192 | Serial.print(F("Pulse 1 output mode (0:imp+exp, 1:imp, 2:exp): ")); Serial.println(parameter.Array[0], HEX); 193 | Serial.print(F("New pulse 1 output mode: ")); 194 | while (!Serial.available()) {} 195 | newParameter = Serial.parseInt(); 196 | if (newParameter >= 0 && newParameter <= 2) { 197 | parameter.Array[0] = newParameter; 198 | Serial.println(parameter.Array[0], HEX); 199 | modbus_construct(parameterPacket, idNumber, PRESET_MULTIPLE_REGISTERS, PULSE1_MODE_ADR, 1, parameter.Array); 200 | if (!processRequest(parameterPacket)) return; 201 | Serial.println(F(" done")); 202 | } 203 | else Serial.println(F("Skip")); 204 | 205 | // Salir del modo -SET- para que se hagan efectivos los cambios, después apagar y encender el SMD120 206 | Serial.println(F("Now long press for 3 seconds to exit the -SET- mode. Then restart the SMD120 to aply the changes.")); 207 | } 208 | 209 | void loop() {} // no hace nada... 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modbus-Energy-Monitor-Arduino 2 | Comunicate with diferent energy monitors using modbus RTU protocol over RS485, mainly Eastron SDM120, SDM220, SDM530 and SDM630. 3 | Implemented function codes 0x03, 0x04 and 0x10. No coils functions have been implemented. 4 | 5 | Achieved milestones: 6 | - Accesible to basic users. All modbus protocol issues are inside the classes. Simple messages to access data values and parameters. 7 | - Poll design. Basic function repeat the array of queries to poll data values. 8 | - Event-driven, non-blocking behaviour. You can use it embebed with different process without delay. 9 | - Escalable. Eastron energy monitors works with ``float`` values. modbusSensor object can do requests of single ``float`` values and contiguous ``float`` values, but also every ``struct`` of data that your slave's registers stores. 10 | - Configurable. You can configure SDM's holding registers 11 | - Exception error process. Adjust response in case of offline status due to power outage. Other errors are user configurable. 12 | 13 | Next objetives: 14 | - Define other energy monitors, like Circutor CVM-MINI 15 | - Implement nodeMCU and other ESP8266 library version. 16 | - Explore other slave modbus RTU equipment 17 | - Create``'modbusSlave`` object, use Arduino as a slave using Modbus RTU protocol. 18 | - Document library functions and example sketchs 19 | 20 | Any comments, bugs and issues are wellcome. 21 | ___________________________________________________________________________________________________________________ 22 | 23 | Librería arduino para comunicar con el protocolo Modbus RTU sobre RS485 y gestionar datos de un medidor de energía, en principio de la marca EASTRON SDM120, SDM220, SDM530 y SDM630. 24 | Se ha implementado la gestión de las funciones 0x03, 0x04 y 0x10 dl protocolo Moddbus. No se ha implementado las funciones de 'coils'. 25 | 26 | Objetivos alcanzados: 27 | - Accesible a usuarios con conocimientos básicos del protocolo Modbus. Toda la gestión del protocolo queda en el interior de las clases, empleado funciones sencillas de lectura y escritura para acceder a los datos y parámetros. 28 | - Petición periódica de datos. Se repite la lista de peticiones para registrar los datos. 29 | - Comportamiento sin bloqueo ni uso de 'delays', que permite ejecutar varios procesos conjuntos sin sufrir retrasos. 30 | - Escalable. Los modelos de EASTRON trabajan con valores tipo 'float'. el objeto 'modbusSensor' puede hacer peticiones de un solo valor o varios valores contiguos, además de permitir al usuario crear estructuras de datos que almacene los registros del esclavo. 31 | - Configurable. Dispone de las funciones y ejemplo de sketch de configuración de los parametros de un Eastron SDM. 32 | - Gestión de errores. Ajusta la respuesta en caso de no responder por apagón. Otros errores configurables por el usuario. 33 | 34 | Siguientes objetivos: 35 | - Definir los registros y formato de otros monitores de energía, como el CIRCUTOR CVM-MINI. 36 | - Implementar la librería sobre nodeMCU y otros ESP8266. 37 | - Explorar otros equipos con protocolo Modbus RTU. 38 | - Crear el objeto 'modbusSlave' y sus métodos, de forma que un arduino pueda operar como esclavo sobre Modbus RTU 39 | - Documentar las funciones y los sketchs de ejemplo. 40 | 41 | Cualquier comentario, incidencia o sugerencia será bien recibida para mejorar la librería. 42 | 43 | * Author: Jaime García @peninquen 44 | * License: Apache License Version 2.0. 45 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-Arduino/Modbus-Energy-Monitor-Arduino.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusEnergyMonitor example 3 | An example to collect data from a Modbus energy monitor using ModbusSensor class 4 | to datalogger, include a RTC DS3231 and a SD card 5 | version 0.4 BETA 31/12/2015 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | **********************************************************************/ 11 | /* 12 | 13 | */ 14 | #define SERIAL_OUTPUT 1 15 | 16 | #if SERIAL_OUTPUT 17 | # define SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 18 | # define SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 19 | # define SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 20 | #else 21 | # define SERIAL_BEGIN(...) 22 | # define SERIAL_PRINT(...) 23 | # define SERIAL_PRINTLN(...) 24 | #endif 25 | 26 | #include "ModbusSensor.h" 27 | #include "SDMdefines.h" 28 | 29 | #define MB_SERIAL_PORT &Serial2 // Arduino has only one serial port, Mega has 3 serial ports. 30 | // if use Serial 0, remember disconect Tx (pin0) when upload sketch, then re-conect 31 | #define MB_BAUDRATE 2400 // b 2400 32 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 33 | #define TxEnablePin 17 34 | 35 | #define ID_1 1 // id 001 modbus id of the energy monitor 36 | #define REFRESH_INTERVAL 5000 // refresh time, 5 SECONDS 37 | #define WRITE_INTERVAL 20000UL // values send to serial port, 1 minute ( 60 * 1000) 38 | #define KWH_2_WS 36000000 39 | 40 | // multiplication factor, store value as an integer 41 | #define VOL_FAC 10 42 | #define CUR_FAC 100 43 | #define POW_FAC 10 44 | #define PFA_FAC 100 45 | #define FRE_FAC 10 46 | #define ENE_FAC 100 47 | 48 | 49 | // global variables to poll, process and send values 50 | modbusSensor volt(ID_1, VOLTAGE, CHANGE_TO_ZERO); 51 | modbusSensor curr(ID_1, CURRENT, CHANGE_TO_ZERO); 52 | modbusSensor pwr(ID_1, POWER, CHANGE_TO_ZERO); 53 | modbusSensor enrg(ID_1, IAENERGY, HOLD_VALUE); 54 | modbusSensor freq(ID_1, FREQUENCY, CHANGE_TO_ZERO); 55 | modbusSensor aPwr(ID_1, APOWER, CHANGE_TO_ZERO); 56 | modbusSensor pwrFact(ID_1, PFACTOR, CHANGE_TO_ONE); 57 | 58 | uint16_t voltage, maxVoltage, minVoltage; // integer, factor x10 59 | uint16_t current, maxCurrent, minCurrent; // integer, factor x100 60 | uint16_t power, maxPower, minPower; // integer, factor x10 61 | uint16_t lastEnergy, energy, avgPower; // integer, factor x100 62 | uint16_t frequency, maxFreq, minFreq; // integer, factor x100 63 | uint16_t aPower, maxApower, minApower; // integer, factor x10 64 | uint16_t powerFactor, maxPF, minPF; // integer, factor x100 65 | 66 | unsigned long previousMillis = 0; 67 | unsigned long currentMillis = 0; 68 | boolean firstData; 69 | 70 | void setup() { 71 | SERIAL_BEGIN(9600); 72 | MBSerial.config(MB_SERIAL_PORT, TxEnablePin, REFRESH_INTERVAL); 73 | MBSerial.begin(MB_BAUDRATE, MB_BYTEFORMAT); 74 | delay(95); 75 | SERIAL_PRINTLN("time(s), maxVolt(V), minVolt(V), maxCurr(A) minCurr(A), maxPower(W), minPower(W), maxApPower(VA), minApPower(VA), maxFreq(Hz), minFreq(Hz), AvgPower (W), Energy(Kwh)"); 76 | 77 | firstData = false; 78 | power = 0; 79 | maxPower = 0; // in case it has been recorded, use it 80 | minPower = 0; 81 | lastEnergy = 0; // in case it has been recorded, use it 82 | energy = lastEnergy; 83 | } 84 | 85 | void loop() { 86 | sei(); 87 | if (MBSerial.available()) { 88 | voltage = volt.read(VOL_FAC); 89 | current = curr.read(CUR_FAC); 90 | power = pwr.read(POW_FAC); 91 | aPower = aPwr.read(POW_FAC); 92 | frequency = freq.read(FRE_FAC); 93 | energy = enrg.read(ENE_FAC); 94 | 95 | if (!firstData) { 96 | if (maxVoltage < voltage) maxVoltage = voltage; 97 | if (minVoltage > voltage) minVoltage = voltage; 98 | if (maxCurrent < current) maxCurrent = current; 99 | if (minCurrent > current) minCurrent = current; 100 | if (maxPower < power) maxPower = power; 101 | if (minPower > power) minPower = power; 102 | if (maxApower < aPower) maxApower = aPower; 103 | if (minApower > aPower) minApower = aPower; 104 | if (maxFreq < frequency) maxFreq = frequency; 105 | if (minFreq > frequency) minFreq = frequency; 106 | if (maxPower < power) maxPower = power; 107 | if (minPower > power) minPower = power; 108 | } 109 | else { 110 | maxVoltage = voltage; 111 | minVoltage = voltage; 112 | maxCurrent = current; 113 | minCurrent = current; 114 | maxPower = power; 115 | minPower = power; 116 | maxApower = aPower; 117 | minApower = aPower; 118 | maxFreq = frequency; 119 | minFreq = frequency; 120 | firstData = false; 121 | } 122 | } 123 | 124 | currentMillis = millis(); 125 | if (currentMillis - previousMillis >= WRITE_INTERVAL) { 126 | previousMillis = currentMillis; 127 | avgPower = (energy - lastEnergy) * KWH_2_WS / (WRITE_INTERVAL / 1000); //average power KWh/s to W 128 | lastEnergy = energy; 129 | firstData = true; 130 | 131 | SERIAL_PRINT(currentMillis / 1000); 132 | SERIAL_PRINT(","); 133 | SERIAL_PRINT((float)maxVoltage / VOL_FAC, 1); 134 | SERIAL_PRINT(","); 135 | SERIAL_PRINT((float)minVoltage / VOL_FAC, 1); 136 | SERIAL_PRINT(","); 137 | SERIAL_PRINT((float)maxCurrent / CUR_FAC, 2); 138 | SERIAL_PRINT(","); 139 | SERIAL_PRINT((float)minCurrent / CUR_FAC, 2); 140 | SERIAL_PRINT(","); 141 | SERIAL_PRINT((float)maxPower / POW_FAC, 2); 142 | SERIAL_PRINT(","); 143 | SERIAL_PRINT((float)minPower / POW_FAC, 2); 144 | SERIAL_PRINT(","); 145 | SERIAL_PRINT((float)maxApower / POW_FAC, 2); 146 | SERIAL_PRINT(","); 147 | SERIAL_PRINT((float)minApower / POW_FAC, 2); 148 | SERIAL_PRINT(","); 149 | SERIAL_PRINT((float)maxFreq / FRE_FAC, 2); 150 | SERIAL_PRINT(","); 151 | SERIAL_PRINT((float)minFreq / FRE_FAC, 2); 152 | SERIAL_PRINT(","); 153 | SERIAL_PRINT((float)avgPower / ENE_FAC, 2); 154 | SERIAL_PRINT(","); 155 | SERIAL_PRINTLN((float)energy / ENE_FAC, 2); 156 | 157 | } 158 | } 159 | 160 | 161 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-Arduino/ModbusSensor.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ModbusSensor class 3 | A class to collect data from a Modbus energy monitor model SDM120 and family 4 | 5 | version 0.4 BETA 31/12/2015 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | *******************************************************************************/ 11 | //------------------------------------------------------------------------------ 12 | 13 | #define MODBUS_SERIAL_OUTPUT //Verbose MODBUS messages and timing 14 | 15 | #ifdef MODBUS_SERIAL_OUTPUT 16 | #define MODBUS_SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 17 | #define MODBUS_SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 18 | #define MODBUS_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 19 | #else 20 | #define MODBUS_SERIAL_BEGIN(...) 21 | #define MODBUS_SERIAL_PRINT(...) 22 | #define MODBUS_SERIAL_PRINTLN(...) 23 | #endif 24 | 25 | #include "ModbusSensor.h" 26 | 27 | // Finite state machine status 28 | #define STOP 0 29 | #define SEND 1 30 | #define SENDING 2 31 | #define RECEIVING 3 32 | #define IDLE 4 33 | #define WAITING_NEXT_POLL 5 34 | 35 | #define READ_COIL_STATUS 0x01 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 36 | #define READ_INPUT_STATUS 0x02 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 37 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary contents of holding registers (4X references) in the slave. 38 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 39 | #define FORCE_MULTIPLE_COILS 0x0F // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 40 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 41 | 42 | #define MB_VALID_DATA 0x00 43 | #define MB_INVALID_ID 0xE0 44 | #define MB_INVALID_FC 0xE1 45 | #define MB_TIMEOUT 0xE2 46 | #define MB_INVALID_CRC 0xE3 47 | #define MB_INVALID_BUFF 0xE4 48 | #define MB_ILLEGAL_FC 0x01 49 | #define MB_ILLEGAL_ADR 0x02 50 | #define MB_ILLEGAL_DATA 0x03 51 | #define MB_SLAVE_FAIL 0x04 52 | #define MB_EXCEPTION 0x05 53 | 54 | // when _status is diferent to MB_VALID_DATA change it to zero or hold last valid value? 55 | //#define CHANGE_TO_ZERO 0x00 56 | //#define CHANGE_TO_ONE 0x01 57 | //#define HOLD_VALUE 0xFF 58 | 59 | 60 | uint16_t calculateCRC(uint8_t *array, uint8_t num) { 61 | uint16_t temp, flag; 62 | temp = 0xFFFF; 63 | for (uint8_t i = 0; i < num; i++) { 64 | temp = temp ^ array[i]; 65 | for (uint8_t j = 8; j; j--) { 66 | flag = temp & 0x0001; 67 | temp >>= 1; 68 | if (flag) 69 | temp ^= 0xA001; 70 | } 71 | } 72 | return temp; 73 | } 74 | 75 | // Constructor 76 | modbusSensor::modbusSensor(modbusMaster * mbm, uint8_t id, uint16_t adr, uint8_t hold) { 77 | _frame[0] = id; 78 | _frame[1] = READ_INPUT_REGISTERS; 79 | _frame[2] = adr >> 8; 80 | _frame[3] = adr & 0x00FF; 81 | _frame[4] = 0x00; 82 | _frame[5] = 0x02; 83 | uint16_t crc = calculateCRC(_frame, 6); 84 | _frame[6] = crc & 0x00FF; 85 | _frame[7] = crc >> 8; 86 | _status = MB_TIMEOUT; 87 | _hold = hold; 88 | _value.f = 0.0; 89 | (*mbm).connect(this); 90 | } 91 | 92 | // Constructor 93 | modbusSensor::modbusSensor(uint8_t id, uint16_t adr, uint8_t hold) { 94 | _frame[0] = id; 95 | _frame[1] = READ_INPUT_REGISTERS; 96 | _frame[2] = adr >> 8; 97 | _frame[3] = adr & 0x00FF; 98 | _frame[4] = 0x00; 99 | _frame[5] = 0x02; 100 | uint16_t crc = calculateCRC(_frame, 6); 101 | _frame[6] = crc & 0x00FF; 102 | _frame[7] = crc >> 8; 103 | _status = MB_TIMEOUT; 104 | _hold = hold; 105 | _value.f = 0.0; 106 | MBSerial.connect(this); 107 | } 108 | 109 | // Destructor 110 | modbusSensor::~modbusSensor() { 111 | MBSerial.disconnect(this); 112 | } 113 | 114 | // read value in defined units 115 | float modbusSensor::read() { 116 | if (_status == MB_TIMEOUT) 117 | switch (_hold) { 118 | case CHANGE_TO_ZERO: return 0.0; 119 | case CHANGE_TO_ONE: return 1.0; 120 | case HOLD_VALUE: return _value.f; 121 | } 122 | return _value.f; 123 | } 124 | 125 | // read value as a integer multiplied by factor 126 | uint16_t modbusSensor::read(uint16_t factor) { 127 | if (_status == MB_TIMEOUT) 128 | switch (_hold) { 129 | case CHANGE_TO_ZERO: return (uint16_t) 0; 130 | case CHANGE_TO_ONE: return (uint16_t) factor; 131 | case HOLD_VALUE: return (uint16_t)(_value.f * factor); 132 | } 133 | return (uint16_t)(_value.f * factor); 134 | } 135 | // get status of the value 136 | /*inline*/ uint8_t modbusSensor::getStatus() { 137 | return _status; 138 | } 139 | 140 | // write sensor value 141 | inline void modbusSensor::write(float value) { 142 | _value.f = value; 143 | } 144 | 145 | // put new status 146 | /*inline*/ uint8_t modbusSensor::putStatus(uint8_t status) { 147 | _status = status; 148 | return _status; 149 | } 150 | 151 | // get pointer to _poll frame 152 | /*inline*/ uint8_t *modbusSensor::getFramePtr() { 153 | return _frame; 154 | } 155 | 156 | //---------------------------------------------------------------------------------------// 157 | //---------------------------------------------------------------------------------------// 158 | 159 | //configure object and conections 160 | void modbusMaster::config(HardwareSerial * hwSerial, uint8_t TxEnPin, uint16_t pollInterval) { 161 | _state = STOP; 162 | _TxEnablePin = TxEnPin; 163 | pinMode(_TxEnablePin, OUTPUT); 164 | _hwSerial = hwSerial; 165 | _pollInterval = pollInterval - 1; 166 | _totalSensors = 0; 167 | for (uint8_t i = 0; i < MAX_SENSORS; i++) 168 | _mbSensorsPtr[i] = 0; 169 | } 170 | 171 | //------------------------------------------------------------------------------ 172 | // Connect a modbusSensor to the modbusMaster array of queries 173 | void modbusMaster::connect(modbusSensor * mbSensor) { 174 | if (_totalSensors < MAX_SENSORS) { 175 | _mbSensorsPtr[_totalSensors] = mbSensor; 176 | _totalSensors++; 177 | } 178 | return; 179 | } 180 | 181 | //------------------------------------------------------------------------------ 182 | // Disconnect a modbusSensor to the modbusMaster array of queries 183 | void modbusMaster::disconnect(modbusSensor * mbSensor) { 184 | for (uint8_t i = 0; i < _totalSensors; i++) { 185 | if (_mbSensorsPtr[i] == mbSensor) { 186 | for (uint8_t j = i; j < _totalSensors - 1; j++) { 187 | _mbSensorsPtr[j] = _mbSensorsPtr[j + 1]; 188 | } 189 | _totalSensors--; 190 | _mbSensorsPtr[_totalSensors] = 0; 191 | } 192 | } 193 | } 194 | 195 | //------------------------------------------------------------------------------ 196 | // begin communication using ModBus protocol over RS485 197 | void modbusMaster::begin(uint16_t baudrate, uint8_t byteFormat) { 198 | if (baudrate > 19200) 199 | _T2_5 = 1250; 200 | //_T3_5 = 1750; _T1_5 = 750; 201 | else 202 | _T2_5 = 27500000 / baudrate; // 2400 bauds --> 11458 us; 9600 bauds --> 2864 us 203 | //_T3_5 = 38500000 / baudrate; // number of bits 11 * 3.5 = 204 | //_T1_5 = 16500000 / baudrate; // 1T * 1.5 = T1.5 205 | (*_hwSerial).begin(baudrate, byteFormat); 206 | _state = SEND; 207 | digitalWrite(_TxEnablePin, LOW); 208 | } 209 | 210 | //------------------------------------------------------------------------------ 211 | // end communication over serial port 212 | inline void modbusMaster::end() { 213 | _state = STOP; 214 | (*_hwSerial).end(); 215 | digitalWrite(_TxEnablePin, LOW); 216 | } 217 | 218 | //------------------------------------------------------------------------------ 219 | // Finite State Machine core, 220 | boolean modbusMaster::available() { 221 | static uint8_t indexSensor = 0; // index of arrray of sensors 222 | static uint8_t frameSize; // size of the answer frame 223 | static uint32_t tMicros; // time to check between characters in a frame 224 | static uint32_t nowMillis = millis(); 225 | static uint32_t lastPollMillis = nowMillis; // time to check poll interval 226 | static uint32_t sendMillis = nowMillis; // time to check timeout interval 227 | static uint32_t receivedMillis = nowMillis; // time to check waiting interval 228 | 229 | switch (_state) { 230 | //----------------------------------------------------------------------------- 231 | case SEND: 232 | 233 | if (indexSensor < _totalSensors) { 234 | _mbSensorPtr = _mbSensorsPtr[indexSensor]; 235 | _framePtr = (*_mbSensorPtr).getFramePtr(); 236 | digitalWrite(_TxEnablePin, HIGH); 237 | sendFrame(); 238 | 239 | _state = SENDING; 240 | return false; 241 | } 242 | else { 243 | indexSensor = 0; 244 | _state = WAITING_NEXT_POLL; 245 | return true; 246 | } 247 | 248 | //----------------------------------------------------------------------------- 249 | case SENDING: 250 | 251 | if ((*_hwSerial).availableForWrite() == SERIAL_TX_BUFFER_SIZE - 1) { //TX buffer empty 252 | delayMicroseconds(_T2_5); // time to be sure last byte sended 253 | while ((*_hwSerial).available()) (*_hwSerial).read(); // clean RX buffer 254 | digitalWrite(_TxEnablePin, LOW); 255 | sendMillis = millis(); //starts slave timeOut 256 | _state = RECEIVING; 257 | frameSize = 0; 258 | } 259 | return false; 260 | 261 | //----------------------------------------------------------------------------- 262 | case RECEIVING: 263 | 264 | if (!(*_hwSerial).available()) { 265 | if (millis() - sendMillis > TIMEOUT) { 266 | (*_mbSensorPtr).putStatus(MB_TIMEOUT); 267 | indexSensor++; 268 | _state = SEND; 269 | } 270 | return false; 271 | } 272 | 273 | if ((*_hwSerial).available() > frameSize) { 274 | frameSize++; 275 | tMicros = micros(); 276 | } 277 | else { 278 | if (micros() - tMicros > _T2_5) { // inter-character time exceeded 279 | readBuffer(frameSize); 280 | MODBUS_SERIAL_PRINTLN((*_mbSensorPtr).getStatus(), HEX); 281 | indexSensor++; 282 | receivedMillis = millis(); //starts waiting interval to next request 283 | _state = IDLE; 284 | } 285 | } 286 | return false; 287 | 288 | //----------------------------------------------------------------------------- 289 | case IDLE: 290 | if (millis() - receivedMillis > WAITING_INTERVAL) 291 | _state = SEND; 292 | return false; 293 | 294 | //----------------------------------------------------------------------------- 295 | case WAITING_NEXT_POLL: 296 | nowMillis = millis(); 297 | if ((nowMillis - lastPollMillis) > _pollInterval) { 298 | lastPollMillis = nowMillis; 299 | _state = SEND; 300 | } 301 | return false; 302 | 303 | //----------------------------------------------------------------------------- 304 | case STOP: // do nothing 305 | 306 | return false; 307 | 308 | } 309 | } 310 | 311 | //----------------------------------------------------------------------------- 312 | /*inline*/ void modbusMaster::readBuffer(uint8_t frameSize) { 313 | uint8_t index = 0; 314 | // boolean ovfFlag = false; 315 | MODBUS_SERIAL_PRINT(millis()); 316 | MODBUS_SERIAL_PRINT(" SLAVE:"); 317 | for (index = 0; index < frameSize; index++) { 318 | _buffer[index] = (*_hwSerial).read(); 319 | #ifdef MODBUS_SERIAL_OUTPUT 320 | if (_buffer[index] < 0x10) 321 | Serial.print(F(" 0")); 322 | else 323 | Serial.print(F(" ")); 324 | Serial.print(_buffer[index], HEX); 325 | #endif 326 | } 327 | MODBUS_SERIAL_PRINT(" "); 328 | MODBUS_SERIAL_PRINTLN(millis()); 329 | 330 | // The minimum buffer size from a slave can be an exception response of 5 bytes. 331 | // If the buffer was partially filled set a frame_error. 332 | if (frameSize < 5) { 333 | (*_mbSensorPtr).putStatus(MB_SLAVE_FAIL); 334 | return; 335 | } 336 | 337 | if (_buffer[0] != _framePtr[0]) { 338 | (*_mbSensorPtr).putStatus(MB_INVALID_ID); 339 | return; 340 | } 341 | 342 | uint16_t crc = calculateCRC(_buffer, index - 2); 343 | if (_buffer[frameSize - 1] != crc >> 8 && _buffer[frameSize - 2] != crc & 0x00FF) { 344 | (*_mbSensorPtr).putStatus(MB_INVALID_CRC); 345 | return; 346 | } 347 | if (_buffer[1] & 0x80 == 0x80) { 348 | (*_mbSensorPtr).putStatus(_buffer[2]); // see exception codes in define area 349 | return; 350 | } 351 | 352 | if (_buffer[1] != _framePtr[1]) { 353 | (*_mbSensorPtr).putStatus(MB_INVALID_FC); 354 | return; 355 | } 356 | 357 | switch (_buffer[1]) { 358 | case READ_INPUT_REGISTERS: 359 | if (_buffer[2] == 4) { 360 | dataFloat temp; 361 | temp.array[3] = _buffer[3]; 362 | temp.array[2] = _buffer[4]; 363 | temp.array[1] = _buffer[5]; 364 | temp.array[0] = _buffer[6]; 365 | MODBUS_SERIAL_PRINTLN(temp.f); 366 | (*_mbSensorPtr).write(temp.f); 367 | (*_mbSensorPtr).putStatus(MB_VALID_DATA); 368 | return; 369 | } 370 | else { 371 | (*_mbSensorPtr).putStatus(MB_ILLEGAL_DATA); 372 | return; 373 | } 374 | default: 375 | (*_mbSensorPtr).putStatus(MB_INVALID_FC); 376 | return; 377 | } 378 | } 379 | 380 | //----------------------------------------------------------------------------- 381 | // 382 | /*inline*/ void modbusMaster::sendFrame(uint8_t frameSize) { 383 | MODBUS_SERIAL_PRINT(millis()); 384 | MODBUS_SERIAL_PRINT(F(" MASTER:")); 385 | 386 | (*_hwSerial).write(_framePtr, frameSize); 387 | 388 | #ifdef MODBUS_SERIAL_OUTPUT 389 | for (uint8_t i = 0; i < frameSize; i++) { 390 | if (_framePtr[i] < 0x10) 391 | Serial.print(F(" 0")); 392 | else 393 | Serial.print(F(" ")); 394 | Serial.print(_framePtr[i], HEX); 395 | } 396 | #endif 397 | MODBUS_SERIAL_PRINT(" "); 398 | MODBUS_SERIAL_PRINTLN(millis()); 399 | 400 | } 401 | 402 | //----------------------------------------------------------------------------- 403 | // 404 | /*inline*/ void modbusMaster::sendFrame() { 405 | MODBUS_SERIAL_PRINT(millis()); 406 | MODBUS_SERIAL_PRINT(F(" MASTER:")); 407 | 408 | (*_hwSerial).write(_framePtr, 8); 409 | 410 | #ifdef MODBUS_SERIAL_OUTPUT 411 | for (uint8_t i = 0; i < 8; i++) { 412 | if (_framePtr[i] < 0x10) 413 | Serial.print(F(" 0")); 414 | else 415 | Serial.print(F(" ")); 416 | Serial.print(_framePtr[i], HEX); 417 | } 418 | #endif 419 | MODBUS_SERIAL_PRINT(" "); 420 | MODBUS_SERIAL_PRINTLN(millis()); 421 | 422 | } 423 | 424 | //predefined instance to poll, collect a process values from modbus protocol 425 | modbusMaster MBSerial; 426 | 427 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-Arduino/ModbusSensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.h 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and family. 5 | 6 | version 0.4 BETA 31/12/2015 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | 13 | #ifndef ModbusSensor_h 14 | #define ModbusSensor_h 15 | 16 | #include "Arduino.h" 17 | #define MAX_SENSORS 16 18 | // The maximum number of bytes in a modbus packet is 256 bytes. 19 | // The serial buffer limits this to 128 bytes. 20 | // We can reduce it to maximum data sending by a slave SMD120 9 bytes, SMD 630 is 85 bytes 21 | // Three phase meters are 3 values, 6 registers and 12 bytes, plus 5 frame bytes, total 17 22 | #define BUFFER_SIZE 32 23 | #define TIMEOUT 110 // time to fail a request 24 | #define WAITING_INTERVAL 40 // time required by SDM120 to be prepared to receive a new request 25 | 26 | // What happens when _status is diferent to MB_VALID_DATA? 27 | // in case of offline device (MB_TIMEOUT) read value holds or changes to ... 28 | #define CHANGE_TO_ZERO 0x00 29 | #define CHANGE_TO_ONE 0x01 30 | #define HOLD_VALUE 0xFF 31 | 32 | // forward definition 33 | class modbusMaster; 34 | 35 | // float 32 bit IEEE754 36 | union dataFloat { 37 | float f; 38 | uint8_t array[4]; 39 | }; 40 | 41 | //------------------------------------------------------------------------------ 42 | class modbusSensor { 43 | public: 44 | // Constructor 45 | modbusSensor(modbusMaster *mbm, uint8_t id, uint16_t adr, uint8_t hold); 46 | 47 | // Constructor 48 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold); 49 | 50 | // Destructor, remember disconnect object before leaving the scope, no automatic feature except for MBSerial 51 | ~modbusSensor(); 52 | 53 | // read value in defined units 54 | float read(); 55 | 56 | // read value as a integer multiplied by factor 57 | uint16_t read(uint16_t factor); 58 | 59 | // get status of the value 60 | /*inline*/ uint8_t getStatus(); 61 | 62 | // write sensor value 63 | inline void write(float value); 64 | 65 | // change status, return new status 66 | /*inline*/ uint8_t putStatus(uint8_t status); 67 | 68 | // get pointer to _poll frame 69 | /*inline*/ uint8_t *getFramePtr(); 70 | 71 | private: 72 | uint8_t _frame[8]; 73 | dataFloat _value; 74 | uint8_t _status; 75 | uint8_t _hold; 76 | }; 77 | 78 | //------------------------------------------------------------------------------ 79 | class modbusMaster { 80 | public: 81 | // constructor 82 | // modbusMaster(); 83 | 84 | // configure conection 85 | void config(HardwareSerial *hwSerial, uint8_t TxEnPin, uint16_t pollInterval); 86 | 87 | // Connect a modbusSensor to modbusMaster array of queries 88 | void connect(modbusSensor *mbSensor); 89 | 90 | // Disconnect a modbusSensor to modbusMaster array of queries 91 | void disconnect(modbusSensor *mbSensor); 92 | 93 | // begin communication using ModBus protocol over RS485 94 | void begin(uint16_t baudrate, uint8_t byteFormat); 95 | 96 | // end communication over serial port 97 | void end(); 98 | 99 | // Finite State Machine core, process FSM and check if the array of sensors has been requested 100 | boolean available(); 101 | 102 | private: 103 | /*inline*/ void sendFrame(uint8_t frameSize); 104 | /*inline*/ void sendFrame(); 105 | /*inline*/ void readBuffer(uint8_t frameSize); 106 | uint8_t _state; // Modbus FSM status (SENDING, RECEIVING, STANDBY, WAINTING_NEXT_POLL) 107 | uint8_t _TxEnablePin; // pin to enable transmision in MAX485 108 | uint8_t _totalSensors; // constant, max number of sensors to poll 109 | uint16_t _pollInterval; // constant, time between polling same data 110 | uint32_t _T2_5; // time between characters in a frame, in microseconds 111 | uint8_t _buffer[BUFFER_SIZE]; // buffer to process rececived frame 112 | // uint8_t _availableSensors; // number of refreshed sensors, decrement when read, increment when write 113 | // uint16_t _availableSensorsFlag; // array of flags to register new available sensor values 114 | HardwareSerial *_hwSerial; 115 | modbusSensor *_mbSensorsPtr[MAX_SENSORS]; // array of modbusSensor's pointers 116 | modbusSensor *_mbSensorPtr; 117 | uint8_t *_framePtr; 118 | } ; 119 | extern modbusMaster MBSerial; 120 | 121 | #endif 122 | 123 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-Arduino/Monitor Serial debug.txt: -------------------------------------------------------------------------------- 1 | time(s), maxVolt(V), minVolt(V), maxCurr(A) minCurr(A), maxPower(W), minPower(W), maxApPower(VA), minApPower(VA), maxFreq(Hz), minFreq(Hz), AvgPower (W), Energy(Kwh) 2 | 200 MASTER: 01 04 00 00 00 02 71 CB 241 3 | 384 SLAVE: 01 04 04 43 64 33 33 FB 3A 385 4 | 228.20 5 | 0 6 | 427 MASTER: 01 04 00 06 00 02 91 CA 428 7 | 604 SLAVE: 01 04 04 3E 2E 14 7B D9 46 605 8 | 0.17 9 | 0 10 | 647 MASTER: 01 04 00 0C 00 02 B1 C8 648 11 | 824 SLAVE: 01 04 04 42 0A 66 66 64 74 825 12 | 34.60 13 | 0 14 | 867 MASTER: 01 04 00 48 00 02 F1 DD 868 15 | 1037 SLAVE: 01 04 04 3E 90 E5 60 BC F9 1039 16 | 0.28 17 | 0 18 | 1080 MASTER: 01 04 00 46 00 02 90 1E 1081 19 | 1258 SLAVE: 01 04 04 42 48 00 00 6F EA 1259 20 | 50.00 21 | 0 22 | 1301 MASTER: 01 04 00 12 00 02 D1 CE 1302 23 | 1482 SLAVE: 01 04 04 42 11 FA 24 FD 42 1483 24 | 36.49 25 | 0 26 | 1525 MASTER: 01 04 00 1E 00 02 11 CD 1526 27 | 1702 SLAVE: 01 04 04 3F 72 EC E8 1B 05 1703 28 | 0.95 29 | 0 30 | 5200 MASTER: 01 04 00 00 00 02 71 CB 5201 31 | 5370 SLAVE: 01 04 04 43 64 00 00 AF DF 5371 32 | 228.00 33 | 0 34 | 5413 MASTER: 01 04 00 06 00 02 91 CA 5414 35 | 5581 SLAVE: 01 04 04 3E 2E 14 7B D9 46 5582 36 | 0.17 37 | 0 38 | 5624 MASTER: 01 04 00 0C 00 02 B1 C8 5625 39 | 5793 SLAVE: 01 04 04 42 0A 66 66 64 74 5794 40 | 34.60 41 | 0 42 | 5836 MASTER: 01 04 00 48 00 02 F1 DD 5837 43 | 6016 SLAVE: 01 04 04 3E 90 E5 60 BC F9 6017 44 | 0.28 45 | 0 46 | 6059 MASTER: 01 04 00 46 00 02 90 1E 6060 47 | 6236 SLAVE: 01 04 04 42 48 00 00 6F EA 6237 48 | 50.00 49 | 0 50 | 6279 MASTER: 01 04 00 12 00 02 D1 CE 6280 51 | 6459 SLAVE: 01 04 04 42 11 E1 0A 77 AE 6460 52 | 36.47 53 | 0 54 | 6502 MASTER: 01 04 00 1E 00 02 11 CD 6503 55 | 6680 SLAVE: 01 04 04 3F 73 69 82 A8 7A 6682 56 | 0.95 57 | 0 58 | 10200 MASTER: 01 04 00 00 00 02 71 CB 10201 59 | 10369 SLAVE: 01 04 04 43 63 66 66 B5 94 10371 60 | 227.40 61 | 0 62 | 10413 MASTER: 01 04 00 06 00 02 91 CA 10414 63 | 10590 SLAVE: 01 04 04 3E 2E 14 7B D9 46 10591 64 | 0.17 65 | 0 66 | 10633 MASTER: 01 04 00 0C 00 02 B1 C8 10634 67 | 10801 SLAVE: 01 04 04 42 0A 66 66 64 74 10803 68 | 34.60 69 | 0 70 | 10844 MASTER: 01 04 00 48 00 02 F1 DD 10845 71 | 11023 SLAVE: 01 04 04 3E 90 E5 60 BC F9 11025 72 | 0.28 73 | 0 74 | 11066 MASTER: 01 04 00 46 00 02 90 1E 11067 75 | 11243 SLAVE: 01 04 04 42 48 00 00 6F EA 11244 76 | 50.00 77 | 0 78 | 11286 MASTER: 01 04 00 12 00 02 D1 CE 11287 79 | 11466 SLAVE: 01 04 04 42 11 89 24 D8 72 11468 80 | 36.38 81 | 0 82 | 11510 MASTER: 01 04 00 1E 00 02 11 CD 11511 83 | 11687 SLAVE: 01 04 04 3F 73 9F D2 EF E6 11689 84 | 0.95 85 | 0 86 | 15200 MASTER: 01 04 00 00 00 02 71 CB 15201 87 | 15377 SLAVE: 01 04 04 43 63 B3 33 2B 3B 15378 88 | 227.70 89 | 0 90 | 15420 MASTER: 01 04 00 06 00 02 91 CA 15421 91 | 15597 SLAVE: 01 04 04 3E 2E 14 7B D9 46 15598 92 | 0.17 93 | 0 94 | 15640 MASTER: 01 04 00 0C 00 02 B1 C8 15641 95 | 15817 SLAVE: 01 04 04 42 0A CC CD 5B 6B 15818 96 | 34.70 97 | 0 98 | 15860 MASTER: 01 04 00 48 00 02 F1 DD 15861 99 | 16041 SLAVE: 01 04 04 3E 90 E5 60 BC F9 16044 100 | 0.28 101 | 0 102 | 16086 MASTER: 01 04 00 46 00 02 90 1E 16087 103 | 16259 SLAVE: 01 04 04 42 48 00 00 6F EA 16260 104 | 50.00 105 | 0 106 | 16302 MASTER: 01 04 00 12 00 02 D1 CE 16303 107 | 16484 SLAVE: 01 04 04 42 11 F4 70 F8 DD 16485 108 | 36.49 109 | 0 110 | 16527 MASTER: 01 04 00 1E 00 02 11 CD 16528 111 | 16706 SLAVE: 01 04 04 3F 73 7F 4A A7 8C 16707 112 | 0.95 113 | 0 114 | 20,228.2,0.0,0.17,0.00,34.70,0.00,36.40,0.00,50.00,0.00,28.16,0.28 115 | 20200 MASTER: 01 04 00 00 00 02 71 CB 20201 116 | 20374 SLAVE: 01 04 04 43 63 CC CD 8A 8B 20375 117 | 227.80 118 | 0 -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-Arduino/SDMdefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | Eastron addresses registers 3 | */ 4 | 5 | // Read values use function code 4 (0x04) 6 | #define VOLTAGE 0x0000 //All Float 7 | #define CURRENT 0x0006 8 | #define POWER 0x000C 9 | #define APOWER 0x0012 10 | #define RAPOWER 0x0018 11 | #define PFACTOR 0x001E 12 | #define PANGLE 0x0024 13 | #define FREQUENCY 0x0046 14 | #define IAENERGY 0x0048 15 | #define EAENERGY 0x004A 16 | #define IRAENERGY 0x004C 17 | #define ERAENERGY 0x004E 18 | #define TAENERGY 0x0156 19 | #define TRENERGY 0x0158 20 | 21 | // Read/Write Configuration. 22 | // Read function code 3 (0x03) 23 | // Write function code 16 (0x10) 24 | #define NPARSTOP 0x0012 //HEX 25 | #define DEVICE_ID 0x0014 //Float 26 | #define BAUD_RATE 0x001C //Float 27 | #define TIME_DISP_220 0xF500 //BCD 28 | #define TIME_DISP 0xF900 //BCD 29 | #define TOT_MODE 0xF920 //HEX 30 | 31 | /* 32 | Maintaining old definitions. 33 | */ 34 | 35 | 36 | // Direcciones registros de datos solo lectura. Valores tipo float. 37 | // Utilizar funcion 04 lectura, numero de bytes 4. 38 | 39 | #define VOL_ADR VOLTAGE // VOLTAJE. 40 | #define CUR_ADR CURRENT // CORRIENTE. 41 | #define POW_ADR POWER // POTENCIA ACTIVA. 42 | #define APO_ADR APOWER // Potencia Aparente. 43 | #define RPO_ADR RAPOWER // Potencia Reactiva. 44 | #define PFA_ADR PFACTOR // Factor de potencia. 45 | #define PAN_ADR PANGLE // ANGULO PHI. desfase entre voltaje y corriente 46 | #define FRE_ADR FREQUENCY // Frecuencia. 47 | // REVISAR FALTAN PARAMENTROS O PARAMENTROS PARA SDM220 48 | 49 | #define PEN_ADR IAENERGY // ENERGIA IMPORTADA KWH 50 | #define REN_ADR EAENERGY // Energia exportada. 51 | #define TEN_ADR TAENERGY // Energia activa Total. 52 | #define TRE_ADR TRENERGY // Energia reactiva Total. 53 | 54 | /* Default SDM configuration. 55 | #define SDM120C_METER_NUMBER 1 56 | #define SDM120C_BAUDRATE 2400 57 | #define SDM120C_BYTEFORMAT SERIAL_8N2 //Prty n 58 | */ 59 | 60 | /*Modbus optimum settings 61 | #define TIMEOUT 1000 62 | #define POLLING 200 // the scan rate 63 | #define RETRYCOUNT 10 // numero de reintentos, para volver set the "connection" variable to 64 | */ 65 | 66 | // Get Parameter 67 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-max-min/Modbus-Energy-Monitor-max-min.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * ModbusEnergyMonitor example 3 | * An example to collect data from a Modbus energy monitor using ModbusSensor class 4 | * to datalogger, include a RTC DS3231 and a SD card 5 | * version 0.1 ALPHA 14/12/2015 6 | * 7 | * Author: Jaime García @peninquen 8 | * License: Apache License Version 2.0. 9 | * 10 | **********************************************************************/ 11 | /* 12 | 13 | */ 14 | #define SERIAL_OUTPUT 1 15 | 16 | #if SERIAL_OUTPUT 17 | # define SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 18 | # define SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 19 | # define SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 20 | #else 21 | # define SERIAL_BEGIN(...) 22 | # define SERIAL_PRINT(...) 23 | # define SERIAL_PRINTLN(...) 24 | #endif 25 | 26 | #include "ModbusSensor.h" 27 | 28 | #define MB_SERIAL_PORT &Serial // Arduino has only one serial port, Mega has 3 serial ports. 29 | // if use Serial 0, remember disconect Tx (pin0) when upload sketch, then re-conect 30 | #define MB_BAUDRATE 2400 // b 2400 31 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 32 | #define TxEnablePin 17 33 | #define TIMEOUT 100 34 | 35 | 36 | #define ID_1 1 // id 001 modbus id of the energy monitor 37 | #define REFRESH_INTERVAL 5000 // refresh time, 5 SECONDS 38 | #define WRITE_INTERVAL 20000UL // values send to serial port, 1 minute ( 60 * 1000) 39 | #define KWH_2_WS 36000000 40 | 41 | // Direcciones registros de datos solo lectura. Valores tipo float. 42 | // Utilizar funcion 04 lectura, numero de registros 16-bits 2. 43 | 44 | #define VOL_ADR 0x0000 // VOLTAJE. 45 | #define CUR_ADR 0x0006 // CORRIENTE. 46 | #define POW_ADR 0x000C // POTENCIA ACTIVA. 47 | #define APO_ADR 0x0012 // Potencia Aparente. 48 | #define PFA_ADR 0x001E // Factor de potencia. 49 | #define FRE_ADR 0x0046 // Frecuencia. 50 | #define PEN_ADR 0x0048 // ENERGIA IMPORTADA KWH 51 | #define REN_ADR 0x004A // Energia exportada. 52 | #define TEN_ADR 0x0156 // Energia activa Total. 53 | #define TRE_ADR 0x0158 // Energia reactiva Total. 54 | 55 | // multiplication factor, store value as an integer 56 | #define VOL_FAC 10 57 | #define CUR_FAC 100 58 | #define POW_FAC 10 59 | #define PFA_FAC 100 60 | #define FRE_FAC 10 61 | #define ENE_FAC 100 62 | 63 | 64 | modbusMaster MBserial(MB_SERIAL_PORT, TxEnablePin); // instance to collect data using Modbus protocol over RS485 65 | 66 | //variables to poll, process and send values 67 | modbusSensor volt(&MBserial, ID_1, VOL_ADR, CHANGE_TO_ZERO); 68 | modbusSensor curr(&MBserial, ID_1, CUR_ADR, CHANGE_TO_ZERO); 69 | modbusSensor pwr(&MBserial, ID_1, POW_ADR, CHANGE_TO_ZERO); 70 | modbusSensor enrg(&MBserial, ID_1, PEN_ADR, HOLD_VALUE); 71 | modbusSensor freq(&MBserial, ID_1, FRE_ADR, CHANGE_TO_ZERO); 72 | modbusSensor aPwr(&MBserial, ID_1, APO_ADR, CHANGE_TO_ZERO); 73 | modbusSensor pwrFact(&MBserial, ID_1, PFA_ADR, CHANGE_TO_ONE); 74 | 75 | uint16_t voltage, maxVoltage, minVoltage; // integer, factor x10 76 | uint16_t current, maxCurrent, minCurrent; // integer, factor x100 77 | uint16_t power, maxPower, minPower; // integer, factor x10 78 | uint16_t lastEnergy, energy, avgPower; // integer, factor x100 79 | uint16_t frequency, maxFreq, minFreq; // integer, factor x100 80 | uint16_t aPower, maxApower, minApower; // integer, factor x10 81 | uint16_t powerFactor, maxPF, minPF; // integer, factor x100 82 | 83 | unsigned long previousMillis = 0; 84 | unsigned long currentMillis = 0; 85 | boolean firstData; 86 | 87 | void setup() { 88 | SERIAL_BEGIN(9600); 89 | MBserial.begin(MB_BAUDRATE, MB_BYTEFORMAT, TIMEOUT, REFRESH_INTERVAL); 90 | delay(95); 91 | SERIAL_PRINTLN("time(s), maxVolt(V), minVolt(V), maxCurr(A) minCurr(A), maxPower(W), minPower(W), maxApPower(VA), minApPower(VA), maxFreq(Hz), minFreq(Hz), AvgPower (W), Energy(Kwh)"); 92 | 93 | firstData = false; 94 | power = 0; 95 | maxPower = 0; // in case it has been recorded, use it 96 | minPower = 0; 97 | lastEnergy = 0; // in case it has been recorded, use it 98 | energy = lastEnergy; 99 | } 100 | 101 | void loop() { 102 | sei(); 103 | if (MBserial.available()) { 104 | voltage = volt.read(VOL_FAC); 105 | current = curr.read(CUR_FAC); 106 | power = pwr.read(POW_FAC); 107 | aPower = aPwr.read(POW_FAC); 108 | frequency = freq.read(FRE_FAC); 109 | energy = enrg.read(ENE_FAC); 110 | 111 | if (!firstData) { 112 | if (maxVoltage < voltage) maxVoltage = voltage; 113 | if (minVoltage > voltage) minVoltage = voltage; 114 | if (maxCurrent < current) maxCurrent = current; 115 | if (minCurrent > current) minCurrent = current; 116 | if (maxPower < power) maxPower = power; 117 | if (minPower > power) minPower = power; 118 | if (maxApower < aPower) maxApower = aPower; 119 | if (minApower > aPower) minApower = aPower; 120 | if (maxFreq < frequency) maxFreq = frequency; 121 | if (minFreq > frequency) minFreq = frequency; 122 | if (maxPower < power) maxPower = power; 123 | if (minPower > power) minPower = power; 124 | } 125 | else { 126 | maxVoltage = voltage; 127 | minVoltage = voltage; 128 | maxCurrent = current; 129 | minCurrent = current; 130 | maxPower = power; 131 | minPower = power; 132 | maxApower = aPower; 133 | minApower = aPower; 134 | maxFreq = frequency; 135 | minFreq = frequency; 136 | firstData = false; 137 | } 138 | } 139 | 140 | currentMillis = millis(); 141 | if (currentMillis - previousMillis >= WRITE_INTERVAL) { 142 | previousMillis = currentMillis; 143 | avgPower = (energy - lastEnergy) * KWH_2_WS / (WRITE_INTERVAL / 1000); //average power KWh/s to W 144 | lastEnergy = energy; 145 | firstData = true; 146 | 147 | SERIAL_PRINT(currentMillis / 1000); 148 | SERIAL_PRINT(","); 149 | SERIAL_PRINT((float)maxVoltage / VOL_FAC, 1); 150 | SERIAL_PRINT(","); 151 | SERIAL_PRINT((float)minVoltage /VOL_FAC, 1); 152 | SERIAL_PRINT(","); 153 | SERIAL_PRINT((float)maxCurrent /CUR_FAC, 2); 154 | SERIAL_PRINT(","); 155 | SERIAL_PRINT((float)minCurrent / CUR_FAC, 2); 156 | SERIAL_PRINT(","); 157 | SERIAL_PRINT((float)maxPower / POW_FAC, 2); 158 | SERIAL_PRINT(","); 159 | SERIAL_PRINT((float)minPower / POW_FAC, 2); 160 | SERIAL_PRINT(","); 161 | SERIAL_PRINT((float)maxApower / POW_FAC, 2); 162 | SERIAL_PRINT(","); 163 | SERIAL_PRINT((float)minApower /POW_FAC, 2); 164 | SERIAL_PRINT(","); 165 | SERIAL_PRINT((float)maxFreq / FRE_FAC, 2); 166 | SERIAL_PRINT(","); 167 | SERIAL_PRINT((float)minFreq / FRE_FAC, 2); 168 | SERIAL_PRINT(","); 169 | SERIAL_PRINT((float)avgPower / ENE_FAC, 2); 170 | SERIAL_PRINT(","); 171 | SERIAL_PRINTLN((float)energy /ENE_FAC, 2); 172 | 173 | } 174 | } 175 | 176 | 177 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-max-min/ModbusSensor.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.cpp 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and energy monitor family. 5 | 6 | version 0.1 ALPHA 14/12/2015 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | //------------------------------------------------------------------------------ 13 | 14 | #define MODBUS_SERIAL_OUTPUT //VERBOSE 15 | 16 | #ifdef MODBUS_SERIAL_OUTPUT 17 | #define MODBUS_SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 18 | #define MODBUS_SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 19 | #define MODBUS_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 20 | #else 21 | #define MODBUS_SERIAL_BEGIN(...) 22 | #define MODBUS_SERIAL_PRINT(...) 23 | #define MODBUS_SERIAL_PRINTLN(...) 24 | #endif 25 | 26 | 27 | 28 | #include "ModbusSensor.h" 29 | 30 | // Finite state machine status 31 | #define STOP 0 32 | #define SENDING 1 33 | #define RECEIVING 2 34 | #define STANDBY 3 35 | 36 | #define WAITING_INTERVAL 10 37 | 38 | #define READ_COIL_STATUS 0x01 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 39 | #define READ_INPUT_STATUS 0x02 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 40 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary contents of holding registers (4X references) in the slave. 41 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 42 | #define FORCE_MULTIPLE_COILS 0x0F // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 43 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 44 | 45 | #define MB_VALID_DATA 0x00 // ok 46 | #define MB_INVALID_ID 0xE0 // id received don't match 47 | #define MB_INVALID_FC 0xE1 // function code don't match 48 | #define MB_TIMEOUT 0xE2 // slave don't respond ¿maybe off? 49 | #define MB_INVALID_CRC 0xE3 // calculated CRC don't match with recived CRC 50 | #define MB_INVALID_BUFF 0xE4 // corrupted frame or overflow 51 | #define MB_ILLEGAL_FC 0x01 // The function code is not supported by the product 52 | #define MB_ILLEGAL_ADR 0x02 // Attempt to access an invalid address or an attempt to read or write part of a floating point value 53 | #define MB_ILLEGAL_DATA 0x03 // Attempt to set a floating point variable to an invalid value 54 | #define MB_SLAVE_FAIL 0x05 // An error occurred when the instrument attempted to store an update to it’s configuration 55 | 56 | // What happens when _status is diferent to MB_VALID_DATA? 57 | #define CHANGE_TO_ZERO 0x00 58 | #define CHANGE_TO_ONE 0x01 59 | #define HOLD_VALUE 0xFF 60 | 61 | uint16_t calculateCRC(uint8_t *array, uint8_t num) { 62 | uint16_t temp, temp2, flag; 63 | temp = 0xFFFF; 64 | for (uint8_t i = 0; i < num; i++) 65 | { 66 | temp = temp ^ array[i]; 67 | for (uint8_t j = 1; j <= 8; j++) 68 | { 69 | flag = temp & 0x0001; 70 | temp >>= 1; 71 | if (flag) 72 | temp ^= 0xA001; 73 | } 74 | } 75 | // the returned value is already swapped 76 | // crcLo byte is first & crcHi byte is last 77 | return temp; 78 | } 79 | 80 | // Constructor 81 | modbusSensor::modbusSensor(modbusMaster * mbm, uint8_t id, uint16_t adr, uint8_t hold) { 82 | _frame[0] = id; 83 | _frame[1] = READ_INPUT_REGISTERS; 84 | _frame[2] = adr >> 8; 85 | _frame[3] = adr & 0x00FF; 86 | _frame[4] = 0x00; 87 | _frame[5] = 0x02; 88 | uint16_t crc = calculateCRC(_frame, 6); 89 | _frame[6] = crc & 0x00FF; 90 | _frame[7] = crc >> 8; 91 | _status = MB_TIMEOUT; 92 | _hold = hold; 93 | _value.f = 0.0; 94 | (*mbm).connect(this); 95 | } 96 | 97 | // read value in defined units 98 | float modbusSensor::read() { 99 | if (_status == MB_TIMEOUT) 100 | switch (_hold) { 101 | case CHANGE_TO_ZERO: return 0.0; 102 | case CHANGE_TO_ONE: return 1.0; 103 | case HOLD_VALUE: return _value.f; 104 | } 105 | return _value.f; 106 | } 107 | 108 | // read value as a integer multiplied by factor 109 | uint16_t modbusSensor::read(uint16_t factor) { 110 | if (_status == MB_TIMEOUT) 111 | switch (_hold) { 112 | case CHANGE_TO_ZERO: return (uint16_t) 0; 113 | case CHANGE_TO_ONE: return (uint16_t) factor; 114 | case HOLD_VALUE: return (uint16_t)(_value.f * factor); 115 | } 116 | return (uint16_t)(_value.f * factor); 117 | } 118 | 119 | // get status of the value 120 | uint8_t modbusSensor::getStatus() { 121 | return _status; 122 | } 123 | 124 | // write sensor value 125 | void modbusSensor::write(float value) { 126 | _value.f = value; 127 | } 128 | 129 | // put new status 130 | uint8_t modbusSensor::putStatus(uint8_t status) { 131 | _status = status; 132 | return _status; 133 | } 134 | 135 | // get pointer to _poll frame 136 | uint8_t *modbusSensor::getFramePtr() { 137 | return _frame; 138 | } 139 | 140 | //---------------------------------------------------------------------------------------// 141 | 142 | //constructor 143 | modbusMaster::modbusMaster(HardwareSerial * MBSerial, uint8_t TxEnPin) { 144 | _state = STOP; 145 | _TxEnablePin = TxEnPin; 146 | pinMode(_TxEnablePin, OUTPUT); 147 | _MBSerial = MBSerial; 148 | _totalSensors = 0; 149 | for (uint8_t i = 0; i < MAX_SENSORS; i++) { 150 | _mbSensorsPtr[i] = 0; 151 | } 152 | } 153 | 154 | // Connect modbusSensor to modbusMaster array of queries 155 | boolean modbusMaster::connect(modbusSensor * mbs) { 156 | if (_totalSensors < MAX_SENSORS) { 157 | _mbSensorsPtr[_totalSensors] = mbs; 158 | _totalSensors++; 159 | return true; 160 | } 161 | else return false; 162 | } 163 | 164 | // begin comunication using ModBus protocol over RS485 165 | void modbusMaster::begin(uint16_t baudrate, uint8_t byteFormat, uint16_t timeOut, uint16_t pollInterval) { 166 | _timeOut = timeOut; 167 | _pollInterval = pollInterval - 1; // reduce 1 to compensate proccess delays 168 | if (baudrate > 19200) 169 | _T1_5 = 750; 170 | else 171 | _T1_5 = 16500000 / baudrate; // 1T * 1.5 = T1.5 172 | (*_MBSerial).begin(baudrate, byteFormat); 173 | _state = SENDING; 174 | digitalWrite(_TxEnablePin, LOW); 175 | } 176 | 177 | // process FSM and check if the array of sensors has been requested and processed 178 | boolean modbusMaster::available() { 179 | static uint8_t indexSensor = 0; // index of arrry of sensors 180 | static uint32_t nowMillis = millis(); 181 | static uint32_t lastPollMillis = nowMillis; // time to check poll interval 182 | static uint32_t sendMillis = nowMillis; // time to check timeout interval 183 | static uint32_t receiveMillis = 0; 184 | // static uint32_t offlineMillis = nowMillis; // time to check offline interval 185 | // static uint8_t lastStatus = MB_TIMEOUT; // ¿offline? 186 | 187 | switch (_state) { 188 | case SENDING: 189 | if (millis() - receiveMillis < WAITING_INTERVAL) 190 | return false; 191 | if (indexSensor < _totalSensors) { 192 | _mbSensorPtr = _mbSensorsPtr[indexSensor]; 193 | _framePtr = (*_mbSensorPtr).getFramePtr(); 194 | sendFrame(); 195 | sendMillis = millis(); 196 | _state = RECEIVING; 197 | return false; 198 | } 199 | else { 200 | indexSensor = 0; 201 | _state = STANDBY; 202 | return true; 203 | } 204 | case RECEIVING: 205 | if ((*_MBSerial).available()) { 206 | readBuffer(); 207 | MODBUS_SERIAL_PRINTLN((*_mbSensorPtr).getStatus(), HEX); 208 | receiveMillis = millis(); 209 | indexSensor++; 210 | _state = SENDING; 211 | } 212 | else if (millis() - sendMillis > _timeOut) { 213 | (*_mbSensorPtr).putStatus(MB_TIMEOUT); 214 | indexSensor++; 215 | _state = SENDING; 216 | } 217 | return false; 218 | case STANDBY: 219 | nowMillis = millis(); 220 | if (nowMillis - lastPollMillis > _pollInterval) { 221 | lastPollMillis = nowMillis; 222 | _state = SENDING; 223 | } 224 | return false; 225 | case STOP: // do nothing 226 | return false; 227 | } 228 | } 229 | 230 | uint8_t modbusMaster::readBuffer() { 231 | uint8_t index = 0; 232 | boolean ovfFlag = false; 233 | MODBUS_SERIAL_PRINT(millis()); 234 | MODBUS_SERIAL_PRINT(" SLAVE:"); 235 | while ((*_MBSerial).available()) { 236 | // The maximum number of bytes is limited to the serial buffer size 237 | // of BUFFER_SIZE. If more bytes is received than the BUFFER_SIZE the 238 | // overflow flag will be set and the serial buffer will be read until 239 | // all the data is cleared from the receive buffer, while the slave is 240 | // still responding. 241 | if (ovfFlag) 242 | (*_MBSerial).read(); 243 | else { 244 | if (index == BUFFER_SIZE) ovfFlag = true; 245 | _buffer[index] = (*_MBSerial).read(); 246 | //#ifdef MODBUS_SERIAL_OUTPUT 247 | if (_buffer[index] < 0x10) 248 | Serial.print(F(" 0")); 249 | else 250 | Serial.print(F(" ")); 251 | Serial.print(_buffer[index], HEX); 252 | //#endif 253 | index++; 254 | } 255 | // This is not 100% correct but it will suffice. 256 | // worst case scenario is if more than one character time expires 257 | // while reading from the buffer then the buffer is most likely empty 258 | // If there are more bytes after such a delay it is not supposed to 259 | // be received and thus will force a frame_error. 260 | 261 | delayMicroseconds(_T1_5); // inter character time out 262 | } 263 | MODBUS_SERIAL_PRINT(" "); 264 | MODBUS_SERIAL_PRINTLN(millis()); 265 | 266 | // The minimum buffer size from a slave can be an exception response of 5 bytes. 267 | // If the buffer was partially filled set a frame_error. 268 | if (index < 5 || ovfFlag) 269 | return ((*_mbSensorPtr).putStatus(MB_SLAVE_FAIL)); 270 | 271 | if (_buffer[0] != _framePtr[0]) 272 | return ((*_mbSensorPtr).putStatus(MB_INVALID_ID)); 273 | 274 | uint16_t crc = calculateCRC(_buffer, index - 2); 275 | if (_buffer[index-1] != crc >> 8 && _buffer[index-2] != crc & 0x00FF) 276 | return ((*_mbSensorPtr).putStatus(MB_INVALID_CRC)); 277 | 278 | if (_buffer[1] & 0x80 == 0x80) { 279 | MODBUS_SERIAL_PRINTLN(_buffer[0], HEX); 280 | MODBUS_SERIAL_PRINTLN(_framePtr[0], HEX); 281 | return ((*_mbSensorPtr).putStatus(_buffer[2])); // see exception codes in define area 282 | } 283 | 284 | switch (_buffer[1]) { 285 | case READ_INPUT_REGISTERS: 286 | 287 | if (_buffer[2] == 4) { 288 | dataFloat temp; 289 | temp.arr[3] = _buffer[3]; 290 | temp.arr[2] = _buffer[4]; 291 | temp.arr[1] = _buffer[5]; 292 | temp.arr[0] = _buffer[6]; 293 | MODBUS_SERIAL_PRINTLN(temp.f); 294 | (*_mbSensorPtr).write(temp.f); 295 | return ((*_mbSensorPtr).putStatus(MB_VALID_DATA)); 296 | } 297 | else 298 | return ((*_mbSensorPtr).putStatus(MB_ILLEGAL_DATA)); 299 | 300 | default: 301 | return ((*_mbSensorPtr).putStatus(MB_ILLEGAL_FC)); 302 | } 303 | } 304 | 305 | void modbusMaster::sendFrame() { 306 | digitalWrite(_TxEnablePin, HIGH); 307 | MODBUS_SERIAL_PRINT(millis()); 308 | MODBUS_SERIAL_PRINT(F(" MASTER:")); 309 | for (uint8_t i; i < 8; i++) { 310 | (*_MBSerial).write(_framePtr[i]); 311 | #ifdef MODBUS_SERIAL_OUTPUT 312 | if (_framePtr[i] < 0x10) 313 | Serial.print(F(" 0")); 314 | else 315 | Serial.print(F(" ")); 316 | Serial.print(_framePtr[i], HEX); 317 | #endif 318 | } 319 | (*_MBSerial).flush(); 320 | MODBUS_SERIAL_PRINT(" "); 321 | MODBUS_SERIAL_PRINTLN(millis()); 322 | // It may be necessary to add a another character delay T1_5 here to 323 | // avoid truncating the message on slow and long distance connections 324 | 325 | digitalWrite(_TxEnablePin, LOW); 326 | } 327 | 328 | -------------------------------------------------------------------------------- /Stable version/Modbus-Energy-Monitor-max-min/ModbusSensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.h 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and family. 5 | 6 | version 0.1 ALPHA 14/12/2015 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | 13 | #ifndef ModbusSensor_h 14 | #define ModbusSensor_h 15 | 16 | #include "Arduino.h" 17 | #define MAX_SENSORS 16 18 | // The maximum number of bytes in a modbus packet is 256 bytes. 19 | // The serial buffer limits this to 128 bytes. 20 | // We can reduce it to maximum data sending by a slave SMD120 9 bytes, SMD 630 is 85 bytes 21 | // Three phase meters are 3 values, 6 registers and 12 bytes, plus 5 frame bytes, total 17 22 | #define BUFFER_SIZE 32 23 | 24 | // What happens when _status is diferent to MB_VALID_DATA? 25 | #define CHANGE_TO_ZERO 0x00 26 | #define CHANGE_TO_ONE 0x01 27 | #define HOLD_VALUE 0xFF 28 | 29 | class modbusMaster; 30 | 31 | union pollFrame { 32 | uint8_t array[8]; 33 | struct { 34 | uint8_t id; 35 | uint8_t fc; 36 | uint16_t address; 37 | uint16_t data; 38 | uint16_t crc; 39 | }; 40 | }; 41 | 42 | union dataFloat { 43 | float f; 44 | uint8_t arr[4]; 45 | }; 46 | 47 | 48 | class modbusSensor { 49 | public: 50 | // Constructor 51 | modbusSensor(modbusMaster *mbm, uint8_t id, uint16_t adr, uint8_t hold); 52 | 53 | // read value in defined units 54 | float read(); 55 | 56 | // read value as a integer multiplied by factor 57 | uint16_t read(uint16_t factor); 58 | 59 | // get status of the value 60 | uint8_t getStatus(); 61 | 62 | // write sensor value 63 | void write(float value); 64 | 65 | // change status 66 | uint8_t putStatus(uint8_t status); 67 | 68 | // get pointer to _poll frame 69 | uint8_t *getFramePtr(); 70 | 71 | private: 72 | uint8_t _frame[8]; 73 | dataFloat _value; 74 | uint8_t _status; 75 | uint8_t _hold; 76 | }; 77 | 78 | class modbusMaster { 79 | public: 80 | //constructor 81 | modbusMaster(HardwareSerial *MBSerial, uint8_t TxEnPin); 82 | 83 | // Connect modbusSensor to modbusMaster array of queries 84 | boolean connect(modbusSensor *mbs); 85 | 86 | // begin comunication using ModBus protocol over RS485 87 | void begin(uint16_t baudrate, uint8_t byteFormat, uint16_t timeOut, uint16_t pollInterval); 88 | 89 | // process FSM and check if the array of sensors has been requested 90 | boolean available(); 91 | 92 | private: 93 | void sendFrame(); 94 | uint8_t readBuffer(); 95 | uint8_t _state; // Modbus FSM status (SENDING, RECEIVING, STANDBY, STOP) 96 | uint8_t _TxEnablePin; // pin to enable transmision in MAX485 97 | uint8_t _totalSensors; // constant, max number of sensors to poll 98 | uint16_t _timeOut; // constant, time since lastMillis to fail poll 99 | uint16_t _pollInterval; // constant, time between polling same data 100 | uint16_t _T1_5; // inter-character time in microseconds 101 | uint8_t _buffer[BUFFER_SIZE]; // buffer to process rececived frame 102 | HardwareSerial *_MBSerial; 103 | modbusSensor *_mbSensorsPtr[MAX_SENSORS]; // array of modbusSensor's pointers 104 | modbusSensor *_mbSensorPtr; 105 | uint8_t *_framePtr; 106 | }; 107 | 108 | #endif 109 | 110 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino/Modbus-Energy-Monitor-Arduino.ino: -------------------------------------------------------------------------------- 1 | #include "ModbusSensor.h" 2 | #include "SDMdefines.h" 3 | 4 | 5 | #define MB_SERIAL_PORT &Serial1 // Arduino has only one serial port, Mega has 3 serial ports. 6 | // if use Serial 0, remember disconect Tx (pin0) when upload sketch, then re-conect 7 | #define MB_BAUDRATE 2400 // b 2400 8 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 9 | #define TxEnablePin 17 10 | 11 | #define ID_1 1 // id 001 modbus id of the energy monitor 12 | #define POLL_INTERVAL 5000 - 1 // refresh time, 5 SECONDS (adjust 1 ms duty cycle) 13 | 14 | 15 | struct three_phase { 16 | float line3, line2, line1; 17 | } voltage, current, power; 18 | 19 | float energy; 20 | boolean dataPrinted; 21 | 22 | // global variables to poll, process and send values 23 | 24 | //modbusSensor(uint8_t id, uint8_t fc, uint16_t adr, uint8_t hold, uint8_t sizeofValue) 25 | modbusSensor volt(ID_1, VOLTAGE, CHANGE_TO_ZERO, sizeof(three_phase), READ_INPUT_REGISTERS); 26 | modbusSensor curr(ID_1, CURRENT, CHANGE_TO_ZERO, sizeof(three_phase), READ_INPUT_REGISTERS); 27 | modbusSensor pwr(ID_1, POWER, CHANGE_TO_ZERO, sizeof(three_phase), READ_INPUT_REGISTERS); 28 | 29 | //modbusSensor(uint8_t uint16_t adr, uint8_t hold) 30 | modbusSensor enrg(ID_1, IAENERGY, HOLD_VALUE); 31 | uint32_t previousMillis = 10000; 32 | 33 | void setup() { 34 | Serial.begin(9600); 35 | delay(95); 36 | MBSerial.config(MB_SERIAL_PORT, TxEnablePin); 37 | MBSerial.begin(MB_BAUDRATE, MB_BYTEFORMAT); 38 | Serial.println("time(s),Volt1(V), Volt2(V), Volt3(V), Curr1(A) Curr2(A), Curr3(A), Power1(W), Power2(W), Power3(W), Energy(Kwh)"); 39 | dataPrinted = false; 40 | } 41 | 42 | void loop() { 43 | uint32_t currentMillis = millis(); 44 | if (currentMillis - previousMillis > POLL_INTERVAL) 45 | if ( MBSerial.sendRequest() == true) { 46 | previousMillis = currentMillis; 47 | dataPrinted = false; 48 | } 49 | 50 | if (MBSerial.available() && !dataPrinted) { 51 | volt.read(voltage); 52 | curr.read(current); 53 | pwr.read(power); 54 | energy = enrg.read(); 55 | 56 | Serial.print(millis() / 1000); 57 | Serial.print(","); 58 | Serial.print(voltage.line1, 1); 59 | Serial.print(","); 60 | Serial.print(voltage.line2, 1); 61 | Serial.print(","); 62 | Serial.print(voltage.line3, 1); 63 | Serial.print(","); 64 | Serial.print(current.line1, 2); 65 | Serial.print(","); 66 | Serial.print(current.line2, 2); 67 | Serial.print(","); 68 | Serial.print(current.line3, 2); 69 | Serial.print(","); 70 | Serial.print(power.line1, 2); 71 | Serial.print(","); 72 | Serial.print(power.line2, 2); 73 | Serial.print(","); 74 | Serial.print(power.line3, 2); 75 | Serial.print(","); 76 | Serial.println(energy, 2); 77 | dataPrinted = true; 78 | 79 | } 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino/ModbusSensor.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ModbusSensor class 3 | 4 | 5 | version 0.5.6 BETA 19/01/2016 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | *******************************************************************************/ 11 | //------------------------------------------------------------------------------ 12 | 13 | //#define MODBUS_SERIAL_OUTPUT //Verbose MODBUS messages and timing 14 | 15 | #ifdef MODBUS_SERIAL_OUTPUT 16 | #define MODBUS_SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 17 | #define MODBUS_SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 18 | #define MODBUS_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 19 | #else 20 | #define MODBUS_SERIAL_BEGIN(...) 21 | #define MODBUS_SERIAL_PRINT(...) 22 | #define MODBUS_SERIAL_PRINTLN(...) 23 | #endif 24 | 25 | #include "ModbusSensor.h" 26 | 27 | // Finite state machine status 28 | #define STOP 0 29 | #define IDLE 1 30 | #define SENDING 2 31 | #define RECEIVING 3 32 | #define WAITING_TURNAROUND 4 33 | 34 | /*#define READ_COIL_STATUS 0x01 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 35 | #define READ_INPUT_STATUS 0x02 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 36 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary contents of holding registers (4X references) in the slave. 37 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 38 | #define FORCE_MULTIPLE_COILS 0x0F // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 39 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 40 | 41 | #define MB_VALID_DATA 0x00 42 | #define MB_INVALID_ID 0xE0 43 | #define MB_INVALID_FC 0xE1 44 | #define MB_TIMEOUT 0xE2 45 | #define MB_INVALID_CRC 0xE3 46 | #define MB_INVALID_BUFF 0xE4 47 | #define MB_ILLEGAL_FC 0x01 48 | #define MB_ILLEGAL_ADR 0x02 49 | #define MB_ILLEGAL_DATA 0x03 50 | #define MB_SLAVE_FAIL 0x04 51 | #define MB_EXCEPTION 0x05 52 | 53 | // when _status is diferent to MB_VALID_DATA change it to zero or hold last valid value? 54 | //#define CHANGE_TO_ZERO 0x00 55 | //#define CHANGE_TO_ONE 0x01 56 | //#define HOLD_VALUE 0xFF 57 | */ 58 | 59 | uint16_t calculateCRC(uint8_t *array, uint8_t num) { 60 | uint16_t temp, flag; 61 | temp = 0xFFFF; 62 | for (uint8_t i = 0; i < num; i++) { 63 | temp = temp ^ array[i]; 64 | for (uint8_t j = 8; j; j--) { 65 | flag = temp & 0x0001; 66 | temp >>= 1; 67 | if (flag) 68 | temp ^= 0xA001; 69 | } 70 | } 71 | return temp; 72 | } 73 | 74 | //------------------------------------------------------------------------------ 75 | //configure sketch variables 76 | void modbusMaster::config(HardwareSerial * hwSerial, uint8_t TxEnPin) { 77 | _hwSerial = hwSerial; 78 | _TxEnablePin = TxEnPin; 79 | pinMode(_TxEnablePin, OUTPUT); 80 | _state = STOP; 81 | MODBUS_SERIAL_PRINT(_totalSensors); MODBUS_SERIAL_PRINTLN(" total sensors attached at config"); 82 | } 83 | 84 | 85 | //------------------------------------------------------------------------------ 86 | // attach a modbusSensor to the modbusMaster array of queries 87 | void modbusMaster::attach(modbusSensor * mbSensor) { 88 | if (_totalSensors < MAX_SENSORS) { 89 | _mbSensorsPtr[_totalSensors] = mbSensor; 90 | _totalSensors++; 91 | } 92 | return; 93 | } 94 | 95 | //------------------------------------------------------------------------------ 96 | // detach a modbusSensor to the modbusMaster array of queries 97 | void modbusMaster::detach(modbusSensor * mbSensor) { 98 | uint8_t i = 0; 99 | while (i < _totalSensors) { 100 | if (_mbSensorsPtr[i] == mbSensor) { 101 | MODBUS_SERIAL_PRINT(i); MODBUS_SERIAL_PRINTLN(" sensor detached"); 102 | while (i < _totalSensors - 1) { 103 | _mbSensorsPtr[i] = _mbSensorsPtr[i + 1]; //shift down pointers one position 104 | } 105 | _totalSensors--; // reduce number of total sensors in the array 106 | _mbSensorsPtr[_totalSensors] = 0; // delete last position 107 | } 108 | i++; 109 | } 110 | } 111 | 112 | //------------------------------------------------------------------------------ 113 | // begin communication using ModBus protocol over RS485 114 | void modbusMaster::begin(uint16_t baudrate, uint8_t byteFormat) { 115 | if (baudrate > 19200) 116 | _T2_5 = 1250; 117 | else 118 | _T2_5 = 27500000 / baudrate; // 2400 bauds --> 11458 us; 9600 bauds --> 2864 us 119 | (*_hwSerial).begin(baudrate, byteFormat); 120 | _indexSensor = _totalSensors; 121 | _state = IDLE; 122 | digitalWrite(_TxEnablePin, LOW); 123 | } 124 | 125 | //------------------------------------------------------------------------------ 126 | // end communication over serial port 127 | inline void modbusMaster::end() { 128 | _state = STOP; 129 | (*_hwSerial).end(); 130 | digitalWrite(_TxEnablePin, LOW); 131 | } 132 | 133 | //------------------------------------------------------------------------------ 134 | // Start process to send requests 135 | boolean modbusMaster::sendRequest() { 136 | if (_state = IDLE) { 137 | _indexSensor = 0; 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | //------------------------------------------------------------------------------ 144 | // Finite State Machine core, 145 | boolean modbusMaster::available() { 146 | static uint8_t frameSize; // size of the RX frame 147 | static uint32_t tMicros; // time to check between characters in a frame 148 | static uint32_t nowMillis; 149 | 150 | switch (_state) { 151 | //----------------------------------------------------------------------------- 152 | case IDLE: 153 | if (_indexSensor < _totalSensors) { 154 | digitalWrite(_TxEnablePin, HIGH); 155 | uint8_t *frame = (*_mbSensorsPtr[_indexSensor])._frame; 156 | uint8_t frameSize = (*_mbSensorsPtr[_indexSensor])._frameSize; 157 | sendFrame(frame, frameSize); 158 | tMicros = micros(); 159 | _state = SENDING; 160 | return false; 161 | } 162 | // if _indexSensor == _totalSensors, all request done 163 | return true; 164 | 165 | //----------------------------------------------------------------------------- 166 | case SENDING: 167 | 168 | if ((*_hwSerial).availableForWrite() < SERIAL_TX_BUFFER_SIZE - 1) { //TX buffer not empty 169 | tMicros = micros(); 170 | return false; 171 | } 172 | // delayMicroseconds(_T2_5); 173 | // time to send last byte and required empty time space 174 | if (micros() - tMicros < _T2_5) return false; 175 | 176 | // clean RX buffer 177 | while ((*_hwSerial).available()) (*_hwSerial).read(); 178 | 179 | // MAX485 Receiving mode 180 | digitalWrite(_TxEnablePin, LOW); 181 | _state = RECEIVING; 182 | 183 | //starts slave's timeOut 184 | _timeoutMillis = millis(); 185 | frameSize = 0; 186 | 187 | return false; 188 | 189 | //----------------------------------------------------------------------------- 190 | case RECEIVING: 191 | 192 | if (!(*_hwSerial).available()) { 193 | if (millis() - _timeoutMillis > TIMEOUT) { 194 | (*_mbSensorsPtr[_indexSensor])._status = MB_TIMEOUT; 195 | #ifdef MODBUS_SERIAL_OUTPUT 196 | (*_mbSensorsPtr[_indexSensor]).printStatus(); 197 | #endif 198 | _indexSensor++; 199 | _state = IDLE; 200 | } 201 | return false; 202 | } 203 | 204 | if ((*_hwSerial).available() > frameSize) { 205 | frameSize++; 206 | tMicros = micros(); 207 | } 208 | else { 209 | if (micros() - tMicros > _T2_5) { 210 | readBuffer(frameSize); 211 | (*_mbSensorsPtr[_indexSensor]).processBuffer(_rx_buffer, frameSize); 212 | #ifdef MODBUS_SERIAL_OUTPUT 213 | (*_mbSensorsPtr[_indexSensor]).printStatus(); 214 | #endif 215 | _indexSensor++; 216 | _waitingMillis = millis(); //starts waiting interval to next request 217 | _state = WAITING_TURNAROUND; 218 | } 219 | } 220 | return false; 221 | 222 | //----------------------------------------------------------------------------- 223 | case WAITING_TURNAROUND: 224 | if (millis() - _waitingMillis > WAITING_INTERVAL) 225 | _state = IDLE; 226 | return false; 227 | 228 | //----------------------------------------------------------------------------- 229 | case STOP: 230 | for (; _indexSensor < _totalSensors; _indexSensor++) 231 | (*_mbSensorsPtr[_indexSensor])._status = MB_MASTER_STOP; 232 | return true; 233 | } 234 | } 235 | 236 | //----------------------------------------------------------------------------- 237 | // 238 | void modbusMaster::sendFrame(uint8_t *frame, uint8_t frameSize) { 239 | MODBUS_SERIAL_PRINT(millis()); 240 | MODBUS_SERIAL_PRINT(" MASTER:"); 241 | MODBUS_SERIAL_PRINTLN((*_hwSerial).availableForWrite()); 242 | 243 | (*_hwSerial).write(frame, frameSize); 244 | 245 | MODBUS_SERIAL_PRINTLN((*_hwSerial).availableForWrite()); 246 | 247 | #ifdef MODBUS_SERIAL_OUTPUT 248 | for (uint8_t index = 0; index < frameSize; index++) { 249 | if (frame[index] < 0x10) 250 | Serial.print(" 0"); 251 | else 252 | Serial.print(" "); 253 | Serial.print(frame[index], HEX); 254 | } 255 | Serial.print(" "); 256 | Serial.println(millis()); 257 | #endif 258 | } 259 | 260 | //----------------------------------------------------------------------------- 261 | inline void modbusMaster::readBuffer(uint8_t frameSize) { 262 | 263 | MODBUS_SERIAL_PRINT(millis()); 264 | MODBUS_SERIAL_PRINT(" SLAVE:"); 265 | 266 | (*_hwSerial).readBytes(_rx_buffer, frameSize); 267 | 268 | #ifdef MODBUS_SERIAL_OUTPUT 269 | for (uint8_t index = 0; index < frameSize; index++) { 270 | if (_rx_buffer[index] < 0x10) 271 | Serial.print(" 0"); 272 | else 273 | Serial.print(" "); 274 | Serial.print(_rx_buffer[index], HEX); 275 | } 276 | Serial.print(" "); 277 | Serial.println(millis()); 278 | #endif 279 | } 280 | 281 | // Create an instance of modbusMaster 282 | modbusMaster MBSerial; 283 | 284 | //----------------------------------------------------------------------------- 285 | //----------------------------------------------------------------------------- 286 | // Constructor 287 | modbusSensor::modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue, uint8_t fc) { 288 | // reserve space to new struct of value 289 | _value = new uint8_t[sizeofValue]; 290 | // pointer to the first byte 291 | uint8_t *ptr = _value; 292 | // reset content 293 | for (int count = sizeofValue; count; --count) *ptr++ = 0; 294 | 295 | switch (fc) { 296 | case PRESET_MULTIPLE_REGISTERS: 297 | case READ_HOLDING_REGISTERS: 298 | _frame = new uint8_t[9 + sizeofValue]; //reserve space for fc PRESET_MULTIPLE_REGISTERS 299 | _frame[1] = READ_HOLDING_REGISTERS; 300 | break; 301 | case READ_INPUT_REGISTERS: 302 | _frame = new uint8_t[8]; 303 | _frame[1] = READ_INPUT_REGISTERS; 304 | break; 305 | default: 306 | // exit without attach to MBSerial 307 | _frame = 0; 308 | _frameSize = 0; 309 | _status = MB_INVALID_FC; 310 | return; 311 | } 312 | _frameSize = 8; // always read function, change in preset() 313 | 314 | _frame[0] = id; 315 | //_frame[1] defined previously; 316 | _frame[2] = adr >> 8; 317 | _frame[3] = adr & 0x00FF; 318 | _frame[4] = 0x00; 319 | _frame[5] = sizeofValue / 2; 320 | uint16_t crc = calculateCRC(_frame, 6); 321 | _frame[6] = crc & 0x00FF; 322 | _frame[7] = crc >> 8; 323 | 324 | _status = MB_MASTER_STOP; 325 | _hold = hold; 326 | 327 | MBSerial.attach(this); 328 | } 329 | 330 | //------------------------------------------------------------------------------ 331 | //Process RX buffer 332 | void modbusSensor::processBuffer(uint8_t *rxFrame, uint8_t rxFrameSize) { 333 | 334 | // check minimum response frame size 335 | if (rxFrameSize < 5) { 336 | _status = MB_SLAVE_FAIL; 337 | return; 338 | } 339 | 340 | // check slave id 341 | if (rxFrame[0] != _frame[0]) { 342 | _status = MB_INVALID_ID; 343 | return; 344 | } 345 | 346 | // check CRC 347 | uint16_t crc = calculateCRC(rxFrame, rxFrameSize - 2); 348 | if (rxFrame[rxFrameSize - 1] != crc >> 8 && rxFrame[rxFrameSize - 2] != crc & 0x00FF) { 349 | _status = MB_INVALID_CRC; 350 | return; 351 | } 352 | 353 | // check slave's exception in function code 354 | if (rxFrame[1] & 0x80 == 0x80) { 355 | _status = rxFrame[2]; // see exception codes in define area or printStatus() 356 | return; 357 | } 358 | 359 | // check function code 360 | if (rxFrame[1] != _frame[1]) { 361 | _status = MB_INVALID_FC; 362 | return; 363 | } 364 | 365 | // READ frame, transfer modbus registers to _value 366 | switch (rxFrame[1]) { 367 | case READ_HOLDING_REGISTERS: 368 | case READ_INPUT_REGISTERS: 369 | // check byte count equals to registers request 370 | if (rxFrame[2] == _frame[2] * 2) { 371 | uint8_t *ptr = (uint8_t *) &_value; 372 | ptr += rxFrame[2] - 1; // pointer to object last byte 373 | uint8_t i = 3; 374 | for (int count = rxFrame[2]; count; --count, i++, ptr--) *ptr = rxFrame[i]; 375 | _status = MB_VALID_DATA; 376 | return; 377 | } 378 | else { 379 | _status = MB_ILLEGAL_DATA; 380 | return; 381 | } 382 | 383 | // PRESET frame, return OK 384 | case PRESET_MULTIPLE_REGISTERS: 385 | _status = MB_VALID_DATA; 386 | return; 387 | 388 | default: 389 | _status = MB_INVALID_FC; 390 | return; 391 | } 392 | } 393 | 394 | //------------------------------------------------------------------------------ 395 | // Construct Tx frame, send it, receive response and process it, reconstruct READ frame 396 | void modbusSensor::processPreset(uint8_t *ptr, uint8_t objectSize) { 397 | 398 | if (_frame[2] != READ_HOLDING_REGISTERS) { 399 | _status = MB_INVALID_ADR; 400 | return; 401 | } 402 | 403 | if (objectSize != _frame[5] * 2) { 404 | _status = MB_INVALID_DATA; 405 | return; 406 | } 407 | 408 | // Construct PRESET frame 409 | _frame[2] = PRESET_MULTIPLE_REGISTERS; 410 | _frame[6] = objectSize; 411 | uint8_t i = 7; 412 | 413 | ptr += objectSize - 1; // pointer to object last byte 414 | for (int count = objectSize; count; count--, i++) _frame[i] = *ptr--; 415 | uint16_t crc = calculateCRC(_frame, i); 416 | _frame[i++] = crc & 0x00FF; 417 | _frame[i++] = crc >> 8; 418 | _frameSize = i; 419 | 420 | // Send the PRESET frame, receive response and process it 421 | MBSerial.sendRequest(); 422 | while (!MBSerial.available()) {} 423 | 424 | // reconstruct READ frame 425 | _frame[2] = READ_HOLDING_REGISTERS; 426 | crc = calculateCRC(_frame, 6); 427 | _frame[6] = crc & 0x00FF; 428 | _frame[7] = crc >> 8; 429 | _frameSize = 8; 430 | } 431 | 432 | //------------------------------------------------------------------------------ 433 | // read value in defined units 434 | float modbusSensor::read() { 435 | if (_status == MB_TIMEOUT) 436 | switch (_hold) { 437 | case CHANGE_TO_ZERO: return 0.0; 438 | case CHANGE_TO_ONE: return 1.0; 439 | case HOLD_VALUE:; 440 | } 441 | return (float) * _value; 442 | } 443 | 444 | //------------------------------------------------------------------------------ 445 | // process status and return the predetermined response 446 | void modbusSensor::processRead(uint8_t *ptr, const uint8_t objectSize) { 447 | if (_status == MB_TIMEOUT) 448 | switch (_hold) { 449 | case CHANGE_TO_ZERO: 450 | for (int count = objectSize; count; --count) *ptr++ = 0; 451 | return; 452 | case CHANGE_TO_ONE:; 453 | case HOLD_VALUE:; 454 | } 455 | // copy _value on the returned struct 456 | const uint8_t *e = _value; 457 | for (int count = objectSize; count; --count, ++e) *ptr++ = *e; 458 | } 459 | 460 | //------------------------------------------------------------------------------ 461 | // print status message 462 | uint8_t modbusSensor::printStatus() { 463 | switch (_status) { 464 | case MB_VALID_DATA: 465 | Serial.println("Transmision successful"); 466 | return _status; 467 | case MB_INVALID_ID: 468 | Serial.println("No valid Id"); 469 | return _status; 470 | case MB_INVALID_FC: 471 | Serial.println("No valid FC"); 472 | return _status; 473 | case MB_TIMEOUT: 474 | Serial.println("Time out"); 475 | return _status; 476 | case MB_INVALID_CRC: 477 | Serial.println("incorrect CRC"); 478 | return _status; 479 | case MB_INVALID_BUFF: 480 | Serial.println("Invalid buffer size"); 481 | return _status; 482 | case MB_INVALID_ADR: 483 | Serial.println("No valid address"); 484 | return _status; 485 | case MB_INVALID_DATA: 486 | Serial.println("No valid data"); 487 | return _status; 488 | case MB_MASTER_STOP: 489 | Serial.println("Master in STOP mode"); 490 | return _status; 491 | case MB_ILLEGAL_FC: 492 | Serial.println("Exception: Illegal FC"); 493 | return _status; 494 | case MB_ILLEGAL_ADR: 495 | Serial.println("Exception: Illegal address"); 496 | return _status; 497 | case MB_ILLEGAL_DATA: 498 | Serial.println("Exception: Illegal data"); 499 | return _status; 500 | case MB_SLAVE_FAIL: 501 | Serial.println("Exception: Slave failed "); 502 | return _status; 503 | case MB_EXCEPTION: 504 | Serial.println("Exception"); 505 | return _status; 506 | default: 507 | Serial.println ("** ERROR **"); 508 | } 509 | } 510 | 511 | 512 | //---------------------------------------------------------------------------------------// 513 | //---------------------------------------------------------------------------------------// 514 | 515 | 516 | 517 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino/ModbusSensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.h 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and family. 5 | 6 | version 0.5.6 BETA 19/01/2016 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | 13 | #ifndef ModbusSensor_h 14 | #define ModbusSensor_h 15 | 16 | #include "Arduino.h" 17 | #define MAX_SENSORS 16 18 | // The maximum number of bytes in a modbus packet is 256 bytes. 19 | // The serial buffer limits this to 128 bytes. 20 | // We can reduce it to maximum data sending by a slave SMD120 9 bytes, SMD 630 is 85 bytes 21 | // Three phase meters are 3 values, 6 registers and 12 bytes, plus 5 frame bytes, total 17 22 | #define BUFFER_SIZE 32 23 | #define TIMEOUT 110 // time to fail a request 24 | #define WAITING_INTERVAL 40 // time required by SDM120 to be prepared to receive a new request 25 | 26 | // What happens when _status is diferent to MB_VALID_DATA? 27 | #define CHANGE_TO_ZERO 0x00 28 | #define CHANGE_TO_ONE 0x01 29 | #define HOLD_VALUE 0xFF 30 | 31 | // Function codes operative 32 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary content of holding registers (4X references) in the slave. 33 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary content of input registers (3X references) in the slave. Not writable. 34 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 35 | 36 | #define MB_VALID_DATA 0x00 37 | #define MB_INVALID_ID 0xE0 38 | #define MB_INVALID_FC 0xE1 39 | #define MB_TIMEOUT 0xE2 40 | #define MB_INVALID_CRC 0xE3 41 | #define MB_INVALID_BUFF 0xE4 42 | #define MB_INVALID_ADR 0xE5 43 | #define MB_INVALID_DATA 0xE6 44 | #define MB_MASTER_STOP 0xE7 45 | #define MB_ILLEGAL_FC 0x01 46 | #define MB_ILLEGAL_ADR 0x02 47 | #define MB_ILLEGAL_DATA 0x03 48 | #define MB_SLAVE_FAIL 0x04 49 | #define MB_EXCEPTION 0x05 50 | 51 | // forward definition 52 | class modbusSensor; 53 | 54 | //------------------------------------------------------------------------------ 55 | //------------------------------------------------------------------------------ 56 | class modbusMaster { 57 | protected: 58 | HardwareSerial *_hwSerial; 59 | modbusSensor *_mbSensorsPtr[MAX_SENSORS]; // array of modbusSensor's pointers 60 | uint8_t _TxEnablePin; // pin to enable transmision in MAX485 61 | uint8_t _totalSensors; // constant, max number of sensors to poll 62 | uint8_t _indexSensor; // index number of sensors 63 | uint8_t _rx_buffer[BUFFER_SIZE]; // buffer to process rececived frame 64 | uint8_t _state; // Modbus FSM state (STOP, IDLE, SENDING, RECEIVING, WAITING_TURNAROUND) 65 | uint32_t _T2_5; // time between characters in a frame, in microseconds 66 | uint32_t _timeoutMillis; // time to check timeout interval 67 | uint32_t _waitingMillis; // time to check waiting turnaround interval 68 | 69 | inline void sendFrame(uint8_t *frame, uint8_t frameSize); 70 | inline void readBuffer(uint8_t frameSize); 71 | 72 | public: 73 | //constructor 74 | modbusMaster() { 75 | _totalSensors = 0; 76 | for (uint8_t i = 0; i < MAX_SENSORS; i++) 77 | _mbSensorsPtr[i] = 0; 78 | }; 79 | 80 | //constructor 81 | void config(HardwareSerial *mbSerial, uint8_t TxEnPin); 82 | 83 | // attach a modbusSensor to modbusMaster array of queries 84 | void attach(modbusSensor *mbSensor); 85 | 86 | // detach a modbusSensor to modbusMaster array of queries 87 | void detach(modbusSensor *mbSensor); 88 | 89 | // begin communication using ModBus protocol over RS485 90 | void begin(uint16_t baudrate, uint8_t byteFormat); 91 | 92 | // end communication over serial port 93 | inline void end(); 94 | 95 | // Start process to send requests 96 | boolean sendRequest(); 97 | 98 | // Finite State Machine core, process FSM 99 | // It returns 'true' when finish to request all the array of modbusSensors. 100 | // Non-blocking function, put the instrucction in a loop function to make the proccess work properly 101 | boolean available(); 102 | }; 103 | 104 | extern modbusMaster MBSerial; 105 | 106 | //------------------------------------------------------------------------------ 107 | //------------------------------------------------------------------------------ 108 | class modbusSensor { 109 | protected: 110 | uint8_t * _value; // pointer to a dinamic allocated object, size inside _frame[5] 111 | uint8_t * _frame; // pointer to a dinamic allocated array, 112 | uint8_t _frameSize; // size of the _frame, 8 in read function, 9+sizeof(T) in preset function 113 | uint8_t _status; // register of the result of communication 114 | uint8_t _hold; // predefined behaiviour in case of timeout exception 115 | 116 | void processPreset(uint8_t *ptr, uint8_t objectSize); 117 | void processRead(uint8_t *ptr, uint8_t objectSize); 118 | void processBuffer(uint8_t *rxFrame, uint8_t rxFrameSize); 119 | 120 | //MBSerial.available need access to _frame, _frameSize and _status variables 121 | friend boolean modbusMaster::available(); 122 | 123 | public: 124 | // Target constructor 125 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue, uint8_t fc); 126 | 127 | // delegating constructor 128 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue): modbusSensor(id, adr, hold, sizeofValue, READ_INPUT_REGISTERS) {}; 129 | 130 | // delegating constructor 131 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold): modbusSensor(id, adr, hold, 4, READ_INPUT_REGISTERS) {}; 132 | 133 | // Destructor 134 | ~modbusSensor() { 135 | delete[] _value; 136 | delete[] _frame; 137 | MBSerial.detach(this); 138 | } 139 | 140 | // attach to MBSerial array of sensors 141 | void attach() { 142 | MBSerial.attach(this); 143 | }; 144 | 145 | // detach to MBSerial array of sensors 146 | void detach() { 147 | MBSerial.detach(this); 148 | }; 149 | 150 | // Preset sensor value, fc 0x10, only holding registers defined with fc 0x03 151 | // complete funtion to make and send the frame and process response, check status 152 | template < typename T > void preset(const T &t) { 153 | processPreset((uint8_t *) &t, sizeof(T)); 154 | }; 155 | 156 | // read value in defined units 157 | float read(); 158 | 159 | // read every struct value from object buffer, non-blocking function 160 | template< typename T > T &read(T &t) { 161 | processRead((uint8_t *) &t, sizeof(T)); 162 | return t; 163 | } 164 | 165 | // print status message 166 | uint8_t printStatus(); 167 | }; 168 | 169 | #endif 170 | 171 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino/Monitor Serial debug.txt: -------------------------------------------------------------------------------- 1 | time(s), maxVolt(V), minVolt(V), maxCurr(A) minCurr(A), maxPower(W), minPower(W), maxApPower(VA), minApPower(VA), maxFreq(Hz), minFreq(Hz), AvgPower (W), Energy(Kwh) 2 | 200 MASTER: 01 04 00 00 00 02 71 CB 241 3 | 384 SLAVE: 01 04 04 43 64 33 33 FB 3A 385 4 | 228.20 5 | 0 6 | 427 MASTER: 01 04 00 06 00 02 91 CA 428 7 | 604 SLAVE: 01 04 04 3E 2E 14 7B D9 46 605 8 | 0.17 9 | 0 10 | 647 MASTER: 01 04 00 0C 00 02 B1 C8 648 11 | 824 SLAVE: 01 04 04 42 0A 66 66 64 74 825 12 | 34.60 13 | 0 14 | 867 MASTER: 01 04 00 48 00 02 F1 DD 868 15 | 1037 SLAVE: 01 04 04 3E 90 E5 60 BC F9 1039 16 | 0.28 17 | 0 18 | 1080 MASTER: 01 04 00 46 00 02 90 1E 1081 19 | 1258 SLAVE: 01 04 04 42 48 00 00 6F EA 1259 20 | 50.00 21 | 0 22 | 1301 MASTER: 01 04 00 12 00 02 D1 CE 1302 23 | 1482 SLAVE: 01 04 04 42 11 FA 24 FD 42 1483 24 | 36.49 25 | 0 26 | 1525 MASTER: 01 04 00 1E 00 02 11 CD 1526 27 | 1702 SLAVE: 01 04 04 3F 72 EC E8 1B 05 1703 28 | 0.95 29 | 0 30 | 5200 MASTER: 01 04 00 00 00 02 71 CB 5201 31 | 5370 SLAVE: 01 04 04 43 64 00 00 AF DF 5371 32 | 228.00 33 | 0 34 | 5413 MASTER: 01 04 00 06 00 02 91 CA 5414 35 | 5581 SLAVE: 01 04 04 3E 2E 14 7B D9 46 5582 36 | 0.17 37 | 0 38 | 5624 MASTER: 01 04 00 0C 00 02 B1 C8 5625 39 | 5793 SLAVE: 01 04 04 42 0A 66 66 64 74 5794 40 | 34.60 41 | 0 42 | 5836 MASTER: 01 04 00 48 00 02 F1 DD 5837 43 | 6016 SLAVE: 01 04 04 3E 90 E5 60 BC F9 6017 44 | 0.28 45 | 0 46 | 6059 MASTER: 01 04 00 46 00 02 90 1E 6060 47 | 6236 SLAVE: 01 04 04 42 48 00 00 6F EA 6237 48 | 50.00 49 | 0 50 | 6279 MASTER: 01 04 00 12 00 02 D1 CE 6280 51 | 6459 SLAVE: 01 04 04 42 11 E1 0A 77 AE 6460 52 | 36.47 53 | 0 54 | 6502 MASTER: 01 04 00 1E 00 02 11 CD 6503 55 | 6680 SLAVE: 01 04 04 3F 73 69 82 A8 7A 6682 56 | 0.95 57 | 0 58 | 10200 MASTER: 01 04 00 00 00 02 71 CB 10201 59 | 10369 SLAVE: 01 04 04 43 63 66 66 B5 94 10371 60 | 227.40 61 | 0 62 | 10413 MASTER: 01 04 00 06 00 02 91 CA 10414 63 | 10590 SLAVE: 01 04 04 3E 2E 14 7B D9 46 10591 64 | 0.17 65 | 0 66 | 10633 MASTER: 01 04 00 0C 00 02 B1 C8 10634 67 | 10801 SLAVE: 01 04 04 42 0A 66 66 64 74 10803 68 | 34.60 69 | 0 70 | 10844 MASTER: 01 04 00 48 00 02 F1 DD 10845 71 | 11023 SLAVE: 01 04 04 3E 90 E5 60 BC F9 11025 72 | 0.28 73 | 0 74 | 11066 MASTER: 01 04 00 46 00 02 90 1E 11067 75 | 11243 SLAVE: 01 04 04 42 48 00 00 6F EA 11244 76 | 50.00 77 | 0 78 | 11286 MASTER: 01 04 00 12 00 02 D1 CE 11287 79 | 11466 SLAVE: 01 04 04 42 11 89 24 D8 72 11468 80 | 36.38 81 | 0 82 | 11510 MASTER: 01 04 00 1E 00 02 11 CD 11511 83 | 11687 SLAVE: 01 04 04 3F 73 9F D2 EF E6 11689 84 | 0.95 85 | 0 86 | 15200 MASTER: 01 04 00 00 00 02 71 CB 15201 87 | 15377 SLAVE: 01 04 04 43 63 B3 33 2B 3B 15378 88 | 227.70 89 | 0 90 | 15420 MASTER: 01 04 00 06 00 02 91 CA 15421 91 | 15597 SLAVE: 01 04 04 3E 2E 14 7B D9 46 15598 92 | 0.17 93 | 0 94 | 15640 MASTER: 01 04 00 0C 00 02 B1 C8 15641 95 | 15817 SLAVE: 01 04 04 42 0A CC CD 5B 6B 15818 96 | 34.70 97 | 0 98 | 15860 MASTER: 01 04 00 48 00 02 F1 DD 15861 99 | 16041 SLAVE: 01 04 04 3E 90 E5 60 BC F9 16044 100 | 0.28 101 | 0 102 | 16086 MASTER: 01 04 00 46 00 02 90 1E 16087 103 | 16259 SLAVE: 01 04 04 42 48 00 00 6F EA 16260 104 | 50.00 105 | 0 106 | 16302 MASTER: 01 04 00 12 00 02 D1 CE 16303 107 | 16484 SLAVE: 01 04 04 42 11 F4 70 F8 DD 16485 108 | 36.49 109 | 0 110 | 16527 MASTER: 01 04 00 1E 00 02 11 CD 16528 111 | 16706 SLAVE: 01 04 04 3F 73 7F 4A A7 8C 16707 112 | 0.95 113 | 0 114 | 20,228.2,0.0,0.17,0.00,34.70,0.00,36.40,0.00,50.00,0.00,28.16,0.28 115 | 20200 MASTER: 01 04 00 00 00 02 71 CB 20201 116 | 20374 SLAVE: 01 04 04 43 63 CC CD 8A 8B 20375 117 | 227.80 118 | 0 -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino/SDMdefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | Eastron address register values 3 | */ 4 | 5 | // Read values use function code 4 (0x04) 6 | #define VOLTAGE 0x0000 //All Float 7 | #define CURRENT 0x0006 8 | #define POWER 0x000C 9 | #define APOWER 0x0012 10 | #define RAPOWER 0x0018 11 | #define PFACTOR 0x001E 12 | #define PANGLE 0x0024 13 | #define FREQUENCY 0x0046 14 | #define IAENERGY 0x0048 15 | #define EAENERGY 0x004A 16 | #define IRAENERGY 0x004C 17 | #define ERAENERGY 0x004E 18 | #define TAENERGY 0x0156 19 | #define TRENERGY 0x0158 20 | 21 | // Read/Write Configuration. 22 | // Read function code 3 (0x03) 23 | // Write function code 16 (0x10) 24 | #define NPARSTOP 0x0012 // HEX, bit de parada, (0:none, 1:even, 2:odd) 25 | #define DEVICE_ID 0x0014 // Float, identificador esclavo (1 - 247) 26 | #define BAUD_RATE 0x001C // Float, Baud rate (0:2400 1:4800 2:9600 5:1200) 27 | #define TIME_DISP_220 0xF500 // BCD, Tiempo entre pantallas (0-30s) SDM220 28 | #define TIME_DISP 0xF900 // BCD, Tiempo entre pantallas (0-30s) SDM120 29 | #define PULSE1_KWH 0xF910 // Pulsos/Kwh (0:1000, 1:100, 2:10 3:1 pulso/Kwh) 30 | #define TOT_MODE 0xF920 // HEX, modo medida energía (1-3) 31 | #define PULSE1_MODE 0xF930 // Modo salida pulsos a led (0:imp+exp, 1:imp, 2:exp) 32 | 33 | /* 34 | Maintaining old definitions. 35 | */ 36 | 37 | 38 | // Direcciones registros de datos solo lectura. Valores tipo float. 39 | // Utilizar funcion 04 lectura, numero de bytes 4. 40 | 41 | #define VOL_ADR VOLTAGE // VOLTAJE. 42 | #define CUR_ADR CURRENT // CORRIENTE. 43 | #define POW_ADR POWER // POTENCIA ACTIVA. 44 | #define APO_ADR APOWER // Potencia Aparente. 45 | #define RPO_ADR RAPOWER // Potencia Reactiva. 46 | #define PFA_ADR PFACTOR // Factor de potencia. 47 | #define PAN_ADR PANGLE // ANGULO PHI. desfase entre voltaje y corriente 48 | #define FRE_ADR FREQUENCY // Frecuencia. 49 | // REVISAR FALTAN PARAMENTROS O PARAMENTROS PARA SDM220 50 | 51 | #define PEN_ADR IAENERGY // ENERGIA IMPORTADA KWH 52 | #define REN_ADR EAENERGY // Energia exportada. 53 | #define TEN_ADR TAENERGY // Energia activa Total. 54 | #define TRE_ADR TRENERGY // Energia reactiva Total. 55 | 56 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino/keywords.txt: -------------------------------------------------------------------------------- 1 | ######################################## 2 | # Syntax Coloring Map for ModbusSensor 3 | # Datatypes (KEYWORD1) 4 | ######################################## 5 | modbusSensor KEYWORD1 6 | modbusMaster KEYWORD1 7 | 8 | 9 | ######################################## 10 | # Methods and Functions (KEYWORD2) 11 | ######################################## 12 | MBSerial KEYWORD2 13 | sendFrame KEYWORD2 14 | readBuffer KEYWORD2 15 | config KEYWORD2 16 | connect KEYWORD2 17 | disconnect KEYWORD2 18 | begin KEYWORD2 19 | end KEYWORD2 20 | available KEYWORD2 21 | processPreset KEYWORD2 22 | processRead KEYWORD2 23 | processBuffer KEYWORD2 24 | preset KEYWORD2 25 | read KEYWORD2 26 | printStatus KEYWORD2 27 | calculateCRC KEYWORD2 28 | 29 | 30 | 31 | 32 | ######################################## 33 | # Constants (LITERAL1) 34 | ######################################## 35 | MAX_SENSORS LITERAL1 36 | BUFFER_SIZE LITERAL1 37 | TIMEOUT LITERAL1 38 | WAITING INTERVAL LITERAL1 39 | CHANGE_TO_ZERO LITERAL1 40 | CHANGE_TO_ONE LITERAL1 41 | HOLD_VALUE LITERAL1 42 | READ_HOLDING_REGISTERS LITERAL1 43 | READ_INPUT_REGISTERS LITERAL1 44 | PRESET_MULTIPLE_REGISTERS LITERAL1 45 | MB_VALID_DATA LITERAL1 46 | MB_INVALID_ID LITERAL1 47 | MB_INVALID_FC LITERAL1 48 | MB_TIMEOUT LITERAL1 49 | MB-INVALID_CRC LITERAL1 50 | MB_INVALID_BUFF LITERAL1 51 | MB_INVALID_ADR LITERAL1 52 | MB_INVALID_DATA LITERAL1 53 | MB_MASTER_STOP LITERAL1 54 | MB_ILLEGAL_FC LITERAL1 55 | MB_ILLEGAL_ADR LITERAL1 56 | MB_ILLEGAL_DATA LITERAL1 57 | MB_SLAVE_FAIL LITERAL1 58 | MB_EXCEPTION LITERAL1 59 | STOP LITERAL1 60 | SEND LITERAL1 61 | SENDING LITERAL1 62 | RECIVING LITERAL1 63 | IDLE LITERAL1 64 | WAITING_NEXT_POLL LITERAL1 -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino054/Modbus-Energy-Monitor-Arduino054.ino: -------------------------------------------------------------------------------- 1 | #include "ModbusSensor.h" 2 | #include "SDMdefines.h" 3 | 4 | 5 | #define MB_SERIAL_PORT &Serial1 // Arduino has only one serial port, Mega has 3 serial ports. 6 | // if use Serial 0, remember disconect Tx (pin0) when upload sketch, then re-conect 7 | #define MB_BAUDRATE 2400 // b 2400 8 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 9 | #define TxEnablePin 17 10 | 11 | #define ID_1 1 // id 001 modbus id of the energy monitor 12 | #define REFRESH_INTERVAL 5000 // refresh time, 5 SECONDS 13 | 14 | 15 | struct three_phase { 16 | float line3, line2, line1; 17 | } voltage, current, power; 18 | 19 | float energy; 20 | 21 | // global variables to poll, process and send values 22 | 23 | //modbusSensor(uint8_t id, uint8_t fc, uint16_t adr, uint8_t hold, uint8_t sizeofValue) 24 | modbusSensor volt(ID_1, VOLTAGE, CHANGE_TO_ZERO, sizeof(three_phase), READ_INPUT_REGISTERS); 25 | modbusSensor curr(ID_1, CURRENT, CHANGE_TO_ZERO, sizeof(three_phase), READ_INPUT_REGISTERS); 26 | modbusSensor pwr(ID_1, POWER, CHANGE_TO_ZERO, sizeof(three_phase), READ_INPUT_REGISTERS); 27 | 28 | //modbusSensor(uint8_t uint16_t adr, uint8_t hold) 29 | modbusSensor enrg(ID_1, IAENERGY, HOLD_VALUE); 30 | 31 | 32 | void setup() { 33 | Serial.begin(9600); 34 | delay(95); 35 | MBSerial.config(MB_SERIAL_PORT, TxEnablePin, REFRESH_INTERVAL); 36 | MBSerial.begin(MB_BAUDRATE, MB_BYTEFORMAT); 37 | Serial.println("time(s),Volt1(V), Volt2(V), Volt3(V), Curr1(A) Curr2(A), Curr3(A), Power1(W), Power2(W), Power3(W), Energy(Kwh)"); 38 | } 39 | 40 | void loop() { 41 | 42 | if (MBSerial.available()) { 43 | volt.read(voltage); 44 | curr.read(current); 45 | pwr.read(power); 46 | energy = enrg.read(); 47 | 48 | Serial.print(millis() / 1000); 49 | Serial.print(","); 50 | Serial.print(voltage.line1, 1); 51 | Serial.print(","); 52 | Serial.print(voltage.line2, 1); 53 | Serial.print(","); 54 | Serial.print(voltage.line3, 1); 55 | Serial.print(","); 56 | Serial.print(current.line1, 2); 57 | Serial.print(","); 58 | Serial.print(current.line2, 2); 59 | Serial.print(","); 60 | Serial.print(current.line3, 2); 61 | Serial.print(","); 62 | Serial.print(power.line1, 2); 63 | Serial.print(","); 64 | Serial.print(power.line2, 2); 65 | Serial.print(","); 66 | Serial.print(power.line3, 2); 67 | Serial.print(","); 68 | Serial.println(energy, 2); 69 | 70 | } 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino054/ModbusSensor.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ModbusSensor class 3 | 4 | 5 | version 0.5.4 BETA 10/01/2016 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | *******************************************************************************/ 11 | //------------------------------------------------------------------------------ 12 | 13 | #define MODBUS_SERIAL_OUTPUT //Verbose MODBUS messages and timing 14 | 15 | #ifdef MODBUS_SERIAL_OUTPUT 16 | #define MODBUS_SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 17 | #define MODBUS_SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 18 | #define MODBUS_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 19 | #else 20 | #define MODBUS_SERIAL_BEGIN(...) 21 | #define MODBUS_SERIAL_PRINT(...) 22 | #define MODBUS_SERIAL_PRINTLN(...) 23 | #endif 24 | 25 | #include "ModbusSensor.h" 26 | 27 | // Finite state machine status 28 | #define STOP 0 29 | #define SEND 1 30 | #define SENDING 2 31 | #define RECEIVING 3 32 | #define IDLE 4 33 | #define WAITING_NEXT_POLL 5 34 | 35 | /*#define READ_COIL_STATUS 0x01 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 36 | #define READ_INPUT_STATUS 0x02 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 37 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary contents of holding registers (4X references) in the slave. 38 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 39 | #define FORCE_MULTIPLE_COILS 0x0F // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 40 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 41 | 42 | #define MB_VALID_DATA 0x00 43 | #define MB_INVALID_ID 0xE0 44 | #define MB_INVALID_FC 0xE1 45 | #define MB_TIMEOUT 0xE2 46 | #define MB_INVALID_CRC 0xE3 47 | #define MB_INVALID_BUFF 0xE4 48 | #define MB_ILLEGAL_FC 0x01 49 | #define MB_ILLEGAL_ADR 0x02 50 | #define MB_ILLEGAL_DATA 0x03 51 | #define MB_SLAVE_FAIL 0x04 52 | #define MB_EXCEPTION 0x05 53 | 54 | // when _status is diferent to MB_VALID_DATA change it to zero or hold last valid value? 55 | //#define CHANGE_TO_ZERO 0x00 56 | //#define CHANGE_TO_ONE 0x01 57 | //#define HOLD_VALUE 0xFF 58 | */ 59 | 60 | uint16_t calculateCRC(uint8_t *array, uint8_t num) { 61 | uint16_t temp, flag; 62 | temp = 0xFFFF; 63 | for (uint8_t i = 0; i < num; i++) { 64 | temp = temp ^ array[i]; 65 | for (uint8_t j = 8; j; j--) { 66 | flag = temp & 0x0001; 67 | temp >>= 1; 68 | if (flag) 69 | temp ^= 0xA001; 70 | } 71 | } 72 | return temp; 73 | } 74 | 75 | //------------------------------------------------------------------------------ 76 | //configure sketch variables 77 | void modbusMaster::config(HardwareSerial * hwSerial, uint8_t TxEnPin, uint16_t pollInterval) { 78 | _hwSerial = hwSerial; 79 | _TxEnablePin = TxEnPin; 80 | pinMode(_TxEnablePin, OUTPUT); 81 | _pollInterval = pollInterval - 1; 82 | _state = STOP; 83 | MODBUS_SERIAL_PRINT(_totalSensors); MODBUS_SERIAL_PRINTLN(" total sensors connected at config"); 84 | } 85 | 86 | 87 | //------------------------------------------------------------------------------ 88 | // Connect a modbusSensor to the modbusMaster array of queries 89 | void modbusMaster::connect(modbusSensor * mbSensor) { 90 | if (_totalSensors < MAX_SENSORS) { 91 | _mbSensorsPtr[_totalSensors] = mbSensor; 92 | _totalSensors++; 93 | } 94 | return; 95 | } 96 | 97 | //------------------------------------------------------------------------------ 98 | // Disconnect a modbusSensor to the modbusMaster array of queries 99 | void modbusMaster::disconnect(modbusSensor * mbSensor) { 100 | uint8_t i = 0; 101 | while (i < _totalSensors) { 102 | if (_mbSensorsPtr[i] == mbSensor) { 103 | MODBUS_SERIAL_PRINT(i); MODBUS_SERIAL_PRINTLN(" sensor disconnected"); 104 | while (i < _totalSensors - 1) { 105 | _mbSensorsPtr[i] = _mbSensorsPtr[i + 1]; //shift down pointers one position 106 | } 107 | _totalSensors--; // reduce number of total sensors in the array 108 | _mbSensorsPtr[_totalSensors] = 0; // delete last position 109 | } 110 | i++; 111 | } 112 | } 113 | 114 | //------------------------------------------------------------------------------ 115 | // begin communication using ModBus protocol over RS485 116 | void modbusMaster::begin(uint16_t baudrate, uint8_t byteFormat) { 117 | if (baudrate > 19200) 118 | _T2_5 = 1250; 119 | else 120 | _T2_5 = 27500000 / baudrate; // 2400 bauds --> 11458 us; 9600 bauds --> 2864 us 121 | (*_hwSerial).begin(baudrate, byteFormat); 122 | _state = SEND; 123 | digitalWrite(_TxEnablePin, LOW); 124 | } 125 | 126 | //------------------------------------------------------------------------------ 127 | // end communication over serial port 128 | inline void modbusMaster::end() { 129 | _state = STOP; 130 | (*_hwSerial).end(); 131 | digitalWrite(_TxEnablePin, LOW); 132 | } 133 | 134 | //------------------------------------------------------------------------------ 135 | // Finite State Machine core, 136 | boolean modbusMaster::available() { 137 | static uint8_t indexSensor = 0; // index of arrray of sensors 138 | static uint8_t frameSize; // size of the RX frame 139 | static uint32_t tMicros; // time to check between characters in a frame 140 | uint32_t nowMillis; 141 | 142 | switch (_state) { 143 | //----------------------------------------------------------------------------- 144 | case SEND: 145 | 146 | if (indexSensor < _totalSensors) { 147 | digitalWrite(_TxEnablePin, HIGH); 148 | uint8_t *frame = (*_mbSensorsPtr[indexSensor])._frame; 149 | uint8_t frameSize = (*_mbSensorsPtr[indexSensor])._frameSize; 150 | sendFrame(frame, frameSize); 151 | _state = SENDING; 152 | return false; 153 | } 154 | else { 155 | indexSensor = 0; 156 | _state = WAITING_NEXT_POLL; 157 | return true; 158 | } 159 | 160 | //----------------------------------------------------------------------------- 161 | case SENDING: 162 | 163 | if ((*_hwSerial).availableForWrite() == SERIAL_TX_BUFFER_SIZE - 1) { //TX buffer empty 164 | // time to be sure last byte sended and required empty space 165 | delayMicroseconds(_T2_5); 166 | 167 | // clean RX buffer 168 | while ((*_hwSerial).available()) (*_hwSerial).read(); 169 | 170 | // MAX485 Receiving mode 171 | digitalWrite(_TxEnablePin, LOW); 172 | _state = RECEIVING; 173 | 174 | //starts slave's timeOut 175 | _timeoutMillis = millis(); 176 | frameSize = 0; 177 | } 178 | return false; 179 | 180 | //----------------------------------------------------------------------------- 181 | case RECEIVING: 182 | 183 | if (!(*_hwSerial).available()) { 184 | if (millis() - _timeoutMillis > TIMEOUT) { 185 | (*_mbSensorsPtr[indexSensor])._status = MB_TIMEOUT; 186 | #ifdef MODBUS_SERIAL_OUTPUT 187 | (*_mbSensorsPtr[indexSensor]).printStatus(); 188 | #endif 189 | indexSensor++; 190 | _state = SEND; 191 | } 192 | return false; 193 | } 194 | 195 | if ((*_hwSerial).available() > frameSize) { 196 | frameSize++; 197 | tMicros = micros(); 198 | } 199 | else { 200 | if (micros() - tMicros > _T2_5) { 201 | readBuffer(frameSize); 202 | (*_mbSensorsPtr[indexSensor]).processBuffer(_rx_buffer, frameSize); 203 | #ifdef MODBUS_SERIAL_OUTPUT 204 | (*_mbSensorsPtr[indexSensor]).printStatus(); 205 | #endif 206 | indexSensor++; 207 | _waitingMillis = millis(); //starts waiting interval to next request 208 | _state = IDLE; 209 | } 210 | } 211 | return false; 212 | 213 | //----------------------------------------------------------------------------- 214 | case IDLE: 215 | if (millis() - _waitingMillis > WAITING_INTERVAL) 216 | _state = SEND; 217 | return false; 218 | 219 | //----------------------------------------------------------------------------- 220 | case WAITING_NEXT_POLL: 221 | nowMillis = millis(); 222 | if ((nowMillis - _lastPollMillis) > _pollInterval) { 223 | _lastPollMillis = nowMillis; 224 | _state = SEND; 225 | } 226 | return false; 227 | 228 | //----------------------------------------------------------------------------- 229 | case STOP: 230 | for (;indexSensor < _totalSensors;indexSensor++) 231 | (*_mbSensorsPtr[indexSensor])._status = MB_MASTER_STOP; 232 | indexSensor = 0; 233 | return true; 234 | } 235 | } 236 | 237 | //----------------------------------------------------------------------------- 238 | // 239 | void modbusMaster::sendFrame(uint8_t *frame, uint8_t frameSize) { 240 | MODBUS_SERIAL_PRINT(millis()); 241 | MODBUS_SERIAL_PRINT(" MASTER:"); 242 | 243 | (*_hwSerial).write(frame, frameSize); 244 | 245 | #ifdef MODBUS_SERIAL_OUTPUT 246 | for (uint8_t index = 0; index < frameSize; index++) { 247 | if (frame[index] < 0x10) 248 | Serial.print(" 0"); 249 | else 250 | Serial.print(" "); 251 | Serial.print(frame[index], HEX); 252 | } 253 | Serial.print(" "); 254 | Serial.println(millis()); 255 | #endif 256 | } 257 | 258 | //----------------------------------------------------------------------------- 259 | inline void modbusMaster::readBuffer(uint8_t frameSize) { 260 | 261 | MODBUS_SERIAL_PRINT(millis()); 262 | MODBUS_SERIAL_PRINT(" SLAVE:"); 263 | 264 | (*_hwSerial).readBytes(_rx_buffer, frameSize); 265 | 266 | #ifdef MODBUS_SERIAL_OUTPUT 267 | for (uint8_t index = 0; index < frameSize; index++) { 268 | if (_rx_buffer[index] < 0x10) 269 | Serial.print(" 0"); 270 | else 271 | Serial.print(" "); 272 | Serial.print(_rx_buffer[index], HEX); 273 | } 274 | Serial.print(" "); 275 | Serial.println(millis()); 276 | #endif 277 | } 278 | 279 | // Create an instance of modbusMaster 280 | modbusMaster MBSerial; 281 | 282 | //----------------------------------------------------------------------------- 283 | //----------------------------------------------------------------------------- 284 | // Constructor 285 | modbusSensor::modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue, uint8_t fc) { 286 | // reserve space to new struct of value 287 | _value = new uint8_t[sizeofValue]; 288 | // pointer to the first byte 289 | uint8_t *ptr = _value; 290 | // reset content 291 | for (int count = sizeofValue; count; --count) *ptr++ = 0; 292 | 293 | switch (fc) { 294 | case PRESET_MULTIPLE_REGISTERS: 295 | case READ_HOLDING_REGISTERS: 296 | _frame = new uint8_t[9 + sizeofValue]; //reserve space for fc PRESET_MULTIPLE_REGISTERS 297 | _frame[1] = READ_HOLDING_REGISTERS; 298 | break; 299 | case READ_INPUT_REGISTERS: 300 | _frame = new uint8_t[8]; 301 | _frame[1] = READ_INPUT_REGISTERS; 302 | break; 303 | default: 304 | // exit without connect to MBSerial 305 | _frame = 0; 306 | _frameSize = 0; 307 | _status = MB_INVALID_FC; 308 | return; 309 | } 310 | _frameSize = 8; // always read function, change in preset() 311 | 312 | _frame[0] = id; 313 | //_frame[1] defined previously; 314 | _frame[2] = adr >> 8; 315 | _frame[3] = adr & 0x00FF; 316 | _frame[4] = 0x00; 317 | _frame[5] = sizeofValue / 2; 318 | uint16_t crc = calculateCRC(_frame, 6); 319 | _frame[6] = crc & 0x00FF; 320 | _frame[7] = crc >> 8; 321 | 322 | _status = MB_MASTER_STOP; 323 | _hold = hold; 324 | 325 | MBSerial.connect(this); 326 | } 327 | 328 | //------------------------------------------------------------------------------ 329 | //Process RX buffer 330 | void modbusSensor::processBuffer(uint8_t *rxFrame, uint8_t rxFrameSize) { 331 | 332 | // check minimum response frame size 333 | if (rxFrameSize < 5) { 334 | _status = MB_SLAVE_FAIL; 335 | return; 336 | } 337 | 338 | // check slave id 339 | if (rxFrame[0] != _frame[0]) { 340 | _status = MB_INVALID_ID; 341 | return; 342 | } 343 | 344 | // check CRC 345 | uint16_t crc = calculateCRC(rxFrame, rxFrameSize - 2); 346 | if (rxFrame[rxFrameSize - 1] != crc >> 8 && rxFrame[rxFrameSize - 2] != crc & 0x00FF) { 347 | _status = MB_INVALID_CRC; 348 | return; 349 | } 350 | 351 | // check slave's exception in function code 352 | if (rxFrame[1] & 0x80 == 0x80) { 353 | _status = rxFrame[2]; // see exception codes in define area or printStatus() 354 | return; 355 | } 356 | 357 | // check function code 358 | if (rxFrame[1] != _frame[1]) { 359 | _status = MB_INVALID_FC; 360 | return; 361 | } 362 | 363 | // READ frame, transfer modbus registers to _value 364 | switch (rxFrame[1]) { 365 | case READ_HOLDING_REGISTERS: 366 | case READ_INPUT_REGISTERS: 367 | // check byte count equals to registers request 368 | if (rxFrame[2] == _frame[2] * 2) { 369 | uint8_t *ptr = (uint8_t *) &_value; 370 | ptr += rxFrame[2] - 1; // pointer to object last byte 371 | uint8_t i = 3; 372 | for (int count = rxFrame[2]; count; --count, i++, ptr--) *ptr = rxFrame[i]; 373 | _status = MB_VALID_DATA; 374 | return; 375 | } 376 | else { 377 | _status = MB_ILLEGAL_DATA; 378 | return; 379 | } 380 | 381 | // PRESET frame, return OK 382 | case PRESET_MULTIPLE_REGISTERS: 383 | _status = MB_VALID_DATA; 384 | return; 385 | 386 | default: 387 | _status = MB_INVALID_FC; 388 | return; 389 | } 390 | } 391 | 392 | //------------------------------------------------------------------------------ 393 | // Construct Tx frame, send it, receive response and process it, reconstruct READ frame 394 | void modbusSensor::processPreset(uint8_t *ptr, uint8_t objectSize) { 395 | 396 | if (_frame[2] != READ_HOLDING_REGISTERS) { 397 | _status = MB_INVALID_ADR; 398 | return; 399 | } 400 | 401 | if (objectSize != _frame[5] * 2) { 402 | _status = MB_INVALID_DATA; 403 | return; 404 | } 405 | 406 | // Construct PRESET frame 407 | _frame[2] = PRESET_MULTIPLE_REGISTERS; 408 | _frame[6] = objectSize; 409 | uint8_t i = 7; 410 | 411 | ptr += objectSize - 1; // pointer to object last byte 412 | for (int count = objectSize; count; count--, i++) _frame[i] = *ptr--; 413 | uint16_t crc = calculateCRC(_frame, i); 414 | _frame[i++] = crc & 0x00FF; 415 | _frame[i++] = crc >> 8; 416 | _frameSize = i; 417 | 418 | // Send the PRESET frame, receive response and process it 419 | while (!MBSerial.available()) {} 420 | 421 | // reconstruct READ frame 422 | _frame[2] = READ_HOLDING_REGISTERS; 423 | crc = calculateCRC(_frame, 6); 424 | _frame[6] = crc & 0x00FF; 425 | _frame[7] = crc >> 8; 426 | _frameSize = 8; 427 | } 428 | 429 | //------------------------------------------------------------------------------ 430 | // read value in defined units 431 | float modbusSensor::read() { 432 | if (_status == MB_TIMEOUT) 433 | switch (_hold) { 434 | case CHANGE_TO_ZERO: return 0.0; 435 | case CHANGE_TO_ONE: return 1.0; 436 | case HOLD_VALUE:; 437 | } 438 | return (float) * _value; 439 | } 440 | 441 | //------------------------------------------------------------------------------ 442 | // process status and return the predetermined response 443 | void modbusSensor::processRead(uint8_t *ptr, const uint8_t objectSize) { 444 | if (_status == MB_TIMEOUT) 445 | switch (_hold) { 446 | case CHANGE_TO_ZERO: 447 | for (int count = objectSize; count; --count) *ptr++ = 0; 448 | return; 449 | case CHANGE_TO_ONE:; 450 | case HOLD_VALUE:; 451 | } 452 | // copy _value on the returned struct 453 | const uint8_t *e = _value; 454 | for (int count = objectSize; count; --count, ++e) *ptr++ = *e; 455 | } 456 | 457 | //------------------------------------------------------------------------------ 458 | // print status message 459 | uint8_t modbusSensor::printStatus() { 460 | switch (_status) { 461 | case MB_VALID_DATA: 462 | Serial.println("Transmision successful"); 463 | return _status; 464 | case MB_INVALID_ID: 465 | Serial.println("No valid Id"); 466 | return _status; 467 | case MB_INVALID_FC: 468 | Serial.println("No valid FC"); 469 | return _status; 470 | case MB_TIMEOUT: 471 | Serial.println("Time out"); 472 | return _status; 473 | case MB_INVALID_CRC: 474 | Serial.println("incorrect CRC"); 475 | return _status; 476 | case MB_INVALID_BUFF: 477 | Serial.println("Invalid buffer size"); 478 | return _status; 479 | case MB_INVALID_ADR: 480 | Serial.println("No valid address"); 481 | return _status; 482 | case MB_INVALID_DATA: 483 | Serial.println("No valid data"); 484 | return _status; 485 | case MB_MASTER_STOP: 486 | Serial.println("Master in STOP mode"); 487 | return _status; 488 | case MB_ILLEGAL_FC: 489 | Serial.println("Exception: Illegal FC"); 490 | return _status; 491 | case MB_ILLEGAL_ADR: 492 | Serial.println("Exception: Illegal address"); 493 | return _status; 494 | case MB_ILLEGAL_DATA: 495 | Serial.println("Exception: Illegal data"); 496 | return _status; 497 | case MB_SLAVE_FAIL: 498 | Serial.println("Exception: Slave failed "); 499 | return _status; 500 | case MB_EXCEPTION: 501 | Serial.println("Exception"); 502 | return _status; 503 | default: 504 | Serial.println ("** ERROR **"); 505 | } 506 | } 507 | 508 | 509 | //---------------------------------------------------------------------------------------// 510 | //---------------------------------------------------------------------------------------// 511 | 512 | 513 | 514 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino054/ModbusSensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.h 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and family. 5 | 6 | version 0.5.4 BETA 10/01/2016 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | 13 | #ifndef ModbusSensor_h 14 | #define ModbusSensor_h 15 | 16 | #include "Arduino.h" 17 | #define MAX_SENSORS 16 18 | // The maximum number of bytes in a modbus packet is 256 bytes. 19 | // The serial buffer limits this to 128 bytes. 20 | // We can reduce it to maximum data sending by a slave SMD120 9 bytes, SMD 630 is 85 bytes 21 | // Three phase meters are 3 values, 6 registers and 12 bytes, plus 5 frame bytes, total 17 22 | #define BUFFER_SIZE 32 23 | #define TIMEOUT 110 // time to fail a request 24 | #define WAITING_INTERVAL 40 // time required by SDM120 to be prepared to receive a new request 25 | 26 | // What happens when _status is diferent to MB_VALID_DATA? 27 | #define CHANGE_TO_ZERO 0x00 28 | #define CHANGE_TO_ONE 0x01 29 | #define HOLD_VALUE 0xFF 30 | 31 | // Function codes operative 32 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary content of holding registers (4X references) in the slave. 33 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary content of input registers (3X references) in the slave. Not writable. 34 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 35 | 36 | #define MB_VALID_DATA 0x00 37 | #define MB_INVALID_ID 0xE0 38 | #define MB_INVALID_FC 0xE1 39 | #define MB_TIMEOUT 0xE2 40 | #define MB_INVALID_CRC 0xE3 41 | #define MB_INVALID_BUFF 0xE4 42 | #define MB_INVALID_ADR 0xE5 43 | #define MB_INVALID_DATA 0xE6 44 | #define MB_MASTER_STOP 0xE7 45 | #define MB_ILLEGAL_FC 0x01 46 | #define MB_ILLEGAL_ADR 0x02 47 | #define MB_ILLEGAL_DATA 0x03 48 | #define MB_SLAVE_FAIL 0x04 49 | #define MB_EXCEPTION 0x05 50 | 51 | // forward definition 52 | class modbusSensor; 53 | 54 | //------------------------------------------------------------------------------ 55 | //------------------------------------------------------------------------------ 56 | class modbusMaster { 57 | protected: 58 | HardwareSerial *_hwSerial; 59 | modbusSensor *_mbSensorsPtr[MAX_SENSORS]; // array of modbusSensor's pointers 60 | uint8_t _TxEnablePin; // pin to enable transmision in MAX485 61 | uint8_t _totalSensors; // constant, max number of sensors to poll 62 | uint16_t _pollInterval; // constant, time between polling same data 63 | uint8_t _rx_buffer[BUFFER_SIZE]; // buffer to process rececived frame 64 | uint8_t _state; // Modbus FSM state (STOP, SEND, SENDING, RECEIVING, IDLE, WAITING_NEXT_POLL) 65 | uint32_t _T2_5; // time between characters in a frame, in microseconds 66 | uint32_t _lastPollMillis; // time to check poll interval 67 | uint32_t _timeoutMillis; // time to check timeout interval 68 | uint32_t _waitingMillis; // time to check waiting interval 69 | 70 | inline void sendFrame(uint8_t *frame, uint8_t frameSize); 71 | inline void readBuffer(uint8_t frameSize); 72 | 73 | public: 74 | //constructor 75 | modbusMaster() { 76 | _totalSensors = 0; 77 | for (uint8_t i = 0; i < MAX_SENSORS; i++) 78 | _mbSensorsPtr[i] = 0; 79 | }; 80 | 81 | //constructor 82 | void config(HardwareSerial *mbSerial, uint8_t TxEnPin, uint16_t pollInterval); 83 | 84 | // Connect a modbusSensor to modbusMaster array of queries 85 | void connect(modbusSensor *mbSensor); 86 | 87 | // Disconnect a modbusSensor to modbusMaster array of queries 88 | void disconnect(modbusSensor *mbSensor); 89 | 90 | // begin communication using ModBus protocol over RS485 91 | void begin(uint16_t baudrate, uint8_t byteFormat); 92 | 93 | // end communication over serial port 94 | void end(); 95 | 96 | // Finite State Machine core, process FSM 97 | // It returns 'true' when finish to request all the array of modbusSensors. 98 | // Non-blocking function, put the instrucction in a loop function to make the proccess work properly 99 | boolean available(); 100 | }; 101 | 102 | extern modbusMaster MBSerial; 103 | 104 | //------------------------------------------------------------------------------ 105 | //------------------------------------------------------------------------------ 106 | class modbusSensor { 107 | protected: 108 | uint8_t * _value; // pointer to a dinamic allocated object, size inside _frame[5] 109 | uint8_t * _frame; // pointer to a dinamic allocated array, 110 | uint8_t _frameSize; // size of the _frame, 8 in read function, 9+sizeof(T) in preset function 111 | uint8_t _status; // register of the result of communication 112 | uint8_t _hold; // predefined behaiviour in case of timeout exception 113 | 114 | void processPreset(uint8_t *ptr, uint8_t objectSize); 115 | void processRead(uint8_t *ptr, uint8_t objectSize); 116 | void processBuffer(uint8_t *rxFrame, uint8_t rxFrameSize); 117 | 118 | //MBSerial.available need access to _frame, _frameSize and _status variables 119 | friend boolean modbusMaster::available(); 120 | 121 | public: 122 | // Target constructor 123 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue, uint8_t fc); 124 | 125 | // delegating constructor 126 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue): modbusSensor(id, adr, hold, sizeofValue, READ_INPUT_REGISTERS) {}; 127 | 128 | // delegating constructor 129 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold): modbusSensor(id, adr, hold, 4, READ_INPUT_REGISTERS) {}; 130 | 131 | // Destructor 132 | ~modbusSensor() { 133 | delete[] _value; 134 | delete[] _frame; 135 | MBSerial.disconnect(this); 136 | } 137 | 138 | // Connect to MBSerial array of sensors 139 | void connect() { 140 | MBSerial.connect(this); 141 | }; 142 | 143 | // Disconnect to MBSerial array of sensors 144 | void disconnect() { 145 | MBSerial.disconnect(this); 146 | }; 147 | 148 | // Preset sensor value, fc 0x10, only holding registers defined with fc 0x03 149 | // complete funtion to make and send the frame and process response, check status 150 | template < typename T > void preset(const T &t) { 151 | processPreset((uint8_t *) &t, sizeof(T)); 152 | }; 153 | 154 | // read value in defined units 155 | float read(); 156 | 157 | // read every struct value from object buffer, non-blocking function 158 | template< typename T > T &read(T &t) { 159 | processRead((uint8_t *) &t, sizeof(T)); 160 | return t; 161 | } 162 | 163 | // print status message 164 | uint8_t printStatus(); 165 | }; 166 | 167 | #endif 168 | 169 | -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino054/Monitor Serial debug.txt: -------------------------------------------------------------------------------- 1 | time(s), maxVolt(V), minVolt(V), maxCurr(A) minCurr(A), maxPower(W), minPower(W), maxApPower(VA), minApPower(VA), maxFreq(Hz), minFreq(Hz), AvgPower (W), Energy(Kwh) 2 | 200 MASTER: 01 04 00 00 00 02 71 CB 241 3 | 384 SLAVE: 01 04 04 43 64 33 33 FB 3A 385 4 | 228.20 5 | 0 6 | 427 MASTER: 01 04 00 06 00 02 91 CA 428 7 | 604 SLAVE: 01 04 04 3E 2E 14 7B D9 46 605 8 | 0.17 9 | 0 10 | 647 MASTER: 01 04 00 0C 00 02 B1 C8 648 11 | 824 SLAVE: 01 04 04 42 0A 66 66 64 74 825 12 | 34.60 13 | 0 14 | 867 MASTER: 01 04 00 48 00 02 F1 DD 868 15 | 1037 SLAVE: 01 04 04 3E 90 E5 60 BC F9 1039 16 | 0.28 17 | 0 18 | 1080 MASTER: 01 04 00 46 00 02 90 1E 1081 19 | 1258 SLAVE: 01 04 04 42 48 00 00 6F EA 1259 20 | 50.00 21 | 0 22 | 1301 MASTER: 01 04 00 12 00 02 D1 CE 1302 23 | 1482 SLAVE: 01 04 04 42 11 FA 24 FD 42 1483 24 | 36.49 25 | 0 26 | 1525 MASTER: 01 04 00 1E 00 02 11 CD 1526 27 | 1702 SLAVE: 01 04 04 3F 72 EC E8 1B 05 1703 28 | 0.95 29 | 0 30 | 5200 MASTER: 01 04 00 00 00 02 71 CB 5201 31 | 5370 SLAVE: 01 04 04 43 64 00 00 AF DF 5371 32 | 228.00 33 | 0 34 | 5413 MASTER: 01 04 00 06 00 02 91 CA 5414 35 | 5581 SLAVE: 01 04 04 3E 2E 14 7B D9 46 5582 36 | 0.17 37 | 0 38 | 5624 MASTER: 01 04 00 0C 00 02 B1 C8 5625 39 | 5793 SLAVE: 01 04 04 42 0A 66 66 64 74 5794 40 | 34.60 41 | 0 42 | 5836 MASTER: 01 04 00 48 00 02 F1 DD 5837 43 | 6016 SLAVE: 01 04 04 3E 90 E5 60 BC F9 6017 44 | 0.28 45 | 0 46 | 6059 MASTER: 01 04 00 46 00 02 90 1E 6060 47 | 6236 SLAVE: 01 04 04 42 48 00 00 6F EA 6237 48 | 50.00 49 | 0 50 | 6279 MASTER: 01 04 00 12 00 02 D1 CE 6280 51 | 6459 SLAVE: 01 04 04 42 11 E1 0A 77 AE 6460 52 | 36.47 53 | 0 54 | 6502 MASTER: 01 04 00 1E 00 02 11 CD 6503 55 | 6680 SLAVE: 01 04 04 3F 73 69 82 A8 7A 6682 56 | 0.95 57 | 0 58 | 10200 MASTER: 01 04 00 00 00 02 71 CB 10201 59 | 10369 SLAVE: 01 04 04 43 63 66 66 B5 94 10371 60 | 227.40 61 | 0 62 | 10413 MASTER: 01 04 00 06 00 02 91 CA 10414 63 | 10590 SLAVE: 01 04 04 3E 2E 14 7B D9 46 10591 64 | 0.17 65 | 0 66 | 10633 MASTER: 01 04 00 0C 00 02 B1 C8 10634 67 | 10801 SLAVE: 01 04 04 42 0A 66 66 64 74 10803 68 | 34.60 69 | 0 70 | 10844 MASTER: 01 04 00 48 00 02 F1 DD 10845 71 | 11023 SLAVE: 01 04 04 3E 90 E5 60 BC F9 11025 72 | 0.28 73 | 0 74 | 11066 MASTER: 01 04 00 46 00 02 90 1E 11067 75 | 11243 SLAVE: 01 04 04 42 48 00 00 6F EA 11244 76 | 50.00 77 | 0 78 | 11286 MASTER: 01 04 00 12 00 02 D1 CE 11287 79 | 11466 SLAVE: 01 04 04 42 11 89 24 D8 72 11468 80 | 36.38 81 | 0 82 | 11510 MASTER: 01 04 00 1E 00 02 11 CD 11511 83 | 11687 SLAVE: 01 04 04 3F 73 9F D2 EF E6 11689 84 | 0.95 85 | 0 86 | 15200 MASTER: 01 04 00 00 00 02 71 CB 15201 87 | 15377 SLAVE: 01 04 04 43 63 B3 33 2B 3B 15378 88 | 227.70 89 | 0 90 | 15420 MASTER: 01 04 00 06 00 02 91 CA 15421 91 | 15597 SLAVE: 01 04 04 3E 2E 14 7B D9 46 15598 92 | 0.17 93 | 0 94 | 15640 MASTER: 01 04 00 0C 00 02 B1 C8 15641 95 | 15817 SLAVE: 01 04 04 42 0A CC CD 5B 6B 15818 96 | 34.70 97 | 0 98 | 15860 MASTER: 01 04 00 48 00 02 F1 DD 15861 99 | 16041 SLAVE: 01 04 04 3E 90 E5 60 BC F9 16044 100 | 0.28 101 | 0 102 | 16086 MASTER: 01 04 00 46 00 02 90 1E 16087 103 | 16259 SLAVE: 01 04 04 42 48 00 00 6F EA 16260 104 | 50.00 105 | 0 106 | 16302 MASTER: 01 04 00 12 00 02 D1 CE 16303 107 | 16484 SLAVE: 01 04 04 42 11 F4 70 F8 DD 16485 108 | 36.49 109 | 0 110 | 16527 MASTER: 01 04 00 1E 00 02 11 CD 16528 111 | 16706 SLAVE: 01 04 04 3F 73 7F 4A A7 8C 16707 112 | 0.95 113 | 0 114 | 20,228.2,0.0,0.17,0.00,34.70,0.00,36.40,0.00,50.00,0.00,28.16,0.28 115 | 20200 MASTER: 01 04 00 00 00 02 71 CB 20201 116 | 20374 SLAVE: 01 04 04 43 63 CC CD 8A 8B 20375 117 | 227.80 118 | 0 -------------------------------------------------------------------------------- /dev version/Modbus-Energy-Monitor-Arduino054/SDMdefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | Eastron address register values 3 | */ 4 | 5 | // Read values use function code 4 (0x04) 6 | #define VOLTAGE 0x0000 //All Float 7 | #define CURRENT 0x0006 8 | #define POWER 0x000C 9 | #define APOWER 0x0012 10 | #define RAPOWER 0x0018 11 | #define PFACTOR 0x001E 12 | #define PANGLE 0x0024 13 | #define FREQUENCY 0x0046 14 | #define IAENERGY 0x0048 15 | #define EAENERGY 0x004A 16 | #define IRAENERGY 0x004C 17 | #define ERAENERGY 0x004E 18 | #define TAENERGY 0x0156 19 | #define TRENERGY 0x0158 20 | 21 | // Read/Write Configuration. 22 | // Read function code 3 (0x03) 23 | // Write function code 16 (0x10) 24 | #define NPARSTOP 0x0012 // HEX, bit de parada, (0:none, 1:even, 2:odd) 25 | #define DEVICE_ID 0x0014 // Float, identificador esclavo (1 - 247) 26 | #define BAUD_RATE 0x001C // Float, Baud rate (0:2400 1:4800 2:9600 5:1200) 27 | #define TIME_DISP_220 0xF500 // BCD, Tiempo entre pantallas (0-30s) SDM220 28 | #define TIME_DISP 0xF900 // BCD, Tiempo entre pantallas (0-30s) SDM120 29 | #define PULSE1_KWH 0xF910 // Pulsos/Kwh (0:1000, 1:100, 2:10 3:1 pulso/Kwh) 30 | #define TOT_MODE 0xF920 // HEX, modo medida energía (1-3) 31 | #define PULSE1_MODE 0xF930 // Modo salida pulsos a led (0:imp+exp, 1:imp, 2:exp) 32 | 33 | /* 34 | Maintaining old definitions. 35 | */ 36 | 37 | 38 | // Direcciones registros de datos solo lectura. Valores tipo float. 39 | // Utilizar funcion 04 lectura, numero de bytes 4. 40 | 41 | #define VOL_ADR VOLTAGE // VOLTAJE. 42 | #define CUR_ADR CURRENT // CORRIENTE. 43 | #define POW_ADR POWER // POTENCIA ACTIVA. 44 | #define APO_ADR APOWER // Potencia Aparente. 45 | #define RPO_ADR RAPOWER // Potencia Reactiva. 46 | #define PFA_ADR PFACTOR // Factor de potencia. 47 | #define PAN_ADR PANGLE // ANGULO PHI. desfase entre voltaje y corriente 48 | #define FRE_ADR FREQUENCY // Frecuencia. 49 | // REVISAR FALTAN PARAMENTROS O PARAMENTROS PARA SDM220 50 | 51 | #define PEN_ADR IAENERGY // ENERGIA IMPORTADA KWH 52 | #define REN_ADR EAENERGY // Energia exportada. 53 | #define TEN_ADR TAENERGY // Energia activa Total. 54 | #define TRE_ADR TRENERGY // Energia reactiva Total. 55 | 56 | /* Default SDM configuration. 57 | #define SDM120C_METER_NUMBER 1 58 | #define SDM120C_BAUDRATE 2400 59 | #define SDM120C_BYTEFORMAT SERIAL_8N2 //Prty n 60 | */ 61 | 62 | /*Modbus optimum settings 63 | #define TIMEOUT 1000 64 | #define POLLING 200 // the scan rate 65 | #define RETRYCOUNT 10 // numero de reintentos, para volver set the "connection" variable to 66 | */ 67 | 68 | // Get Parameter 69 | -------------------------------------------------------------------------------- /dev version/SDM120configure/SDM120configure.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | SDM120 configure 3 | Sketch to configure and test de parameters of a modbus energy monitor 4 | model EASTRON SDM120, SDM220, SDM320, SDM630 5 | this sketch uses two hardsare serial ports: 6 | - Serial to comunicate to monitor serial via USB 7 | - Serial1 to comunicate to modbus RS485 SDM single device. 8 | TX enable pin 17 9 | Default SDM configuration: 10 | - Id number: 1 11 | - Baud rate: 2400 12 | - parity bit: none 13 | 14 | version 0.5 BETA 4/01/2016 15 | 16 | Author: Jaime García @peninquen 17 | License: Apache License Version 2.0. 18 | 19 | **********************************************************************/ 20 | 21 | #include "ModbusSensor.h" 22 | #include "SDMdefines.h" 23 | 24 | #define MB_SERIAL_PORT &Serial1 // Arduino has only one serial port, Mega has 3 serial ports. 25 | 26 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 27 | #define TxEnablePin 17 28 | 29 | // Forward declarations 30 | void id_configure(); 31 | void baudrate_configure(); 32 | void turnDisplay_configure(); 33 | void pulse1kwh_configure(); 34 | void energyMode_configure(); 35 | void pulse1Mode_configure(); 36 | 37 | 38 | uint8_t idNumber = 1; // Id 001 modbus id of the energy monitor 39 | uint16_t baudRate = 2400; // b 2400 40 | uint16_t byteFormat = SERIAL_8N2; // Prty n 41 | #define REFRESH_INTERVAL 1000 // refresh time, 1 SECOND 42 | 43 | void setup() { 44 | 45 | Serial.begin(9600); 46 | delay(95); 47 | Serial.println(F("Config SDM120 - Modbus")); 48 | MBSerial.config(MB_SERIAL_PORT, TxEnablePin, REFRESH_INTERVAL); 49 | MBSerial.begin(baudRate, byteFormat); 50 | 51 | id_configure(); 52 | baudrate_configure(); 53 | turnDisplay_configure(); 54 | pulse1kwh_configure(); 55 | energyMode_configure(); 56 | pulse1Mode_configure(); 57 | } 58 | 59 | void loop() {} // no hace nada... 60 | 61 | //----------------------------------------------------------------------------------------- 62 | //Procesa el valor de registro id 63 | void id_configure() { 64 | Serial.println(F("Enter expected Id: ")); 65 | while (!Serial.available()) {} 66 | idNumber = Serial.parseInt(); 67 | 68 | modbusSensor id(idNumber, DEVICE_ID, HOLD_VALUE, sizeof(float), READ_HOLDING_REGISTERS); 69 | while (!MBSerial.available()) {} 70 | 71 | Serial.print(F("Meter Id: ")); Serial.println(id.read(), 0); 72 | Serial.print(F("New Id: ")); 73 | while (!Serial.available()) {} 74 | int16_t iParameter = Serial.parseInt(); 75 | if (iParameter >= 1 && iParameter <= 247) { 76 | float fParameter = (float)iParameter; 77 | id.preset(fParameter); 78 | if (id.printStatus() != MB_VALID_DATA) return; 79 | idNumber == iParameter; // actuliza el id para las siguientes peticiones 80 | } 81 | else Serial.println(F("Skip")); 82 | } 83 | 84 | //----------------------------------------------------------------------------------------- 85 | //Procesa el valor de registro baud rate 86 | void baudrate_configure() { 87 | modbusSensor baudrate(idNumber, BAUD_RATE, HOLD_VALUE, sizeof(float), READ_HOLDING_REGISTERS); 88 | while (!MBSerial.available()) {} 89 | float defBaudRate = baudrate.read(); //default 90 | Serial.print(F("Baud rate (0:2400 1:4800 2:9600 5:1200): ")); 91 | Serial.println(defBaudRate, 0); 92 | Serial.print(F("New baud rate: ")); 93 | while (!Serial.available()) {} 94 | int16_t iParameter = Serial.parseInt(); 95 | switch (iParameter) { 96 | case 0: 97 | baudRate = 2400; 98 | break; 99 | case 1: 100 | baudRate = 4800; 101 | break; 102 | case 2: 103 | baudRate = 9600; 104 | break; 105 | case 5: 106 | baudRate = 1200; 107 | break; 108 | default: 109 | Serial.println(F("Skip")); 110 | return; 111 | } 112 | float fParameter = (float)iParameter; 113 | Serial.println(fParameter, 0); 114 | baudrate.preset(fParameter); 115 | if(baudrate.printStatus() != MB_VALID_DATA) return; 116 | if (defBaudRate != iParameter) { 117 | MBSerial.end(); 118 | MBSerial.begin(baudRate, byteFormat); 119 | } 120 | } 121 | 122 | //----------------------------------------------------------------------------------------- 123 | //Procesa el valor de registro 'tiempo entre pantallas' 124 | void turnDisplay_configure() { 125 | uint16_t bcd; 126 | modbusSensor turnDisplay(idNumber, TIME_DISP, HOLD_VALUE, sizeof(bcd), READ_HOLDING_REGISTERS); 127 | while (!MBSerial.available()) {} 128 | Serial.print(F("Time of display in turns (0 - 30 seconds): ")); 129 | Serial.print(turnDisplay.read(bcd), HEX); 130 | Serial.print(F("New time: ")); 131 | while (!Serial.available()) {} 132 | bcd = Serial.parseInt(); 133 | if (bcd >= 0 && bcd <= 30) { 134 | bcd = bcd % 10 | (bcd / 10 << 4); //convert to BCD 135 | Serial.println(bcd, HEX); 136 | turnDisplay.preset(bcd); 137 | if (turnDisplay.printStatus() != MB_VALID_DATA) return; 138 | } 139 | else Serial.println(F("Skip")); 140 | } 141 | 142 | //----------------------------------------------------------------------------------------- 143 | //Procesa el valor de registro 'salida pulso 1' 144 | void pulse1kwh_configure() { 145 | uint16_t hex; 146 | modbusSensor pulse1kwh(idNumber, PULSE1_KWH, HOLD_VALUE, sizeof(hex), READ_HOLDING_REGISTERS); 147 | while (!MBSerial.available()) {} 148 | Serial.print(F("Pulse 1 output (0:1000, 1:100, 2:10 3:1 imp/Kwh): ")); 149 | Serial.println(pulse1kwh.read(hex), HEX); 150 | Serial.print(F("New pulse 1 output value: ")); 151 | while (!Serial.available()) {} 152 | hex = Serial.parseInt(); 153 | if (hex >= 0 && hex <= 3) { 154 | Serial.println(hex, HEX); 155 | pulse1kwh.preset(hex); 156 | if (pulse1kwh.printStatus() != MB_VALID_DATA) return; 157 | } 158 | else Serial.println(F("Skip")); 159 | } 160 | 161 | //----------------------------------------------------------------------------------------- 162 | //Procesa el valor de registro 'modo medida de energía' 163 | void energyMode_configure() { 164 | uint16_t hex; 165 | modbusSensor enrgMode(idNumber, TOT_MODE, HOLD_VALUE, sizeof(hex), READ_HOLDING_REGISTERS); 166 | while (!MBSerial.available()) {} 167 | Serial.print(F("Measurement mode (0:mode 1, 1:mode 2, 2:mode 3): ")); 168 | Serial.println(enrgMode.read(hex), HEX); 169 | Serial.print(F("New Measure mode: ")); 170 | while (!Serial.available()) {} 171 | hex = Serial.parseInt(); 172 | if (hex >= 0 && hex <= 2) { 173 | Serial.println(hex, HEX); 174 | enrgMode.preset(hex); 175 | if (enrgMode.printStatus() != MB_VALID_DATA) return; 176 | } 177 | else Serial.println(F("Skip")); 178 | } 179 | 180 | //----------------------------------------------------------------------------------------- 181 | //Procesa el valor de registro 'modo salida pulso 1' 182 | void pulse1Mode_configure() { 183 | uint16_t hex; 184 | modbusSensor pulse1Mode(idNumber, PULSE1_MODE, HOLD_VALUE, sizeof(hex), READ_HOLDING_REGISTERS); 185 | while (!MBSerial.available()) {} 186 | Serial.print(F("Pulse 1 output mode (0:imp+exp, 1:imp, 2:exp): ")); 187 | Serial.println(pulse1Mode.read(hex), HEX); 188 | Serial.print(F("New pulse 1 output mode: ")); 189 | while (!Serial.available()) {} 190 | hex = Serial.parseInt(); 191 | if (hex >= 0 && hex <= 2) { 192 | Serial.println(hex, HEX); 193 | pulse1Mode.preset(hex); 194 | if (pulse1Mode.printStatus() != MB_VALID_DATA) return; 195 | } 196 | else Serial.println(F("Skip")); 197 | 198 | // Salir del modo -SET- para que se hagan efectivos los cambios, después apagar y encender el SMD120 199 | Serial.println(F("Now long press for 3 seconds to exit the -SET- mode. Then restart the SMD120 to aply the changes.")); 200 | } 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /dev version/esp8266modbusEnergyMonitor/ModbusSensor.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ModbusSensor class 3 | 4 | 5 | version 0.5.6 BETA 19/01/2016 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | *******************************************************************************/ 11 | //------------------------------------------------------------------------------ 12 | 13 | //#define MODBUS_SERIAL_OUTPUT //Verbose MODBUS messages and timing 14 | 15 | #ifdef MODBUS_SERIAL_OUTPUT 16 | #define MODBUS_SERIAL_BEGIN(...) Serial.begin(__VA_ARGS__) 17 | #define MODBUS_SERIAL_PRINT(...) Serial.print(__VA_ARGS__) 18 | #define MODBUS_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__) 19 | #else 20 | #define MODBUS_SERIAL_BEGIN(...) 21 | #define MODBUS_SERIAL_PRINT(...) 22 | #define MODBUS_SERIAL_PRINTLN(...) 23 | #endif 24 | 25 | #include "ModbusSensor.h" 26 | 27 | // Finite state machine status 28 | #define STOP 0 29 | #define IDLE 1 30 | #define SENDING 2 31 | #define RECEIVING 3 32 | #define WAITING_TURNAROUND 4 33 | 34 | /*#define READ_COIL_STATUS 0x01 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. 35 | #define READ_INPUT_STATUS 0x02 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. 36 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary contents of holding registers (4X references) in the slave. 37 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary contents of input registers (3X references) in the slave. Not writable. 38 | #define FORCE_MULTIPLE_COILS 0x0F // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. 39 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 40 | 41 | #define MB_VALID_DATA 0x00 42 | #define MB_INVALID_ID 0xE0 43 | #define MB_INVALID_FC 0xE1 44 | #define MB_TIMEOUT 0xE2 45 | #define MB_INVALID_CRC 0xE3 46 | #define MB_INVALID_BUFF 0xE4 47 | #define MB_ILLEGAL_FC 0x01 48 | #define MB_ILLEGAL_ADR 0x02 49 | #define MB_ILLEGAL_DATA 0x03 50 | #define MB_SLAVE_FAIL 0x04 51 | #define MB_EXCEPTION 0x05 52 | 53 | // when _status is diferent to MB_VALID_DATA change it to zero or hold last valid value? 54 | //#define CHANGE_TO_ZERO 0x00 55 | //#define CHANGE_TO_ONE 0x01 56 | //#define HOLD_VALUE 0xFF 57 | */ 58 | 59 | uint16_t calculateCRC(uint8_t *array, uint8_t num) { 60 | uint16_t temp, flag; 61 | temp = 0xFFFF; 62 | for (uint8_t i = 0; i < num; i++) { 63 | temp = temp ^ array[i]; 64 | for (uint8_t j = 8; j; j--) { 65 | flag = temp & 0x0001; 66 | temp >>= 1; 67 | if (flag) 68 | temp ^= 0xA001; 69 | } 70 | } 71 | return temp; 72 | } 73 | 74 | //------------------------------------------------------------------------------ 75 | //configure sketch variables 76 | void modbusMaster::config(HardwareSerial * hwSerial, uint8_t TxEnPin) { 77 | _hwSerial = hwSerial; 78 | _TxEnablePin = TxEnPin; 79 | pinMode(_TxEnablePin, OUTPUT); 80 | _state = STOP; 81 | MODBUS_SERIAL_PRINT(_totalSensors); MODBUS_SERIAL_PRINTLN(" total sensors attached at config"); 82 | } 83 | 84 | 85 | //------------------------------------------------------------------------------ 86 | // attach a modbusSensor to the modbusMaster array of queries 87 | void modbusMaster::attach(modbusSensor * mbSensor) { 88 | if (_totalSensors < MAX_SENSORS) { 89 | _mbSensorsPtr[_totalSensors] = mbSensor; 90 | _totalSensors++; 91 | } 92 | return; 93 | } 94 | 95 | //------------------------------------------------------------------------------ 96 | // detach a modbusSensor to the modbusMaster array of queries 97 | void modbusMaster::detach(modbusSensor * mbSensor) { 98 | uint8_t i = 0; 99 | while (i < _totalSensors) { 100 | if (_mbSensorsPtr[i] == mbSensor) { 101 | MODBUS_SERIAL_PRINT(i); MODBUS_SERIAL_PRINTLN(" sensor detached"); 102 | while (i < _totalSensors - 1) { 103 | _mbSensorsPtr[i] = _mbSensorsPtr[i + 1]; //shift down pointers one position 104 | } 105 | _totalSensors--; // reduce number of total sensors in the array 106 | _mbSensorsPtr[_totalSensors] = 0; // delete last position 107 | } 108 | i++; 109 | } 110 | } 111 | 112 | //------------------------------------------------------------------------------ 113 | // begin communication using ModBus protocol over RS485 114 | void modbusMaster::begin(uint16_t baudrate, uint8_t byteFormat) { 115 | if (baudrate > 19200) 116 | _T2_5 = 1250; 117 | else 118 | _T2_5 = 27500000 / baudrate; // 2400 bauds --> 11458 us; 9600 bauds --> 2864 us 119 | (*_hwSerial).begin(baudrate, byteFormat); 120 | _indexSensor = _totalSensors; 121 | _state = IDLE; 122 | digitalWrite(_TxEnablePin, LOW); 123 | } 124 | 125 | //------------------------------------------------------------------------------ 126 | // end communication over serial port 127 | inline void modbusMaster::end() { 128 | _state = STOP; 129 | (*_hwSerial).end(); 130 | digitalWrite(_TxEnablePin, LOW); 131 | } 132 | 133 | //------------------------------------------------------------------------------ 134 | // Start process to send requests 135 | boolean modbusMaster::sendRequest() { 136 | if (_state = IDLE) { 137 | _indexSensor = 0; 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | //------------------------------------------------------------------------------ 144 | // Finite State Machine core, 145 | boolean modbusMaster::available() { 146 | static uint8_t frameSize; // size of the RX frame 147 | static uint32_t tMicros; // time to check between characters in a frame 148 | static uint32_t nowMillis; 149 | 150 | switch (_state) { 151 | //----------------------------------------------------------------------------- 152 | case IDLE: 153 | if (_indexSensor < _totalSensors) { 154 | digitalWrite(_TxEnablePin, HIGH); 155 | uint8_t *frame = (*_mbSensorsPtr[_indexSensor])._frame; 156 | uint8_t frameSize = (*_mbSensorsPtr[_indexSensor])._frameSize; 157 | sendFrame(frame, frameSize); 158 | tMicros = micros(); 159 | _state = SENDING; 160 | return false; 161 | } 162 | // if _indexSensor == _totalSensors, all request done 163 | return true; 164 | 165 | //----------------------------------------------------------------------------- 166 | case SENDING: 167 | 168 | if ((*_hwSerial).availableForWrite() < SERIAL_TX_BUFFER_SIZE - 1) { //TX buffer not empty 169 | tMicros = micros(); 170 | return false; 171 | } 172 | // delayMicroseconds(_T2_5); 173 | // time to send last byte and required empty time space 174 | if (micros() - tMicros < _T2_5) return false; 175 | 176 | // clean RX buffer 177 | while ((*_hwSerial).available()) (*_hwSerial).read(); 178 | 179 | // MAX485 Receiving mode 180 | digitalWrite(_TxEnablePin, LOW); 181 | _state = RECEIVING; 182 | 183 | //starts slave's timeOut 184 | _timeoutMillis = millis(); 185 | frameSize = 0; 186 | 187 | return false; 188 | 189 | //----------------------------------------------------------------------------- 190 | case RECEIVING: 191 | 192 | if (!(*_hwSerial).available()) { 193 | if (millis() - _timeoutMillis > TIMEOUT) { 194 | (*_mbSensorsPtr[_indexSensor])._status = MB_TIMEOUT; 195 | #ifdef MODBUS_SERIAL_OUTPUT 196 | (*_mbSensorsPtr[_indexSensor]).printStatus(); 197 | #endif 198 | _indexSensor++; 199 | _state = IDLE; 200 | } 201 | return false; 202 | } 203 | 204 | if ((*_hwSerial).available() > frameSize) { 205 | frameSize++; 206 | tMicros = micros(); 207 | } 208 | else { 209 | if (micros() - tMicros > _T2_5) { 210 | readBuffer(frameSize); 211 | (*_mbSensorsPtr[_indexSensor]).processBuffer(_rx_buffer, frameSize); 212 | #ifdef MODBUS_SERIAL_OUTPUT 213 | (*_mbSensorsPtr[_indexSensor]).printStatus(); 214 | #endif 215 | _indexSensor++; 216 | _waitingMillis = millis(); //starts waiting interval to next request 217 | _state = WAITING_TURNAROUND; 218 | } 219 | } 220 | return false; 221 | 222 | //----------------------------------------------------------------------------- 223 | case WAITING_TURNAROUND: 224 | if (millis() - _waitingMillis > WAITING_INTERVAL) 225 | _state = IDLE; 226 | return false; 227 | 228 | //----------------------------------------------------------------------------- 229 | case STOP: 230 | for (; _indexSensor < _totalSensors; _indexSensor++) 231 | (*_mbSensorsPtr[_indexSensor])._status = MB_MASTER_STOP; 232 | return true; 233 | } 234 | } 235 | 236 | //----------------------------------------------------------------------------- 237 | // 238 | void modbusMaster::sendFrame(uint8_t *frame, uint8_t frameSize) { 239 | MODBUS_SERIAL_PRINT(millis()); 240 | MODBUS_SERIAL_PRINT(" MASTER:"); 241 | MODBUS_SERIAL_PRINTLN((*_hwSerial).availableForWrite()); 242 | 243 | (*_hwSerial).write(frame, frameSize); 244 | 245 | MODBUS_SERIAL_PRINTLN((*_hwSerial).availableForWrite()); 246 | 247 | #ifdef MODBUS_SERIAL_OUTPUT 248 | for (uint8_t index = 0; index < frameSize; index++) { 249 | if (frame[index] < 0x10) 250 | Serial.print(" 0"); 251 | else 252 | Serial.print(" "); 253 | Serial.print(frame[index], HEX); 254 | } 255 | Serial.print(" "); 256 | Serial.println(millis()); 257 | #endif 258 | } 259 | 260 | //----------------------------------------------------------------------------- 261 | inline void modbusMaster::readBuffer(uint8_t frameSize) { 262 | 263 | MODBUS_SERIAL_PRINT(millis()); 264 | MODBUS_SERIAL_PRINT(" SLAVE:"); 265 | 266 | (*_hwSerial).readBytes(_rx_buffer, frameSize); 267 | 268 | #ifdef MODBUS_SERIAL_OUTPUT 269 | for (uint8_t index = 0; index < frameSize; index++) { 270 | if (_rx_buffer[index] < 0x10) 271 | Serial.print(" 0"); 272 | else 273 | Serial.print(" "); 274 | Serial.print(_rx_buffer[index], HEX); 275 | } 276 | Serial.print(" "); 277 | Serial.println(millis()); 278 | #endif 279 | } 280 | 281 | // Create an instance of modbusMaster 282 | modbusMaster MBSerial; 283 | 284 | //----------------------------------------------------------------------------- 285 | //----------------------------------------------------------------------------- 286 | // Constructor 287 | modbusSensor::modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue, uint8_t fc) { 288 | // reserve space to new struct of value 289 | _value = new uint8_t[sizeofValue]; 290 | // pointer to the first byte 291 | uint8_t *ptr = _value; 292 | // reset content 293 | for (int count = sizeofValue; count; --count) *ptr++ = 0; 294 | 295 | switch (fc) { 296 | case PRESET_MULTIPLE_REGISTERS: 297 | case READ_HOLDING_REGISTERS: 298 | _frame = new uint8_t[9 + sizeofValue]; //reserve space for fc PRESET_MULTIPLE_REGISTERS 299 | _frame[1] = READ_HOLDING_REGISTERS; 300 | break; 301 | case READ_INPUT_REGISTERS: 302 | _frame = new uint8_t[8]; 303 | _frame[1] = READ_INPUT_REGISTERS; 304 | break; 305 | default: 306 | // exit without attach to MBSerial 307 | _frame = 0; 308 | _frameSize = 0; 309 | _status = MB_INVALID_FC; 310 | return; 311 | } 312 | _frameSize = 8; // always read function, change in preset() 313 | 314 | _frame[0] = id; 315 | //_frame[1] defined previously; 316 | _frame[2] = adr >> 8; 317 | _frame[3] = adr & 0x00FF; 318 | _frame[4] = 0x00; 319 | _frame[5] = sizeofValue / 2; 320 | uint16_t crc = calculateCRC(_frame, 6); 321 | _frame[6] = crc & 0x00FF; 322 | _frame[7] = crc >> 8; 323 | 324 | _status = MB_MASTER_STOP; 325 | _hold = hold; 326 | 327 | MBSerial.attach(this); 328 | } 329 | 330 | //------------------------------------------------------------------------------ 331 | //Process RX buffer 332 | void modbusSensor::processBuffer(uint8_t *rxFrame, uint8_t rxFrameSize) { 333 | 334 | // check minimum response frame size 335 | if (rxFrameSize < 5) { 336 | _status = MB_SLAVE_FAIL; 337 | return; 338 | } 339 | 340 | // check slave id 341 | if (rxFrame[0] != _frame[0]) { 342 | _status = MB_INVALID_ID; 343 | return; 344 | } 345 | 346 | // check CRC 347 | uint16_t crc = calculateCRC(rxFrame, rxFrameSize - 2); 348 | if (rxFrame[rxFrameSize - 1] != crc >> 8 && rxFrame[rxFrameSize - 2] != crc & 0x00FF) { 349 | _status = MB_INVALID_CRC; 350 | return; 351 | } 352 | 353 | // check slave's exception in function code 354 | if (rxFrame[1] & 0x80 == 0x80) { 355 | _status = rxFrame[2]; // see exception codes in define area or printStatus() 356 | return; 357 | } 358 | 359 | // check function code 360 | if (rxFrame[1] != _frame[1]) { 361 | _status = MB_INVALID_FC; 362 | return; 363 | } 364 | 365 | // READ frame, transfer modbus registers to _value 366 | switch (rxFrame[1]) { 367 | case READ_HOLDING_REGISTERS: 368 | case READ_INPUT_REGISTERS: 369 | // check byte count equals to registers request 370 | if (rxFrame[2] == _frame[2] * 2) { 371 | uint8_t *ptr = (uint8_t *) &_value; 372 | ptr += rxFrame[2] - 1; // pointer to object last byte 373 | uint8_t i = 3; 374 | for (int count = rxFrame[2]; count; --count, i++, ptr--) *ptr = rxFrame[i]; 375 | _status = MB_VALID_DATA; 376 | return; 377 | } 378 | else { 379 | _status = MB_ILLEGAL_DATA; 380 | return; 381 | } 382 | 383 | // PRESET frame, return OK 384 | case PRESET_MULTIPLE_REGISTERS: 385 | _status = MB_VALID_DATA; 386 | return; 387 | 388 | default: 389 | _status = MB_INVALID_FC; 390 | return; 391 | } 392 | } 393 | 394 | //------------------------------------------------------------------------------ 395 | // Construct Tx frame, send it, receive response and process it, reconstruct READ frame 396 | void modbusSensor::processPreset(uint8_t *ptr, uint8_t objectSize) { 397 | 398 | if (_frame[2] != READ_HOLDING_REGISTERS) { 399 | _status = MB_INVALID_ADR; 400 | return; 401 | } 402 | 403 | if (objectSize != _frame[5] * 2) { 404 | _status = MB_INVALID_DATA; 405 | return; 406 | } 407 | 408 | // Construct PRESET frame 409 | _frame[2] = PRESET_MULTIPLE_REGISTERS; 410 | _frame[6] = objectSize; 411 | uint8_t i = 7; 412 | 413 | ptr += objectSize - 1; // pointer to object last byte 414 | for (int count = objectSize; count; count--, i++) _frame[i] = *ptr--; 415 | uint16_t crc = calculateCRC(_frame, i); 416 | _frame[i++] = crc & 0x00FF; 417 | _frame[i++] = crc >> 8; 418 | _frameSize = i; 419 | 420 | // Send the PRESET frame, receive response and process it 421 | MBSerial.sendRequest(); 422 | while (!MBSerial.available()) {} 423 | 424 | // reconstruct READ frame 425 | _frame[2] = READ_HOLDING_REGISTERS; 426 | crc = calculateCRC(_frame, 6); 427 | _frame[6] = crc & 0x00FF; 428 | _frame[7] = crc >> 8; 429 | _frameSize = 8; 430 | } 431 | 432 | //------------------------------------------------------------------------------ 433 | // read value in defined units 434 | float modbusSensor::read() { 435 | if (_status == MB_TIMEOUT) 436 | switch (_hold) { 437 | case CHANGE_TO_ZERO: return 0.0; 438 | case CHANGE_TO_ONE: return 1.0; 439 | case HOLD_VALUE:; 440 | } 441 | return (float) * _value; 442 | } 443 | 444 | //------------------------------------------------------------------------------ 445 | // process status and return the predetermined response 446 | void modbusSensor::processRead(uint8_t *ptr, const uint8_t objectSize) { 447 | if (_status == MB_TIMEOUT) 448 | switch (_hold) { 449 | case CHANGE_TO_ZERO: 450 | for (int count = objectSize; count; --count) *ptr++ = 0; 451 | return; 452 | case CHANGE_TO_ONE:; 453 | case HOLD_VALUE:; 454 | } 455 | // copy _value on the returned struct 456 | const uint8_t *e = _value; 457 | for (int count = objectSize; count; --count, ++e) *ptr++ = *e; 458 | } 459 | 460 | //------------------------------------------------------------------------------ 461 | // print status message 462 | uint8_t modbusSensor::printStatus() { 463 | switch (_status) { 464 | case MB_VALID_DATA: 465 | Serial.println("Transmision successful"); 466 | return _status; 467 | case MB_INVALID_ID: 468 | Serial.println("No valid Id"); 469 | return _status; 470 | case MB_INVALID_FC: 471 | Serial.println("No valid FC"); 472 | return _status; 473 | case MB_TIMEOUT: 474 | Serial.println("Time out"); 475 | return _status; 476 | case MB_INVALID_CRC: 477 | Serial.println("incorrect CRC"); 478 | return _status; 479 | case MB_INVALID_BUFF: 480 | Serial.println("Invalid buffer size"); 481 | return _status; 482 | case MB_INVALID_ADR: 483 | Serial.println("No valid address"); 484 | return _status; 485 | case MB_INVALID_DATA: 486 | Serial.println("No valid data"); 487 | return _status; 488 | case MB_MASTER_STOP: 489 | Serial.println("Master in STOP mode"); 490 | return _status; 491 | case MB_ILLEGAL_FC: 492 | Serial.println("Exception: Illegal FC"); 493 | return _status; 494 | case MB_ILLEGAL_ADR: 495 | Serial.println("Exception: Illegal address"); 496 | return _status; 497 | case MB_ILLEGAL_DATA: 498 | Serial.println("Exception: Illegal data"); 499 | return _status; 500 | case MB_SLAVE_FAIL: 501 | Serial.println("Exception: Slave failed "); 502 | return _status; 503 | case MB_EXCEPTION: 504 | Serial.println("Exception"); 505 | return _status; 506 | default: 507 | Serial.println ("** ERROR **"); 508 | } 509 | } 510 | 511 | 512 | //---------------------------------------------------------------------------------------// 513 | //---------------------------------------------------------------------------------------// 514 | 515 | 516 | 517 | -------------------------------------------------------------------------------- /dev version/esp8266modbusEnergyMonitor/ModbusSensor.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusSensor.h 3 | create ModbusSensor and ModbusMaster classes to process values from 4 | a Eastron SMD120 and family. 5 | 6 | version 0.5.6 BETA 19/01/2016 7 | 8 | Author: Jaime García @peninquen 9 | License: Apache License Version 2.0. 10 | 11 | **********************************************************************/ 12 | 13 | #ifndef ModbusSensor_h 14 | #define ModbusSensor_h 15 | 16 | #include "Arduino.h" 17 | #define MAX_SENSORS 16 18 | // The maximum number of bytes in a modbus packet is 256 bytes. 19 | // The serial buffer limits this to 128 bytes. 20 | // We can reduce it to maximum data sending by a slave SMD120 9 bytes, SMD 630 is 85 bytes 21 | // Three phase meters are 3 values, 6 registers and 12 bytes, plus 5 frame bytes, total 17 22 | #define BUFFER_SIZE 32 23 | #define TIMEOUT 110 // time to fail a request 24 | #define WAITING_INTERVAL 40 // time required by SDM120 to be prepared to receive a new request 25 | 26 | // What happens when _status is diferent to MB_VALID_DATA? 27 | #define CHANGE_TO_ZERO 0x00 28 | #define CHANGE_TO_ONE 0x01 29 | #define HOLD_VALUE 0xFF 30 | 31 | // Function codes operative 32 | #define READ_HOLDING_REGISTERS 0x03 // Reads the binary content of holding registers (4X references) in the slave. 33 | #define READ_INPUT_REGISTERS 0x04 // Reads the binary content of input registers (3X references) in the slave. Not writable. 34 | #define PRESET_MULTIPLE_REGISTERS 0x10 // Presets values into a sequence of holding registers (4X references). 35 | 36 | #define MB_VALID_DATA 0x00 37 | #define MB_INVALID_ID 0xE0 38 | #define MB_INVALID_FC 0xE1 39 | #define MB_TIMEOUT 0xE2 40 | #define MB_INVALID_CRC 0xE3 41 | #define MB_INVALID_BUFF 0xE4 42 | #define MB_INVALID_ADR 0xE5 43 | #define MB_INVALID_DATA 0xE6 44 | #define MB_MASTER_STOP 0xE7 45 | #define MB_ILLEGAL_FC 0x01 46 | #define MB_ILLEGAL_ADR 0x02 47 | #define MB_ILLEGAL_DATA 0x03 48 | #define MB_SLAVE_FAIL 0x04 49 | #define MB_EXCEPTION 0x05 50 | 51 | // forward definition 52 | class modbusSensor; 53 | 54 | //------------------------------------------------------------------------------ 55 | //------------------------------------------------------------------------------ 56 | class modbusMaster { 57 | protected: 58 | HardwareSerial *_hwSerial; 59 | modbusSensor *_mbSensorsPtr[MAX_SENSORS]; // array of modbusSensor's pointers 60 | uint8_t _TxEnablePin; // pin to enable transmision in MAX485 61 | uint8_t _totalSensors; // constant, max number of sensors to poll 62 | uint8_t _indexSensor; // index number of sensors 63 | uint8_t _rx_buffer[BUFFER_SIZE]; // buffer to process rececived frame 64 | uint8_t _state; // Modbus FSM state (STOP, IDLE, SENDING, RECEIVING, WAITING_TURNAROUND) 65 | uint32_t _T2_5; // time between characters in a frame, in microseconds 66 | uint32_t _timeoutMillis; // time to check timeout interval 67 | uint32_t _waitingMillis; // time to check waiting turnaround interval 68 | 69 | inline void sendFrame(uint8_t *frame, uint8_t frameSize); 70 | inline void readBuffer(uint8_t frameSize); 71 | 72 | public: 73 | //constructor 74 | modbusMaster() { 75 | _totalSensors = 0; 76 | for (uint8_t i = 0; i < MAX_SENSORS; i++) 77 | _mbSensorsPtr[i] = 0; 78 | }; 79 | 80 | //constructor 81 | void config(HardwareSerial *mbSerial, uint8_t TxEnPin); 82 | 83 | // attach a modbusSensor to modbusMaster array of queries 84 | void attach(modbusSensor *mbSensor); 85 | 86 | // detach a modbusSensor to modbusMaster array of queries 87 | void detach(modbusSensor *mbSensor); 88 | 89 | // begin communication using ModBus protocol over RS485 90 | void begin(uint16_t baudrate, uint8_t byteFormat); 91 | 92 | // end communication over serial port 93 | inline void end(); 94 | 95 | // Start process to send requests 96 | boolean sendRequest(); 97 | 98 | // Finite State Machine core, process FSM 99 | // It returns 'true' when finish to request all the array of modbusSensors. 100 | // Non-blocking function, put the instrucction in a loop function to make the proccess work properly 101 | boolean available(); 102 | }; 103 | 104 | extern modbusMaster MBSerial; 105 | 106 | //------------------------------------------------------------------------------ 107 | //------------------------------------------------------------------------------ 108 | class modbusSensor { 109 | protected: 110 | uint8_t * _value; // pointer to a dinamic allocated object, size inside _frame[5] 111 | uint8_t * _frame; // pointer to a dinamic allocated array, 112 | uint8_t _frameSize; // size of the _frame, 8 in read function, 9+sizeof(T) in preset function 113 | uint8_t _status; // register of the result of communication 114 | uint8_t _hold; // predefined behaiviour in case of timeout exception 115 | 116 | void processPreset(uint8_t *ptr, uint8_t objectSize); 117 | void processRead(uint8_t *ptr, uint8_t objectSize); 118 | void processBuffer(uint8_t *rxFrame, uint8_t rxFrameSize); 119 | 120 | //MBSerial.available need access to _frame, _frameSize and _status variables 121 | friend boolean modbusMaster::available(); 122 | 123 | public: 124 | // Target constructor 125 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue, uint8_t fc); 126 | 127 | // delegating constructor 128 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold, uint8_t sizeofValue): modbusSensor(id, adr, hold, sizeofValue, READ_INPUT_REGISTERS) {}; 129 | 130 | // delegating constructor 131 | modbusSensor(uint8_t id, uint16_t adr, uint8_t hold): modbusSensor(id, adr, hold, 4, READ_INPUT_REGISTERS) {}; 132 | 133 | // Destructor 134 | ~modbusSensor() { 135 | delete[] _value; 136 | delete[] _frame; 137 | MBSerial.detach(this); 138 | } 139 | 140 | // attach to MBSerial array of sensors 141 | void attach() { 142 | MBSerial.attach(this); 143 | }; 144 | 145 | // detach to MBSerial array of sensors 146 | void detach() { 147 | MBSerial.detach(this); 148 | }; 149 | 150 | // Preset sensor value, fc 0x10, only holding registers defined with fc 0x03 151 | // complete funtion to make and send the frame and process response, check status 152 | template < typename T > void preset(const T &t) { 153 | processPreset((uint8_t *) &t, sizeof(T)); 154 | }; 155 | 156 | // read value in defined units 157 | float read(); 158 | 159 | // read every struct value from object buffer, non-blocking function 160 | template< typename T > T &read(T &t) { 161 | processRead((uint8_t *) &t, sizeof(T)); 162 | return t; 163 | } 164 | 165 | // print status message 166 | uint8_t printStatus(); 167 | }; 168 | 169 | #endif 170 | 171 | -------------------------------------------------------------------------------- /dev version/esp8266modbusEnergyMonitor/NODEMCU_DEVKIT_SCH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/dev version/esp8266modbusEnergyMonitor/NODEMCU_DEVKIT_SCH.png -------------------------------------------------------------------------------- /dev version/esp8266modbusEnergyMonitor/SDMdefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | Eastron address register values 3 | */ 4 | 5 | // Read values use function code 4 (0x04) 6 | #define VOLTAGE 0x0000 //All Float 7 | #define CURRENT 0x0006 8 | #define POWER 0x000C 9 | #define APOWER 0x0012 10 | #define RAPOWER 0x0018 11 | #define PFACTOR 0x001E 12 | #define PANGLE 0x0024 13 | #define FREQUENCY 0x0046 14 | #define IAENERGY 0x0048 15 | #define EAENERGY 0x004A 16 | #define IRAENERGY 0x004C 17 | #define ERAENERGY 0x004E 18 | #define TAENERGY 0x0156 19 | #define TRENERGY 0x0158 20 | 21 | // Read/Write Configuration. 22 | // Read function code 3 (0x03) 23 | // Write function code 16 (0x10) 24 | #define NPARSTOP 0x0012 // HEX, bit de parada, (0:none, 1:even, 2:odd) 25 | #define DEVICE_ID 0x0014 // Float, identificador esclavo (1 - 247) 26 | #define BAUD_RATE 0x001C // Float, Baud rate (0:2400 1:4800 2:9600 5:1200) 27 | #define TIME_DISP_220 0xF500 // BCD, Tiempo entre pantallas (0-30s) SDM220 28 | #define TIME_DISP 0xF900 // BCD, Tiempo entre pantallas (0-30s) SDM120 29 | #define PULSE1_KWH 0xF910 // Pulsos/Kwh (0:1000, 1:100, 2:10 3:1 pulso/Kwh) 30 | #define TOT_MODE 0xF920 // HEX, modo medida energía (1-3) 31 | #define PULSE1_MODE 0xF930 // Modo salida pulsos a led (0:imp+exp, 1:imp, 2:exp) 32 | 33 | /* 34 | Maintaining old definitions. 35 | */ 36 | 37 | 38 | // Direcciones registros de datos solo lectura. Valores tipo float. 39 | // Utilizar funcion 04 lectura, numero de bytes 4. 40 | 41 | #define VOL_ADR VOLTAGE // VOLTAJE. 42 | #define CUR_ADR CURRENT // CORRIENTE. 43 | #define POW_ADR POWER // POTENCIA ACTIVA. 44 | #define APO_ADR APOWER // Potencia Aparente. 45 | #define RPO_ADR RAPOWER // Potencia Reactiva. 46 | #define PFA_ADR PFACTOR // Factor de potencia. 47 | #define PAN_ADR PANGLE // ANGULO PHI. desfase entre voltaje y corriente 48 | #define FRE_ADR FREQUENCY // Frecuencia. 49 | // REVISAR FALTAN PARAMENTROS O PARAMENTROS PARA SDM220 50 | 51 | #define PEN_ADR IAENERGY // ENERGIA IMPORTADA KWH 52 | #define REN_ADR EAENERGY // Energia exportada. 53 | #define TEN_ADR TAENERGY // Energia activa Total. 54 | #define TRE_ADR TRENERGY // Energia reactiva Total. 55 | 56 | /* Default SDM configuration. 57 | #define SDM120C_METER_NUMBER 1 58 | #define SDM120C_BAUDRATE 2400 59 | #define SDM120C_BYTEFORMAT SERIAL_8N2 //Prty n 60 | */ 61 | 62 | /*Modbus optimum settings 63 | #define TIMEOUT 1000 64 | #define POLLING 200 // the scan rate 65 | #define RETRYCOUNT 10 // numero de reintentos, para volver set the "connection" variable to 66 | */ 67 | 68 | // Get Parameter 69 | -------------------------------------------------------------------------------- /dev version/esp8266modbusEnergyMonitor/esp8266-sdm120c-Aledav.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peninquen/Modbus-Energy-Monitor-Arduino/fdea36922b3334e94333903761aee2caf9f748b7/dev version/esp8266modbusEnergyMonitor/esp8266-sdm120c-Aledav.jpg -------------------------------------------------------------------------------- /dev version/esp8266modbusEnergyMonitor/esp8266modbusEnergyMonitor.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | ModbusEnergyMonitor example 3 | An example to collect data from a Modbus energy monitor using ModbusSensor class 4 | to datalogger, include a RTC DS3231 and a SD card 5 | version 0.5 BETA 4/01/2016 6 | 7 | Author: Jaime García @peninquen 8 | License: Apache License Version 2.0. 9 | 10 | **********************************************************************/ 11 | 12 | 13 | #include //ESP library from http://github.com/esp8266/Arduino 14 | #include // MQTT library from https://github.com/knolleary/pubsubclient 15 | #include "ModbusSensor.h" 16 | #include "SDMdefines.h" 17 | 18 | // Update these with values suitable for your network. 19 | 20 | const char* ssid = "........"; 21 | const char* password = "........"; 22 | const char* mqtt_server = "broker.mqtt-dashboard.com"; 23 | 24 | WiFiClient espClient; 25 | PubSubClient client(espClient); 26 | uint8_t msg[50]; 27 | 28 | #define MB_SERIAL_PORT &Serial // ESP8266 has two serial ports, but Serial1 only Tx 29 | // Serial.swap() change pins to Tx and Rx 30 | 31 | #define MB_BAUDRATE 2400 // b 2400 32 | #define MB_BYTEFORMAT SERIAL_8N2 // Prty n 33 | #define TxEnablePin 2 34 | 35 | #define ID_1 1 // id 001 modbus id of the energy monitor 36 | #define REFRESH_INTERVAL 5000 // refresh time, 5 SECONDS 37 | 38 | 39 | struct three_phase { 40 | float line3, line2, line1; 41 | } voltage, current, power; 42 | 43 | float energy = 0.0; 44 | 45 | // global variables to poll, process and send values 46 | 47 | //modbusSensor(uint8_t id, uint8_t fc, uint16_t adr, uint8_t hold, uint8_t sizeofValue) 48 | modbusSensor volt(ID_1, VOLTAGE, CHANGE_TO_ZERO, sizeof(three_phase)); 49 | modbusSensor curr(ID_1, CURRENT, CHANGE_TO_ZERO, sizeof(three_phase)); 50 | modbusSensor pwr(ID_1, POWER, CHANGE_TO_ZERO, sizeof(three_phase)); 51 | //modbusSensor(uint8_t uint16_t adr, uint8_t hold) 52 | modbusSensor enrg(ID_1, IAENERGY, HOLD_VALUE); 53 | 54 | void setup() { 55 | pinMode(BUILTIN_LED, OUTPUT); 56 | setup_wifi(); 57 | client.setServer(mqtt:server,1883); 58 | client.setCallback(callback); 59 | 60 | Serial.begin(9600); 61 | MBSerial.config(MB_SERIAL_PORT, TxEnablePin, REFRESH_INTERVAL); 62 | MBSerial.begin(MB_BAUDRATE, MB_BYTEFORMAT); 63 | 64 | delay(95); 65 | Serial.println("time(s),Volt1(V), Volt2(V), Volt3(V), Curr1(A) Curr2(A), Curr3(A), Power1(W), Power2(W), Power3(W), Energy(Kwh)"); 66 | } 67 | 68 | void loop() { 69 | 70 | if (MBSerial.available()) { 71 | volt.read(voltage); 72 | curr.read(current); 73 | pwr.read(power); 74 | energy = enrg.read(); 75 | 76 | Serial.print(millis() / 1000); 77 | Serial.print(","); 78 | Serial.print(voltage.line1, 1); 79 | Serial.print(","); 80 | Serial.print(voltage.line2, 1); 81 | Serial.print(","); 82 | Serial.print(voltage.line3, 1); 83 | Serial.print(","); 84 | Serial.print(current.line1, 2); 85 | Serial.print(","); 86 | Serial.print(current.line2, 2); 87 | Serial.print(","); 88 | Serial.print(current.line3, 2); 89 | Serial.print(","); 90 | Serial.print(power.line1, 2); 91 | Serial.print(","); 92 | Serial.print(power.line2, 2); 93 | Serial.print(","); 94 | Serial.print(power.line3, 2); 95 | Serial.print(","); 96 | Serial.println(energy, 2); 97 | } 98 | } 99 | 100 | void setup_wifi() { 101 | delay(10); 102 | // We start by connecting to a WiFi network 103 | Serial.println(); 104 | Serial.print("Connecting to "); 105 | Serial.println(ssid); 106 | 107 | WiFi.begin(ssid, password); 108 | 109 | while (WiFi.status() != WL_CONNECTED) { 110 | delay(500); 111 | Serial.print("."); 112 | } 113 | Serial.println(""); 114 | Serial.println("WiFi connected"); 115 | Serial.println("IP address: "); 116 | Serial.println(WiFi.localIP()); 117 | } 118 | 119 | --------------------------------------------------------------------------------