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

TLV2556: a pipelined external ADC

Twelve bits and eleven channels over SPI — where the result you read belongs to the channel you asked for last time.

The pipeline

The TLV2556 is a 12-bit, 11-channel, 200-kSPS ADC with an internal reference, on SPI mode 0. Each I/O cycle clocks an 8-bit command in on DATA IN — top four bits select the analog input or a command, low four are configuration register 1 — while simultaneously clocking the previous conversion's result out on DATA OUT, MSB first.

The converter is pipelined: the result of a channel you address now arrives on the next cycle. Read the datasheet's timing naively and you will attribute every sample to the wrong channel — a bug that produces entirely plausible numbers.

Read hides this by priming the conversion, waiting it out, then reading it back, so the value you get belongs to the channel you asked for.

type Sample       is range 0 .. 4095;
type Analog_Input is ...;                                   --  11 channels
type Reference    is (Internal_4096mV, Internal_2048mV, External);

The internal reference is the reason to choose this part over the on-chip SAR ADC: a known 4.096 V or 2.048 V full scale gives you an absolute voltage, where the ESP's own ADC gives a raw code needing per-chip calibration.

Sharing the bus

Like W25Q, chip select is application-driven through an SPI CS_Select callback (active-low here), so the ADC shares a bus with other devices. The driver always uses 16-clock (2-byte) transfers with unipolar, MSB-first output.

The in-tree example is a genuinely good bring-up test: it runs a reference-independent self-test — asking the chip for its internal zero, half-scale and full-scale conversions and checking they come back as 0, 2048 and 4095. That proves the SPI framing, the pipeline handling and the converter itself without needing a known voltage on any input pin.