Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 24 of 56

Timers and pulse counting

A 54-bit counter with an alarm, and four edge counters — the two ways to measure time and events without the CPU watching.

General-purpose timers

Two timer groups, TIMG0 and TIMG1. Each has one 54-bit up/down counter clocked from the APB clock through a 16-bit prescaler, with a programmable alarm. (The per-group watchdogs are separate and this driver does not touch them.)

type Timer_Index is range 0 .. 1;          --  0 = TIMG0, 1 = TIMG1
type Ticks is new Interfaces.Unsigned_64;

procedure Configure (T : in out Timer; Tick_Hz : Positive := 1_000_000);
procedure Start (T : Timer);
procedure Stop  (T : Timer);
procedure Reset (T : Timer);              --  reload to 0, running or not
function  Value (T : Timer) return Ticks; --  latched, then read

procedure Set_Alarm    (T : Timer; At_Ticks : Ticks);
function  Alarm_Fired  (T : Timer) return Boolean;
procedure Clear_Alarm  (T : Timer);

The default 1 MHz tick makes Value read directly in microseconds. Fifty-four bits at that rate is a bit over five centuries, so wrap-around is not a design consideration.

Value latches before reading, which matters on a 54-bit counter sampled by a 32-bit CPU: without the latch you could catch the low word after a carry and the high word before it, and read a time that never existed.

This is not the runtime's clock. Ada.Real_Time.Clock and delay until are served by the runtime's own tick on the systimer — see step 6. These timers are an independent measurement resource for your application, which is exactly what makes them useful for cross-checking the runtime: ./x run esp32s3_timer_count does that against the wall clock.

Alarm_Fired stays set until Clear_Alarm, so a polled loop cannot miss the event between samples.

PCNT: counting edges

Four counter units, each counting into a signed 16-bit counter as edges arrive on its input pin. The classic uses are a tachometer, a flow meter, or a quadrature encoder.

type Unit_Index is range 0 .. 3;

procedure Configure (U : in out Unit; Pin : ESP32S3.GPIO.Pin_Id;
                     Both_Edges : Boolean := False);
function  Count (U : Unit) return Integer;   --  signed; wraps at +/- 32768

By default each rising edge counts; Both_Edges counts falling ones too, doubling the resolution of a symmetric signal.

The counter is 16-bit and wraps at ±32768. On a fast input that is not long: 10 kHz overflows in about three seconds. Poll often enough to catch every wrap, or the count you accumulate will be wrong in a way that looks plausible.

This driver exposes the common "count edges on a pin" case; the per-unit direction-control input and the threshold-event comparators are left at their pass-through defaults.