ULP ADC Sampling

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
Post Reply
dairykillsme
Posts: 1
Joined: Sun Jan 31, 2021 4:41 pm

ULP ADC Sampling

Post by dairykillsme » Sun Jan 31, 2021 5:08 pm

Hey folks! I'm pretty new to micropython, but I have been loving it so far. However, I have been programming the ESP32 through the ESP IDF for a little bit longer, so I'm a little more familiar with interacting with it through C. I've been messing with the ULP coprocessor and found Thomas Waldmann's awesome library for assembling instructions in python! This has worked great so far and I wrote a short set of instructions for sampling the ADC and saving the measurement to memory. I wrote a class to abstract this and now have a way faster method of sampling one ADC pin that runs in parallel.

Code: Select all

"""This module creates an abstraction for an ADC sampling program
running concurrently on the ESP32 ULP coprocessor"""

from esp32 import ULP
from machine import mem32, ADC, Pin
from esp32_ulp.__main__ import src_to_binary

class ULP_ADC:
    SOURCE = """\
data:       .long    0

entry:      move     r3, data     # load data address into r3
            adc      r2, 0, 4     # measure voltage with ADC1 pad 3 and store result in r2
            st       r2, r3, 0    # store r2 in data [r3 + 0]
            halt                  # halt ULP co-processor until woken up again
"""

    ULP_MEM_BASE = 0x50000000
    ULP_DATA_MASK = 0xffff  # ULP data is only in lower 16 bits

    LOAD_ADDR = 0
    ENTRY_ADDR = 4

    def __init__(self, gpio):
        self.binary = src_to_binary(ULP_ADC.SOURCE)
        # we only use this object to configure the ADC.
        # we don't actually need it for sampling
        self.ulp = ULP()
        self.gpio = gpio

    def start(self, wakeup_period_cyc, atten, width):
        self.ulp.set_wakeup_period(0, wakeup_period_cyc) # use timer 0
        self.ulp.load_binary(ULP_ADC.LOAD_ADDR, self.binary)

        mem32[ULP_ADC.ULP_MEM_BASE + ULP_ADC.LOAD_ADDR] = 0x1000
        self.ulp.run(ULP_ADC.ENTRY_ADDR)

        self.adc = ADC(Pin(self.gpio))
        self.adc.atten(atten)
        self.adc.width(width)

    def get_reading(self):
        return mem32[ULP_ADC.ULP_MEM_BASE + ULP_ADC.LOAD_ADDR] & ULP_ADC.ULP_DATA_MASK
I am running into trouble with configuring the adc though. All the samples I get from this are 9 bits or less. Strangely configuring width through the micropython adc interface does actually change the width but they are 9,8,7, and 6 bit values instead of the configured 12,11,10 or 9. Reading ESP documentation, I wonder if this is because ad1_ulp_enable() is not being called.

I know a small amount about the ESP32 low level operations, but I'm starting to get lost trying to solve this problem. I would appreciate a second set of eyes to point out obvious mistake, or someone who know the granularity of working with the ESP32. I'm also curious if there is a way to call the ad1_ulp_enable() function.

User avatar
viktor.holova
Posts: 1
Joined: Tue Jul 20, 2021 1:59 pm

Re: ULP ADC Sampling

Post by viktor.holova » Tue Jul 20, 2021 2:40 pm

dairykillsme wrote:
Sun Jan 31, 2021 5:08 pm
I am running into trouble with configuring the adc though. All the samples I get from this are 9 bits or less. Strangely configuring width through the micropython adc interface does actually change the width but they are 9,8,7, and 6 bit values instead of the configured 12,11,10 or 9. Reading ESP documentation, I wonder if this is because ad1_ulp_enable() is not being called.
I am not completely sure what issue you are having as far as the numbers but I did confirm that in order for ULP ADC to return proper numbers we do need to call ad1_ulp_enable()

This call helped in two ways: 1) started returning numbers that make sense (based on set atten and width) 2) WiFi/BLE stopped causing ULP ADC numbers to jump around

I implemented following small patch for my local patched MicroPython firmware in ports/esp32/machine_adc.c. You can easily do the same if you know how to recompile MicroPython firmware (follow this README how to do it using Docker)

1) Added new method:

Code: Select all

STATIC mp_obj_t madc_ulp_enable(mp_obj_t self_in) {
    //madc_obj_t *self = self_in;
    adc1_ulp_enable();
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_ulp_enable_obj, madc_ulp_enable);
2) added following element to the "STATIC const mp_rom_map_elem_t madc_locals_dict_table[]" array:

Code: Select all

{ MP_ROM_QSTR(MP_QSTR_ulp_enable), MP_ROM_PTR(&madc_ulp_enable_obj) },

Then your MicroPython code would look like following example. Run this before starting your ULP ADC program.

Code: Select all

adc = machine.ADC(machine.Pin(32))
adc.ulp_enable()     #calls adc1_ulp_enable() in C++

Post Reply