pyb.PinAF documentation and examples improvement?

C programming, build, interpreter/VM.
Target audience: MicroPython Developers.
Post Reply
SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

pyb.PinAF documentation and examples improvement?

Post by SpotlightKid » Sun Mar 19, 2017 6:30 pm

I'm currently working on a clean-up of the pyb.Pin documentation page, and in the section on Pin alternate functions and the pyb.PinAF object, I noticed that the example using pin X3 and how the TIM2_CH3 alternate function can be used, can be confusing, because it's not immediately clear what the relation between Pin.AF1_TIM2 and TIM2_CH3 is.

The concept of timer channels isn't really explained succinctly in the documentation anywhere, AFAICS. The docs on pyb.TimerChannel give the API description and the docs on pyb.Timer shows how to create channels, but how the the different channels map to pins isn't made clear.

I want to eliminate this possible source of confusion in the pyb.Pin docs but I'm not sure what's the best way:

* Briefly mention the concept of timer and channels in the PinAF docs and link the py.TimerChannel docs,
* or change the example to use another, simpler alternate function (e.g. AF7_USART2) or another pin,

What do you think? Does somebody have a good brief explanation of the relation of timers, channels and assignment to pins, because I can't come up with a good one, which probably means I don't fully understand it myself yet.

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

Re: pyb.PinAF documentation and examples improvement?

Post by dhylands » Sun Mar 19, 2017 6:51 pm

The relationship of timer channels to pins is hard coded (on the STM32 chips).

I think that if clearer documentation is needed on the relationship between timers and channels, then that probably belongs in the Timer documentation.

For example, PWM generation requires a counter, a reload register (determines the total cycle time of the PWM signal) and the duty-cycle.

The counter and reload register are associated with the timer, and the duty-cycle is associated with the timer channel. For PWM generation, since there is only one pin associated with the function, you can specify the pin when you initialize the timer and it will initialize the af for the pin properly.

However, some functions, like quadrature decoding, require 2 channels (and therefore 2 pins).

I figured that using the timer example would be more appropriate that say a UART, since if you specify pins when you initialize the uart then the AF is all initialized properly. The only time I've had to use the AF stuff directly is with timers.

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: pyb.PinAF documentation and examples improvement?

Post by SpotlightKid » Sun Mar 19, 2017 9:10 pm

Thanks for the explanation. So, if I get that correctly, the PinAF objects, that you get back from pyb.af_list(), name the general alternate function groups available for a particular pin, e.g. USART2, SPI2, TIM5 etc., but when you pass the 'af' argument to pyb.Pin(), you have to pass in the constant naming the actual assignment of that pin within that alternate function group, e.g. USART2_TX, SPI2_NSS, TIMB5_CH3 and so on. And this assignment is determined by the MPU. Could you pass an PinAF instance as an argument to 'af' too?

Also, I think it would be good to insert a paragraph in this section that explains that you rarely have to configure alternate pin functions directly, since the other pyb module classes (like Spi, UART, I2C, etc) do it for you, but that it useful for timer channels. And then link to the timer docs and move this concrete example there.

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

Re: pyb.PinAF documentation and examples improvement?

Post by dhylands » Sun Mar 19, 2017 9:52 pm

The af parameter (to pyb.Pin) wants an integer. If you run the following snippet:

Code: Select all

>>> for af in pyb.Pin('A0').af_list():
...     print('af {} index {}'.format(af, af.index()))
... 
af Pin.AF1_TIM2 index 1
af Pin.AF1_TIM2 index 1
af Pin.AF2_TIM5 index 2
af Pin.AF3_TIM8 index 3
af Pin.AF7_USART2 index 7
af Pin.AF8_UART4 index 8
you'll see that each AF is really just the number that corresponds to the digit after the AF. So each of these is the same:

Code: Select all

>>> p = pyb.Pin('A0', mode=pyb.Pin.AF_PP, af=pyb.Pin.AF3_TIM8)
>>> p
Pin(Pin.cpu.A0, mode=Pin.ALT, af=Pin.AF3_TIM8)
>>> p = pyb.Pin('A0', mode=pyb.Pin.AF_PP, af=3)
>>> p
Pin(Pin.cpu.A0, mode=Pin.ALT, af=Pin.AF3_TIM8)
The PinAF instances are really there just that when we call af_list() and print the results that it prints like the first example rather than the second:

Code: Select all

>>> pyb.Pin('A0').af_list()
[Pin.AF1_TIM2, Pin.AF1_TIM2, Pin.AF2_TIM5, Pin.AF3_TIM8, Pin.AF7_USART2, Pin.AF8_UART4]
>>> [af.index() for af in pyb.Pin('A0').af_list()]
[1, 1, 2, 3, 7, 8]
There's a little sleight of hand going on here. pyb.Pin('A0').af_list() returns an array of PinAF objects. pyb.Pin(... af=something) expects something to be an int, and pyb.Pin.AF1_TIM2 is actually an int.

If I tried to pass an actual PinAF object in as an argument to af= then it would fail:

Code: Select all

>>> type(pyb.Pin('A0').af_list()[0])
<class 'PinAF'>
>>> type(pyb.Pin.AF1_TIM2)
<class 'int'>
>>> pyb.Pin('A0', mode=pyb.Pin.AF_PP, af=pyb.Pin('A0').af_list()[0])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't convert PinAF to int
>>> pyb.Pin('A0', mode=pyb.Pin.AF_PP, af=pyb.Pin.AF1_TIM2)
Pin(Pin.cpu.A0, mode=Pin.ALT, af=Pin.AF1_TIM2)

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: pyb.PinAF documentation and examples improvement?

Post by SpotlightKid » Sun Mar 19, 2017 10:12 pm

Yeah, I figured you could pass e.g. af=pyb.Pin('A0').af_list()[0].index() but this seems a strange API and the PinAF objects are kinda useless.

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

Re: pyb.PinAF documentation and examples improvement?

Post by dhylands » Sun Mar 19, 2017 10:29 pm

Agreed. PinAF objects are primarily a form of documentation.

There is a pin_af.py script generated for each board in the build directory that has more detailed info about the pins. If you copy the pins_af.py and the examples/pins.py to yout board, then you can use pins.pins() to show the actual pin function currently configured for each pin:

Code: Select all

>>> u = pyb.UART(6, 9600)
>>> pins.pins()
X1         AF_PP          1: TIM2_CH1  
X2         IN                          
...snip...
X21        IN                          
X22        IN                          
Y1         AF_PP  PULL_UP 8: USART6_TX 
Y2         AF_PP  PULL_UP 8: USART6_RX 
Y3         IN                          
Y4         IN                          
...snip...
and with pins.af() you can see the actual pin function, not just the peripheral block:

Code: Select all

>>> pins.af()
X1          1: TIM2_CH1    1: TIM2_ETR    2: TIM5_CH1    3: TIM8_ETR    7: USART2_CTS  8: UART4_TX   
X2          1: TIM2_CH2    2: TIM5_CH2    7: USART2_RTS  8: UART4_RX   
...snip...
X21         5: SPI2_MISO   6: I2S2_EXTSD 
X22         5: SPI2_MOSI   5: I2S2_SD    
Y1          2: TIM3_CH1    3: TIM8_CH1    5: I2S2_MCK    8: USART6_TX  
Y2          2: TIM3_CH2    3: TIM8_CH2    6: I2S3_MCK    8: USART6_RX  
Y3          2: TIM4_CH3    3: TIM10_CH1   4: I2C1_SCL   
Y4          2: TIM4_CH4    3: TIM11_CH1   4: I2C1_SDA    5: SPI2_NSS    5: I2S2_WS    
...snip...

User avatar
jgriessen
Posts: 191
Joined: Mon Sep 29, 2014 4:20 pm
Contact:

Re: pyb.PinAF documentation and examples improvement?

Post by jgriessen » Fri Dec 01, 2017 4:57 pm

How would one use this from the docs: https://docs.micropython.org/en/latest/ ... b.Pin.html

Pin.init(mode, pull=Pin.PULL_NONE, af=-1)
mode can be one of:

Pin.IN - configure the pin for input;
Pin.OUT_PP - configure the pin for output, with push-pull control;
Pin.OUT_OD - configure the pin for output, with open-drain control;
Pin.AF_PP - configure the pin for alternate function, pull-pull;
Pin.AF_OD - configure the pin for alternate function, open-drain;
Pin.ANALOG - configure the pin for analog.

I'm not sure how to use this form. There is no mention of where to say pin numbers.

Meanwhile, I will use the syntax:
pinxyz = pyb.Pin(pyb.Pin.cpu.B13, pyb.Pin.AF_PP, pyb.Pin.PULL_NONE, 9)
John Griessen blog.kitmatic.com

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: pyb.PinAF documentation and examples improvement?

Post by SpotlightKid » Fri Dec 01, 2017 5:07 pm

`Pin.init` is different from `Pin.__init__`. The latter is called when you instantiate the Pin object, e.g. `p = Pin(pin_no, ...)`. The former you call on an existing instance, so the pin number is already set.

User avatar
jgriessen
Posts: 191
Joined: Mon Sep 29, 2014 4:20 pm
Contact:

Re: pyb.PinAF documentation and examples improvement?

Post by jgriessen » Fri Dec 01, 2017 5:44 pm

The former you call on an existing instance, so the pin number is already set.
Can you point me to an example? Not sure how to do that.
John Griessen blog.kitmatic.com

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

Re: pyb.PinAF documentation and examples improvement?

Post by dhylands » Fri Dec 01, 2017 6:11 pm

Let's say that you want to initialize Pin X22 to be SPI2_MOSI. From your output:

Code: Select all

X22         5: SPI2_MOSI   5: I2S2_SD
The 5: SPI2_MOSI bit says that alternate function 5 is SPI2_MOSI.

There are 2 ways you can setup the pin. The first is to do everything in one call:

Code: Select all

pin = pyb.Pin('X22', pyb.Pin.AF_PP, af=5)
or you can do it 2 steps:

Code: Select all

pin = pyb.Pin('X22')
pin.init(pyb.Pin.AF_PP, af=5)
You can then examine the pin object to verify that it was initialized properly:

Code: Select all

>>> pin
Pin(Pin.cpu.C3, mode=Pin.ALT, af=Pin.AF5_SPI2)
Some of the alternate functions have symbolic constants, so you could also use pyb.Pin.AF5_SPI2 in place of 5:

Code: Select all

pin = pyb.Pin('X22', pyb.Pin.AF_PP, af=pyb.Pin.AF5_SPI2)
You can determine the available constants for a particular pin by doing something like this:

Code: Select all

>>> pin = pyb.Pin('X22')
>>> pin.af_list()
[Pin.AF5_SPI2]
or by examing the output of:

Code: Select all

>>> dir(pyb.Pin)
['init', 'value', 'low', 'high', 'name', 'names', 'af_list', 'port', 'pin', 'gpio', 'mode', 'pull', 'af',
'mapper', 'dict', 'debug', 'board', 'cpu', 'IN', 'OUT', 'OPEN_DRAIN', 'ALT', 'ALT_OPEN_DRAIN',
'ANALOG', 'PULL_UP', 'PULL_DOWN', 'OUT_PP', 'OUT_OD', 'AF_PP', 'AF_OD', 'PULL_NONE',
'AF1_TIM1', 'AF1_TIM2', 'AF2_TIM3', 'AF2_TIM4', 'AF2_TIM5', 'AF3_TIM10', 'AF3_TIM11',
'AF3_TIM8', 'AF3_TIM9', 'AF4_I2C1', 'AF4_I2C2', 'AF5_SPI1', 'AF5_SPI2', 'AF7_USART1',
'AF7_USART2', 'AF7_USART3', 'AF8_UART4', 'AF8_USART6', 'AF9_TIM12', 'AF9_TIM13',
'AF9_TIM14']

Post Reply