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

Skip to content

Conversation

dpgeorge
Copy link
Member

@dpgeorge dpgeorge commented Oct 4, 2018

Based on the discussion in #3943 here is my proposal for a revised machine.ADC class which is intended to be implemented by all ports to make the ADC class more standardised. The changes here include a new machine.ADCBlock class as well, which is used to allow further configuration of ADC if a port supports it.

In summary the new interface is:

ADC(id, *, sample_ns, gain_mult, gain_div) # create ADC object
ADC.init(sample_ns, gain_mult, gain_div) # initialise
val = ADC.read_u16() # read value between 0-65535
val = ADC.read_v() # read voltage, float
val = ADC.read_mv() # read mV, integer

ADCBlock(id, *, bits) # create ADC peripheral object
ADCBlock.init(bits) # initialise
adc = ADCBlock.connect(channel) # connect to a channel
adc = ADCBlock.connect(source) # connect up a pin or other object
adc = ADCBlock.connect(channel, source) # connect a channel to a pin

The API is split up this way into two classes (ADC and ADCBlock) so that basic and common functionality is easily available in the ADC class, and extended functionality is in ADCBlock. A given port only needs to implement ADC if that is all it can do, and already that gives 80% of the functionality. The ADC class is the one that would be used most of the time.

If a port wants to expose more control, and the user needs to use it, then the ADCBlock class is there for that. It allows to specify the resolution, and which channels are connected to which pins.

All existing ports should be able to fit into this model without a problem. In particular the gain settings can be used by nrf and esp32. stm32 can use the sample time.

It should be possible to update the ports in a backwards-compatible way to this new API because most of the methods are new and don't clash with existing ones. ADCBlock is completely new so won't clash with anything.

There is room for extensions to this API: ADC can have additional methods like read_u24() and read_uv(), and ADCBlock might get methods to read internal channels, like voltage references and temperature.

Note: it might be easier to read the diff of this PR in split view.

@pfalcon
Copy link
Contributor

pfalcon commented Oct 5, 2018

val = ADC.read_v() # read voltage, float
val = ADC.read_mv() # read mV, integer

Why there's need for duplicate functions? Just read_mv() should be enough and will work on any port. (Btw, cc3200 is very good port to have in the mainline, should not be dropped.) And that's if "ADC" really should be able to read volts, instead of just doing generic analog to digital conversion.

And was mentioned in previous discussions, "read" is a stream interface verb, if there's another easy alternative, it should be used, and here get_u16, etc. is an obvious choice.

@pfalcon
Copy link
Contributor

pfalcon commented Oct 5, 2018

split up this way into two classes (ADC and ADCBlock)

Well, there's no pattern of this "Block" suffix in the machine API, while the issue of "basic" vs "advanced" control of peripherals definitely exists. And makes sense to consider whether this "Block" naming would scale. For example, why not use "ADCExt"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remember this pattern for the next time I2C/SPI API is discussed. Because with I2C, it should be the same - there should be a way to create an "I2C connection" object, over which normal stream interface works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I2C doesn't work with a stream interface because it's inherently a message based bus.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I2C doesn't work with a stream interface because it's inherently a message based bus.

There's nothing in stream interface per se which precludes it working with a stream of messages. It's a simple, powerful, reusable, composable abstraction. (Though yes, currently, mostly stream of bytes mode is well exercised with it. And yes, there're potential drawbacks with too much generalization and packing too much semantics in one interface, but issues with inconsistent design is far more numerous and obvious.)

@dpgeorge
Copy link
Member Author

Why there's need for duplicate functions? Just read_mv() should be enough and will work on any port.

Because adc.read_v() will be the main, convenience one that is used at the REPL and in quick scripts (the reason why i2c.readfrom exists, as well as i2c.readfrom_into), and because a float will give more precision than an integer in millivolts. Ports without floating point can just return an integer for adc.read_v().

And that's if "ADC" really should be able to read volts, instead of just doing generic analog to digital conversion.

As talked about in #3943 (comment), a lot of actual ADCs provide a calibrated reading, and this seems the natural way to get such a calibrated value (and if it's not calibrated automatically then it would need to be done manually in the code to make sense of the measured value).

And was mentioned in previous discussions, "read" is a stream interface verb, if there's another easy alternative, it should be used, and here get_u16, etc. is an obvious choice.

Read was chosen because it is an obvious choice: it's making a reading of the voltage, doing an action. "Get" implies that it's just a constant value that can be retrieved, and that getting twice in a row would yield the same value (which it won't).

Using read keeps things more consistent because there's no need to remember if this particular class uses read, or get, or something else. Everything uses read, no need to think, muscle memory typing it is the same.

If there was an extension added to read multiple values at once (eg over time, like stm32 has read_timed()) then read as a verb makes more sense because it's doing a reading of many values.

And read_u16() has the same signature and return value as it would if it were a stream method, so why not use it here?

@dpgeorge
Copy link
Member Author

Well, there's no pattern of this "Block" suffix in the machine API, while the issue of "basic" vs "advanced" control of peripherals definitely exists. And makes sense to consider whether this "Block" naming would scale. For example, why not use "ADCExt"?

Because it's not an extension to ADC. ADCBlock is supposed to represent an actual entity, the ADC converter block itself. Really ADC and ADCBlock should be ADCChannel and ADC respectively (like it is currently in cc3200), but I wanted to make it simpler for the vast majority of cases where only the "channel" is needed, and for ports that don't want to/can't expose the concept of ADCBlock. So ADC is an endpoint that reads values, and ADCBlock is the super-entity that "contains" ADC's. On some MCUs there can be multiple ADCBlock's, each representing one of the converters.

@pfalcon
Copy link
Contributor

pfalcon commented Oct 13, 2018

... ADCBlock is supposed to represent an actual entity, the ADC converter block itself. ...

Ok, sounds good. But another question isn't answered - is it a new pattern reusable across machine API, or just random adhoc case for ADC?

@pfalcon
Copy link
Contributor

pfalcon commented Oct 13, 2018

Because adc.read_v() will be the main, convenience one that is used at the

Well, "main" and "convenience" contradict each other. "Main" should be something which is consistent and reusable across ports, and "convenience" can go into "from easy_hw import *".

Ports without floating point can just return an integer for adc.read_v().

Yes, that's the point - everyone will use it, and it won't be usable on non-FP ports. And (AFAIK) it's the first case of adding float func to machine API, and opens black hole with this stuff. And I'm not aware of any C low-level lib/HALs with return floats, and the promise was that MicroPython tries to not be much worse than C (and yes, using floats is worse - more overhead, slower operation).

@pfalcon
Copy link
Contributor

pfalcon commented Oct 13, 2018

"Get" implies that it's just a constant value that can be retrieved, and that getting twice in a row would yield the same value (which it won't).

Why suddenly? "Get" is a standard accessor name, there's an "ADC" block, it maintains a current value, you use "get" to get it.

Using read keeps things more consistent because there's no need to remember if this particular class uses read, or get, or something else.

Easy to remember - "read" is reserved for streams and stream-likes, everything else tries to use "get".

Everything uses read, no need to think, muscle memory typing it is the same.

That's exactly a danger of too much generalization, where it may lead to confusion or at least less clarity.

@pfalcon
Copy link
Contributor

pfalcon commented Oct 13, 2018

(I obviously don't try oppose it in any way, just trying to provide a all-rounded review, including "devil's advocate" one. Because fairly speaking, nobody else cares how it's designed. The biggest issue IMHO is tainting machine API with floats. I'd bring an argument that if there's desire for cuteness, there's already CircuitPython API as an alternative, so it only makes to stick with "efficiency" requirement of the original machine API.)

@dpgeorge
Copy link
Member Author

... ADCBlock is supposed to represent an actual entity, the ADC converter block itself. ...

Ok, sounds good. But another question isn't answered - is it a new pattern reusable across machine API, or just random adhoc case for ADC?

It could be reused for PWM and DAC, with PWMBlock and DACBlock. PWMBlock is very similar to Timer though. I guess anything that has the concept of "channel" could have a block that represents a combination of "channels".

An alternative to ADCBlock would be to both:

  1. allow to specify bits in the ADC constructor
  2. allow to specify block in the ADC constructor, to have finer control over the ADC channel that is used for the ADC object.

See #4237 for a proposal about machine.PWM which is similar to ADC (although the true counterpart to ADC is DAC) and shows the idea of a using block argument instead of PWMBlock.

@dpgeorge
Copy link
Member Author

dpgeorge commented Sep 5, 2019

Renaming of old machine.ADC to machine.ADCWiPy was done in 8a23723, and initial specification of new machine.ADC class was done in e509da2

This PR is now rebased against those changes.

kamtom480 pushed a commit to kamtom480/micropython that referenced this pull request Feb 22, 2021
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Sep 14, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding 
support for calibrated voltage readings and the ADC2 block.
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Sep 14, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding 
support for calibrated voltage readings and the ADC2 block. Resolves 
micropython#6129. Part-resolves micropython#3943.
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Sep 14, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding 
support for calibrated voltage readings and the ADC2 block. Resolves 
micropython#6219. Part-resolves micropython#3943.
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Sep 15, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding 
support for calibrated voltage readings and the ADC2 block. Resolves 
micropython#6219. Part-resolves micropython#3943.
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Sep 15, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding 
support for calibrated voltage readings and the ADC2 block. Resolves 
micropython#6219. Part-resolves micropython#3943.
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Sep 15, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding 
support for calibrated voltage readings and the ADC2 block. Resolves 
micropython#6219. Part-resolves micropython#3943.
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Oct 18, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding
support for calibrated voltage readings and the ADC2 block. Resolves
@dpgeorge dpgeorge added the docs label Nov 30, 2021
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Dec 2, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding
support for calibrated voltage readings and the ADC2 block. Resolves
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Dec 13, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding
support for calibrated voltage readings and the ADC2 block. Resolves
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Dec 21, 2021
Rework the ADC implementation to use the micropython#4213 API (mostly) adding
support for calibrated voltage readings and the ADC2 block. Resolves
jonathanhogg added a commit to jonathanhogg/micropython that referenced this pull request Jan 13, 2022
Rework the ADC implementation to use the micropython#4213 API (mostly) adding
support for calibrated voltage readings and the ADC2 block. Resolves
@dpgeorge dpgeorge force-pushed the docs-standardise-adc branch 2 times, most recently from 26ba500 to 644df4e Compare January 21, 2022 04:54
The new ADC methods are: init(), read_uv() and block().

The new ADCBlock class has methods: init() and connect().

See related discussions in micropython#3943, micropython#4213.

Signed-off-by: Damien George <[email protected]>
@dpgeorge dpgeorge force-pushed the docs-standardise-adc branch from 644df4e to 4d2f487 Compare January 21, 2022 11:35
@dpgeorge
Copy link
Member Author

I've updated this based on discussion in #7788. The updated API looks like this:

ADC(id, *, sample_ns, atten) # create ADC object
ADC.init(*, sample_ns, atten) # initialise
block = ADC.block() # get the associated ADCBlock
val = ADC.read_u16() # read value between 0-65535
val = ADC.read_uv() # read uV, integer

ADCBlock(id, *, bits) # create ADC peripheral object
ADCBlock.init(*, bits) # initialise
adc = ADCBlock.connect(channel) # connect to a channel
adc = ADCBlock.connect(source) # connect up a pin or other object
adc = ADCBlock.connect(channel, source) # connect a channel to a pin

@dpgeorge dpgeorge merged commit 4d2f487 into micropython:master Jan 21, 2022
@dpgeorge dpgeorge deleted the docs-standardise-adc branch January 21, 2022 11:39
leifbirger pushed a commit to leifbirger/micropython that referenced this pull request Jun 14, 2023
The new ADC methods are: init(), read_uv() and block().

The new ADCBlock class has methods: init() and connect().

See related discussions in micropython#3943, micropython#4213.

Signed-off-by: Damien George <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants