Reducing bloat in STM32 HAL Timer interrupts? - Page 1
Forum section: electronics / general

author: Pack34
author_rank: Frequent Contributor
timestamp: September 07, 2016, 02:51:19 pm
content: I've been doing some experiments using timer interrupts using an STM32F746 that's configured for the maximum 216MHz clock.End goal is to calculate a waveform to be sent to a DAC (Simple triangle). The update rate is 160 kHz (6.25us).For some reason this seems to not be achievable. I can generate pulses fine, but as soon as I have about ten operations in the interrupt, an output framing pulse I have setup to show the overall timing of the routine, increases by nearly 100% and results in a 12-ish us period.At a clock rate of the 216MHz, I should have about 1,352 clock cycles within the defined 6.25us.For a comparisson, I'm looking at moving on from an STM32F4 using the older standard periph libraries. When clocked at 168MHz I'm note able to get the performance I want, but I can achieve my goals fine at 12us periods.Is the interrupt handler really that bloated using the HAL libraries? Is there anyway to clean it up? I've noticed significant bloat with simple operations such as raising and lowering a digital pin.The work around for that was easy, but I'm unsure on how to optimize the interrupt handling.

author: Kalvin
author_rank: Super Contributor
timestamp: September 07, 2016, 03:17:18 pm
content: Are there any other interrupts enabled at the same time with higher priority, like system tick timer etc. which would interrupt your ISR handler?Fromhttp://homepage.cem.itesm.mx/carbajal/Microcontrollers/SLIDES/Interrupts.pdf:"The NVIC is designed for fast and efficient interrupt handling; on a Cortex-M4 you will reach the first line of C code in your interrupt routine after 12 cycles for a zero wait state memory system.""A Beginner’s Guide on Interrupt Latency - and Interrupt Latency of the ARM® Cortex®-M processors":https://community.arm.com/docs/DOC-2607

author: Pack34
author_rank: Frequent Contributor
timestamp: September 07, 2016, 03:23:20 pm
content: Originally, yes. I had two timers setup in tandem, but even when I disable the second timer I'm still running into this problem when I put the function call in there to setup the DAC. It's just manual port manipulation.EDIT:// That's interesting, I removed the function calls and moved all the code locally in the interrupt function and that specific issue went away.I'll be sure to read through the documentation you provided. There's definitely something I'm missing here.

author: Kalvin
author_rank: Super Contributor
timestamp: September 07, 2016, 03:38:03 pm
content: Yes, that is interesting indeed! Pls report your findings here.

author: tatus1969
author_rank: Super Contributor
timestamp: September 07, 2016, 03:58:00 pm
content: ideas: compiling in debug mode? passing complex arguments as values (forcing compiler making copies)? any hidden conversion to expensive base types like float, any hidden expensive operations like division? check assembly code for those.

author: obiwanjacobi
author_rank: Super Contributor
timestamp: September 07, 2016, 04:51:39 pm
content: Quote from: Pack34 on September 07, 2016, 03:23:20 pmI know that with AVR when the compiler sees that function call are made, a whole lot more is pushed onto the stack before releasing to your ISR. Maybe something of that sort is going on here...?

author: Yansi
author_rank: Super Contributor
timestamp: September 07, 2016, 04:55:18 pm
content: There is one very simple way around your problem: Dont f*cking use the HAL libraries, at most for initialization ONLY. But I think this wasn't what you have wanted to hear, but it is true.If the waveform to be generated is not dependent on anything other and should produce only a static waveform (i.e. no closed loop control on the waveform or so), it is plainbadstupid idea to have an interrupt at 160kHz.You shall use the DMA using a double buffer mode and an interrupt on every buffer exchange. In each interrupt, calculate a whole bunch of new samples to fill the buffer, done. The interrupt rate will therefore decrease by the amount of samples in the buffer. For example if the buffer will be 16 samples in length, the interrupt will be called only at the rate of 10kHz and will compute 16 samples forward. That also means the overall interrupt handling overhead will be reduced 16 times.But I don't know the whole background of your application, so this might not be possible to do.EDIT:A struggle to generate a sinewave/triangle wave (any other simple static wave) with a DAC using 200MHz 32bit MCU is a little bit ... well.. strange. A 200MHz 32bit MCU will make you a somewhat decent audio equaliser at 192kHz stereo 24bit. Generating a static waveform like a triangleshouldbe a very easy task.How I would do this task (generate any static waveform): Set up the DAC to be triggered by a timer and set the DAC to generate DMA requests. Set the DMA in a double buffer mode, prepare a pair of buffer of desired sample length. Enable the transfer complete interrupt in the DMA controller for the DAC data stream.The interrupt handler really does not need to be bloated at all. Just clear the IT Pending bit and check which buffer has to be filled with new data. Something like this:This is a piece of code I have used in one of my DSP audio projects:Code:void DMA1_Stream4_IRQHandler(void){/* Clear IT Pending bit */DMA1->HIFCR = DMA_HIFCR_CTCIF4;/* Which buffer has to be currently filled? */if ((DMA1_Stream4->CR & DMA_SxCR_CT) == 0) {/* First buffer to be written */} else {/* Second buffer to be written */}...}And let me tell ya, this code still can be made faster by a few clocks by replacing the structure dereferencing with a simple pointer operation.

author: JPortici
author_rank: Super Contributor
timestamp: September 07, 2016, 05:26:13 pm
content: check the optimizations.on XC16 with -O0- if you call a function that is in the same .c file the compiler will only push the required parameters in the stack and call the subroutine.- if it is in another .c file it will always calculate the new program counter page, push a bunch of registers and then call. even if it's not necessarywith -O1 onwards the calculations will be made only if necessarymaybe something like this is happening?then, for very simple routines like put this value in this register you should use "static inline". the call will be substituted with the actual code (which is why it's used by single line functions or simillar)mm.. there was a topic about how to write better C code the other day which had very interesting links.I second the 'forget about using the HAL, it's crap'. It really is. Unfortunately it is necessary to set up the peripherals because the datasheet is so much badly written... for example sometimes the info you need is scattered amongst chapter (like how to set up the SPI. only in the I2S section it is written that if I2S is disabled then the peripheral works as SPI. oh lord)Personally, I decided to get away from STM and moved to NXP. A bit more difficult to get chips with as much peripheral as the STM (i miss the 12 bit dacs) but at least i'm developing code like a breeze, i think that in the end that's more important.On another subject, why not use DMA for this task?

author: mac.6
author_rank: Regular Contributor
timestamp: September 07, 2016, 07:27:26 pm
content: 1- be sure your ISR table is in RAM and not in ROM, same for ISR handler code if you can.2- disable FPU or use lazy staking to avoid saving FPU state (but you can't use FPU in your ISR handler).3- ckeck your ISR handler code for any lock/delay or side effects.4- do only strict minimum work in the ISR, avoid any kind of call to hungry API like malloc, printf family etc...

author: Pack34
author_rank: Frequent Contributor
timestamp: September 08, 2016, 04:05:47 am
content: Quote from: Yansi on September 07, 2016, 04:55:18 pmI'm beginning to understand that with the HAL.The purpose of the waveforms generated are used in a sort of precision motor control system. In one situation it'll be triangle waves, another sawtooth, another is spiral, and another is concentric circles. Then there's user defined routines....The entirety of a single routine can have up to 1,048,576 data points. This is then repeated as necessary.

author: westfw
author_rank: Super Contributor
timestamp: September 08, 2016, 07:56:19 pm
content: First step: confirm that code being produced *IS* bloated. You haven't posted your source, so we'd just be guessing which HAL functions you might be using. Look at the assembler listing from the compiler, or at disassembled code, and trace out what is actually happening...QuoteThat could be pretty important: 26 words of saved context vs 8...

author: AndyC_772
author_rank: Super Contributor
timestamp: September 08, 2016, 08:11:06 pm
content: I second the idea of using DMA to drive the DAC.It's always a bad idea to have a 'real time' output driven a tick at a time by an interrupt routine, IMHO. It's usually inefficient, and it completely depends on being able to run the ISR at predictable, regular intervals, which means you can't have the CPU doing anything that's a higher priority.Generate a bunch of samples at a time, DMA them to the DAC, and fill one buffer while another empties.The other option I sometimes use is to offload all the 'hard' real time operations to an FPGA. Many of my designs end up using one anyway, and there's no better suited device for doing things that are absolutely regular and deterministic.

author: langwadt
author_rank: Super Contributor
timestamp: September 08, 2016, 09:14:35 pm
content: Quote from: westfw on September 08, 2016, 07:56:19 pmif that is the difference between success and failure you are cutting it close