RFC: Hardware API: finalising machine.Pin
Re: RFC: Hardware API: finalising machine.Pin
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 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.
Re: RFC: Hardware API: finalising machine.Pin
Extensibility is essential for porting to new platforms. Also, the spec should call out features as "required for all ports" or "optional".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?
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.
Re: RFC: Hardware API: finalising machine.Pin
Sorry I digressed from Pin. Back to Pin.
+1 for toggle and/or pin.out_value() and open drain.
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.
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
+1 for toggle and/or pin.out_value() and open drain.
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.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 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.
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 inDoing pin.value(not pin.value()) does the same thing, but much slower
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
Re: RFC: Hardware API: finalising machine.Pin
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.marfis wrote:my view as well. Pins have no inverted characteristics, but module have (LED's etc).1. no LED, SERVO, ACCEL, BUTTON, etc
2. no INVERT for pins. This is not a built in function of the MCU GPIO hardware ...
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.
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 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.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)
See: https://github.com/hackndev/zinc/blob/m ... rs#L23-L71 for a simple example.
Re: RFC: Hardware API: finalising machine.Pin
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?Damien wrote: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.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.
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.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"))`?
Re: RFC: Hardware API: finalising machine.Pin
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.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.
+1 to None
Re: RFC: Hardware API: finalising machine.Pin
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.deshipu wrote: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).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.
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.
That would force all the code that actually uses the pins to allocate additional objects, which is quite costly on MicroPython.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.
Re: RFC: Hardware API: finalising machine.Pin
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.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?
Re: RFC: Hardware API: finalising machine.Pin
@tannewt Actually, after a moment of thought, you already have your DigitalIO objects in the current machine API -- they are called Pin.
Re: RFC: Hardware API: finalising machine.Pin
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: 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.
The MSP430 architecture has that - only specific ports are IRQ capable. I haven't checked STM on this matter.dhylands wrote: To the best of my knowledge, at least for STM32 the IRQ functionality isn't really associated with a 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: Things like slew rate often apply to every pin,..
Thanks for the link. It's interesting indeed. This project seems to target a similar thing like uPy - Rust running on the bare metal.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.
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.