Files
esp32-learning/04-adc/README.md
2026-06-01 12:51:13 -04:00

77 lines
7.4 KiB
Markdown

## Analog to Digital Converters
![breadboard](breadboard.png)
This project lets you control a couple of LEDs with a potentiometer and a capacitive touch sensor, while reading out the values of the Analog to Digital Converter (ADC) from the potentiometer.
The wire coming off pin 14 in the breadboard is just a bare wire hanging off the side for capacitive touch sensing.
## Lessons Learned
- How potentiometers actually work (surprise: it's more complicated than I thought, as usual)
- How an ADC works
- How the ESP32-S3 ADC in particular works
- How to build a Flash ADC on a breadboard
- How integrated circuits measure capacitance
## Measuring potentiometers
I've had some prior experience working with potentiometers in electric guitars, but never really did much beyond wiring them and comparing the way they colored the tone of the guitar. I knew that they are essentially adjustable resistors, but beyond that, I never really did much with them.
```
GND LOAD SUPPLY
1 2 3
| | |
```
The project connects a potentiometer's pin 3 to the 3.3V power rail and the wiper (pin 2) to the ADC input pin (pin 1) of the ESP32-S3. It reads the computed value of the Analog to Digital Converter from that pin. Reading those values out and printing them on the serial UART was really easy, but I wanted to verify the numbers. The voltage measurement should be easy to check with a voltmeter. I assumed that I could check across pins 1 and 3 and, as I adjust the potentiometer, I would see the output voltage on the GND side adjust.
Wrong! The voltage across pins 3 and 1 stays constant as the brush moves along the potentiometer's range. The difference can only be measured across the load and supply pins (1 and 2). This is because the wiper physically moves between the points 1 and 2 which is how the resistance changes, so there will only be a visible difference between the wiper and ground.
That didn't make sense with my previous understanding of potentiometers as "adjustable resistors", because that understanding was incomplete. Potentiometers are adjustable resistors (just like a rheostat), but with 3 pins, they're acting as two resistors in series like a voltage divider. Inside a potentiometer, there's a resistive element that stretches all the way from pin 3 to pin 1. Think of it like a resistor that's been stretched out. The wiper, attached to pin 2, is less like a resistor adjuster, and more like a probe that contacts the resistive material at various points along its length. The further away from 3 and closer to 1 we get, the higher that resistance value gets. The wiper itself isn't actually introducing significant resistance, and is not therefore significantly changing the potential difference between 1 and 3. It's just putting in a tap at the point where the wiper contacts the resistive track.
Now if you connect the load pin to something that is ACTUALLY drawing a load, rather than something like an ADC that really isn't drawing much at all, then current will be pulled from the wiper contact point and the voltage observed at the wiper pin will sag. This isn't really a necessary detail for this project, but it's something to keep in mind for future projects where we might put something like a motor on a potentiometer.
## How an ADC works
The ESP32 GPIO pins understand `HIGH` and `LOW`. So if we want to say "How wide has the user opened that potentiometer over there?", the GPIO pins can only tell us "fully open" or "fully closed" (keeping in mind that the specific voltage that the GPIO pin understands as `HIGH` and `LOW` may or may not directly correspond to the fully open and fully closed position of the potentiometer). To do that, we need to use an Analog to Digital Converter (ADC) to express the analog value as a magnitude between 0 and V, rather than ON and OFF.
An ADC measures the voltage on an input and returns a digital value as an integer having precision P in bits. So if an ADC returns a reading with a precision of 12 bits, the reading can range from 0 (being 0v) to 4095 (being 100% of the possible input voltage). An ADC is a collection of circuits that perform this job as a single package, whether integrated on the chip, or as a separate discrete component. There's a bunch of different ways this can be done, but the general theory is that the ADC is a circuit that takes an input voltage and expresses it as an integer representing a quantity between 0 and 100% of the potential maximum.
One of the simplest ADCs to implement is called a Flash ADC, and that one is simple enough we can build it on a breadboard. I can't build this one yet - I need to come back to this when some components arrive in the mail.
- Need to come back and build a flash ADC when my components get here
## How the ESP32-S3 ADC works
I really wanted to understand how the ESP32-S3 in particular accomplishes this. So I went through a deep dive of the reference manual, plus some googling, plus some GPT interpretation of the TRM, and figured some things out.
![hold on to your butts](holdontoyourbutts.gif)
The ESP32-S3 has two ADCs on the package that both perform in the same fashion. Per the technical reference manual
> Two 12-bit Successive Approximation ADCs (SAR ADCs) controlled by five dedicated controllers that can input analog signals from total of 20 channels. The SAR ADCs can operate in a high-performance mode or a low-power mode.
![ESP32-S3 SAR ADC diagram](esp32sdcadc.png)
There are two ADCs on the ESP32-S3, being ADC1 and ADC2. They can both be operated in a low power mode (by driving them with the real-time clock controller) or in a high power mode (using the digital controller). ADC2 is shared among many peripherals such as WiFi, and so it has an "arbiter" hardware component that controls who gets to use it at which times and which controller drives it. ADC1 appears to be dedicated to userland programs running on the CPU, so it does not have a separate hardware arbiter, but you can select which driver to use by controlling a hardware register.
### ADC hardware registers
The ADC hardware registers are mapped into memory between `0x6000_8000` and `0x6000_8FFF`. These memory addresses are memory mapped in from the low power mode peripherals in the address controller. Basically, when something requests data from those memory regions, the memory address controller redirects the request out to the appropriate peripheral. The peripheral in question then manages the requested item, which is usually stored in a hardware register inside the peripheral. The ADC registers that are of most interest to this particular explanation are:
| ADC | Register | Address | Purpose |
|-----|----------|---------|---------|
| ADC1|`SENS_SAR_MEAS1_MUX_REG`|`0x6000_8010`| Selecting the RTC or Digital controller for ADC1 operations|
| ADC1|`SENS_SAR_MEAS1_CTRL2_REG`|`0x6000_800C`| Activating ADC1 and reading the output of the operation|
| ADC1|`SENS_SAR_READER1_CTRL_REG`|`0x6000_8000`| Controlling ADC1 data and sampling|
| ADC1|`SENS_SAR_ATTEN1_REG`|`0x6000_8014`| Setting the attenuation for ADC1|
| ADC2|`SENS_SAR_MEAS2_MUX_REG`|`0x6000_8024`| Selecting the RTC or Digital controller for ADC1 operations|
| ADC2|`SENS_SAR_MEAS2_CTRL2_REG`|`0x6000_8030`| Activating ADC1 and reading the output of the operation|
| ADC2|`SENS_SAR_READER2_CTRL_REG`|`0x6000_8034`| Controlling ADC1 data and sampling|
| ADC2|`SENS_SAR_ATTEN2_REG`|`0x6000_8038`| Setting the attenuation for ADC1|
| Both|`SENS_SAR_POWER_XPD_SAR_REG`|`0x6000_803C`| SAR ADC Power Control|
## Measuring Capacitance