Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

dpgeorge
Copy link
Member

This PR implements the beginnings of a revised machine.ADC class which is intended to make the ADC class more standardised across ports. See #3943 and #4213 for background discussion.

The PR here implements the following (simple) API for stm32, esp8266, esp32 and nrf ports:

from machine import ADC

adc = ADC(source) # source can be a pin or channel (eg int)
adc.read_u16() # take a reading, returning a value between 0 and 65535 inclusive

On stm32 the source can be any pin that has ADC support, or a constant ADC.CORE_VREF, ADC.CORE_VBAT, ADC.CORE_TEMP for internal channels. It will pick the correct ADC peripheral that supports that source (eg ADC1, ADC2 or ADC3). Other ports support taking a pin and/or a channel number.

The method read_u16() is named to emphasise the units, that it's an unsigned 16-bit number, and is the raw ADC reading scaled to 16 bits.

The aim of this PR is to make basic ADC functionality available in a consistent way across ports. Other things (eg setting resolution, sample time, selecting ADC peripheral block) can be added later, but at least construction and reading (implemented by this PR) are necessary features and the approach taken here is hopefully obvious and not controversial (up to perhaps the name read_u16). In other words, it should be possible to decide on the API presented here regardless of how additional features (mentioned above) might be implemented and exposed to the user.

Comments/discussion are very welcome.

@carterw
Copy link

carterw commented Aug 26, 2019

I like the idea of returning the type of the ADC data. How do you distinguish between pin and channel on devices that have both pins and channels? Is the 'source' argument compounded? Or just whatever the device can do somehow?

@dpgeorge
Copy link
Member Author

How do you distinguish between pin and channel on devices that have both pins and channels?

For an already-constructed ADC object you can't tell the difference. I don't really see a need for this because a pin source is really just a pin (in analog mode) connected to a specific channel (at least that's how it works on stm32, which has both internal and pin-connected channels).

Is the 'source' argument compounded? Or just whatever the device can do somehow?

It's up to a port/device to decide what the "source" argument means, and the ADC constructor is in general overloaded to accept different types for "source". All ports should allow a pin to be passed in as the source, and the corresponding ADC "channel" (if a port has such a concept) will then be selected. And then usually passing in an integer would select a channel.

@carterw
Copy link

carterw commented Aug 26, 2019

It's up to a port/device to decide what the "source" argument means, and the ADC constructor is in general overloaded to accept different types for "source".

Got it, thanks. I'm working with the ESP32 and an ADC class implementation described here, would you have a look? Apparently there are two SAR's, each associated with a specific set of pins. So it seems like in your implementation the appropriate SAR would be derived from the pin number instead of being specified directly there as 'unit'? There is also a 'chan' argument, not sure what that does.

@dpgeorge
Copy link
Member Author

So it seems like in your implementation the appropriate SAR would be derived from the pin number instead of being specified directly there as 'unit'?

Yes, correct. Although there are currently no plans to implement ADC2 because it's used by the WiFi (but it could fit into this API in the future).

@carterw
Copy link

carterw commented Aug 27, 2019

I use ADC2 in one of my applications, so I would hope that it be included. It uses LoRa instead of WiFi. That ties up a lot of pins for SPI, also using I2C and various other pins for sensors and indicators, so I need access to all of the pins.

Is there a need for a 'deinit' method? I have a situation where I connect an analog sensor to a pin that is set up to interrupt on a 0-1 state transition. I also want to be able to measure the voltage on the pin when an interrupt occurs, and then go back to the state transition mode. Does this require a deinit?

@carterw
Copy link

carterw commented Aug 27, 2019

I see that you are also working on the PWM class, and there you are including a 'block' parameter in the init method. Not the case for ADC?

@dpgeorge
Copy link
Member Author

there you are including a 'block' parameter in the init method. Not the case for ADC?

That PWM proposal shows an alternative way of specifying "blocks". But I do prefer what is proposed in #4213, where a block is exposed as an actual object/class like ADCBlock. That seems to me to be a more powerful approach.

@carterw
Copy link

carterw commented Aug 30, 2019

So in the 4213 PR there are two forms of the ADC class. One is 'generic' and intended to provide basic commonly used setups and the xxxBlock() class would be an alternative with platform-specific settings? That seems reasonable to me.

I'm concerned that the resolution of this class signature semantics issue is taking a long time - going on a year. And until it does get resolved none of the PR's can be completed and closed, it is a log jam. What can be done to move it to resolution?

@dpgeorge
Copy link
Member Author

dpgeorge commented Sep 5, 2019

xxxBlock() class would be an alternative with platform-specific settings?

The ADCBlock class is intended to represent an actual ADC peripheral which does the conversion of the analog value, and has multiple channels (in contrast to ADC which is just a single endpoint/channel). But yes, ADCBlock would probably have more settings/configuration. That's the intention anyway.

@dpgeorge
Copy link
Member Author

dpgeorge commented Sep 5, 2019

I merged this PR in ebacdfa, 625609a, 0e72cc9, 983283a, 9cad134

@dpgeorge dpgeorge closed this Sep 5, 2019
@dpgeorge dpgeorge deleted the machine-adc-read_u16 branch September 5, 2019 12:26
@nevercast
Copy link
Contributor

So it seems like in your implementation the appropriate SAR would be derived from the pin number instead of being specified directly there as 'unit'?

Yes, correct. Although there are currently no plans to implement ADC2 because it's used by the WiFi (but it could fit into this API in the future).

You would fit ADC2 in as ADCBlock only? Perhaps throwing an exception if a machine.ADC is constructed with an ADC2 pin on ESP32? The extra hoop may give sufficient caution regarding conflict with WiFi.

>>> adc = machine.ADC(pin=machine.Pin(4)) # ADC2/0
ValueError: Pin(4) uses the ADC2 peripheral and conflicts with WiFi usage. See http://docs.micropython.org/l/fha7haf for more information, and workarounds.
>>>

@carterw
Copy link

carterw commented Jun 9, 2020

I use an ADC2 pin successfully in an application (loboris port) when WiFi isn't activated. That's because literally all the other ADC-capable pins are already assigned and in use. So really I think there is no reason to not include it, let the developer make the choice.

@nevercast
Copy link
Contributor

What's the consequence when WiFi is in use? Is it just an IDF error?

@carterw
Copy link

carterw commented Jun 9, 2020

I don't know, I reboot with a different setup if I need WiFi and don't activate that pin.

@kevinjwalters
Copy link

kevinjwalters commented Jan 31, 2021

There's some variation in how an ADC sample that's smaller than 16 bits is converted to that width.

A straight shift:

return MP_OBJ_NEW_SMALL_INT(raw << 8 | raw);

Something more elaborate putting some values in the lsbs:

// Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16)
uint32_t u16 = raw << (16 - adc_bit_width) | raw >> (2 * adc_bit_width - 16);

Is that as intended?

@dpgeorge
Copy link
Member Author

There's some variation in how an ADC sample that's smaller than 16 bits is converted to that width.
...
Is that as intended?

Yes that's intended. Both those examples above are the same, it's just that the nrf one is simplified when adc_bit_width=8. It's a Taylor expansion of:

u16 = raw * 65535 / ((1 << adc_bit_width) - 1)

@kevinjwalters
Copy link

Oh, I somehow misread the nrf one as return MP_OBJ_NEW_SMALL_INT(raw << 8), sorry about that! Is there a reason to limit the nRF52 to 8bit? It's a bit noisy at 12bits but that's what CircuitPython runs it at.

I've been having a look at the various Espressif ADCs and we were discussing whether MicroPython applied a correction in the code to the rather shoddy raw ADC output, hence my interest in the code. Some graphs in Adafruit Forums: Feather ADC comparison including 2.6V limited ESP32-S2 if you're interested. I think the regular ESP users all know this but I got surprised by the ESP32-S2 giving some confusing results with an analogue peripheral.

@dpgeorge
Copy link
Member Author

dpgeorge commented Feb 2, 2021

Is there a reason to limit the nRF52 to 8bit?

No reason, it could be changed to 12 bits (and maybe given an option to select the bits, following esp32).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants