-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathPowerMeter.ino
326 lines (245 loc) · 9.01 KB
/
PowerMeter.ino
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/*
nrf51822 based Power Meter
Measure the current from two 50A/1V current transformer on the power line (hot-neutral-hot)
Measure the AC voltage using a voltage transformer
Both CT's on two hot lines are measured separately, and three waveforms are DC offset to Vcc/2.
CT (SCT-013-050) internal burden R ~= 37 ohm
nrf51288 ADC = crap. Serial.begin has to be called for accurate readings
ADC output has to be adjusted for gain and offset constants
nrf analogRead = ~70 uS
Circuit:
-------
AC 9V adapter is connected in series with 220ohm and 1N4007 diode and 220 uF filter capacitor.
AMS1117 3.3V regulator feeds off this DC input and generates a 3.3V Vcc
A voltage divider is created from Vcc and Gnd and adapter input is connected to it through 15K resistor
A germanium diode prevents reverse biasing of this junction. This creates a DC offset input AC voltage which is read at A3
Another voltage divider from Vcc and Gnd is stabilized with a 100 uF cap and one end of both CT is connected to this junction
Other end of CT is connected to A1 and A2. This creates DC offset AC current waveforms
*/
#define CT_1_IN_PIN P0_2 // CT1 input connected here, A1 = P0_2
#define CT_2_IN_PIN P0_3 // CT1 input connected here, A2 = P0_3
#define VOLTAGE_PIN P0_4 // AC adapter lead connected here
#define WAVE_FREQUENCY 60 // Power line = 60 Hz
#define NUMBER_CYCLES_SAMPLE 10 // how long to sample input waveform
#define NUM_ADV_SEND 1 // how many advertisements sent before turning adv off
#define AVG_SIZE 5 // how many reading averaged before Tx
#define ADC_SPEED_US 80
#define VOLTAGE_CURRENT_FACTOR 0.0688 // adjust this multiplier when calibrating
// derived constants
#define WAVE_TIME_PERIOD_US (1000000/WAVE_FREQUENCY)
#define NUM_SAMPLES_WAVE (WAVE_TIME_PERIOD_US/ADC_SPEED_US)
#define NUM_SAMPLES_HALF_WAVE (NUM_SAMPLES_WAVE/2)
#define NUM_SAMPLES_FULL_SAMPLE (NUM_SAMPLES_WAVE * NUMBER_CYCLES_SAMPLE)
#define TOTAL_SAMPLE_TIME_US (WAVE_TIME_PERIOD_US * NUMBER_CYCLES_SAMPLE)
//#define APP_DBG
// Setup debug printing macros.
#ifdef APP_DBG
#define DBG_INIT(...) { Serial.begin(__VA_ARGS__); }
#define DBG_PRINT(...) { Serial.print(__VA_ARGS__); }
#define DBG_PRINTLN(...) { Serial.println(__VA_ARGS__); }
#else
#define DBG_INIT(...) {}
#define DBG_PRINT(...) {}
#define DBG_PRINTLN(...) {}
#endif
#include <BLE_API.h>
BLE ble;
boolean advDone = false;
uint8_t numAdvSent;
uint8_t name[10];
// ADC built in constants
int8_t offsetError;
int8_t gainError;
uint16_t adcLookup[1024];
uint16_t runAvg[AVG_SIZE];
uint8_t counter = 0;
// waveforms are sampled and stored here before processing to improve sampling speed
int16_t voltageWave[NUM_SAMPLES_FULL_SAMPLE], currentWave[NUM_SAMPLES_FULL_SAMPLE];
uint16_t vOffset, cOffset;
// ------------------------
void rnCallback(bool radio_active) {
// ------------------------
DBG_PRINT("Rn cb: "); DBG_PRINTLN(radio_active);
// radio messes analog reading sometimes, so synchronize with radio
if(radio_active)
numAdvSent++;
if(!radio_active && (numAdvSent == NUM_ADV_SEND))
advDone = true;
}
// ------------------------
void initWDT(int seconds) {
// ------------------------
NRF_WDT->CONFIG = (WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos);
NRF_WDT->CRV = seconds * 32768; // 32k tick
NRF_WDT->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos;
NRF_WDT->TASKS_START = 1;
}
// ------------------------
void kickWDT() {
// ------------------------
NRF_WDT->RR[0] = WDT_RR_RR_Reload;
}
// ------------------------
void setup() {
// ------------------------
Serial.begin(250000); // ??? The reading from ADC is incorrect if Serial not begin ??
DBG_PRINTLN("Power meter start");
ble.init();
// set adv_type
ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
// set tx power,valid values are -40, -20, -16, -12, -8, -4, 0, 4
ble.setTxPower(4);
// set adv_interval to 100ms
ble.setAdvertisingInterval(100);
// set adv_timeout, in seconds
ble.setAdvertisingTimeout(0);
// hookup for radio events
ble.gap().onRadioNotification(rnCallback);
ble.gap().initRadioNotification();
// initialize the ADC module
// Ref = 1.2V internal without prescaling; input = 1/3 prescaling
analogReference(REFSEL_VBG, INPSEL_AIN_1_3_PS);
// read the built in gain and offset constants
uint32_t ficr_value_32 = *(uint32_t*) 0x10000024;
offsetError = ficr_value_32;
gainError = ficr_value_32 >> 8;
DBG_PRINT("ADC OFFSET: "); DBG_PRINT(offsetError); DBG_PRINT(", GAIN: "); DBG_PRINTLN(gainError);
// build a lookup table for converting readings instead of using floating point calculation at run time
for(int i = 0; i < 1024; i++) {
int16_t corrected = i * (1024 + gainError)/1024 + offsetError - 0.5;
if(corrected < 0)
adcLookup[i] = 0;
else
adcLookup[i] = corrected;
}
// force the 0th reading to 0
adcLookup[0] = 0;
// get DC offset for voltage and current
vOffset = getDCOffset(VOLTAGE_PIN);
cOffset = getDCOffset(CT_1_IN_PIN);
DBG_PRINTLN("Starting Advertising");
// manually advertise first time
ble.startAdvertising();
// start the watchdog timer
initWDT(5);
}
// ------------------------
void loop() {
// ------------------------
ble.waitForEvent();
// WFE is triggered after every radio notification callback
if(advDone) {
DBG_PRINTLN("Adv done");
advDone = false;
ble.stopAdvertising();
numAdvSent = 0;
// 170 ms approx
accumulateCyclePower();
// update payload once all cycles accumulated
if(!counter) {
updateAdvPayload();
kickWDT();
}
// advertise again
ble.startAdvertising();
}
}
// ------------------------
void updateAdvPayload() {
// ------------------------
uint32_t avgWatts = 0;
for(int i = 0; i < AVG_SIZE; i++)
avgWatts += runAvg[i];
avgWatts = avgWatts/AVG_SIZE;
// convert data it into String
sprintf((char*) name, "W: %d", avgWatts);
ble.clearAdvertisingPayload();
// setup adv_data and srp_data
ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
ble.accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, name, sizeof(name) - 1);
}
// ------------------------
void accumulateCyclePower() {
// ------------------------
DBG_PRINTLN("Getting cycle power");
// accumulate the power for averaging
runAvg[counter] = getCyclePower(CT_1_IN_PIN);
runAvg[counter] += getCyclePower(CT_2_IN_PIN);
counter++;
if(counter == AVG_SIZE)
counter = 0;
}
// ------------------------
uint16_t getCyclePower(int ctPin) {
// ------------------------
// Measuring the instantaneous power of waveform
int idx = 0;
uint32_t tStart = micros();
uint32_t tEnd = tStart + TOTAL_SAMPLE_TIME_US;
// will microseconds overflow from 32 bit counter
if(tEnd < tStart) {
delayMicroseconds(TOTAL_SAMPLE_TIME_US * 2);
tStart = micros();
tEnd = tStart + TOTAL_SAMPLE_TIME_US;
}
// start the full wave multiple cycle sampling
// this loop takes 101 samples of both voltage and current per wave cycle
// 1.6416 degree lag from V -> I measurement can be ignored
while(micros() < tEnd) {
voltageWave[idx] = readAnalog(VOLTAGE_PIN) - vOffset;
currentWave[idx] = readAnalog(ctPin) - cOffset;
idx++;
}
// debug captured data
#ifdef APP_DBG
if(!counter) {
for(int i = 0; i < idx; i++) {
DBG_PRINT(voltageWave[i]);
DBG_PRINT("\t");
DBG_PRINTLN(currentWave[i]);
}
}
#endif
// calculate power = average of sum of instantaneous power
int32_t sumIPower = 0;
int32_t Vsum = 0;
int32_t Csum = 0;
for(int i = 0; i < idx; i++) {
sumIPower += voltageWave[i] * currentWave[i];
// if old offset is correct, then sum should be zero
Vsum += voltageWave[i];
Csum += currentWave[i];
}
// average the sum of instantaneous power and multiply it by factor of voltage and current to get real power
// Voltage: using adapter and potential divider: 423.5 units = 169.70 V => 0.4007 V/reading
// Current: using 50A/1V CT: 1 unit = 3.6/1024 = 3.515 mV => 0.17578125 A/reading
int32_t realPower = (sumIPower * VOLTAGE_CURRENT_FACTOR)/ idx;
vOffset += Vsum / idx;
cOffset += Csum / idx;
// voltage is sensed in one phase only. Other CT will produce negative power
if(realPower < 0)
realPower = -realPower;
DBG_PRINT(counter);DBG_PRINT(":");DBG_PRINTLN(realPower);
return realPower;
}
// ------------------------
uint16_t getDCOffset(int pin) {
// ------------------------
uint32_t sumVal = 0;
long tStart = micros();
long tEnd = tStart + TOTAL_SAMPLE_TIME_US * 2;
int idx = 0;
while(micros() < tEnd) {
sumVal += readAnalog(pin);
idx++;
}
// return the average of values
return sumVal/idx;
}
// ------------------------
uint16_t readAnalog(int ctPin) {
// ------------------------
// read the analog input
uint16_t x = analogRead(ctPin);
return adcLookup[x];
}