-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
How to name pins and peripherals #265
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
Comments
Ultimately, with one-to-one (no-op) mapping.
Ok, let's consider how to implement it without any "extra" mapping:
As you see, there's no "mapping" - all port/pin values used as is, with minimum possible transformations (it would be possible to get rid of << 16 by using uint16_t pointers). Viper would turn that into the most efficient asm code (if it could lower class abstraction, but that's different matter). Implementation which would use scalar number for pin, would be less efficient, even if by 2-3 instructions. But that's the point - if we talk the ultimately efficient implementation, that's it. |
It would be a bit perfectionist to just consider performance gain of 2-3 instructions (even though 2-3 python instructions is considerable performance hit when we talk MHz GPIO flip performance). But as mentioned above, using ports provides also more faithful representation of the hardware. And note that that may be either physical hardware level of particular MCU, or "virtual" level of particular board. Pyboard example with ports X and Y was given, but I don't know if pins in these virtual ports are truly equivalent, so instead may present more vivid case where that holds. Jeenode board is designed to provide 4 inasmuch identical ports as possible: http://jeelabs.net/projects/hardware/wiki/JeeNode#Portpin-mapping . So, daughterboards can be plugged into any such port, and replugged freely. And when user replugs a daughterboard, it would be natural to expect to update just a single port number in the app somewhere, not hunt thru the app to do search and replace of "PA1" => "PB1", etc. |
In reality, I don't see how GPIO is going to be implemented as you suggest. That code is going to need to be a module, so use of PortA, etc, will need to be something like pyb.PortA. Optimisation of constants from an imported module needs a bit/lot of thought (see #266). For the pin read/write, yes, it'll be implemented efficiently like your example (but in C). Optimising the initialisation of a port is much less important, and will anyway also include assertions to check that you pass the correct parameters. The Jeenode board and it's 4 ports are very similar in spirit to the skin X and Y positions. In these 2 cases, the ports are all logically equivalent, but still require a mapping to get to the underlying hardware pins. In practice it's not common to be able to simply change the port for a peripheral from A to B. For proper configurability, each logical pin used for an attached peripheral has the physical port and pin (and sometimes other things) specified. Eg, the CC3000 driver uses PA14, PC6, PC7, PB13, PB14 and PB15. It's generally like this, a mish-mash of pins from different ports. |
Yes, but let's try not to mix 2 different issues, like "as we talk dynamic language which takes hundreds of cycles to schedule a new VM instruction, then it makes no sense to optimize for 2-3 cycles". Dynamic language gets JIT as a next step, and 2-3 cycles count for 50% slowdown.
I hope you meant "it can be implemented ...", because example I gave is not some theoretical example, but pretty legitimate and practical case how it can be implemented either ;-).
That's why I mentioned that perfectionist optimization is not the only usecase for using explicit ports ;-).
Again, the keyword here is "these 2 cases". There are cases when no mapping required.
I object. USB, PCI, PCI-X - it's very common (billion cases) for being able to swap ports. Cannot argue for devices like JNode and Freesoc, which probably have just few hundred cases ;-). Arduino? Well, even there exist SoftwareSerial and SoftwareServo libs to put related peripherals on any pins.
It's just a specific driver which uses these pins. CC3000 itself requires just just an SPI interface (which can be easily bitbanged) and couple of GPIOs (one optionally with irq capability). It's all flexible (well, in my list ;-) ). Anyway, we digressed again. For me, following differences are clear: GPIO(scalar_pin) - forces to do pin/port mapping |
Let's better discuss why GPIO(port, pin) could be worse. One thing is obvious - it's of course easier (especially for beginners) to work with scalar numbers. You could just define board pin to a symbolic name for your current project, voila. Let's take OneWire implementation as an example:
With GPIO(port, pin), it seemed that one would need to define both in-port pin and port:
But that's actually not the best way to do it. Instead of passing pin "parameters", one should use dependency injection pattern and pass pin itself (i.e. object):
That will allow to configure hardware in one place (and app "config" or initialization routine), and to freely use and mix different GPIO class implementations (e.g. MCU pins and I2C extender). End result? Beginners learnt something new and useful (you don't have to tell them it's dependency injection, but when they learn they did it all the time thanks to uPy, they will be grateful ;-) ). Still too complex for beginners? Well:
|
Well, your example still needs proper pin initialisation (direction, mode, speed). And that requires mapping the pin number to bits in the mode selection register. I meant that pin read/write will be a simple bit set/read in from an appropriate memory address. And it'll be done in C.
What I meant is that it's not common to change PortA to PortB without then also having to also change some pins on those ports. See the CC3000 example. You wouldn't change PA14 to PD14 (say). You'd most likely change also the pin number, eg PD9. With the CC3000, you are spread over 3 ports, so you don't just change 1 definition to change where you plug the CC3000 in to. My point here is that it's easier to specify the port together with the pin, and consider that as 1 logical entity. The CC3000 is on (PA14, PC6, PC7, PB13, PB14, PB15) and not PORTA(14), PORTC(6, 7), PORTB(13, 14, 14).
Not true. Depending on the MCU, you could used the high bits to represent the port, and low bits to represent the pin. Eg on the STM, the port address is the base address 0x40020000 plus the high 16 bits of the scalar pin value, and the pin is the low 16 bits. Eg, PA4 = 0x00000004, PB9 = 0x04000009.
Not true. Eg on 8-bit AVR you need to know the PORT, DDR and PIN registers for a given port (yes they are sequential). And it depends on whether you use memory access instructions or IO instruction as to their address. On the STM you also need to enable the clock for the GPIO port, which requires a mapping from the GPIO port number to the appropriate RCC register, as well as a mapping to the appropriate bit in that RCC register. My point is that for a given piece of hardware there is in general going to be a lot more low-level information needed than just (port, pin), and so some mapping/lookup/translation is needed. |
Hello, On Sun, 09 Feb 2014 08:48:23 -0800
Yes, you're right with this.
Well, per my dependency injection example, that would rather be
Well, that doesn't work in general case ;-).
That's true, as I agreed.
And as a software guy, I can't give up an idea that there's a So, dropping "no mapping" argument, there're still few left:
All in all, I even wanted to give up on this discussion already - I But as you opened a dedicated ticket for that, I decided to sum up So, circa 2005, when I contributed to handhelds.org (Linux For me, it's a failure to design good GPIO API, and someone should Best regards, |
I've been thinking about this a bit more, and would like to throw out the What if we were to create Pin objects. These pin objects could have So a pin might include a pointer to a GPIO port, and a bit-mask. It might You could also have names for the pins. Obvious choices are CPU names (i.e. You'd probably want to describe these pins using some type of python script The APIs, like GPIO, ADC, and UART would get passed pointers to pin To support the namespaces, you could have: pyb.Pin.board.X2 and You could have peripheral specific namespaces as well, which makes sense Basically, the namespaces are just convenient ways of identifying a pin. |
It sure is an interesting topic isn't it. I think the discussion to date is excellent BTW. A simple, clear, memory-efficient, extendable interface. I would like to restate the problem so as to avoid rabbit-holing on solutions. I hope its OK if we step back for a moment. As usual I'd like to suggest we approach it from several directions with a view to determining the best approach for each type of thing. By this I mean GPIOs are conceptually different to (say) I2C ports. An I2C port has two useful signals and the chip will not allow me to arbitrarily swap which pin is SDA. But for a GPIO - even though they are sharing a port - each pin can have its own dir, PU setting, mode etc. So conceptually they are different even though they are all implemented as port/pin/settings (which may have side effects that affect other pin capabilities). I include ADC, DAC in GPIO basket even though dir is determined - they are pin based. Critical elements common to stating the problem are (did I miss one, do you agree):
So how would it be most useful for a user to define a comm port like I2C. Its similar for many other kinds of ports but GPIOs are a little different: So I restate my initial point. Lets consider:
If we write what we want the user to see first. Then work out 'best' way to define it. Uh-oh - couldn't stop myself -
|
Unfortunately, the i2c pins are available on multiple pins, so you still need to configure which pins the signals should show up on. I2C1_SDA - B7, B9 So merely selecting that you want I2C1 isn't enough. It's similar with many of the other peripherals. |
I took a look at what Espruino does and they have python scripts which parse a .csv file and generate something like this (for the STMF3DISCOVERY):
|
Yes. I'd forgotten about that. (should have remembered - recently been doing the Timers which are similar.) We can see that for
Or could add that to the config as required args (which would have to be validated in the constructor by referring to the kind of map table you referred to earlier - but in C):
or even as virtual I2c ports. Only one of which can be active at a time:
In all cases you need access to documentation to find out how the ports are mapped on your board. The advantage of the first two being the docs are supplied by the chipset vendor or board supplier and not the micropython port (which the last one requires). |
I think that this is the right way to do it. If you look at I2C2, there are 3 pairs which leads to 9 valid combinations. When you start looking at bitbanged peripherals (like say the LCD) then the number of unique possibilities is astronomic. Some peripherals, like i2c and spi can also easily be emulated in software, and the driver might choose to do software emulation, if the selected pins don't support the hardware functionality. |
Yeah - I wasn't including adhoc devices like LCDs which can be connected in so many ways. I was meaning devices the chipset supports directly. |
Here's A solution that satisfies the criteria:
Migrating code form one board to another then becomes a process of abstracting the lines of code using the (say) pyb module to HAL module(1 line per port/pin). Then changing pins. The creation of a pyb module for a specific board is optional. Remaining questions:
Addtl - for @dpgeorge's board where I2c2 is on Y1,Y2 (PB10,11) he could label it E.g.
|
@dhylands I like your idea of having all pins pre-defined and in flash. That way it takes no RAM to make a GPIO object. You just get a reference to the correct one. |
As per latest Pin docs, when pin objects are constructed the physical pin is specified via a single identifier, which can be an integer, string or tuple |
There has been a discussion in #227 on how to name pins (and peripherals such as I2C). Most MCUs have pins named by port and pin number on that port, such as Port A, Pin 4, referred to as PA4 for example. On the pyboard, the pins are not sequential and have been renamed to reflect their location on the board (and also location within a skin), for example X1 or Y12. These names can still be considered to have a port and pin, ie Port X, Pin 1 or Port Y, Pin 12.
For peripherals, they can be numbered simply as USART1, USART2, SPI1, etc. On the pyboard these can be named USART_X, SPI_Y, etc, so that you know exactly where they are.
Coming back to pins, the discussion boiled down to whether we separate the port and pin, or keep them together, eg
Part of the discussion was about efficiency, and that specifying port and pin separately is closer to the hardware and more efficient to map to the underlying hardware implementation. But note that on the STM, each port is actually a pointer and so there is going to be some level of mapping no matter what.
The text was updated successfully, but these errors were encountered: