RFC: Hardware API: finalising machine.Pin

C programming, build, interpreter/VM.
Target audience: MicroPython Developers.
User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.Pin

Post by deshipu » Sat Oct 22, 2016 8:16 pm

Some more notes about the current spec:

* I wonder if it should be obligatory to set a pin to ALT mode when there is any peripheral (such as I2C, timer, ADC) using it? What about the software (bit-banging) peripherals? Should it be possible to set the pin to the ALT mode explicitly, or should it only be done by initializing the corresponding peripheral?
* Should we use None as the "no value provided" values in constructor/init? It's kind of traditional in Python, and would be useful when calling the function with variables as arguments. Using sometimes None and sometimes -1 could be surprising.

User avatar
dbc
Posts: 89
Joined: Fri Aug 28, 2015 11:02 pm
Location: Sunnyvale, CA

Re: RFC: Hardware API: finalising machine.Pin

Post by dbc » Sat Oct 22, 2016 8:59 pm

dhylands wrote:I think that we need to address the more fundamental question, should a port be allowed to add features to say machine.Pin?

For example, many ports support the notion of setting drive-strength. Would it be acceptable for such a port to add a drive_strength parameter to init, and perhaps an accessor/setter method?
Extensibility is essential for porting to new platforms. Also, the spec should call out features as "required for all ports" or "optional".

I think we are talking about the "required for all ports" functionality here in this thread. Code that imports "machine" should be able to count on finding all the "required"s. If not finding something optional raises an exception, that is exactly the way the world should work. Importing machine and not finding something required is a bug in that machine module.

(Note: "ports" below refers to porting of the code, not a hardware port or an instance of machine.Port() -- ain't English grand?)

Ports should be able to add methods. Ports can pick non-conflicting names, and if the core API changes in the future such that it creates a name conflict, too bad for the port -- the port is the one that must change.

Ports should be able to add init parameters, but IMHO only as kwargs. The definition of the positional parameters of a core machine API class __init__() method should be the private preserve of the core API.

chrismas9
Posts: 152
Joined: Wed Jun 25, 2014 10:07 am

Re: RFC: Hardware API: finalising machine.Pin

Post by chrismas9 » Sun Oct 23, 2016 2:59 am

Sorry I digressed from Pin. Back to Pin.

+1 for toggle and/or pin.out_value() and open drain.
Do we need pin.out_value() to get the output buffer value of a pin?

• I didn't even realise that pin.toggle() was specified in these specs :) Do we need it? It's listed in the specs as "optional" but I think it's a bad idea to have it optional because then some ports will have it and others will not.
I changed my mind to vote when I realised the use case that I use quite often. The application is driving blue, white and some green LEDs or high power solid state relays. The blue LED on pyboard is a rare species chosen for its low forward drop of about 3V max. Most high efficiency LEDs have a 3.5 to 3.9V forward drop and won't work from 3.3V. High power SSR's often have two photovoltaic coupler LEDs in series (to generate 10V gate drive to a high power MOSFET, eg both halves of PVI5033) and require about 4V to turn them on.

I drive these from 5V using a 5V tolerant open drain pin. When you set the pin low it goes to between 0V and probably 0.5V dpending on the load current. When you set the pin high it goes to about 1V due to the series resistor from 5V ad the ~4V drp from the LED(s). pin.value() will read low in both states.
Doing pin.value(not pin.value()) does the same thing, but much slower
This won't toggle a blue LED, it will just turn it off permanently. Hence the need for either toggle or pin.out_value() to use in
pin.value(not pin.out_value())
As for open drain its much easier to document:
"drive the blue LED with an open drain output, to toggle it use pin.toggle()" than
"to turn on the blue led set the pin low, then set it as an output. To turn the blue LED off set it as an input. Make sure you never set the pin high while it is enables as an output. To toggle the blue LED save its state in a variable when you turn it on or off and invert that state when you want to toggle it.

Shadowing the state in a variable is wasteful unless you pack pin states into a bit field. The pin.out_value is a free I bit ram.

A common use case for toggling a LED is an activity indicator. It is common to have an Rx data LED on a Uart to see activity. When you have 4 UARTs and 1 LED you can use pin.toggle to toggle the LED when each URT receives a packet, eg after any CR.

You don't want toggle just in LED function in a periph or driver library because its valid to want to toggle a generic pin such as a relay driver. If that's a 4V SSR then its hard to do without toggle as explained for blue LEDs

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

Re: RFC: Hardware API: finalising machine.Pin

Post by dhylands » Sun Oct 23, 2016 3:08 am

marfis wrote:
1. no LED, SERVO, ACCEL, BUTTON, etc
2. no INVERT for pins. This is not a built in function of the MCU GPIO hardware ...
my view as well. Pins have no inverted characteristics, but module have (LED's etc).

I personally don't care which way "high" or "low" is written (be it set_value, high, low..). Main point is that it is reasonably fast and it's consistent across ports.

pins.csv, pins_af.csv:
The current implementation is limited: it specifies a name for the pin and a list of alternative functions. No way to define IRQ capabilities for example (and other things like slew rate etc). Maybe the CSV files could be rearranged/improved so that it contains this information as well (possibly moving away from CSV files to python data structures)? Using python data structures would allow the file to be frozen so it's available for the uPy VM. That may be nice to have if you want to write generic code for different MCU's.
If you look in the stmhal/build-Xxxx directory, you'll see that we generate a pins_af.py file that provides the alternate functions for all of the pins included in pins.csv.

To the best of my knowledge, at least for STM32 the IRQ functionality isn't really associated with a pin. So I'm not sure how you would even use pins_af.csv to describe it. Things like slew rate often apply to every pin, although I have worked with chips in the past where there were different slew rates available for different pins. But normally this was because the pins had a specialized purpose.
The whole concept could be taken even further by specifying the whole MCU within a CSV/JSON/py file but I think that's not realistic at this point in time (Linux has apparently such an approach, the linux device tree: DTS)
The zinc project, which is an mbed-like OS for rust (another programming language) created something called a platform_tree which was used to describe all of the various bits of functionality for a board.
See: https://github.com/hackndev/zinc/blob/m ... rs#L23-L71 for a simple example.

tannewt
Posts: 51
Joined: Thu Aug 25, 2016 2:43 am

Re: RFC: Hardware API: finalising machine.Pin

Post by tannewt » Sun Oct 23, 2016 4:28 pm

Damien wrote:
tannewt wrote: First, I agree the duplication between value() and low()/high() is unneeded. Furthermore, why is value a function? Shouldn't it be a property? Its weird to me that a function reads and writes based on its arguments. However, `pin.value = True`, `last_pin_state = pin.value` and `pin.value = not pin.value` (for toggle) make more sense to me.
There are many cases of this in the API. Eg machine.freq([value]). See https://github.com/micropython/micropython/issues/378 for background discussion on this point.
The second use is for Digital IO which is `pin = machine.Pin("D13", machine.Pin.OUT); pin.high()`. Instead of changing the constructor arguments to say how you are going to use the pin, why not make it consistent with I2C and make a DigitalWrite object `led = machine.DigitalWrite(machine.Pin("D13"))`?
But then you'd need DigitalRead objects and you wouldn't be able to change the mode of the object from read to write. Pin's are objects which can be useful on their own (eg read/write logic levels), and also used to create other entities (eg i2c, spi). Similarly i2c can be used to create yet higher-level entities, like a driver.
Good point about DigitalRead. Would DigitalIO work better? I know it adds a RAM cost but we're OK paying that cost now with I2C and SPI when its in use. Why not for pin interaction as well?

tannewt
Posts: 51
Joined: Thu Aug 25, 2016 2:43 am

Re: RFC: Hardware API: finalising machine.Pin

Post by tannewt » Sun Oct 23, 2016 4:30 pm

deshipu wrote:Some more notes about the current spec:

* I wonder if it should be obligatory to set a pin to ALT mode when there is any peripheral (such as I2C, timer, ADC) using it? What about the software (bit-banging) peripherals? Should it be possible to set the pin to the ALT mode explicitly, or should it only be done by initializing the corresponding peripheral?
* Should we use None as the "no value provided" values in constructor/init? It's kind of traditional in Python, and would be useful when calling the function with variables as arguments. Using sometimes None and sometimes -1 could be surprising.
I'd rather see ALT mode become port specific with most peripheral classes just doing the right thing internally. I think it gets too varied between MCU designs and having it be port specific allows for clear naming based on the datasheet rather than some generic name.

+1 to None

tannewt
Posts: 51
Joined: Thu Aug 25, 2016 2:43 am

Re: RFC: Hardware API: finalising machine.Pin

Post by tannewt » Sun Oct 23, 2016 4:40 pm

deshipu wrote:
tannewt wrote: First, I agree the duplication between value() and low()/high() is unneeded. Furthermore, why is value a function? Shouldn't it be a property? Its weird to me that a function reads and writes based on its arguments. However, `pin.value = True`, `last_pin_state = pin.value` and `pin.value = not pin.value` (for toggle) make more sense to me.
This is because pin.value() has side effects, both as input (it can change at any time without any code assigning to it) and as output (assigning to it has effects outside of just storing a new value).

Thats interesting! I read the issue you linked to in another post and think the LED example of on, off and toggle as attributes is too literal of conversion of the functions. I think attributes make sense in this case because, if its digital output, you should be able to write and read back the same value. Anything where that isn't the case like an accelerometer or output to UART makes sense as a function to me.
tannewt wrote: Second, it feels to me like there are two main uses of Pin that I think can be split. The first use is as an identifier for a hardware peripheral backed protocol such as I2C to know which pin to use for its I2C such as `I2C(machine.Pin("SCL")...)` In this case the peripheral code will change the underlying pins state and essentially claim it.

The second use is for Digital IO which is `pin = machine.Pin("D13", machine.Pin.OUT); pin.high()`. Instead of changing the constructor arguments to say how you are going to use the pin, why not make it consistent with I2C and make a DigitalWrite object `led = machine.DigitalWrite(machine.Pin("D13"))`? That removes the need to unify alternate functions and modes between ports. Instead, the port can handle the appropriate setup in the use class (DigitalWrite, I2C and SPI etc) and a port specific Pin can expose the exact API of the port separately.
That would force all the code that actually uses the pins to allocate additional objects, which is quite costly on MicroPython.
Only the classes that do direct IO on the Pins would need the additional objects. That seems to be similar to the current uses where you need an object to do I2C for example. I think the value of a much simpler Pin API (just having a reference object) outweighs the cost of an IO object when needed.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.Pin

Post by deshipu » Sun Oct 23, 2016 5:16 pm

tannewt wrote: Would DigitalIO work better? I know it adds a RAM cost but we're OK paying that cost now with I2C and SPI when its in use. Why not for pin interaction as well?
I think this is just building a hierarchy for the hierarchy's sake, and it's not very useful. The idea of a pin mode is easy to understand and maps directly to the underlying mechanisms, making it easy to avoid surprises. The idea of separate input and output objects is artificial, and actually limiting -- it's often useful to switch the mode of the pin inside the program (for instance, when charlieplexing). Having separate ADC or PWM objects (not to mention I2C or SPI), on the other hand, makes sense, because they are actually physically there inside the silicon of the chip.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.Pin

Post by deshipu » Sun Oct 23, 2016 5:32 pm

@tannewt Actually, after a moment of thought, you already have your DigitalIO objects in the current machine API -- they are called Pin.

User avatar
marfis
Posts: 215
Joined: Fri Oct 31, 2014 10:29 am
Location: Zurich / Switzerland

Re: RFC: Hardware API: finalising machine.Pin

Post by marfis » Sun Oct 23, 2016 6:58 pm

dhylands wrote: If you look in the stmhal/build-Xxxx directory, you'll see that we generate a pins_af.py file that provides the alternate functions for all of the pins included in pins.csv.
Yes I'm aware of that. My point was that the existing pin_af.csv / pin.csv combination only resolves the way a pin can be mapped to alternate functions. The existing solution is fine for this sole purpose but it doesn't scale when other properties are added to the pin definitions.
dhylands wrote: To the best of my knowledge, at least for STM32 the IRQ functionality isn't really associated with a pin.
The MSP430 architecture has that - only specific ports are IRQ capable. I haven't checked STM on this matter.
dhylands wrote: Things like slew rate often apply to every pin,..
the intention was to describe that a pin is capable of setting the slew rate. Some MCU architectures cannot do this (I guess that's why it was left as optional in the API spec). But instead of defining this as optional in the API, why not describe this property in the JSON or python file?
dhylands wrote: The zinc project, which is an mbed-like OS for rust (another programming language) created something called a platform_tree which was used to describe all of the various bits of functionality for a board.
See: https://github.com/hackndev/zinc/blob/m ... rs#L23-L71 for a simple example.
Thanks for the link. It's interesting indeed. This project seems to target a similar thing like uPy - Rust running on the bare metal.
They specify a device tree as abstraction layer for their HW API. Something like that would be a nice goal to achieve in the long run for uPy IMO.

Post Reply