ADC Trigger Interrupt on Threshold

The official PYBD running MicroPython, and its accessories.
Target audience: Users with a PYBD
Post Reply
User avatar
FraggaMuffin
Posts: 9
Joined: Sat Nov 11, 2017 1:10 am
Location: Melbourne, Australia
Contact:

ADC Trigger Interrupt on Threshold

Post by FraggaMuffin » Tue May 05, 2020 4:47 am

On a Pyboard D with STM32F767, is it possible to configure an ADC as a comparator?
In other words: to trigger an interrupt on an ADC measuring above, or below a configurable threshold?

From: STM32F767 Datasheet
2.41 Analog-to-digital converters (ADCs)
The ADC can be served by the DMA controller. An analog watchdog feature allows very precise monitoring of the converted voltage of one, some or all selected channels. An interrupt is generated when the converted voltage is outside the programmed thresholds.
No additional components
I'd much rather avoid a PCB change requiring new components, as that will incur another round of EMC testing.
Otherwise the ESP8266 forum discussion: On analog pin change? would be an option.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by pythoncoder » Tue May 05, 2020 5:23 am

As I said in the thread you referenced, this chip functionality is not currently supported. That doesn't mean it can't be accessed given that you're evidently comfortable reading chip datasheets. You can access the chip at register level: see the stm module. Or you could go further and implement support yourself in C and submit a PR - it would be a useful addition.

You'll find many examples of using the stm module in this forum or you could look at this example.
Peter Hinch
Index to my micropython libraries.

User avatar
FraggaMuffin
Posts: 9
Joined: Sat Nov 11, 2017 1:10 am
Location: Melbourne, Australia
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by FraggaMuffin » Tue May 05, 2020 11:06 pm

The thread I referenced was in the ESP8266 forum, so I was hoping your mention of it "not currently supported" being limited to that architecture.

I'll do some experimenting, and post back here with what I find. If anything's worth a PR, I'll contribute that.
In the meantime, anyone else's experience is welcome.

Thanks for the quick response Peter :)

User avatar
FraggaMuffin
Posts: 9
Joined: Sat Nov 11, 2017 1:10 am
Location: Melbourne, Australia
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by FraggaMuffin » Wed May 13, 2020 12:13 am

I believe I've got most of what I need (except time) ;)
With the link you gave me Peter, and the example in pyb.ADC for v1.9.3.

Is it possible to assign a python callback to an interrupt vector?... is that possible from within python?

For example:

Code: Select all

import pyb
import stm

# ... configure ADC for continuous sampling, ADC watchdog, and thresholds

pin = pyb.Pin('X1', pyb.Pin.OUT)
def callback(interrupt):
    pin.high()

# this wouldn't work...
stm.mem32[NVIC + ADC_IRQn] = id(callback)
# but what would?

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by pythoncoder » Wed May 13, 2020 5:29 am

I don't know how you'd set about that. It might be difficult: interrupts have to be delayed until the MicroPython VM is in a suitable state. At the Python level interrupts occur between instructions to ensure that data structures are in an acceptable state. There are also the usual issues of saving and restoring context. You'd have to study the existing C code for interrupt support.

Have you considered an alternative approach? I don't know what sort of response time you need, but if a response at the level of a few tens of ms is acceptable you could write a device driver in Python which made the ADC look like a stream I/O device. Then you could access it with the uasyncio StreamReader class. Fundamentally this uses polling, but the polling is done by the select module. This is written in C so the polling is efficient; it is also transparent to the application.

The solution you seek would be several orders of magnitude faster. I fear it would also be a couple of orders of magnitude harder to write...

For how to implement a uasyncio solution see this link.
Peter Hinch
Index to my micropython libraries.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by dhylands » Wed May 13, 2020 1:07 pm

FraggaMuffin wrote:
Wed May 13, 2020 12:13 am
I believe I've got most of what I need (except time) ;)
With the link you gave me Peter, and the example in pyb.ADC for v1.9.3.

Is it possible to assign a python callback to an interrupt vector?... is that possible from within python?

For example:

Code: Select all

import pyb
import stm

# ... configure ADC for continuous sampling, ADC watchdog, and thresholds

pin = pyb.Pin('X1', pyb.Pin.OUT)
def callback(interrupt):
    pin.high()

# this wouldn't work...
stm.mem32[NVIC + ADC_IRQn] = id(callback)
# but what would?
The problem is that the interrupt vector table is in flash, so the callbacks are determined at the time that the firmware is linked. It is possible to write interrupt callback routines in micropython but it requires that the core interrupt handler be written in C, and that C code has a registration function to register the callback. In order for your ADC example to work, it would require that the ADC module have a way of registering the callback.

For example, this demonstrates a timer IRQ written in MicroPython: https://github.com/dhylands/upy-example ... eat_irq.py

User avatar
FraggaMuffin
Posts: 9
Joined: Sat Nov 11, 2017 1:10 am
Location: Melbourne, Australia
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by FraggaMuffin » Fri May 15, 2020 2:03 am

The requirement is for ~2.5ms reaction time, so that's achievable with polling.
However, the async scheduler is already heavily loaded with high-level tasks, so I can't rely on that.

I've settled for a design that will:
  • Configure ADC to read cyclically
  • pyb.Timer interrupt to check AWD flag, to run every 2ms
Some obligatory borderline psudo-code to illustrate:

Code: Select all

# Half-baked configuration (UNTESTED)
stm.mem32[stm.ADC + stm.ADC_HTR] = <upper threshold>
stm.mem32[stm.ADC + stm.ADC_LTR] = <lower threshold>
stm.mem32[stm.ADC + stm.ADC_CR1] = 11 + more  # AWDSGL | AWDEN | AWDCH=11 | (incomplete)
stm.mem32[stm.ADC + stm.ADC_CR2] = 0x4003  #  SWSTART | CONT | ADON

# Timed interrupt callback
def callback(interrupt):
    if stm.mem32[stm.ADC + stm.ADC_SR] & 0x01: # AWD is set
        do_stuff()
        stm.mem32[stm.ADC + stm.ADC_SR] &= ~0x01  # clear AWD
I don't have time right now, but I'll post code that actually works when I've worked on it a bit more seriously.

Thank you Peter and Dave for your help!

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ADC Trigger Interrupt on Threshold

Post by pythoncoder » Fri May 15, 2020 8:02 am

2ms is definitely too fast for a uasyncio solution, but you might be able to do it without register hacking.

Given a timer running at 500Hz the callback would read the ADC and compare its value to the threshold. If exceeded it would use micropython.schedule to run a callback - this overrange callback would run in a normal context so would not be subject to the constraints of an ISR. On a Pyboard D 2ms should provide plenty of time to do some basic processing and to launch the callback.

Code might be needed to prevent the overrange callback being scheduled while still running. Or you might implement hysteresis in the measurement. I think this issue will arise in any solution: how do you want it to behave if the value goes out of range and stays there for multiple samples?
Peter Hinch
Index to my micropython libraries.

Post Reply