forked from reaper7/SDM_Energy_Meter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSDM.h
250 lines (209 loc) · 13 KB
/
SDM.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/* Template library for reading SDM 120/220/630 Modbus Energy meters.
* Reading via Software Serial library & rs232<->rs485 converter
* 2016 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.6.9 & 2.3.0 esp8266 core)
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
*/
//#define USE_HARDWARESERIAL //option - use hardware serial
#ifndef SDM_h
#define SDM_h
//------------------------------------------------------------------------------
#include <Arduino.h>
#if !defined ( USE_HARDWARESERIAL )
#include <SoftwareSerial.h>
#endif
//------------------------------------------------------------------------------
#define SDM_BAUD 4800 //baudrate
#define MAX_MILLIS_TO_WAIT 500 //max time to wait for responce from SDM
#if !defined ( USE_HARDWARESERIAL )
#define SDMSER_RX_PIN 13 //RX-D7(wemos)-13
#define SDMSER_TX_PIN 15 //TX-D8(wemos)-15
#else
#define SWAPHWSERIAL 0 //when hwserial used, then swap uart pins from 3/1 to 13/15
#endif
#define DERE_PIN NOT_A_PIN //digital pin for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
#define FRAMESIZE 9 //size of out/in array
//------------------------------------------------------------------------------
#define SDM_REPLY_BYTE_COUNT 0x04 //number of bytes with data
#define SDM_B_01 0x01 //BYTE 1 -> slave address (default value 1 read from node 1)
#define SDM_B_02 0x04 //BYTE 2 -> function code (default value 4 read from 3X registers)
//BYTES 3 & 4 (BELOW)
//SDM 120 registers
#define SDM120C_VOLTAGE 0x0000 //V
#define SDM120C_CURRENT 0x0006 //A
#define SDM120C_POWER 0x000C //W
#define SDM120C_ACTIVE_APPARENT_POWER 0x0012 //VA
#define SDM120C_REACTIVE_APPARENT_POWER 0x0018 //VAR
#define SDM120C_POWER_FACTOR 0x001E //
#define SDM120C_FREQUENCY 0x0046 //Hz
#define SDM120C_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
#define SDM120C_EXPORT_ACTIVE_ENERGY 0x004A //Wh
#define SDM120C_TOTAL_ACTIVE_ENERGY 0x0156 //Wh
//SDM 220 registers
#define SDM220T_VOLTAGE 0x0000 //V
#define SDM220T_CURRENT 0x0006 //A
#define SDM220T_POWER 0x000C //W
#define SDM220T_ACTIVE_APPARENT_POWER 0x0012 //VA
#define SDM220T_REACTIVE_APPARENT_POWER 0x0018 //VAR
#define SDM220T_POWER_FACTOR 0x001E //
#define SDM220T_PHASE_ANGLE 0x0024 //DEGREE
#define SDM220T_FREQUENCY 0x0046 //Hz
#define SDM220T_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
#define SDM220T_EXPORT_ACTIVE_ENERGY 0x004A //Wh
#define SDM220T_IMPORT_REACTIVE_ENERGY 0x004C //VARh
#define SDM220T_EXPORT_REACTIVE_ENERGY 0x004E //VARh
#define SDM220T_TOTAL_ACTIVE_ENERGY 0x0156 //Wh
#define SDM220T_TOTAL_REACTIVE_ENERGY 0x0158 //VARh
//SDM 630 registers
#define SDM630_VOLTAGE1 0x0000 //V
#define SDM630_VOLTAGE2 0x0002 //V
#define SDM630_VOLTAGE3 0x0004 //V
#define SDM630_CURRENT1 0x0006 //A
#define SDM630_CURRENT2 0x0008 //A
#define SDM630_CURRENT3 0x000A //A
#define SDM630_CURRENTSUM 0x0030 //A
#define SDM630_POWER1 0x000C //W
#define SDM630_POWER2 0x000E //W
#define SDM630_POWER3 0x0010 //W
#define SDM630_POWERTOTAL 0x0034 //W
#define SDM630_VOLT_AMPS1 0x0012 //VA
#define SDM630_VOLT_AMPS2 0x0014 //VA
#define SDM630_VOLT_AMPS3 0x0016 //VA
#define SDM630_VOLT_AMPS_TOTAL 0x0038 //VA
#define SDM630_VOLT_AMPS_REACTIVE1 0x0018 //VAr
#define SDM630_VOLT_AMPS_REACTIVE2 0x001A //VAr
#define SDM630_VOLT_AMPS_REACTIVE3 0x001C //VAr
#define SDM630_VOLT_AMPS_REACTIVE_TOTAL 0x003C //VAr
#define SDM630_POWER_FACTOR1 0x001E
#define SDM630_POWER_FACTOR2 0x0020
#define SDM630_POWER_FACTOR3 0x0022
#define SDM630_POWER_FACTOR_TOTAL 0x003E
#define SDM630_PHASE_ANGLE1 0x0024 //Degrees
#define SDM630_PHASE_ANGLE2 0x0026 //Degrees
#define SDM630_PHASE_ANGLE3 0x0028 //Degrees
#define SDM630_PHASE_ANGLE_TOTAL 0x0042 //Degrees
#define SDM630_VOLTAGE_AVERAGE 0x002A //V
#define SDM630_CURRENT_AVERAGE 0x002E //A
#define SDM630_FREQUENCY 0x0046 //HZ
#define SDM630_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
#define SDM630_EXPORT_ACTIVE_ENERGY 0x004A //Wh
#define SDM630_IMPORT_REACTIVE_ENERGY 0x004C //VARh
#define SDM630_EXPORT_REACTIVE_ENERGY 0x004E //VARh
#define SDM630_TOTAL_SYSTEM_POWER_DEMAND 0x0054 //W
#define SDM630_MAXIMUM_TOTAL_SYSTEM_POWER 0x0056 //W
#define SDM_B_05 0x00 //BYTE 5
#define SDM_B_06 0x02 //BYTE 6
//------------------------------------------------------------------------------
#define SDM_ERR_NO_ERROR 0 //no error
#define SDM_ERR_CRC_ERROR 1 //crc error
#define SDM_ERR_WRONG_BYTES 2 //bytes b0,b1 or b2 wrong
#define SDM_ERR_NOT_ENOUGHT_BYTES 3 //not enough bytes from sdm
#define SDM_ERR_TIMEOUT 4 //timeout
#define READING_STEP_CNT 4
//------------------------------------------------------------------------------
#if !defined ( USE_HARDWARESERIAL )
template <long _speed = SDM_BAUD, int _rx_pin = SDMSER_RX_PIN, int _tx_pin = SDMSER_TX_PIN, int _dere_pin = DERE_PIN>
#else
template <long _speed = SDM_BAUD, int _dere_pin = DERE_PIN, bool _swapuart = SWAPHWSERIAL>
#endif
struct SDM {
#if !defined ( USE_HARDWARESERIAL )
SoftwareSerial sdmSer = SoftwareSerial(_rx_pin, _tx_pin, false, 32);
#else
HardwareSerial sdmSer = HardwareSerial(0);
#endif
private:
uint16_t readingerrcode = SDM_ERR_NO_ERROR; //4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 or b2 wrong, 1 = crc error
uint16_t readingerrcount = 0; //total errors couter
uint16_t calculateCRC(uint8_t *array, uint8_t num) {
uint16_t temp, flag;
temp = 0xFFFF;
for (uint8_t i = 0; i < num; i++) {
temp = temp ^ array[i];
for (uint8_t j = 8; j; j--) {
flag = temp & 0x0001;
temp >>= 1;
if (flag)
temp ^= 0xA001;
}
}
return temp;
};
public:
uint16_t getErrCode() { //return last errorcode
return (readingerrcode);
};
uint16_t getErrCount() { //return total errors count
return (readingerrcount);
};
void begin() {
sdmSer.begin(_speed);
#if defined ( USE_HARDWARESERIAL )
if (_swapuart)
sdmSer.swap();
#endif
if (_dere_pin != NOT_A_PIN) //set output pin mode for DE/RE pin when used (for control MAX485)
pinMode(_dere_pin, OUTPUT);
};
float readVal(uint16_t reg, uint8_t node = SDM_B_01) { //read value from register = reg and from deviceId = node
uint16_t temp;
unsigned long resptime;
uint8_t sdmarr[FRAMESIZE] = {node, SDM_B_02, 0, 0, SDM_B_05, SDM_B_06, 0, 0, 0};
float res = NAN;
bool timeouterr = false;
uint16_t stepcnt = READING_STEP_CNT;
sdmarr[2] = highByte(reg);
sdmarr[3] = lowByte(reg);
temp = calculateCRC(sdmarr, FRAMESIZE - 3); //calculate out crc only from first 6 bytes
sdmarr[6] = lowByte(temp);
sdmarr[7] = highByte(temp);
#if !defined ( USE_HARDWARESERIAL )
sdmSer.listen(); //enable softserial rx interrupt
#endif
while (sdmSer.available() > 0) { //read serial if any old data is available
sdmSer.read();
}
if (_dere_pin != NOT_A_PIN) //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)
digitalWrite(_dere_pin, HIGH);
delay(2); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524
sdmSer.write(sdmarr, FRAMESIZE - 1); //send 8 bytes
sdmSer.flush(); //clear out tx buffer
if (_dere_pin != NOT_A_PIN) //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
digitalWrite(_dere_pin, LOW);
resptime = millis() + MAX_MILLIS_TO_WAIT;
while (sdmSer.available() < FRAMESIZE) {
if (resptime < millis()) {
timeouterr = true;
break;
}
yield();
}
if (!timeouterr) { //if no timeout...
stepcnt--; //err debug (3)
if(sdmSer.available() == FRAMESIZE) {
for(int n=0; n<FRAMESIZE; n++) {
sdmarr[n] = sdmSer.read();
}
stepcnt--; //err debug (2)
if (sdmarr[0] == node && sdmarr[1] == SDM_B_02 && sdmarr[2] == SDM_REPLY_BYTE_COUNT) {
stepcnt--; //err debug (1)
if ((calculateCRC(sdmarr, FRAMESIZE - 2)) == ((sdmarr[8] << 8) | sdmarr[7])) { //calculate crc from first 7 bytes and compare with received crc (bytes 7 & 8)
stepcnt--; //err debug (0)
((uint8_t*)&res)[3]= sdmarr[3];
((uint8_t*)&res)[2]= sdmarr[4];
((uint8_t*)&res)[1]= sdmarr[5];
((uint8_t*)&res)[0]= sdmarr[6];
}
}
}
}
if (stepcnt > 0) { //if error then copy temp error value to global val and increment global error counter
readingerrcode = stepcnt;
readingerrcount++;
}
#if !defined ( USE_HARDWARESERIAL )
sdmSer.end(); //disable softserial rx interrupt
#endif
return (res);
};
};
#endif