Page 1 of 1

pyb.PinAF documentation and examples improvement?

Posted: Sun Mar 19, 2017 6:30 pm
by SpotlightKid
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.

Re: pyb.PinAF documentation and examples improvement?

Posted: Sun Mar 19, 2017 6:51 pm
by dhylands
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.

Re: pyb.PinAF documentation and examples improvement?

Posted: Sun Mar 19, 2017 9:10 pm
by SpotlightKid
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.

Re: pyb.PinAF documentation and examples improvement?

Posted: Sun Mar 19, 2017 9:52 pm
by dhylands
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)

Re: pyb.PinAF documentation and examples improvement?

Posted: Sun Mar 19, 2017 10:12 pm
by SpotlightKid
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.

Re: pyb.PinAF documentation and examples improvement?

Posted: Sun Mar 19, 2017 10:29 pm
by dhylands
Agreed. PinAF objects are primarily a form of documentation.

There is a script generated for each board in the build directory that has more detailed info about the pins. If you copy the and the examples/ 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                         
X21        IN                         
X22        IN                         
Y1         AF_PP  PULL_UP 8: USART6_TX
Y2         AF_PP  PULL_UP 8: USART6_RX
Y3         IN                         
Y4         IN                         
and with you can see the actual pin function, not just the peripheral block:

Code: Select all

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   
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