DualS0ImpCounter.ino 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. Dual Impulse Counter
  3. Arduino/AVR software to detect 2 impulse inputs, increment counters and save counter values to EEPROM.
  4. With power loss detection / saving.
  5. Copyright 2020 Florian Krauter
  6. This program is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #define SERIALBAUD 57600
  18. #define SERIALCONF SERIAL_8N1
  19. //#define INCLUDE_TESTFUNCTIONS
  20. #define PROJECT_NAME "DualS0ImpCounter"
  21. #define PROJECT_VERSION "1.0"
  22. #include <avr/wdt.h>
  23. #include <EEPROM.h>
  24. // DIGITAL OUTPUTS
  25. #define PIN_OUT_HARDRESET 6
  26. #define PIN_OUT_LED 13
  27. // DIGITAL INPUTS
  28. #define PIN_IN_PULSE1 2
  29. #define PIN_IN_PULSE2 3
  30. // NUMBER OF COUNTERS
  31. #define COUNTERS_COUNT 2
  32. // ANALOG INPUTS
  33. #define PIN_IN_ADC_PWRGOOD A3
  34. // global conf variables
  35. // POWER GOOD - monitors voltage in front of the onboard voltage regulator powering the AVR/Arduino
  36. // in order to save all data to EEPROM before power is really gone
  37. // to enable this, supply voltage is much higher than needed for an AVR/Arduino (9-12V)
  38. // connected to a huge (2200uF) capacitor over a decoupling diode (voltage drop 0.6-0.7V)
  39. // and regulated to Vcc=5V by the Arduino onboard regulator
  40. // Vin is measured through a voltage divider of 56k/24k (after the diode)
  41. // a value of 420 seems to be the minimum at ~7.5V supply voltage (in front of the decoupling diode)
  42. // below this the value increases again due to voltage regulator drop out and therefore sinking Vref
  43. // so a reasonable safety margin is mandatory here
  44. // as we use a 12V supply we set this to 500 for now ( ~= 9.0V)
  45. unsigned int powerGoodMinValue = 500;
  46. int debounceDelay = 40;
  47. int debounceRecoveryDelay = 70;
  48. bool debug = true;
  49. int debuglevel = 1;
  50. bool output_json = true;
  51. // --- END global conf vars
  52. unsigned long currentReading[COUNTERS_COUNT];
  53. uint16_t currentReadingImpulses[COUNTERS_COUNT];
  54. uint16_t currentReadingImpulses_saved[COUNTERS_COUNT];
  55. byte pulseInPins[] = {PIN_IN_PULSE1, PIN_IN_PULSE2};
  56. byte pulseInLastState[2];
  57. unsigned long pulseInLastDebounceTime[] = {0, 0};
  58. bool pulseAlreadyDetected[COUNTERS_COUNT];
  59. unsigned long pulseInLastMillis[COUNTERS_COUNT];
  60. // config vars per counter
  61. uint16_t meter_impPerUnit[] = {1000, 100};
  62. uint16_t meter_noImpulseTimeout_seconds[] = {0, 300};
  63. uint16_t meter_savePulsesOnInterval_mins[] = {15, 0};
  64. //uint16_t meter_savePulsesEveryImpulses[] = {0, 0};
  65. // global vars
  66. unsigned long pulsesLastSaved_seconds[COUNTERS_COUNT];
  67. unsigned long lastDataSent_seconds[COUNTERS_COUNT];
  68. unsigned long lastSaveTime_pulses = 0;
  69. unsigned long millis_everysecond = 0;
  70. unsigned long seconds=0;
  71. // global static vars
  72. // serial commands
  73. #define MAX_CMD_LEN 120
  74. #define MAX_CMDDATA_LEN 90
  75. char cmdBuffer[MAX_CMD_LEN + 1]; // Serial Input-Buffer
  76. int cmdBufferCount = 0; // Anzahl der eingelesenen Zeichen
  77. bool cmdComplete = false;
  78. char cmdDataBuffer[MAX_CMDDATA_LEN + 1];
  79. // EEPROM addresses
  80. // configuration values in EEPROM addr 0-19 (avoid using 0 so starting at 2)
  81. uint16_t eeprom_addr_debug = 2; // 1 byte
  82. uint16_t eeprom_addr_output_json = 3; // 1 byte
  83. uint16_t eeprom_addr_debounce1 = 4; // 2 byte
  84. uint16_t eeprom_addr_debounce2 = 6; // 2 byte
  85. uint16_t eeprom_addr_powerGoodMinValue = 8; // 2 byte
  86. uint16_t eeprom_addr_impPerUnit[] = {10, 12}; // 2 bytes
  87. uint16_t eeprom_addr_noImpTout[] = {14, 16}; // 2 bytes
  88. uint16_t eeprom_addr_saveInt[] = {18, 20}; // 2 bytes
  89. // 4 bytes (unsigned long) for current unit readings
  90. // use 3 EEPROM for each reading addresses (2 as backup, so that the actual value can hopefully always be restored when 2 of them hold the same value)
  91. // as the value ranges from 0 to max. 100000 here we can write this directly on every value change
  92. // reserved EEPROM addr: 20-68 (4 counters a 3 values a 4 bytes
  93. uint16_t eeprom_addr_currentReading[2] = {32, 44};
  94. uint16_t eeprom_addr_currentReading_backup1[2] = {36, 48};
  95. uint16_t eeprom_addr_currentReading_backup2[2] = {40, 52};
  96. // different situation on the impulses where 100 imp ^= 1 reading
  97. // these are updated quite often, so directly writing will bring troubles with EEPROM endurance
  98. // this value has to be written:
  99. // - after every increment of main reading value (set to 0, to ensure it will not hold a higher than real count)
  100. // - after some time of no impulse counter change (to save from time to time so that we don´t loose too many impulses)
  101. // -> When logging a gas meter this should be sufficient, as these typically stand still for a longer period between consuming periods as the heating
  102. // is switched in intervals.
  103. // as these typically have 10 or 100 pulses per unit (m^3), it should be safe to just write every reading rollover
  104. // (without decimals/single impulses) and + write the current impulse count when there was no activity for some minutes.
  105. // When logging i.e. a power meter with 1000 imp/kWh, it would get more complicated. These typically don´t ever stand still, so only writing
  106. // the current pulse count when there is no activity would just not work. Also 1000 pulses are a lot to loose, so for this case it is necessary
  107. // to also write i.E. every 50st or 100st pulse or every 10-60 min additionally.
  108. // -> this all leads to a somehow higher write load
  109. // so using wear leveling is advisable
  110. // a simple approach to that (ring buffer):
  111. // - define an address area on the EEPROM for this use
  112. // - preformat the entire EEPROM area (write 0x00 to every byte)
  113. // - before every EEPROM write, increase a 2 byte (uint16_t) write counter
  114. // - use this counter as offset to point to the actual most current data (has to multiplied with the length one dataset uses for sure)
  115. // - memorize last address written and write next dataset to the next address
  116. // - store the writecounter and the data next to it (examples for 2 byte counter and 2 byte data):
  117. // EEPROM area start address: 128
  118. // counter=0 -> addr 128+129, value -> addr 130+131 (=start_addr + (counter * 4) => 128 + (0*4) = 128
  119. // counter=1 -> addr 132+133, value -> addr 134+135 (=start_addr + (counter * 4) => 128 + (1*4) = 132
  120. // counter=2 -> addr 136+137, value -> addr 138+139 (=start_addr + (counter * 4) => 128 + (2*4) = 136
  121. // - repeat writing at offset 0 when the entire address space is used
  122. // - when restoring data, scan the entire area to find the position where the writecounter value is > than writecounter in the next field
  123. // and restore data from that position
  124. //
  125. static byte eeprom_datasetsize_currentReadingImpulses = 4; // 2 bytes data, 2 bytes write counter
  126. uint16_t eeprom_addr_area_start_currentReadingImpulses[2] = {128, 640};
  127. uint16_t eeprom_addr_area_length_currentReadingImpulses[2] = {512, 128};
  128. uint16_t eeprom_writecounter_currentReadingImpulses[COUNTERS_COUNT];
  129. uint16_t eeprom_nextoffset_currentReadingImpulses[COUNTERS_COUNT];
  130. /**
  131. Setup.
  132. */
  133. void setup() {
  134. // immediately disable watchdog timer so we will not get interrupted
  135. wdt_disable();
  136. // init global vars/arrays
  137. for (int i = 0; i < COUNTERS_COUNT; i++) {
  138. eeprom_writecounter_currentReadingImpulses[i] = 0;
  139. eeprom_nextoffset_currentReadingImpulses[i] = 0;
  140. currentReading[i] = 0;
  141. pulseInLastMillis[i] = 0;
  142. pulseAlreadyDetected[i] = false;
  143. }
  144. // PIN_OUT_HARDRESET is wired to /RESET PIN
  145. // used to hard reset the MCU from software
  146. // when there is no DTS pin connected and a firmware update should be installed
  147. // normally set to INPUT mode (without pullup, so "no" connection to the actual RESET pin)
  148. pinMode(PIN_OUT_HARDRESET, INPUT);
  149. pinMode(PIN_IN_PULSE1, INPUT_PULLUP);
  150. pinMode(PIN_IN_PULSE2, INPUT_PULLUP);
  151. // the following forces a pause before enabling WDT. This gives the IDE a chance to
  152. // call the bootloader in case something dumb happens during development and the WDT
  153. // resets the MCU too quickly. Once the code is solid, remove this.
  154. delay(2L * 1000L);
  155. // enable the watchdog timer. There are a finite number of timeouts allowed (see wdt.h).
  156. // Notes I have seen say it is unwise to go below 250ms as you may get the WDT stuck in a
  157. // loop rebooting.
  158. // The timeouts I'm most likely to use are:
  159. // WDTO_1S
  160. // WDTO_2S
  161. // WDTO_4S
  162. // WDTO_8S
  163. wdt_enable(WDTO_8S);
  164. Serial.begin(SERIALBAUD, SERIALCONF);
  165. Serial.print(PROJECT_NAME);
  166. Serial.print(" v");
  167. Serial.print(PROJECT_VERSION);
  168. Serial.print(F(" starting..."));
  169. // read config from EEPROM
  170. loadConfig();
  171. // restore counters from EEPROM
  172. restoreAllLastData();
  173. // output current counter values
  174. printAllCurrentReadings();
  175. }
  176. void loop() {
  177. if ((millis() - millis_everysecond) >= 1000) {
  178. millis_everysecond = millis();
  179. everySecond();
  180. }
  181. checkInputStates();
  182. checkPowerGood();
  183. wdt_reset();
  184. }
  185. void everySecond() {
  186. onNoImpulseTimeout();
  187. saveDataOnInterval();
  188. seconds++;
  189. }