Skip to content

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Super Counter

A compact multi-mode counter tool written in embedded C for the ATmega328P (e.g. Arduino Uno, 16 MHz).

It turns only three pieces of hardware into a complete, gesture-driven counting tool:

  • 1 push button — all control flows through it
  • 1 built-in LED — visual feedback for the button gestures
  • 1 LCD 1602A (16x2) — shows the menu and the counter state

A UART driver is included and used for debugging (9600 baud).

Features

  • Multiple counting modes: CounterUp, CounterDown, and ManualCounter
  • 4 distinct gestures from a single button:
    • Click (single press)
    • Double-click
    • Long press
    • Press-and-hold
  • A menu mode to pick and switch between the counting modes
  • CounterSetter: an intermediate mode to set the initial value before counting down
  • Non-blocking drivers: everything is pumped from a single while(true) main loop
  • Built-in LED gives feedback for every gesture
  • UART logging of button gestures and LCD command traffic (useful for debugging)

Hardware & Wiring

Circuit diagram from Wokwi

Part Pin attached
Button (to GND, internal pull-up) PB0
Built-in LED PB5
LCD data D4–D7 PD4–PD7 (4-bit mode)
LCD RS PD3
LCD EN PD2
UART TX / RX PD1 / PD0

Button Gestures

The button driver recognizes each press/release pattern and emits a gesture result that the counter logic reacts to:

Gesture Behavior
Click Press and release within ~300 ms
Double-click Two clicks within ~300 ms
Long press Hold 300–1000 ms to release
Hold Keep holding past ~1000 ms (fires repeatedly)

Modes & Gestures

Menu

Entry point after reset. Select a counting mode, then confirm with a hold.

Gesture Action
Click Select next mode (Up / Down / Tick)
Double-click Select previous mode
Hold Enter the selected mode

CounterUp

Counts automatically in steps of 1 while running.

Gesture Action
Click Start / pause counting
Long press Reset count to 0
Hold Back to menu

CounterDown

Counts automatically in steps of 1 while running, starting from the initial value. When the counter hits 0 it reloads the initial value automatically.

Gesture Action
Click Start / pause counting
Long press Reset to the initial value
Hold Back to menu

CounterSetter

Intermediate mode that runs before CounterDown. It lets you build up the initial value by adding fixed steps: 1000, 100, 10, 5, 1.

Gesture Action
Click Move the selection over the step options
Long press Add the selected step to the initial value
Double-click Zero the initial value
Hold Confirm and start CounterDown

ManualCounter ("Ticker")

Lets you count manually, one step per gesture.

Gesture Action
Click Increment by 1
Double-click Increment by 5
Long press Reset to 0
Hold Back to menu

Mode flow

                      ┌──────────┐
                      │   MENU   │   Up / Down / Tick
                      └────┬─────┘
             Hold ─────────┤ Hold ───────────── Hold ──────────────────────┐
                           ▼                                              │
                      ┌──────────┐      Hold (confirm)        ┌──────────┐ │
        ┌────────────▶│ CounterUp│──────▶ CounterDown ◀───────│CounterSet│ │
        │Hold         └──────────┘    Hold(back)              │  ter     │ │
        │                                                      └──────────┘ │
        └──────────────────────────────────────────────────────────────────┤
        Hold ──────────────────────────────────────────────────────────────┘

Every mode links back to the Menu with a Hold.

Project Structure

├── main.c          # init + single main loop; wires everything together
├── drivers/
│   ├── button.c    # gesture state machine (debounce, click, double, long, hold)
│   ├── counter.c   # modes, UI rendering, gesture dispatch
│   ├── lcd.c       # async LCD1602A driver (4-bit) with command queue
│   ├── timer.c     # Timer0 overflow -> 1 ms tick source
│   └── uart.c      # USART debug output
├── include/
│   ├── button.h
│   ├── counter.h
│   ├── gpio.h      # dio_* register macros
│   ├── lcd.h
│   ├── timer.h
│   └── uart.h
├── meson.build       # build + flash (avrdude) targets
└── meson-avr.ini     # avr-gcc cross-file for Meson

The Smart Parts

A gesture engine from one button. button.c is a non-blocking state machine (BS_IDLE → BS_PRESSED → BS_WAIT_SECOND → BS_PRESSED_SECOND → BS_HOLD) with software debouncing. From a single GPIO input it derives four distinct gestures — single click, double-click, long press, and press-and-hold — without ever blocking the main loop.

The UI macro: self-cadenced LCD refresh. In counter.c, each mode screen redraw is wrapped in a for loop that only runs when the display is due, spacing redraws ~800 ms apart and catching up instantly if the loop ever falls behind. The body is called at most once per frame and the loop self-terminates, so no busy-waiting is needed for the UI:

static uint32_t timer = 0;

#define UI                                    \
  uint32_t now = get_ms();                    \
  for (; timer < now; timer = now + 800)

Async LCD driver. lcd.c decouples the application from LCD timing: every command/character is pushed into a small ring buffer and only sent to the display when lcd_update() is pumped from the main loop. Init sequences and redraws get their 1 ms gaps from the shared millisecond timer instead of long blocking delays.

Hardware millisecond clock. timer.c reloads TCNT0 in the Timer0 overflow ISR to generate a 1 ms tick, plus a get_delta_ms() counter and a small timer_target/timer_check API. All timing in the project (debouncing, gesture windows, UI cadence) is built on this one source.

Token-pasting GPIO macros. gpio.h uses the preprocessor's ## operator so you can write dio_high(PORTB, LED) and dio_init_in(B, BTN) — register names are composed at compile time, keeping the call sites readable.

LED as a feedback channel. btn_feedback() flashes the LED on a click/double-click and keeps it lit while a press is inside the "hold zone," so the user gets a live cue of how far a hold is from triggering.

UART for debugging. Every byte written to the LCD is echoed to the UART in hex (%02x\r\n), and a commented-out block in main.c logs the detected gesture — handy for tracing button behavior on the bench. The firmware greets with !!Super Counter!! at 9600 baud.

Building & Flashing

The project uses Meson with an AVR cross-file:

# configure (once)
meson setup build --cross meson-avr.ini

# build the firmware + .hex
ninja -C build

# flash via avrdude (Arduino bootloader, /dev/ttyUSB0)
ninja -C build flash

meson.build targets ATmega328P at 16 MHz (F_CPU), builds with -Os (buildtype=minsize), and generates dio.hex with avr-objcopy. The flash target calls avrdude through doas in Arduino mode at 115200.

Future Improvements

The current control model is fully button-driven. The next step is a UART command interface: the firmware will accept commands from a PC over the same UART channel to:

  • switch the counter mode (menu / up / down / setter / manual),
  • start, pause, or reset a mode's state,
  • read the current value, mode, and running state.

This opens the door to PC-driven automation and remote control without changing the hardware or the mode logic — the gesture driver remains for standalone use.

Known Limitations

  • The menu "previous" selection uses % 3 on a decremented value; C truncates toward zero, so double-clicking from the first option temporarily lands on an invalid index (fixed by the next click).

  • Everything is driven from one button, so some gestures overlap between modes by design — the mode tables above are the contract.

About

Multi-mode counter tool for ATmega328P (embedded C): gesture-driven button, LED feedback, LCD 1602A, UART debug

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages