-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Added mbed hal support for gpio and adc #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
To answer my own question - you use rt_call_function_n to call a function (I found examples in stm/timer.c) |
So would it be good to have:
The class could then get these values from the HAL. IMHO Adc() and Gpio() should both have read/write function name conventions. When setting up an ADC - you can get to choose the bit depth, or it is defined by the h/w. So you'd want to able to say something like:
IMHO we probably want to generalise this further to allow args to go through to the devices. E.g.
Then we can get:
On the stm32F405 the hardware supports setting an ADC to trigger when its input passes a threshold set by checking a timer. This generates an interrupt which can fire a DMA action. |
The STM ADCs have a resolution of 12, 10, 8 or 6 bits, configurable in software. There are 3 ADC blocks, and it looks like they can be individually configured to different resolutions. But, not all ADC channels can be connected to all 3 ADC blocks. |
Could elaborate on this? I'm not quite sure what you mean. |
@dpgeorge can you point me at the relevant pdf ? I'd like to se if I can tease a useful structure out of it. To elaborate on 6,8,10,12 bits. IMHO A floating point rep will offer the most resolution independence. An integer rep would be more +/- 1 bit accurate but typically a 1 bit error is noise anyway. A byte vs a word seems an unneccesary detail as 1 word is typical size. ??? |
@Neon22 So the API I presented is already read/write. There are no set/get. For Adc, the read method returns a float, and I presented read_u16 which returns an unsigned 16-bit int (in a python int, which covers the 16-bit range). I see that some platforms in the mbed hal have read_u32 as well. As for the pins, I think that they should run through the pinmap and we can automatically deal with integer pin numbers and pin numbers like C0 A3, etc. We could also do the pin mapping as a straight dictionary. Lookup whatever string is passed in and convert to an int. That would allow for board designators (like X1, Y3), or the user could even add arbitrary pinname mappings. Or we could allow a python pin mapping function to be registered, and we'd pass the pin argument through the python function expecting to get an integer back. The mbed hal uses integers. All of these seem fairly straight-forward to implement, and performance shouldn't be an issue at pin-mapping time. |
@dhylands Yes I agree on the pin mapping. The HAL should abstract the string to an int. In fact - I hope you can see I don't disagree with you on anything. My comment was not meant as a criticism. As for my wanting to also use the HAL to allow us access to the more in-depth regions of the h/w - which in a naive implementation could be one of the arguments against using python in embedded control (i.e. you need to be able to bit twidlle very deeply so a high level lang is useless. stick with C) - I hope you can see that a keyword arg layer on top of the constructors, and some well designed classes, might allow us a way to do this. |
@Neon22 Sorry - I'm used to the code review culture at work where every comment is a critique of some sort :) So its good we agree. I agree that a kw layer could definitely be benficial. My initial implementation was basically a straight reflection of what's available in the mbed hal API, and then anything we do in addition to that will need to be done carefully, with an eye to portability across platforms (and the kw interface is actually a really good way to support platform specific stuff too - we just need to figure out how to have some platform specific stuff that isn't in the mbed hal). |
Oh yeah - so something else I was thinking about was that the python class for gpio could have an attribute which describes the 'modes' which are available ('PULLUP', 'PULLDOWN', 'OPENDRAIN' 'NONE') Also, I think that to support extra stuff that we want layered on the mbed hal, put which wouldn't necessarily be accepted in the mbed hal git tree, we can create a hal directory inside each target (so have micropython/stm/hal) and put stuff in there, like the supported gpio modes and any associated string to mode mapping, etc. |
I see the ADC, Timer, DMA, and numerous other nightmare scenarios are all outlined in the STM32F405 Reference pdf. DM00031020.pdf... Oh joy :) |
I just skimmed through this, so sorry if I missed anything, anyway, you should add pin speeds too, 2MHz, 25MHz, 50MHz and 100MHz, running at full speed might not be needed or even possible for some pins:
|
Updated GPIO stuff
Just got to look at this, few comments:
That's cool, but you never know what kind of properties vendors can invent for their pins. There's always direction, but anything else indeed can be anything. So, just allow generic "mode" arg with bitmask:
etc, etc. |
So the problem that I have is that the constants like DIR_INPUT and DIR_OUTPUT need to be added by the target. I also wanted the target to be able to add additional methods. The way that I coded it, if you call g = pyb.Gpio(4,speed=pyb.Gpio.SPEED_LOW) then it will wind up calling g.speed(pyb.Gpio.SPEED_LOW) (and the speed method was added by the target, not in the generic code). I suppose I could add a generic function that the unrecognized kwargs coud be passed to (so that the target can add target-specific features like speed, or schmitt trigger or whatever.
I didn't want to duplicate that code in each and every hardware abstraction (gio, adc, i2c, etc) which is why I created a routine. Making it inline should make it optimizable.
From a user perspective (the people who will actually be using pyboards), I wouldn't expect specifying ports as K 31 to b better, I'd expect that using the documented identifiers on the board itself (X1, Y3) would be much more natural from the programmers perspective. Myself, I' much rather use direction = pyb.Gpio('Left-Motor-Direction') The mbed hal uses a simple pin number, and I find pasing around a single thing is much easier than passing around 2 things. My intention is to allow the user to provide a pin mapping function which will map arbitrary pyhon objects into pin numbers. So you could provide a function which maps a tuple (PortK, 31) into the appopriate pin number, or you could provide a function to map 'X1' or 'K31' or anything else. Then the user has total control over how the pins are named, and internally we'll use the pin numbers that the mbed hal wants.
I was basically allowing the target to add any number of arbitrary extra parameters (through kwargs). In the code I posted, I added the speed parameter as an example. I'll see what' involved in storing the constants in the type and see if I can convert this back to using a type. |
I vote for using the board pin names (X1, Y3, etc). It makes the code more portable and easier for end users. Also the skins are reversible. It's much easier to change all the X's to Y's than try to find what the corresponding MCU port name is for TxD or SDA on the other side. Also the MCU manufacturers are not very good at keeping their pin multiplexing the same between generations, and the next generation pyboard may not even use an ST MCU. That's the advantage of non changing pin names. |
So the problem that I have is that the constants like DIR_INPUT and DIR_OUTPUT need to be added by the target. I also wanted the target to be able to add additional methods. The way that I coded it, if you call g = pyb.Gpio(4,speed=pyb.Gpio.SPEED_LOW) then it will wind up calling g.speed(pyb.Gpio.SPEED_LOW) (and the speed method was added by the target, not in the generic code). IMHO, that's a bit of over-engineering. What we should do is to define good API how GPIO, etc. modules should work. Then each "platform target" can implement its own module in most efficient way. (And module for "mbed platform" will cover bunch of MCU families at once). But even if you want to do that, the Pythonic way to do that is to subclass a base class and add method overrides, etc. You can treat the way you did it as "optimization", but unfortunately, it is not, as it wastes RAM. |
Ok, so can you please factor mp_map_walk() out as a separate patch, so we can merge it soon? (And my point was that function you pass to mp_map_walk() is not inlinable per standard, and be done so only with advanced compiler-specific methods). |
But we don't talk "the board", we talk how to support "any board" (all the millions of them) and "any MCU" (all the thousands of them). Once we find good representation which covers "a board", we can support any specific board - easily, truthfully, and efficiently. And that's Python, we can always add any "simple to use" abstraction. But we can't improve efficiency with extra abstractions. So, the big question we discuss is whether Gpio constructor should accept some scalar value or pair of <port, pin>. I gave bunch of arguments to support explicit ports already, the main being that there are ports in hardware. Here's another one: there're usually number of SPI, I2C, etc. ports (aka blocks) in an MCU. You would instantiate them using
You can see what mbed does with it first thing - splits out a port again: https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_Freescale/TARGET_K20D5M/gpio_api.c#L31 .
A string?? But real hardware men don't use strings to do GPIO! Yesterday I grepped google for "micropython" and here's a typical reaction:
Please don't make that be true. Python doesn't have to be bloat. You of course can use strings or whatever - Python lets you, but let's not force inefficient APIs on everyone (we'll be laughed at). So, back to you concern, if you want to have X1 for pin, that's oh so easy:
Those initial definitions can (and should) go to a something like board.py, which will be shipped per-board. And we really should start working on Python-source stdlib to accompany interpreter - we can't stuff all those constants in C in flexible manner. |
There's no need provide nay mapping function, because any mapping capabilities are straight at the user fingertips with Python. What we should care is how efficient and general the most basic representation, and provide simple per-board symbolic pin mappings in a module. Anything else users can do themselves. |
kwargs are nice, but unfortunately, they're not efficient. If we'll use kwargs everywhere, we can part with the hope to run something in 8K heap (and I personally have high hopes for that, after all, if you do something, you probably want to do it better then how it was done before). So, if there's no strong need for kwargs, it's better not to use them. And IMHO, pin parameters can be reduced to "direction, and everything else" which requires just tuple and so more efficient. (And everything else can be anything - including a dictionary for particular implementation, though most impls will find just int enough). |
@pfalcon just trying to be clear. Please help me to get a handle on it.
to define that PortA on the chip is mapped to PortX in the virtual mapping. and in some C file a mapping for all the GPIO modes like:
but which may be different for different chipsets and which is exposed up to python also but with a bit more logic so you can define a mode by simply ORing bitfields together. In the example below I manually shifted them but you'd define them as a bit-pattern specific. and in the python i2c.c file we currently see the mapping for I2C ports 1 and 2 mapped in the code (we'd have to abstract this a bit more somehow for N potential i2c ports on a given chipset). because in Python we'll have something like:
I'm not sure I've got it. Do you mind clarifying it for me please ? And I have one more specific question - which is a general one really. If we define all these consts, I assume the linker throws away the C defined ones we didn't use, but what about the python exposed ones we didn't use ? |
@Neon22: What I'm talking about is higher-level API which will give implementations freedom to have efficient implementation, and provide consistent interface to user, whether the implementation optimized for efficiency or user friendliness. I don't say that each implementation must define ports which corresponds to physical hardware ports - no, it can define just a dummy one, and let users just use 0 in place of it. But it should be there, because next thing users will want is to get most speed from that, and the way to squeeze each last cycle of performance is to follow hardware layout of ports.
Well, I exactly don't see the code you quote in mpconfigport.h, so I cannot comment on that. But if you mean how that would map to Pyboard, I already hinted, that it can define just one port, and then define constants X1, X2, ... Y1, Y2, ... in its support file (above I called it board.py). How it will map those "virtual" pins to actual hardware pins is up to Pyboard's GPIO implementation code.
I really think we should target for such stuff to be defined in .py files, but otherwise, yes.
Yes, you wouldn't need to shift them manually. They will be defined as symbolic constants, that's all. And ideally, they will be just the same values you'd write in hardware GPIO configuration register. Of course, a particular implementation can "map" them in whatever way it wants, the whole point is to not force that "mapping" on each and every implementation, as that wastes cycles.
Differences: never use raw numeric values, they usually make no sense, always use symbolic constants. Also, made sure that mode options makes sense for direction=OUTPUT ;-).
Well, the constants we don't use now, we'll use later, and someone else uses all the time. If you want to have dynamic language, then you have to keep all stuff around in case it will be needed soon or later. That's the nature of dynamic languages. And if you put those constant in C module and make them available to Python side, it will be the same. Use C++ with templates if you don't like that ;-). But even with dynamical languages, you can try and optimize that - there were already some discussion about. |
Abandoning for the time being... |
python-on-a-chip has been ported to the mbed(largely?). Maybe it could help ? |
This issue got a bit out of hand and a bit off topic. Apart from mbed HAL itself, there are 3 main issues raised in the above comments:
Point 3 is really, really difficult. mbed HAL is trying to do this and that's why we were trying to piggy back on top of it. But so far mbed HAL is very simplistic and will not in the near future cover all main features of the STM32F405, let alone of all MCUs and boards. Let's open separate issues for these individual points. |
Working towards trying to support compile-time constants (see discussion in issue #227), this patch allows the compiler to look inside arbitrary uPy objects at compile time. The objects to search are given by the macro MICROPY_EXTRA_CONSTANTS (so they must be constant/ROM objects), and the constant folding occures on forms base.attr (both base and attr must be id's). It works, but it breaks strict CPython compatibility, since the lookup will succeed even without importing the namespace.
Please do not merge.
I'm putting this up for revew.
To compile, you'll need to clone https://github.com/mbedmicro/mbed so that mbed directory is in the same directory as the micropython repo (so mbed and micropython are in the same directory).
This adds gpio and adc.
Functions implemented:
g = pyb.Gpio(pin, dir)
pin is an integer such that PA0 = 0, PA1 = 1, ... PA15 = 15, PB0 = 16, PB1 = 17, ...
dir is an integer. 0 = input 1 = output
g.mode(m)
m == 0 no pullup
m == 1 pull up
m == 2 pull down
m == 3 open drain
g.dir(d)
d == 0 input
d == 1 output
g.read() returns 0 or 1 whether the pin is low or high
g.write(v) sets the pin to low or high (dir needs to be output)
the modes and direction integers take on different meanings for different platforms, so we may wish to take a string and map the string to a corresponding integer.
a = pyb.Adc(pin)
a.read() returns a float in the range 0.0 to 1.0
a.read_u16() returns an integer (in the range 0 - 4095
There is a C constant called ADC_RANGE, set to 4095 which we should probably expose.
Passing in an pin which doesn't correspond to an adc pin (for example calling pyb.Adc(8) winds up calling error() in mbed_hal which currently calls mbed_die and then __fatal_error
I think that it should probably throw an exception instead.
And how do we call a python function from C?