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)) };
42constexpr int32_t diversionStartThreshold_inIEU{ static_cast< int32_t >(DIVERSION_START_THRESHOLD_WATTS * (1.0f / powerCal_grid)) };
43
44// When using integer maths, calibration values that have supplied in floating point
45// form need to be rescaled.
46
47// When using integer maths, the SIZE of the ENERGY BUCKET is altered to match the
48// scaling of the energy detection mechanism that is in use. This avoids the need
49// to re-scale every energy contribution, thus saving processing time. This process
50// is described in more detail in the function, allGeneralProcessing(), just before
51// the energy bucket is updated at the start of each new cycle of the mains.
52//
53// An electricity meter has a small range over which energy can ebb and flow without
54// penalty. This has been termed its "sweet-zone". For optimal performance, the energy
55// bucket of a PV Router should match this value. The sweet-zone value is therefore
56// included in the calculation below.
57//
58int32_t energyInBucket_long{ 0 };
61
64
65// For recording the accumulated amount of diverted energy data (using CT2), a similar
66// calibration mechanism is required. Rather than a bucket with a fixed capacity, the
67// accumulator for diverted energy just needs to be scaled correctly. As soon as its
68// value exceeds 1 Wh, an associated WattHour register is incremented, and the
69// accumulator's value is decremented accordingly. The calculation below is to determine
70// the scaling for this accumulator.
71
72constexpr 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.
73
74bool recentTransition{ false };
75uint8_t postTransitionCount{ 0 };
76constexpr uint8_t POST_TRANSITION_MAX_COUNT{ 3 };
78
79int32_t sumP_grid{ 0 };
81int32_t sumP_diverted{ 0 };
84int32_t l_sum_Vsquared{ 0 };
85
86int32_t realEnergy_grid{ 0 };
87int32_t realEnergy_diverted{ 0 };
89int32_t sampleVminusDC_long{ 0 };
90
94
95// For a mechanism to check the continuity of the sampling sequence
98
100
102
105
107
109
110uint8_t perSecondCounter{ 0 };
111
112bool beyondStartUpPeriod{ false };
113
114bool b_diversionStarted{ false };
115
130constexpr uint16_t getOutputPins()
131{
132 uint16_t output_pins{ 0 };
133
134 for (const auto &loadPin : physicalLoadPin)
135 {
136 if (bit_read(output_pins, loadPin))
137 return 0;
138
139 bit_set(output_pins, loadPin);
140 }
141
142 if constexpr (WATCHDOG_PIN_PRESENT)
143 {
144 if (bit_read(output_pins, watchDogPin))
145 return 0;
146
147 bit_set(output_pins, watchDogPin);
148 }
149
150 if constexpr (RELAY_DIVERSION)
151 {
152 for (uint8_t idx = 0; idx < relays.get_size(); ++idx)
153 {
154 const auto relayPin = relays.get_relay(idx).get_pin();
155
156 if (bit_read(output_pins, relayPin))
157 return 0;
158
159 bit_set(output_pins, relayPin);
160 }
161 }
162
163 return output_pins;
164}
165
181constexpr uint16_t getInputPins()
182{
183 uint16_t input_pins{ 0 };
184
185 if constexpr (DUAL_TARIFF)
186 {
187 if (bit_read(input_pins, dualTariffPin))
188 return 0;
189
190 bit_set(input_pins, dualTariffPin);
191 }
192
193 if constexpr (DIVERSION_PIN_PRESENT)
194 {
195 if (bit_read(input_pins, diversionPin))
196 return 0;
197
198 bit_set(input_pins, diversionPin);
199 }
200
201 if constexpr (PRIORITY_ROTATION == RotationModes::PIN)
202 {
203 if (bit_read(input_pins, rotationPin))
204 return 0;
205
206 bit_set(input_pins, rotationPin);
207 }
208
209 if constexpr (OVERRIDE_PIN_PRESENT)
210 {
211 if (bit_read(input_pins, forcePin))
212 return 0;
213
214 bit_set(input_pins, forcePin);
215 }
216
217 return input_pins;
218}
219
234{
235 if constexpr (OLD_PCB)
237 else
238 {
239 setPinsAsOutput(getOutputPins()); // set the output pins as OUTPUT
240 setPinsAsInputPullup(getInputPins()); // set the input pins as INPUT_PULLUP
241
242 for (uint8_t i = 0; i < NO_OF_DUMPLOADS; ++i)
243 {
246 }
247 }
248
249 // First stop the ADC
250 bit_clear(ADCSRA, ADEN);
251
252 // Activate free-running mode
253 ADCSRB = 0x00;
254
255 // Set up the ADC to be free-running
256 bit_set(ADCSRA, ADPS0); // Set the ADC's clock to system clock / 128
257 bit_set(ADCSRA, ADPS1);
258 bit_set(ADCSRA, ADPS2);
259
260 bit_set(ADCSRA, ADATE); // set the Auto Trigger Enable bit in the ADCSRA register. Because
261 // bits ADTS0-2 have not been set (i.e. they are all zero), the
262 // ADC's trigger source is set to "free running mode".
263
264 bit_set(ADCSRA, ADIE); // set the ADC interrupt enable bit. When this bit is written
265 // to one and the I-bit in SREG is set, the
266 // ADC Conversion Complete Interrupt is activated.
267
268 bit_set(ADCSRA, ADEN); // Enable the ADC
269
270 bit_set(ADCSRA, ADSC); // start ADC manually first time
271
272 sei(); // Enable Global Interrupts
273}
274
290{
291 uint16_t pinsON{ 0 };
292 uint16_t pinsOFF{ 0 };
293
294 uint8_t i{ NO_OF_DUMPLOADS };
295
296 do
297 {
298 --i;
299 // update the local load's state.
301 {
302 // setPinOFF(physicalLoadPin[i]);
303 pinsOFF |= bit(physicalLoadPin[i]);
304 }
305 else
306 {
307 ++countLoadON[i];
308 // setPinON(physicalLoadPin[i]);
309 pinsON |= bit(physicalLoadPin[i]);
310 }
311 } while (i);
312
313 setPinsOFF(pinsOFF);
314 setPinsON(pinsON);
315}
316
339{
340 if constexpr (PRIORITY_ROTATION != RotationModes::OFF)
341 {
343 {
344 uint8_t i{ NO_OF_DUMPLOADS - 1 };
345 const auto temp{ loadPrioritiesAndState[i] };
346 do
347 {
349 --i;
350 } while (i);
351 loadPrioritiesAndState[0] = temp;
352
354 }
355 }
356
357 const bool bDiversionEnabled{ Shared::b_diversionEnabled };
358 uint8_t idx{ NO_OF_DUMPLOADS };
359 do
360 {
361 --idx;
362 const auto iLoad{ loadPrioritiesAndState[idx] & loadStateMask };
364 } while (idx);
365}
366
383void processPolarity(const int16_t rawSample)
384{
385 // remove DC offset from the raw voltage sample by subtracting the accurate value
386 // as determined by a LP filter.
387 sampleVminusDC_long = ((long)rawSample << 8) - DCoffset_V_long;
388
389 // determine the polarity of the latest voltage sample
391}
392
411void processGridCurrentRawSample(const int16_t rawSample)
412{
413 // extra items for an LPF to improve the processing of data samples from CT1
414 static int32_t lpf_long{}; // new LPF, for offsetting the behaviour of CTx as a HPF
415
416 // First, deal with the power at the grid connection point (as measured via CT1)
417 // remove most of the DC offset from the current sample (the precise value does not matter)
418 int32_t sampleIminusDC_grid = ((int32_t)(rawSample - DCoffset_I)) << 8;
419 //
420 // extra filtering to offset the HPF effect of CT1
421 const int32_t last_lpf_long = lpf_long;
422 lpf_long += alpha * (sampleIminusDC_grid - last_lpf_long);
423 sampleIminusDC_grid += (lpf_gain * lpf_long);
424
425 // calculate the "real power" in this sample pair and add to the accumulated sum
426 const int32_t filtV_div4 = sampleVminusDC_long >> 2; // reduce to 16-bits (now x64, or 2^6)
427 const int32_t filtI_div4 = sampleIminusDC_grid >> 2; // reduce to 16-bits (now x64, or 2^6)
428 int32_t instP = filtV_div4 * filtI_div4; // 32-bits (now x4096, or 2^12)
429 instP >>= 12; // scaling is now x1, as for Mk2 (V_ADC x I_ADC)
430 sumP_grid += instP; // cumulative power, scaling as for Mk2 (V_ADC x I_ADC)
432}
433
452void processDivertedCurrentRawSample(const int16_t rawSample)
453{
455 {
456 return; // no diverted power when the load is overridden
457 }
458
459 // Now deal with the diverted power (as measured via CT2)
460 // remove most of the DC offset from the current sample (the precise value does not matter)
461 int32_t sampleIminusDC_diverted = ((int32_t)(rawSample - DCoffset_I)) << 8;
462
463 // calculate the "real power" in this sample pair and add to the accumulated sum
464 const int32_t filtV_div4 = sampleVminusDC_long >> 2; // reduce to 16-bits (now x64, or 2^6)
465 const int32_t filtI_div4 = sampleIminusDC_diverted >> 2; // reduce to 16-bits (now x64, or 2^6)
466 int32_t instP = filtV_div4 * filtI_div4; // 32-bits (now x4096, or 2^12)
467 instP >>= 12; // scaling is now x1, as for Mk2 (V_ADC x I_ADC)
468 sumP_diverted += instP; // cumulative power, scaling as for Mk2 (V_ADC x I_ADC)
470}
471
489{
490 /* This routine prevents a zero-crossing point from being declared until
491 * a certain number of consecutive samples in the 'other' half of the
492 * waveform have been encountered.
493 */
494 static uint8_t count{ 0 };
496 {
497 count = 0;
498 return;
499 }
500
502 {
503 count = 0;
505 }
506}
507
526{
528 {
530 {
531 // This is the start of a new +ve half cycle (just after the zero-crossing point)
533 {
535
537 }
538 else
539 {
541 }
542 } // end of processing that is specific to the first Vsample in each +ve half cycle
543
544 // still processing samples where the voltage is Polarities::POSITIVE ...
545 // (in this go-faster code, the action from here has moved to the negative half of the cycle)
546
547 } // end of processing that is specific to samples where the voltage is positive
548 else // the polarity of this sample is negative
549 {
551 {
552 // This is the start of a new -ve half cycle (just after the zero-crossing point)
554 }
555
556 // check to see whether the trigger device can now be reliably armed
558 {
560 {
561 /* Determining whether any of the loads need to be changed is is a 3-stage process:
562 * - change the LOGICAL load states as necessary to maintain the energy level
563 * - update the PHYSICAL load states according to the logical -> physical mapping
564 * - update the driver lines for each of the loads.
565 */
566
567 // Restrictions apply for the period immediately after a load has been switched.
568 // Here the recentTransition flag is checked and updated as necessary.
570 {
572 {
573 recentTransition = false;
574 }
575 }
576
578 {
579 // the energy state is in the upper half of the working range
580 lowerEnergyThreshold = lowerThreshold_default; // reset the "opposite" threshold
582 {
583 b_diversionStarted = true;
584 // Once started, we divert all surplus according to the configured fixed offset
585
586 // Because the energy level is high, some action may be required
588 }
589 }
590 else
591 { // the energy state is in the lower half of the working range
592 upperEnergyThreshold = upperThreshold_default; // reset the "opposite" threshold
594 {
595 // Because the energy level is low, some action may be required
597 }
598 }
599
600 updatePhysicalLoadStates(); // allows the logical-to-physical mapping to be changed
601
602 // update each of the physical loads
604
605 // update the Energy Diversion Detector
607 {
610 }
611 else
612 {
614 }
615
616 // Now that the energy-related decisions have been taken, min and max limits can now
617 // be applied to the level of the energy bucket. This is to ensure correct operation
618 // when conditions change, i.e. when import changes to export, and vice versa.
619 //
621 {
623 }
624 else if (energyInBucket_long < 0)
625 {
627 }
628 }
629 }
630
632 } // end of processing that is specific to samples where the voltage is negative
634}
635
652{
653 long filtV_div4 = sampleVminusDC_long >> 2; // reduce to 16-bits (now x64, or 2^6)
654 int32_t inst_Vsquared{ filtV_div4 * filtV_div4 }; // 32-bits (now x4096, or 2^12)
655
656 inst_Vsquared >>= 12; // scaling is now x1 (V_ADC x I_ADC)
657
658 l_sum_Vsquared += inst_Vsquared; // cumulative V^2 (V_ADC x I_ADC)
659
660 // store items for use during next loop
661 cumVdeltasThisCycle_long += sampleVminusDC_long; // for use with LP filter
662 polarityConfirmedOfLastSampleV = polarityConfirmed; // for identification of half cycle boundaries
663}
664
681void processVoltageRawSample(const int16_t rawSample)
682{
683 processPolarity(rawSample);
685
686 processRawSamples(); // deals with aspects that only occur at particular stages of each mains cycle
687
688 // processing for EVERY set of samples
689 //
691
694}
695
713{
714 // wait until the DC-blocking filters have had time to settle
715 if (millis() <= (delayBeforeSerialStarts + startUpPeriod))
716 {
717 return;
718 }
719
720 beyondStartUpPeriod = true;
721 sumP_grid = 0;
723 sumP_diverted = 0;
725 sampleSetsDuringThisMainsCycle = 0; // not yet dealt with for this cycle
727 // can't say "Go!" here 'cos we're in an ISR!
728}
729
745{
746 // Apply max and min limits to bucket's level. This is to ensure correct operation
747 // when conditions change, i.e. when import changes to export, and vice versa.
748 //
750 {
752 }
753 else if (energyInBucket_long < 0)
754 {
756 }
757
758 // clear the per-cycle accumulators for use in this new mains cycle.
760 sumP_grid = 0;
761 sumP_diverted = 0;
763}
764
781{
782 // a simple routine for checking the performance of this new ISR structure
784 {
786 }
787
789
791}
792
810{
811 // This is the start of a new -ve half cycle (just after the zero-crossing point)
812 // which is a convenient point to update the Low Pass Filter for DC-offset removal
813 // The portion which is fed back into the integrator is approximately one percent
814 // of the average offset of all the Vsamples in the previous mains cycle.
815 //
816 const auto previousOffset{ DCoffset_V_long };
817 DCoffset_V_long = previousOffset + (cumVdeltasThisCycle_long >> 12);
819
820 // To ensure that the LPF will always start up correctly when 240V AC is available, its
821 // output value needs to be prevented from drifting beyond the likely range of the
822 // voltage signal. This avoids the need to use a HPF as was done for initial Mk2 builds.
823 //
825 {
827 }
829 {
831 }
832
833 // The average power that has been measured during the first half of this mains cycle can now be used
834 // to predict the energy state at the end of this mains cycle. That prediction will be used to alter
835 // the state of the load as necessary. The arming signal for the triac can't be set yet - that must
836 // wait until the voltage has advanced further beyond the -ve going zero-crossing point.
837 //
838 long averagePower = sumP_grid / sampleSetsDuringThisMainsCycle; // for 1st half of this mains cycle only
839 //
840 // To avoid repetitive and unnecessary calculations, the increase in energy during each mains cycle is
841 // deemed to be numerically equal to the average power. The predicted value for the energy state at the
842 // end of this mains cycle will therefore be the known energy state at its start plus the average power
843 // as measured. Although the average power has been determined over only half a mains cycle, the correct
844 // number of contributing sample sets has been used so the result can be expected to be a true measurement
845 // of average power, not half of it.
846 //
847 energyInBucket_prediction = energyInBucket_long + averagePower; // at end of this mains cycle
848}
849
865{
866 // Calculate the real power and energy during the last whole mains cycle.
867 //
868 // sumP contains the sum of many individual calculations of instantaneous power. In
869 // order to obtain the average power during the relevant period, sumP must first be
870 // divided by the number of samples that have contributed to its value.
871 //
872 // The next stage would normally be to apply a calibration factor so that real power
873 // can be expressed in Watts. That's fine for floating point maths, but it's not such
874 // a good idea when integer maths is being used. To keep the numbers large, and also
875 // to save time, calibration of power is omitted at this stage. Real Power (stored as
876 // a 'long') is therefore (1/powerCal) times larger than the actual power in Watts.
877 //
878 int32_t realPower_grid = sumP_grid / sampleSetsDuringThisMainsCycle; // proportional to Watts
879 int32_t realPower_diverted = sumP_diverted / sampleSetsDuringThisMainsCycle; // proportional to Watts
880
882 realPower_grid -= diversionStartThreshold_inIEU;
883 else
884 realPower_grid -= requiredExportPerMainsCycle_inIEU; // <- useful for PV simulation
885
886 // Next, the energy content of this power rating needs to be determined. Energy is
887 // power multiplied by time, so the next step would normally be to multiply the measured
888 // value of power by the time over which it was measured.
889 // Instantaneous power is calculated once every mains cycle. When integer maths is
890 // being used, a repetitive power-to-energy conversion seems an unnecessary workload.
891 // As all sampling periods are of similar duration, it is more efficient to just
892 // add all of the power samples together, and note that their sum is actually
893 // SUPPLY_FREQUENCY greater than it would otherwise be.
894 // Although the numerical value itself does not change, I thought that a new name
895 // may be helpful so as to minimise confusion.
896 // The 'energy' variable below is SUPPLY_FREQUENCY * (1/powerCal) times larger than
897 // the actual energy in Joules.
898 //
899 realEnergy_grid = realPower_grid;
900 realEnergy_diverted = realPower_diverted;
901
902 // Energy contributions from the grid connection point (CT1) are summed in an
903 // accumulator which is known as the energy bucket. The purpose of the energy bucket
904 // is to mimic the operation of the supply meter. The range over which energy can
905 // pass to and fro without loss or charge to the user is known as its 'sweet-zone'.
906 // The capacity of the energy bucket is set to this same value within setup().
907 //
908 // The latest contribution can now be added to this energy bucket
910
911 if (Shared::EDD_isActive) // Energy Diversion Display
912 {
913 // For diverted energy, the latest contribution needs to be added to an
914 // accumulator which operates with maximum precision.
915
917 {
919 }
921
922 // Whole kWh are then recorded separately
924 {
927 }
928 }
929
930 // After a pre-defined period of inactivity, the 4-digit display needs to
931 // close down in readiness for the next's day's data.
932 //
934 {
935 // clear the accumulators for diverted energy
938 Shared::EDD_isActive = false; // energy diversion detector is now inactive
939 }
940
942 {
944
946 {
948 // Reset diversion state if we've had no diversion for a full second
949 b_diversionStarted = false;
950 }
951 else
953
954 // The diverted energy total is copied to a variable before it is used.
955 // This is done to avoid the possibility of a race-condition whereby the
956 // diverted energy total is updated while the display is being updated.
958 }
959
960 Shared::b_newCycle = true; // a 50 Hz 'tick' for use by the main code
961}
962
979{
980 bool bOK_toAddLoad{ true };
981 const auto tempLoad{ nextLogicalLoadToBeAdded() };
982
983 if (tempLoad == NO_OF_DUMPLOADS)
984 {
985 return;
986 }
987
988 // a load which is now OFF has been identified for potentially being switched ON
990 {
991 // During the post-transition period, any increase in the energy level is noted.
993
994 // the energy thresholds must remain within range
996 {
998 }
999
1000 // Only the active load may be switched during this period. All other loads must
1001 // wait until the recent transition has had sufficient opportunity to take effect.
1002 bOK_toAddLoad = (tempLoad == activeLoad);
1003 }
1004
1005 if (bOK_toAddLoad)
1006 {
1008 activeLoad = tempLoad;
1010 recentTransition = true;
1011 }
1012}
1013
1031{
1032 bool bOK_toRemoveLoad{ true };
1033 const auto tempLoad{ nextLogicalLoadToBeRemoved() };
1034
1035 if (tempLoad >= NO_OF_DUMPLOADS)
1036 {
1037 return;
1038 }
1039
1040 // a load which is now ON has been identified for potentially being switched OFF
1041 if (recentTransition)
1042 {
1043 // During the post-transition period, any decrease in the energy level is noted.
1045
1046 // the energy thresholds must remain within range
1047 if (lowerEnergyThreshold < 0)
1048 {
1050 }
1051
1052 // Only the active load may be switched during this period. All other loads must
1053 // wait until the recent transition has had sufficient opportunity to take effect.
1054 bOK_toRemoveLoad = (tempLoad == activeLoad);
1055 }
1056
1057 if (bOK_toRemoveLoad)
1058 {
1060 activeLoad = tempLoad;
1062 recentTransition = true;
1063 }
1064}
1065
1084{
1085 for (uint8_t index = 0; index < NO_OF_DUMPLOADS; ++index)
1086 {
1087 if (!(loadPrioritiesAndState[index] & loadStateOnBit))
1088 {
1089 return (index);
1090 }
1091 }
1092
1093 return (NO_OF_DUMPLOADS);
1094}
1095
1111{
1112 uint8_t index{ NO_OF_DUMPLOADS };
1113
1114 do
1115 {
1117 {
1118 return (index);
1119 }
1120 } while (index);
1121
1122 return (NO_OF_DUMPLOADS);
1123}
1124
1140{
1142 {
1143 return; // data logging period not yet reached
1144 }
1145
1147
1150
1153
1155
1157 l_sum_Vsquared = 0;
1158
1159 uint8_t i{ NO_OF_DUMPLOADS };
1160 do
1161 {
1162 --i;
1164 countLoadON[i] = 0;
1165 } while (i);
1166
1170
1173
1174 // signal the main processor that logging data are available
1175 // we skip the period from start to running stable
1177}
1178
1192{
1193 DBUG("\tzero-crossing persistence (sample sets) = ");
1195
1196 DBUG("\tcapacityOfEnergyBucket_long = ");
1198}
1199
1236ISR(ADC_vect)
1237{
1238 static uint8_t sample_index{ 0 };
1239 int16_t rawSample;
1240
1241 switch (sample_index)
1242 {
1243 case 0:
1244 rawSample = ADC; // store the ADC value (this one is for Voltage)
1245 //sampleV = ADC; // store the ADC value (this one is for Voltage)
1246 ADMUX = bit(REFS0) + currentSensor_diverted; // set up the next conversion, which is for Diverted Current
1247 ++sample_index; // increment the control flag
1248 //
1249 processVoltageRawSample(rawSample);
1250 break;
1251 case 1:
1252 rawSample = ADC; // store the ADC value (this one is for current at CT1)
1253 //sampleI_diverted_raw = ADC; // store the ADC value (this one is for Diverted Current)
1254 ADMUX = bit(REFS0) + voltageSensor; // set up the next conversion, which is for Grid Current
1255 ++sample_index; // increment the control flag
1256 //
1257 processGridCurrentRawSample(rawSample);
1258 break;
1259 case 2:
1260 rawSample = ADC; // store the ADC value (this one is for current at CT2)
1261 //sampleI_grid_raw = ADC; // store the ADC value (this one is for Grid Current)
1262 ADMUX = bit(REFS0) + currentSensor_grid; // set up the next conversion, which is for Voltage
1263 sample_index = 0; // reset the control flag
1264 //
1266 break;
1267 default:
1268 sample_index = 0; // to prevent lockup (should never get here)
1269 }
1270}
1271
1293{
1294 for (int16_t i = 0; i < NO_OF_DUMPLOADS; ++i)
1295 {
1297 pinMode(physicalLoadPin[i], OUTPUT); // driver pin for Load #n
1299 }
1300
1301 updatePhysicalLoadStates(); // allows the logical-to-physical mapping to be changed
1302
1303 updatePortsStates(); // updates output pin states
1304
1305 if constexpr (DUAL_TARIFF)
1306 {
1307 pinMode(dualTariffPin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1308 delay(100); // allow time to settle
1309 }
1310
1311 if constexpr (OVERRIDE_PIN_PRESENT)
1312 {
1313 pinMode(forcePin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1314 delay(100); // allow time to settle
1315 }
1316
1317 if constexpr (PRIORITY_ROTATION == RotationModes::PIN)
1318 {
1319 pinMode(rotationPin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1320 delay(100); // allow time to settle
1321 }
1322
1323 if constexpr (DIVERSION_PIN_PRESENT)
1324 {
1325 pinMode(diversionPin, INPUT_PULLUP); // set as input & enable the internal pullup resistor
1326 delay(100); // allow time to settle
1327 }
1328
1329 if constexpr (RELAY_DIVERSION)
1330 {
1331 for (uint8_t idx = 0; idx < relays.get_size(); ++idx)
1332 {
1333 const auto relayPin = relays.get_relay(idx).get_pin();
1334
1335 pinMode(relayPin, OUTPUT);
1336 setPinOFF(relayPin); // set to off
1337 }
1338 }
1339
1340 if constexpr (WATCHDOG_PIN_PRESENT)
1341 {
1342 pinMode(watchDogPin, OUTPUT); // set as output
1343 setPinOFF(watchDogPin); // set to off
1344 }
1345}
1346
1361{
1362#ifdef ENABLE_DEBUG
1363
1364 DBUGLN(F("Load Priorities: "));
1365 for (const auto &loadPrioAndState : loadPrioritiesAndState)
1366 {
1367 DBUG(F("\tload "));
1368 DBUGLN(loadPrioAndState);
1369 }
1370
1371#endif
1372}
1373
1374static_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
constexpr int16_t DIVERSION_START_THRESHOLD_WATTS
#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
bool b_diversionStarted
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
constexpr int32_t diversionStartThreshold_inIEU
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