Skip to main content

Command Palette

Search for a command to run...

How to program an accumulative timer microcontroller?

Published
4 min read
How to program an accumulative timer microcontroller?

Here’s a practical blueprint for building an accumulative timer (aka hour-meter) on a microcontroller—counts run-time across many start/stop cycles and persists across power loss.


1) What you’re building

  • Signal to watch: a “RUN” condition (button, digital input from a machine, or current sense).

  • Time base: a reliable 1 Hz tick (timer/RTC).

  • Accumulator: total_seconds (or minutes/hours).

  • Persistence: write total_seconds to non-volatile memory on a schedule (and/or power-fail).

Key choices:

  • Resolution: seconds are plenty for hour-meters.

  • Keeps time while power is off?

    • If yes, use an RTC + backup battery so it keeps ticking.

    • If no, you just accumulate while RUN is true and power is present.


2) Core algorithm (pseudo)

bool run = read_run_input();         // debounced
if (one_second_tick) {               // from timer/RTC
    if (run) total_seconds++;
    if (++sec_since_last_save >= SAVE_PERIOD) {
        persist(total_seconds);      // wear-friendly save
        sec_since_last_save = 0;
    }
}

Add: save on clean shutdown (if you have a power-fail signal), and CRC when you store.


3) Persistence strategies (from quick to robust)

  • Internal EEPROM (AVR, some PIC): simplest. Write every 1–15 min to reduce wear; or rotate across a ring buffer of slots (wear-leveling).

  • Flash emulation (STM32): store a small record with sequence number + CRC; append-only until a page fills, then compact.

  • FRAM (I²C/SPI): essentially unlimited writes—ideal for frequent saves.

  • RTC backup registers (STM32) + coin cell: great for a single 32-bit counter if the backup battery is present.


4) Debounce & safety

  • Debounce RUN input (10–30 ms).

  • Clamp impossible jumps (e.g., if the tick stalls, don’t add hours at once).

  • Use a watchdog if running unattended.

  • If RUN comes from 12/24 V machinery, add divider, TVS, and opto/isolator.


5) Examples you can paste

A) Arduino / AVR (UNO/Nano) – accumulate while RUN pin is HIGH, save every 5 min

#include <EEPROM.h>

const uint8_t RUN_PIN = 2;          // digital input (use pull-down or external conditioning)
const unsigned SAVE_PERIOD = 300;    // seconds
volatile unsigned long last_ms;
unsigned long ms_accum = 0;
uint32_t total_seconds = 0;
unsigned save_countdown = SAVE_PERIOD;

void setup() {
  pinMode(RUN_PIN, INPUT_PULLUP);   // assume active-LOW button/signal; invert below if needed
  EEPROM.get(0, total_seconds);     // load saved counter (defaults to 0 on fresh EEPROM)
  last_ms = millis();
}

static bool debouncedRun() {
  static uint8_t s=0; // 8-bit integrator debounce
  bool raw = digitalRead(RUN_PIN)==LOW; // active-LOW
  s += raw ? 1 : -1; if (s>200) s=200; if (s<0) s=0;
  return s>150;
}

void loop() {
  unsigned long now = millis();
  unsigned long d = now - last_ms; last_ms = now;

  if (debouncedRun()) ms_accum += d;

  while (ms_accum >= 1000) {   // 1-second resolution
    ms_accum -= 1000;
    if (debouncedRun()) {
      total_seconds++;
      if (--save_countdown == 0) {
        EEPROM.put(0, total_seconds);   // simple store; for long life, store every 5–15 min or add wear-level
        save_countdown = SAVE_PERIOD;
      }
    }
  }
}

Wear tip: UNO EEPROM is rated ~100k writes per cell. Saving every 15 min ≈ 2,400 writes/year. Add a ring buffer across 16–32 slots for multi-year life with 1–5 min cadence.


B) STM32 (HAL) – 1 Hz via RTC Wakeup; store in Backup Register every 10 min

// RTC configured to LSE 32.768 kHz; Wakeup at 1 Hz (ck_spre = 1 Hz).
// RUN pin on, say, PA0 with pull-up and RC debounce in software.

RTC_HandleTypeDef hrtc;
uint32_t total_seconds;
uint16_t save_div = 600;  // 10 minutes

static inline bool run_active(void) {
  // simple debounce: read N times or use a timer-based filter
  return HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET; // active-LOW
}

void load_counter(void){
  // use backup reg (retained by coin cell). BKP_DR1 holds low 16 bits, BKP_DR2 high 16 bits.
  uint32_t lo = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
  uint32_t hi = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
  total_seconds = (hi<<16) | (lo & 0xFFFF);
}

void save_counter(void){
  HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, (uint16_t)(total_seconds & 0xFFFF));
  HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)(total_seconds >> 16));
}

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) {
  static uint16_t save_tick = 0;
  if (run_active()) total_seconds++;
  if (++save_tick >= save_div) { save_counter(); save_tick = 0; }
}

int main(void){
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_RTC_Init();                         // configure LSE + wakeup 1 Hz
  load_counter();
  HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0, RTC_WAKEUPCLOCK_CK_SPRE_16BITS); // 1 Hz
  while(1){ __WFI(); }                   // sleep between ticks
}

If you don’t have a backup battery, store to Flash (with wear-leveled log) or to FRAM over I²C/SPI.


C) ESP32 (IDF/Arduino) – 1 Hz task; save to NVS every 5–15 min

#include <Preferences.h>
Preferences prefs;
uint32_t total_seconds;
unsigned save_div = 600;

bool run_active(){ return digitalRead(4)==HIGH; }

void setup(){
  pinMode(4, INPUT_PULLDOWN);
  prefs.begin("accum", false);
  total_seconds = prefs.getULong("secs", 0);
}

void loop(){
  static unsigned save_tick=0;
  if (run_active()) total_seconds++;
  if (++save_tick >= save_div) { prefs.putULong("secs", total_seconds); save_tick=0; }
  delay(1000);
}

6) Power-fail friendly saving (optional but pro)

  • Add a power-fail input from a supervisor (e.g., open-collector falling when VIN drops).

  • On interrupt, debounce 5–10 ms, then immediately persist the counter. Use a hold-up capacitor (tens of milliseconds) on the MCU rail so the write completes.


7) Display / reporting

  • Convert to hours.tenths:
    hours = total_seconds / 3600; tenths = (total_seconds % 3600) / 360;

  • Show on 7-segment/LCD/OLED; expose over UART/Modbus/RS-485/ BLE as needed.


8) Calibration & drift

  • RC-based ticks (millis, internal RC) can drift by ±1–5%. If accuracy matters, use RTC with crystal (LSE 32.768 kHz) or discipline against mains/ GNSS/ NTP (for connected devices).

More from this blog

A

Ampheo Electronic Blog-Chip and component knowledge sharing

181 posts

Original and Genuine Electronic Components Distributor