Question about STM32 timer usage
- mwm
- Topic Author
- Offline
Anyway, reading the manual for the STM32, it looks like timers can be set up to provide PWM pulse widths via the capture register - there's a recipe for it in the manual. But I couldn't seem to find any examples that did that - they all fell back on the ATMEGA-like interrupt handler to calculate the width.
I'm using micropython on an STM32F4 Discovery card, so I just followed the recipe in that, and got:
from pyb import Timer
from stm import mem16, TIM2, TIM_SMCR, TIM_CCER
# Configure timer2 as a microsecond counter.
tim = Timer(2, prescaler=84, period=0x3fffffff)
# Configure channel1 on PA0 for timer IC.
ch = tim.channel(1, Timer.IC, pin=pyb.Pin('PA0'), polarity=Timer.FALLING)
# Reset the timer counter on rising edge.
mem16[TIM2 + TIM_SMCR] = (5 << 4) + 4
# Capture counter on falling edge.
mem16[TIM2 + TIM_CCER] = 11
Seems to work fine - calling ch.capture() returns the width of the last pulse in microseconds, which is exactly what I want. All the work is done by the timer hardware - I don't need any code in my main or an interrupt handler, I just need to configure the timer and then read the values.
Is there some reason people don't do this? If not, could we use this for protocol/pwm.c instead of bitbanging things?
Thanks,
Mike
Do not ask me questions via PM. Ask in the forums, where I'll answer if I can.
My remotely piloted vehicle ("drone") is a yacht.
Please Log in or Create an account to join the conversation.
- victzh
- Offline
- Posts: 1386
Please Log in or Create an account to join the conversation.
- mwm
- Topic Author
- Offline
Do not ask me questions via PM. Ask in the forums, where I'll answer if I can.
My remotely piloted vehicle ("drone") is a yacht.
Please Log in or Create an account to join the conversation.
- victzh
- Offline
- Posts: 1386
If you look at ppmin.c you'll see some timer logic, a bit more complicated than you presented because it needs to decode the whole PPM package, not just last pulse's width. So it uses interrupts not to miss a pulse.
Please Log in or Create an account to join the conversation.
- mwm
- Topic Author
- Offline
It's clearly not using the auto-preload facility, though. Using that would mean you could skip saving the counter between interrupts and calculating the pulse width.
Do not ask me questions via PM. Ask in the forums, where I'll answer if I can.
My remotely piloted vehicle ("drone") is a yacht.
Please Log in or Create an account to join the conversation.
- victzh
- Offline
- Posts: 1386
So for PWM decoding your proposed procedure works fine, I can't see a way to adapt it to PPM. If you can, please tell - we can implement it.
Please Log in or Create an account to join the conversation.
- hexfet
- Offline
- Posts: 1891
The code in the first post works fine to capture a single PWM signal. When you read ch.capture() it returns the width of the last pulse captured, but says nothing about how many captures may have been missed. For PPM the pulse length must be measured for each of a sequence of pulses, with additional logic to determine which pulse represents the first channel. (Note the code in the first post measures time the pulse is high whereas PPM is total pulse length. With PPM neither the signal polarity nor which edge is measured matters for measuring the pulses.)
The current ppmin.c code sets up an interrupt on the PPM input signal, reads a free-running counter value on interrupt, and subtracts from the previous counter value to determine the pulse length. A timer-based alternative would route the PPM input signal to the timer capture input, set up an interrupt on timer capture, and read the capture value on interrupt. All the other code in ppmin.c is still required - only a subtraction is saved. The precision improves by capturing the counter value automatically, removing any variablility caused by interrupt latency.
On the output side the code in pwm.c is mostly unused when BITBANG_PPM is defined, which it always is. The PPM output pin is controlled directly in protocol/ppmout.c which requires two clock timer interrupts (protocol callbacks) per channel. The commented-out timer code connects the PPM output pin to the timer compare ouput. The timer is configured for PWM with the notch length set in the compare register and the pulse length determined by the reload register. An interrupt is generated when reloading so the next channel value can be loaded, so this method requires half the interrupts of bit-banging (ignoring the callback interrupt). The precision improves by removing interrupt latency in the current bit-bang method.
The change needed in ppmout.c is at line 90, where the notch width should be subtracted from the desired pulse width. This line is the reason the Center and Notch PW default values of 1100 and 400 are needed to produce a pulse length of 1500 for a zero channel value. I consider this a bug because changing the notch width affects the center value. However fixing this would require adding the model files notch width to the center pw value in existing model files, so need model file versioning before a change can be made. Doubt many people change these options anyway.
Most of the interrupts could be eliminated by hooking the timer up to DMA. For output the pulse lengths would be updated in memory by the protocol and transferred to the timer by DMA on timer reloads. For PPM input the capture values would be transferred to a buffer, with a DMA interrupt triggering the channel processing. Some startup code would be needed to implement ppmSync and adjust the number of DMA transfers to the number of channels.
Sidenote: In ppmin.c, because all the variables involved are 16-bit, the line t =
(t1>=t0) ? (t1-t0) : (65536+t1-t0); is exactly the same as t = t1 - t0; . I was impressed that gcc generates the same code for both.
======================
Decided before posting the above I should follow through with some code and see if jitter is really improved by using the timer capture/compare. The PPM output was easiest to implement. Code for devos is here (except 12 - needs generalization).
The previously commented-out code in pwm.c had a couple issues with register defines and didn't set the master output enable. But spent most debugging time figuring out that if more than one peripheral shares an AF pin it's not enough to disable the peripherals not in use - their clock must also be turned off.
I tested the timer-based ppmout.c versus the bit-bang version, measuring jitter as the difference between mininum and maximum measured PPM values for fixed channel values. The timer-based version had jitter of 1-2 microseconds while the bit-bang version is about 4-5 microseconds.
I don't think that's a compelling case for change but don't mind cleaning up the code for a PR if there's a consensus.
Please Log in or Create an account to join the conversation.
- Moeder
- Offline
- Posts: 796
hexfet wrote: Got interested in this and spent too much time on it.
I like your summary
Please Log in or Create an account to join the conversation.
- hexfet
- Offline
- Posts: 1891
After a sleep and further consideration it didn't seem right to leave pwm.c with commented-out incorrect code. Went ahead and cleaned things up and made a PR. Test builds available . I've tested on devo10 and t8sg. Would like volunteers for devo12 and modular build (7e or F series) as those builds are slightly different.
With the cleanup the pulse widths are spot-on and jitter not measurable at 500ns resolution.
Please Log in or Create an account to join the conversation.
- FDR
- Offline
God, I hate libopencm3 so much!!!!
It is the worse documented library I've ever seen! I have to search it's source code for everything...
BTW I couldn't read an input pin from the EXTI interrupt handler. Is that normal?
Please Log in or Create an account to join the conversation.
- hexfet
- Offline
- Posts: 1891
Many times looked at the libopencm3 source to find the functions are just setting register bits. Then go to the ST docs to find what the function does. Seems the advantage of the library is the ability to reuse the source code across multiple mcu models.
Please Log in or Create an account to join the conversation.
- FDR
- Offline
I usually do the same: search for the names given by STM, the constants are usually named similar, then search for those constants (or rather their generic counterparts in their defines) in the functions to identify the function I need.
Of course I want to use the library, so the compiler could do the rest, but IMO it's not normal, that one should investigate the source code just to identify what a function is good for...
I find the timer configuration is the most complicated, but I was struggling with DMA as well,
Regarding the pin read: I'm not sure it couldn't read, but I defined an interrupt to trigger on both rising and falling edge of a pin, but inside the interrupt handler it's value was never high, despite the handler was called continuously...
Please Log in or Create an account to join the conversation.
- Home
- Forum
- Development
- Development
- Question about STM32 timer usage