1-phase PV router
Loading...
Searching...
No Matches
processing.cpp
Go to the documentation of this file.
1
2#include <Arduino.h>
3
4#include "calibration.h"
5#include "dualtariff.h"
6#include "processing.h"
7#include "utils_pins.h"
8#include "utils_display.h"
9
10// allocation of analogue pins which are not dependent on the display type that is in use
11// **************************************************************************************
12inline constexpr uint8_t voltageSensor{ OLD_PCB ? 3 : 0 };
13inline constexpr uint8_t currentSensor_grid{ OLD_PCB ? 5 : 1 };
14inline constexpr uint8_t currentSensor_diverted{ OLD_PCB ? 4 : 3 };
15// ------------------------------------------
16
18
19// For an enhanced polarity detection mechanism, which includes a persistence check
20inline constexpr uint8_t PERSISTENCE_FOR_POLARITY_CHANGE{ 1 };
21
22inline constexpr uint8_t POST_ZERO_CROSSING_MAX_COUNT{ 3 };
23
24// Define operating limits for the LP filters which identify DC offset in the voltage
25// sample streams. By limiting the output range, these filters always should start up
26// correctly.
27int32_t DCoffset_V_long{ 512L * 256 };
28constexpr int32_t DCoffset_V_min{ (512L - 100) * 256 };
29constexpr int32_t DCoffset_V_max{ (512L + 100) * 256 };
30
31constexpr int16_t DCoffset_I{ 512 };
32
33constexpr int32_t capacityOfEnergyBucket_long{ static_cast< int32_t >(WORKING_ZONE_IN_JOULES * SUPPLY_FREQUENCY * (1.0f / powerCal_grid)) };
34
36
39
40constexpr int32_t antiCreepLimit_inIEUperMainsCycle{ static_cast< int32_t >(ANTI_CREEP_LIMIT * (1.0f / powerCal_diverted)) };
41constexpr int32_t requiredExportPerMainsCycle_inIEU{ static_cast< int32_t >(REQUIRED_EXPORT_IN_WATTS * (1.0f / powerCal_grid)) };
42// When using integer maths, calibration values that have supplied in floating point
43// form need to be rescaled.
44
45// When using integer maths, the SIZE of the ENERGY BUCKET is altered to match the
46// scaling of the energy detection mechanism that is in use. This avoids the need
47// to re-scale every energy contribution, thus saving processing time. This process
48// is described in more detail in the function, allGeneralProcessing(), just before
49// the energy bucket is updated at the start of each new cycle of the mains.
50//
51// An electricity meter has a small range over which energy can ebb and flow without
52// penalty. This has been termed its "sweet-zone". For optimal performance, the energy
53// bucket of a PV Router should match this value. The sweet-zone value is therefore
54// included in the calculation below.
55//
56int32_t energyInBucket_long{ 0 };
59
62
63// For recording the accumulated amount of diverted energy data (using CT2), a similar
64// calibration mechanism is required. Rather than a bucket with a fixed capacity, the
65// accumulator for diverted energy just needs to be scaled correctly. As soon as its
66// value exceeds 1 Wh, an associated WattHour register is incremented, and the
67// accumulator's value is decremented accordingly. The calculation below is to determine
68// the scaling for this accumulator.
69
70constexpr int32_t IEU_per_Wh_diverted{ static_cast< int32_t >(JOULES_PER_WATT_HOUR * SUPPLY_FREQUENCY * (1.0f / powerCal_diverted)) }; // depends on powerCal, frequency & the 'sweetzone' size.
71
72bool recentTransition{ false };
73uint8_t postTransitionCount{ 0 };
74constexpr uint8_t POST_TRANSITION_MAX_COUNT{ 3 };
76
77int32_t sumP_grid{ 0 };
79int32_t sumP_diverted{ 0 };
82int32_t l_sum_Vsquared{ 0 };
83
84int32_t realEnergy_grid{ 0 };
85int32_t realEnergy_diverted{ 0 };
87int32_t sampleVminusDC_long{ 0 };
88
92
93// For a mechanism to check the continuity of the sampling sequence
96
98
100
103
105
107
108uint8_t perSecondCounter{ 0 };
109
110bool beyondStartUpPeriod{ false };
111
126constexpr uint16_t getOutputPins()
127{
128 uint16_t output_pins{ 0 };
129
130 for (const auto &loadPin : physicalLoadPin)
131 {
132 if (bit_read(output_pins, loadPin))
133 return 0;
134
135 bit_set(output_pins, loadPin);
136 }
137
138 if constexpr (WATCHDOG_PIN_PRESENT)
139 {
140 if (bit_read(output_pins, watchDogPin))
141 return 0;
142
143 bit_set(output_pins, watchDogPin);
144 }
145
146 if constexpr (RELAY_DIVERSION)
147 {
148 for (uint8_t idx = 0; idx < relays.get_size(); ++idx)
149 {
150 const auto relayPin = relays.get_relay(idx).get_pin();
151
152 if (bit_read(output_pins, relayPin))
153 return 0;
154
155 bit_set(output_pins, relayPin);
156 }
157 }
158
159 return output_pins;
160}
161
177constexpr uint16_t getInputPins()
178{
179 uint16_t input_pins{ 0 };
180
181 if constexpr (DUAL_TARIFF)
182 {
183 if (bit_read(input_pins, dualTariffPin))
184 return 0;
185
186 bit_set(input_pins, dualTariffPin);
187 }
188
189 if constexpr (DIVERSION_PIN_PRESENT)
190 {
191 if (bit_read(input_pins, diversionPin))
192 return 0;
193
194 bit_set(input_pins, diversionPin);
195 }
196
197 if constexpr (PRIORITY_ROTATION == RotationModes::PIN)
198 {
199 if (bit_read(input_pins, rotationPin))
200 return 0;
201
202 bit_set(input_pins, rotationPin);
203 }
204
205 if constexpr (OVERRIDE_PIN_PRESENT)
206 {
207 if (bit_read(input_pins, forcePin))
208 return 0;
209
210 bit_set(input_pins, forcePin);
211 }
212
213 return input_pins;
214}
215
230{
231 if constexpr (OLD_PCB)
233 else
234 {
235 setPinsAsOutput(getOutputPins()); // set the output pins as OUTPUT
236 setPinsAsInputPullup(getInputPins()); // set the input pins as INPUT_PULLUP
237
238 for (uint8_t i = 0; i < NO_OF_DUMPLOADS; ++i)
239 {
242 }
243 }
244
245 // First stop the ADC
246 bit_clear(ADCSRA, ADEN);
247
248 // Activate free-running mode
249 ADCSRB = 0x00;
250
251 // Set up the ADC to be free-running
252 bit_set(ADCSRA, ADPS0); // Set the ADC's clock to system clock / 128
253 bit_set(ADCSRA, ADPS1);
254 bit_set(ADCSRA, ADPS2);
255
256 bit_set(ADCSRA, ADATE); // set the Auto Trigger Enable bit in the ADCSRA register. Because
257 // bits ADTS0-2 have not been set (i.e. they are all zero), the
258 // ADC's trigger source is set to "free running mode".
259
260 bit_set(ADCSRA, ADIE); // set the ADC interrupt enable bit. When this bit is written
261 // to one and the I-bit in SREG is set, the
262 // ADC Conversion Complete Interrupt is activated.
263
264 bit_set(ADCSRA, ADEN); // Enable the ADC
265
266 bit_set(ADCSRA, ADSC); // start ADC manually first time
267
268 sei(); // Enable Global Interrupts
269}
270
286{
287 uint16_t pinsON{ 0 };
288 uint16_t pinsOFF{ 0 };
289
290 uint8_t i{ NO_OF_DUMPLOADS };
291
292 do
293 {
294 --i;
295 // update the local load's state.
297 {
298 // setPinOFF(physicalLoadPin[i]);
299 pinsOFF |= bit(physicalLoadPin[i]);
300 }
301 else
302 {
303 ++countLoadON[i];
304 // setPinON(physicalLoadPin[i]);
305 pinsON |= bit(physicalLoadPin[i]);
306 }
307 } while (i);
308
309 setPinsOFF(pinsOFF);
310 setPinsON(pinsON);
311}
312
335{
336 if constexpr (PRIORITY_ROTATION != RotationModes::OFF)
337 {
339 {
340 uint8_t i{ NO_OF_DUMPLOADS - 1 };
341 const auto temp{ loadPrioritiesAndState[i] };
342 do
343 {
345 --i;
346 } while (i);
347 loadPrioritiesAndState[0] = temp;
348
350 }
351 }
352
353 const bool bDiversionEnabled{ Shared::b_diversionEnabled };
354 uint8_t idx{ NO_OF_DUMPLOADS };
355 do
356 {
357 --idx;
358 const auto iLoad{ loadPrioritiesAndState[idx] & loadStateMask };
360 } while (idx);
361}
362
379void processPolarity(const int16_t rawSample)
380{
381 // remove DC offset from the raw voltage sample by subtracting the accurate value
382 // as determined by a LP filter.
383 sampleVminusDC_long = ((long)rawSample << 8) - DCoffset_V_long;
384 // determine the polarity of the latest voltage sample
386}
387
406void processGridCurrentRawSample(const int16_t rawSample)
407{
408 // extra items for an LPF to improve the processing of data samples from CT1
409 static int32_t lpf_long{}; // new LPF, for offsetting the behaviour of CTx as a HPF
410
411 // First, deal with the power at the grid connection point (as measured via CT1)
412 // remove most of the DC offset from the current sample (the precise value does not matter)
413 int32_t sampleIminusDC_grid = ((int32_t)(rawSample - DCoffset_I)) << 8;
414 //
415 // extra filtering to offset the HPF effect of CT1
416 const int32_t last_lpf_long = lpf_long;
417 lpf_long += alpha * (sampleIminusDC_grid - last_lpf_long);
418 sampleIminusDC_grid += (lpf_gain * lpf_long);
419
420 // calculate the "real power" in this sample pair and add to the accumulated sum
421 const int32_t filtV_div4 = sampleVminusDC_long >> 2; // reduce to 16-bits (now x64, or 2^6)
422 const int32_t filtI_div4 = sampleIminusDC_grid >> 2; // reduce to 16-bits (now x64, or 2^6)
423 int32_t instP = filtV_div4 * filtI_div4; // 32-bits (now x4096, or 2^12)
424 instP >>= 12; // scaling is now x1, as for Mk2 (V_ADC x I_ADC)
425 sumP_grid += instP; // cumulative power, scaling as for Mk2 (V_ADC x I_ADC)
427}
428
447void processDivertedCurrentRawSample(const int16_t rawSample)
448{
450 {
451 return; // no diverted power when the load is overridden
452 }
453
454 // Now deal with the diverted power (as measured via CT2)
455 // remove most of the DC offset from the current sample (the precise value does not matter)
456 int32_t sampleIminusDC_diverted = ((int32_t)(rawSample - DCoffset_I)) << 8;
457
458 // calculate the "real power" in this sample pair and add to the accumulated sum
459 const int32_t filtV_div4 = sampleVminusDC_long >> 2; // reduce to 16-bits (now x64, or 2^6)
460 const int32_t filtI_div4 = sampleIminusDC_diverted >> 2; // reduce to 16-bits (now x64, or 2^6)
461 int32_t instP = filtV_div4 * filtI_div4; // 32-bits (now x4096, or 2^12)
462 instP >>= 12; // scaling is now x1, as for Mk2 (V_ADC x I_ADC)
463 sumP_diverted += instP; // cumulative power, scaling as for Mk2 (V_ADC x I_ADC)
465}
466
484{
485 /* This routine prevents a zero-crossing point from being declared until
486 * a certain number of consecutive samples in the 'other' half of the
487 * waveform have been encountered.
488 */
489 static uint8_t count{ 0 };
491 {
492 count = 0;
493 return;
494 }
495
497 {
498 count = 0;
500 }
501}
502
521{
523 {
525 {
526 // This is the start of a new +ve half cycle (just after the zero-crossing point)
528 {
530
532 }
533 else
534 {
536 }
537 } // end of processing that is specific to the first Vsample in each +ve half cycle
538
539 // still processing samples where the voltage is Polarities::POSITIVE ...
540 // (in this go-faster code, the action from here has moved to the negative half of the cycle)
541
542 } // end of processing that is specific to samples where the voltage is positive
543 else // the polarity of this sample is negative
544 {
546 {
547 // This is the start of a new -ve half cycle (just after the zero-crossing point)
549 }
550
551 // check to see whether the trigger device can now be reliably armed
553 {
555 {
556 /* Determining whether any of the loads need to be changed is is a 3-stage process:
557 * - change the LOGICAL load states as necessary to maintain the energy level
558 * - update the PHYSICAL load states according to the logical -> physical mapping
559 * - update the driver lines for each of the loads.
560 */
561
562 // Restrictions apply for the period immediately after a load has been switched.
563 // Here the recentTransition flag is checked and updated as necessary.
565 {
567 {
568 recentTransition = false;
569 }
570 }
571
573 {
574 // the energy state is in the upper half of the working range
575 lowerEnergyThreshold = lowerThreshold_default; // reset the "opposite" threshold
577 {
578 // Because the energy level is high, some action may be required
580 }
581 }
582 else
583 { // the energy state is in the lower half of the working range
584 upperEnergyThreshold = upperThreshold_default; // reset the "opposite" threshold
586 {
587 // Because the energy level is low, some action may be required
589 }
590 }
591
592 updatePhysicalLoadStates(); // allows the logical-to-physical mapping to be changed
593
594 // update each of the physical loads
596
597 // update the Energy Diversion Detector
599 {
602 }
603 else
604 {
606 }
607
608 // Now that the energy-related decisions have been taken, min and max limits can now
609 // be applied to the level of the energy bucket. This is to ensure correct operation
610 // when conditions change, i.e. when import changes to export, and vice versa.
611 //
613 {
615 }
616 else if (energyInBucket_long < 0)
617 {
619 }
620 }
621 }
622
624 } // end of processing that is specific to samples where the voltage is negative
626}
627
644{
645 long filtV_div4 = sampleVminusDC_long >> 2; // reduce to 16-bits (now x64, or 2^6)
646 int32_t inst_Vsquared{ filtV_div4 * filtV_div4 }; // 32-bits (now x4096, or 2^12)
647
648 inst_Vsquared >>= 12; // scaling is now x1 (V_ADC x I_ADC)
649
650 l_sum_Vsquared += inst_Vsquared; // cumulative V^2 (V_ADC x I_ADC)
651
652 // store items for use during next loop
653 cumVdeltasThisCycle_long += sampleVminusDC_long; // for use with LP filter
654 polarityConfirmedOfLastSampleV = polarityConfirmed; // for identification of half cycle boundaries
655}
656
673void processVoltageRawSample(const int16_t rawSample)
674{
675 processPolarity(rawSample);
677
678 processRawSamples(); // deals with aspects that only occur at particular stages of each mains cycle
679
680 // processing for EVERY set of samples
681 //
683
686}
687
705{
706 // wait until the DC-blocking filters have had time to settle
707 if (millis() <= (delayBeforeSerialStarts + startUpPeriod))
708 {
709 return;
710 }
711
712 beyondStartUpPeriod = true;
713 sumP_grid = 0;
715 sumP_diverted = 0;
717 sampleSetsDuringThisMainsCycle = 0; // not yet dealt with for this cycle
719 // can't say "Go!" here 'cos we're in an ISR!
720}
721
737{
738 // Apply max and min limits to bucket's level. This is to ensure correct operation
739 // when conditions change, i.e. when import changes to export, and vice versa.
740 //
742 {
744 }
745 else if (energyInBucket_long < 0)
746 {
748 }
749
750 // clear the per-cycle accumulators for use in this new mains cycle.
752 sumP_grid = 0;
753 sumP_diverted = 0;
755}
756
773{
774 // a simple routine for checking the performance of this new ISR structure
776 {
778 }
779
781
783}
784
802{
803 // This is the start of a new -ve half cycle (just after the zero-crossing point)
804 // which is a convenient point to update the Low Pass Filter for DC-offset removal
805 // The portion which is fed back into the integrator is approximately one percent
806 // of the average offset of all the Vsamples in the previous mains cycle.
807 //
808 const auto previousOffset{ DCoffset_V_long };
809 DCoffset_V_long = previousOffset + (cumVdeltasThisCycle_long >> 12);
811
812 // To ensure that the LPF will always start up correctly when 240V AC is available, its
813 // output value needs to be prevented from drifting beyond the likely range of the
814 // voltage signal. This avoids the need to use a HPF as was done for initial Mk2 builds.
815 //
817 {
819 }
821 {
823 }
824
825 // The average power that has been measured during the first half of this mains cycle can now be used
826 // to predict the energy state at the end of this mains cycle. That prediction will be used to alter
827 // the state of the load as necessary. The arming signal for the triac can't be set yet - that must
828 // wait until the voltage has advanced further beyond the -ve going zero-crossing point.
829 //
830 long averagePower = sumP_grid / sampleSetsDuringThisMainsCycle; // for 1st half of this mains cycle only
831 //
832 // To avoid repetitive and unnecessary calculations, the increase in energy during each mains cycle is
833 // deemed to be numerically equal to the average power. The predicted value for the energy state at the
834 // end of this mains cycle will therefore be the known energy state at its start plus the average power
835 // as measured. Although the average power has been determined over only half a mains cycle, the correct
836 // number of contributing sample sets has been used so the result can be expected to be a true measurement
837 // of average power, not half of it.
838 //
839 energyInBucket_prediction = energyInBucket_long + averagePower; // at end of this mains cycle
840}
841
857{
858 // Calculate the real power and energy during the last whole mains cycle.
859 //
860 // sumP contains the sum of many individual calculations of instantaneous power. In
861 // order to obtain the average power during the relevant period, sumP must first be
862 // divided by the number of samples that have contributed to its value.
863 //
864 // The next stage would normally be to apply a calibration factor so that real power
865 // can be expressed in Watts. That's fine for floating point maths, but it's not such
866 // a good idea when integer maths is being used. To keep the numbers large, and also
867 // to save time, calibration of power is omitted at this stage. Real Power (stored as
868 // a 'long') is therefore (1/powerCal) times larger than the actual power in Watts.
869 //
870 int32_t realPower_grid = sumP_grid / sampleSetsDuringThisMainsCycle; // proportional to Watts
871 int32_t realPower_diverted = sumP_diverted / sampleSetsDuringThisMainsCycle; // proportional to Watts
872
873 realPower_grid -= requiredExportPerMainsCycle_inIEU; // <- useful for PV simulation
874
875 // Next, the energy content of this power rating needs to be determined. Energy is
876 // power multiplied by time, so the next step would normally be to multiply the measured
877 // value of power by the time over which it was measured.
878 // Instantaneous power is calculated once every mains cycle. When integer maths is
879 // being used, a repetitive power-to-energy conversion seems an unnecessary workload.
880 // As all sampling periods are of similar duration, it is more efficient to just
881 // add all of the power samples together, and note that their sum is actually
882 // SUPPLY_FREQUENCY greater than it would otherwise be.
883 // Although the numerical value itself does not change, I thought that a new name
884 // may be helpful so as to minimise confusion.
885 // The 'energy' variable below is SUPPLY_FREQUENCY * (1/powerCal) times larger than
886 // the actual energy in Joules.
887 //
888 realEnergy_grid = realPower_grid;
889 realEnergy_diverted = realPower_diverted;
890
891 // Energy contributions from the grid connection point (CT1) are summed in an
892 // accumulator which is known as the energy bucket. The purpose of the energy bucket
893 // is to mimic the operation of the supply meter. The range over which energy can
894 // pass to and fro without loss or charge to the user is known as its 'sweet-zone'.
895 // The capacity of the energy bucket is set to this same value within setup().
896 //
897 // The latest contribution can now be added to this energy bucket
899
900 if (Shared::EDD_isActive) // Energy Diversion Display
901 {
902 // For diverted energy, the latest contribution needs to be added to an
903 // accumulator which operates with maximum precision.
904
906 {
908 }
910
911 // Whole kWh are then recorded separately
913 {
916 }
917 }
918
919 // After a pre-defined period of inactivity, the 4-digit display needs to
920 // close down in readiness for the next's day's data.
921 //
923 {
924 // clear the accumulators for diverted energy
927 Shared::EDD_isActive = false; // energy diversion detector is now inactive
928 }
929
931 {
933
936 else
938
939 // The diverted energy total is copied to a variable before it is used.
940 // This is done to avoid the possibility of a race-condition whereby the
941 // diverted energy total is updated while the display is being updated.
943 }
944
945 Shared::b_newCycle = true; // a 50 Hz 'tick' for use by the main code
946}
947
964{
965 bool bOK_toAddLoad{ true };
966 const auto tempLoad{ nextLogicalLoadToBeAdded() };
967
968 if (tempLoad == NO_OF_DUMPLOADS)
969 {
970 return;
971 }
972
973 // a load which is now OFF has been identified for potentially being switched ON
975 {
976 // During the post-transition period, any increase in the energy level is noted.
978
979 // the energy thresholds must remain within range
981 {
983 }
984
985 // Only the active load may be switched during this period. All other loads must
986 // wait until the recent transition has had sufficient opportunity to take effect.
987 bOK_toAddLoad = (tempLoad == activeLoad);
988 }
989
990 if (bOK_toAddLoad)
991 {
993 activeLoad = tempLoad;
995 recentTransition = true;
996 }
997}
998
1016{
1017 bool bOK_toRemoveLoad{ true };
1018 const auto tempLoad{ nextLogicalLoadToBeRemoved() };
1019
1020 if (tempLoad >= NO_OF_DUMPLOADS)
1021 {
1022 return;
1023 }
1024
1025 // a load which is now ON has been identified for potentially being switched OFF
1026 if (recentTransition)
1027 {
1028 // During the post-transition period, any decrease in the energy level is noted.
1030
1031 // the energy thresholds must remain within range
1032 if (lowerEnergyThreshold < 0)
1033 {
1035 }
1036
1037 // Only the active load may be switched during this period. All other loads must
1038 // wait until the recent transition has had sufficient opportunity to take effect.
1039 bOK_toRemoveLoad = (tempLoad == activeLoad);
1040 }
1041
1042 if (bOK_toRemoveLoad)
1043 {
1045 activeLoad = tempLoad;
1047 recentTransition = true;
1048 }
1049}
1050
1069{
1070 for (uint8_t index = 0; index < NO_OF_DUMPLOADS; ++index)
1071 {
1072 if (!(loadPrioritiesAndState[index] & loadStateOnBit))
1073 {
1074 return (index);
1075 }
1076 }
1077
1078 return (NO_OF_DUMPLOADS);
1079}
1080
1096{
1097 uint8_t index{ NO_OF_DUMPLOADS };
1098
1099 do
1100 {
1102 {
1103 return (index);
1104 }
1105 } while (index);
1106
1107 return (NO_OF_DUMPLOADS);
1108}
1109
1125{
1127 {
1128 return; // data logging period not yet reached
1129 }
1130
1132
1135
1138
1140
1142 l_sum_Vsquared = 0;
1143
1144 uint8_t i{ NO_OF_DUMPLOADS };
1145 do
1146 {
1147 --i;
1149 countLoadON[i] = 0;
1150 } while (i);
1151
1155
1158
1159 // signal the main processor that logging data are available
1160 // we skip the period from start to running stable
1162}
1163
1177{
1178 DBUG("\tzero-crossing persistence (sample sets) = ");
1180
1181 DBUG("\tcapacityOfEnergyBucket_long = ");
1183}
1184
1221ISR(ADC_vect)
1222{
1223 static uint8_t sample_index{ 0 };
1224 int16_t rawSample;
1225
1226 switch (sample_index)
1227 {
1228 case 0:
1229 rawSample = ADC; // store the ADC value (this one is for Voltage)
1230 //sampleV = ADC; // store the ADC value (this one is for Voltage)
1231 ADMUX = bit(REFS0) + currentSensor_diverted; // set up the next conversion, which is for Diverted Current
1232 ++sample_index; // increment the control flag
1233 //
1234 processVoltageRawSample(rawSample);
1235 break;
1236 case 1:
1237 rawSample = ADC; // store the ADC value (this one is for current at CT1)
1238 //sampleI_diverted_raw = ADC; // store the ADC value (this one is for Diverted Current)
1239 ADMUX = bit(REFS0) + voltageSensor; // set up the next conversion, which is for Grid Current
1240 ++sample_index; // increment the control flag
1241 //
1242 processGridCurrentRawSample(rawSample);
1243 break;
1244 case 2:
1245 rawSample = ADC; // store the ADC value (this one is for current at CT2)
1246 //sampleI_grid_raw = ADC; // store the ADC value (this one is for Grid Current)
1247 ADMUX = bit(REFS0) + currentSensor_grid; // set up the next conversion, which is for Voltage
1248 sample_index = 0; // reset the control flag
1249 //
1251 break;
1252 default:
1253 sample_index = 0; // to prevent lockup (should never get here)
1254 }
1255}
1256
1278{
1279 for (int16_t i = 0; i < NO_OF_DUMPLOADS; ++i)
1280 {
1282 pinMode(physicalLoadPin[i], OUTPUT); // driver pin for Load #n
1284 }
1285
1286 updatePhysicalLoadStates(); // allows the logical-to-physical mapping to be changed
1287
1288 updatePortsStates(); // updates output pin states
1289
1290 if constexpr (DUAL_TARIFF)
1291 {
1292 pinMode(dualTariffPin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1293 delay(100); // allow time to settle
1294 }
1295
1296 if constexpr (OVERRIDE_PIN_PRESENT)
1297 {
1298 pinMode(forcePin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1299 delay(100); // allow time to settle
1300 }
1301
1302 if constexpr (PRIORITY_ROTATION == RotationModes::PIN)
1303 {
1304 pinMode(rotationPin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1305 delay(100); // allow time to settle
1306 }
1307
1308 if constexpr (DIVERSION_PIN_PRESENT)
1309 {
1310 pinMode(diversionPin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1311 delay(100); // allow time to settle
1312 }
1313
1314 if constexpr (RELAY_DIVERSION)
1315 {
1316 for (uint8_t idx = 0; idx < relays.get_size(); ++idx)
1317 {
1318 const auto relayPin = relays.get_relay(idx).get_pin();
1319
1320 pinMode(relayPin, OUTPUT);
1321 setPinOFF(relayPin); // set to off
1322 }
1323 }
1324
1325 if constexpr (WATCHDOG_PIN_PRESENT)
1326 {
1327 pinMode(watchDogPin, OUTPUT); // set as output
1328 setPinOFF(watchDogPin); // set to off
1329 }
1330}
1331
1346{
1347#ifdef ENABLE_DEBUG
1348
1349 DBUGLN(F("Load Priorities: "));
1350 for (const auto &loadPrioAndState : loadPrioritiesAndState)
1351 {
1352 DBUG(F("\tload "));
1353 DBUGLN(loadPrioAndState);
1354 }
1355
1356#endif
1357}
1358
1359static_assert(IEU_per_Wh_diverted > 4000000, "IEU_per_Wh_diverted calculation is incorrect");
Calibration values definition.
constexpr float alpha
Definition calibration.h:40
constexpr float lpf_gain
Definition calibration.h:39
constexpr float powerCal_grid
Definition calibration.h:34
constexpr float powerCal_diverted
Definition calibration.h:35
constexpr uint8_t loadPrioritiesAtStartup[NO_OF_DUMPLOADS]
Definition config.h:69
constexpr bool RELAY_DIVERSION
Definition config.h:43
constexpr bool OVERRIDE_PIN_PRESENT
Definition config.h:40
constexpr uint8_t dualTariffPin
Definition config.h:73
constexpr uint8_t forcePin
Definition config.h:76
constexpr RelayEngine relays
Definition config.h:79
constexpr uint8_t physicalLoadPin[NO_OF_DUMPLOADS]
Definition config.h:68
constexpr bool DUAL_TARIFF
Definition config.h:44
constexpr uint8_t diversionPin
Definition config.h:74
constexpr bool OLD_PCB
Definition config.h:49
constexpr uint8_t NO_OF_DUMPLOADS
Definition config.h:35
constexpr bool WATCHDOG_PIN_PRESENT
Definition config.h:42
constexpr bool DIVERSION_PIN_PRESENT
Definition config.h:38
constexpr uint8_t rotationPin
Definition config.h:75
constexpr uint8_t watchDogPin
Definition config.h:77
constexpr uint16_t startUpPeriod
constexpr int16_t REQUIRED_EXPORT_IN_WATTS
constexpr uint32_t WORKING_ZONE_IN_JOULES
constexpr conditional< DATALOG_PERIOD_IN_SECONDS *SUPPLY_FREQUENCY >=UINT8_MAX, uint16_t, uint8_t >::type DATALOG_PERIOD_IN_MAINS_CYCLES
constexpr uint32_t JOULES_PER_WATT_HOUR
constexpr uint8_t ANTI_CREEP_LIMIT
constexpr uint16_t delayBeforeSerialStarts
constexpr uint8_t SUPPLY_FREQUENCY
#define DBUGLN(...)
Definition debug.h:97
#define DBUG(...)
Definition debug.h:96
Classes/types needed for dual-tariff support.
void logLoadPriorities()
Prints the load priorities to the Serial output.
constexpr uint16_t getInputPins()
Retrieves the input pins configuration.
constexpr uint16_t getOutputPins()
Retrieves the output pins configuration.
void initializeProcessing()
Initializes the processing engine, including ports, load states, and ADC setup.
void initializeOldPCBPins()
Initializes optional pins for the old PCB configuration.
void refresh7SegDisplay()
Refreshes the display by updating the active digit and its segments.
void processGridCurrentRawSample(const int16_t rawSample)
Processes the raw current sample for the grid connection point.
ISR(ADC_vect)
Interrupt Service Routine - Interrupt-Driven Analog Conversion.
void confirmPolarity()
Confirms the polarity of the current voltage sample.
void processRawSamples()
This routine is called by the ISR when a pair of V & I samples becomes available.
void updatePhysicalLoadStates()
This function provides the link between the logical and physical loads.
void processDivertedCurrentRawSample(const int16_t rawSample)
Processes the raw current sample for the diverted connection point.
void updatePortsStates()
Updates the control ports for each of the physical loads.
void processMinusHalfCycle()
Process the start of a new -ve half cycle, just after the zero-crossing point.
void processStartNewCycle()
This code is executed once per 20ms, shortly after the start of each new mains cycle on phase 0.
void processPolarity(const int16_t rawSample)
Processes the polarity of the current voltage sample.
void proceedLowEnergyLevel()
Process the case of low energy level, some action may be required.
uint8_t nextLogicalLoadToBeAdded()
Retrieve the next load that could be removed (be aware of the reverse-order).
void processVoltage()
Process the calculation for the current voltage sample.
void processDataLogging()
Process with data logging.
void processPlusHalfCycle()
Process the start of a new +ve half cycle, just after the zero-crossing point.
uint8_t nextLogicalLoadToBeRemoved()
Process with data logging.
void processLatestContribution()
Process the latest contribution after each new cycle.
void processVoltageRawSample(const int16_t rawSample)
Process the current voltage raw sample.
void processStartUp()
Process the startup period for the router.
void proceedHighEnergyLevel()
Process the case of high energy level, some action may be required.
volatile uint16_t copyOf_divertedEnergyTotal_Wh_forDL
Definition shared_var.h:25
volatile bool b_newCycle
Definition shared_var.h:11
volatile int32_t copyOf_energyInBucket_long
Definition shared_var.h:27
volatile int32_t copyOf_sum_Vsquared
Definition shared_var.h:26
volatile int32_t copyOf_sumP_grid_overDL_Period
Definition shared_var.h:22
volatile bool b_datalogEventPending
Definition shared_var.h:10
volatile bool b_overrideLoadOn[NO_OF_DUMPLOADS]
Definition shared_var.h:12
volatile bool b_reOrderLoads
Definition shared_var.h:13
volatile uint16_t copyOf_sampleSetsDuringThisDatalogPeriod
Definition shared_var.h:29
volatile int32_t copyOf_sumP_diverted_overDL_Period
Definition shared_var.h:23
volatile uint8_t copyOf_lowestNoOfSampleSetsPerMainsCycle
Definition shared_var.h:28
volatile uint16_t copyOf_divertedEnergyTotal_Wh
Definition shared_var.h:24
volatile uint16_t copyOf_countLoadON[NO_OF_DUMPLOADS]
Definition shared_var.h:30
volatile bool EDD_isActive
Definition shared_var.h:16
volatile uint16_t absenceOfDivertedEnergyCountInSeconds
Definition shared_var.h:17
volatile bool b_diversionEnabled
Definition shared_var.h:14
constexpr int16_t DCoffset_I
uint32_t absenceOfDivertedEnergyCountInMC
int32_t sumP_grid_overDL_Period
bool recentTransition
constexpr int32_t requiredExportPerMainsCycle_inIEU
int32_t cumVdeltasThisCycle_long
constexpr int32_t IEU_per_Wh_diverted
constexpr uint8_t PERSISTENCE_FOR_POLARITY_CHANGE
constexpr int32_t DCoffset_V_min
constexpr uint8_t currentSensor_grid
uint16_t countLoadON[NO_OF_DUMPLOADS]
int32_t divertedEnergyRecent_IEU
int32_t sampleVminusDC_long
uint8_t loadPrioritiesAndState[NO_OF_DUMPLOADS]
constexpr uint8_t voltageSensor
remove_cv< remove_reference< decltype(DATALOG_PERIOD_IN_MAINS_CYCLES)>::type >::type n_cycleCountForDatalogging
constexpr uint8_t POST_TRANSITION_MAX_COUNT
constexpr int32_t antiCreepLimit_inIEUperMainsCycle
int32_t realEnergy_grid
int32_t energyInBucket_long
LoadStates physicalLoadState[NO_OF_DUMPLOADS]
int32_t sumP_diverted
int32_t sumP_grid
Polarities polarityConfirmedOfLastSampleV
constexpr int32_t upperThreshold_default
constexpr int32_t capacityOfEnergyBucket_long
Polarities polarityConfirmed
int32_t l_sum_Vsquared
constexpr int32_t lowerThreshold_default
int32_t upperEnergyThreshold
uint16_t sampleSetsDuringNegativeHalfOfMainsCycle
void printParamsForSelectedOutputMode()
Print the settings used for the selected output mode.
int32_t DCoffset_V_long
int32_t sumP_diverted_overDL_Period
constexpr int32_t DCoffset_V_max
bool beyondStartUpPeriod
int32_t realEnergy_diverted
int32_t lowerEnergyThreshold
uint8_t lowestNoOfSampleSetsPerMainsCycle
constexpr uint8_t POST_ZERO_CROSSING_MAX_COUNT
uint16_t sampleSetsDuringThisDatalogPeriod
constexpr int32_t midPointOfEnergyBucket_long
uint8_t sampleSetsDuringThisMainsCycle
constexpr uint8_t currentSensor_diverted
uint16_t divertedEnergyTotal_Wh
Polarities polarityOfMostRecentVsample
uint8_t activeLoad
uint8_t perSecondCounter
uint8_t postTransitionCount
int32_t energyInBucket_prediction
Public functions/variables of processing engine.
uint8_t i
Polarities
Definition types.h:34
@ NEGATIVE
Definition types.h:35
@ POSITIVE
Definition types.h:36
constexpr uint8_t loadStateMask
Definition types.h:54
constexpr uint8_t loadStateOnBit
Definition types.h:55
LoadStates
Definition types.h:48
@ LOAD_OFF
Definition types.h:49
@ LOAD_ON
Definition types.h:50
7-segments display functions
constexpr uint32_t displayShutdown_inMainsCycles
Some utility functions for pins manipulation.
constexpr void bit_set(T &_dest, const uint8_t bit)
Set the specified bit to 1.
Definition utils_pins.h:54
void setPinsOFF(uint16_t pins)
Set the Pins state to OFF.
Definition utils_pins.h:181
constexpr uint8_t bit_read(const T &_src, const uint8_t bit)
Read the specified bit.
Definition utils_pins.h:67
void setPinsAsInputPullup(uint16_t pins)
Set the pins as INPUT_PULLUP.
Definition utils_pins.h:226
void setPinsAsOutput(uint16_t pins)
Set the pins as OUTPUT.
Definition utils_pins.h:215
constexpr void setPinOFF(uint8_t pin)
Set the Pin state to OFF for the specified pin.
Definition utils_pins.h:160
void setPinsON(uint16_t pins)
Set the Pins state to ON.
Definition utils_pins.h:149
constexpr uint8_t bit_clear(T &_dest, const uint8_t bit)
Clear the specified bit.
Definition utils_pins.h:80