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 25 of 56

Analog in: the SAR ADC and capacitive touch

Two 12-bit converters on fixed pins, and fourteen touch channels that measure a pad's capacitance by counting — both living in the RTC domain.

The SAR ADC

Two units, each with up to ten 12-bit channels on fixed GPIOs. The mapping is not configurable, so the pin decides the channel:

ADC1 channel n -> GPIO (n + 1)     --  ch0 = GPIO1  .. ch9 = GPIO10
ADC2 channel n -> GPIO (n + 11)    --  ch0 = GPIO11 .. ch9 = GPIO20

function Channel_Pin (Unit : ADC_Unit; Ch : Channel_Index)
  return ESP32S3.GPIO.Pin_Id;      --  ask, rather than hard-code the arithmetic

Conversions are software-triggered single shots through the RTC controller, each returning a raw 12-bit code:

type Attenuation is (Db_0, Db_2_5, Db_6, Db_12);   --  ~1.1 V .. ~3.3 V full scale
subtype Raw_Value is Natural range 0 .. 4095;

function Read (R : Reader; Ch : Channel_Index;
               Atten : Attenuation := Db_12) return Raw_Value;

Attenuation is per channel and per read, so one unit can serve a 1 V sensor and a 3.3 V divider without reconfiguration between them. The default Db_12 gives roughly the full 3.3 V range; a lower attenuation on a small signal buys real resolution.

The result is a raw code, not a voltage. The driver does not pretend to give you volts, because an accurate conversion needs the per-chip calibration data and the attenuation curve is not linear at the extremes. Cal_Code exposes the self-calibrated initial code and Last_Done whether the most recent conversion completed — both diagnostics for deciding whether a reading is trustworthy.

Capacitive touch

Fourteen channels on GPIO1 .. GPIO14, and the numbering is the one mnemonic you need: touch channel n is wired to GPIO n.

type Channel is range 1 .. 14;
function Pad (Ch : Channel) return ESP32S3.GPIO.Pin_Id;

procedure Setup;                    --  bring the controller up, start the FSM
procedure Enable (Ch : Channel);    --  put the pad in touch mode, add to the scan
function  Read (Ch : Channel) return Natural;     --  latest raw count

Each channel measures its pad's self-capacitance by counting charge/discharge cycles in a fixed window. A finger near the pad raises the capacitance and changes the count. An FSM scans the enabled channels continuously on the RTC timer, so Read returns the latest sample rather than triggering one — it never blocks.

Detection is relative, not absolute: Touched compares a channel's current count against a reference you supply. That reference is board-specific — pad size, overlay thickness and stray capacitance all move it — so sample a known-untouched value at startup rather than hard-coding a threshold from someone else's board.

Touch needs no tasking runtime: it is register pokes into the RTC/SENS domain, so unlike most of the HAL it works under light-tasking too.