Why is my microsecond delay function suddenly malfunctioning on my STM32? - Page 1
Forum section: electronics / general

author: TC2
author_rank: Newbie
timestamp: September 16, 2021, 08:06:57 pm
content: Hi all! Hope everything is going well. I'm honestly at a loss here, hoping you guys can help. I am returning to a project from a few months ago I was working on and was trying to get some video of it. For example, I have a ultrasonic sensor that I wanted to show working in my video. It wasn't working, and I had no idea why so I did some debugging. Keep in mind this use to work fine for the past year, I don't get what's wrong.I narrowed it down to what seems to be my microsecond delay function. It would produce a delay that I didn't specify. I tried reconfiguring the clocks so it didn't work, so I opened an older test project and it still is acting up.Some infoMy board is an STM32F746ZGI'm using TIM3 for the delay functionMy code for the delay functionCode:void delay_us(uint16_t us){__HAL_TIM_SET_COUNTER(&htim3,0); // Set counter to 0while(__HAL_TIM_GET_COUNTER(&htim3) < us);}TIM3 according to the datasheet is connected to APB1. Here is what it's set toTIM3 itself has a prescalar of 4.5-1, because 4.5Mhz/4.5 = 1Mhz, 1/1Mhz = 1uS for microseconds. The period is 0xFFFF-1.Below is some example test codeCode:HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_11);delay_us(10);Here is the result on the oscillscope. So instead of 10uS, using cursors for measurements (sorry it's not in this photo), it's around 14.7uS instead. Or if I do something like 100uS, I get 94uS instead.Am I missing something? I don't know if I'm forgetting something since I took a break, or something else. Thanks

author: abyrvalg
author_rank: Frequent Contributor
timestamp: September 16, 2021, 11:27:10 pm
content: TIMx prescaler is integer, so your 4.5 gets truncated to 4, resulting in 88us delay for 100 counts and 8.8us for 10. Additional ~6us in both cases must be coming from bloated HAL inefficiency and slow APB1 register accesses at such low bus frequency.

author: thm_w
author_rank: Super Contributor
timestamp: September 17, 2021, 01:12:24 am
content: They are macro defines, not bloated:Code:__HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__) ((__HANDLE__)->Instance->CNT = (__COUNTER__))Sets the TIM Counter Register value on runtime.Code:__HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNT)Gets the TIM Counter Register value on runtime.

author: gnif
author_rank: Unknown
timestamp: September 17, 2021, 01:44:39 am
content: Quote from: thm_w on September 17, 2021, 01:12:24 am`HAL_GPIO_TogglePin` is in also use... that said though it's not a complex function so shouldn't add much latency.Since you're running at a fixed frequency wouldn't it be better to simply use a timed nop loop for such short delays? This is what I have done in the pastCode:#define _NOP_LOOP(x) \__asm__( \"mov r0,#" #x "\n" \"1:\n" \"sub r0,#1\n" \"cmp r0,#0\n" \"bge 1b\n" \::: "r0" \)#define NOP() __asm__("nop");#define NOP_LOOP(x ) _NOP_LOOP(((x) / 6) - 1)#define DELAY_NS(ns) NOP_LOOP((ns * 1000) / (1000000000/72000000))Note: I am not a professional firmware developer, the above may be terrible

author: lucazader
author_rank: Regular Contributor
timestamp: September 17, 2021, 06:37:34 am
content: There is anopther topic around here that was looking at using the CYCCNT register (which is present in your F7) as a timer and then using that to do timing.I generally dont choose this route and choose something similar to you.However instead of setting up the timer like you do. I start a free-running timer with a 1us tick rate that just counts up forever (usually use tim6).But functionally it does similar with a while loop waiting to check if the timer tick if above the time I wish.As others have said; I would suggest clocking the timer peripheral to an integer value (say 4, 8 or something like that)And then setting the pre scaler appropriately. This works super well in all my projects.Another quick way that you can sanity check the timer is giving you the correct time is to compare it against the HAL tick in HAL_GetTick().If you delay for say 16000us, and then either using a debugger or printf find out the Hal time before and after you entered your custom delay. and check that the value makes sense.

author: TC2
author_rank: Newbie
timestamp: September 17, 2021, 07:30:03 pm
content: Quote from: abyrvalg on September 16, 2021, 11:27:10 pmSorry I should of been more specific, this is just one out of many examples I tried. I've used 36Mhz for example, with 36 prescalar. Still same issue.Quote from: lucazader on September 17, 2021, 06:37:34 amI did some sanity checks like you said. If I do 16000us for example, the reported number from the debugger is 16ms. 20000us is 20ms, etc.

author: bson
author_rank: Unknown
timestamp: September 17, 2021, 10:37:50 pm
content: Are you optimizing the code with inlining enabled? If not, you might have significant function call overhead. At what speed is the core running?Regardless, consider moving the delay_us() function to a header file and define it there as:Code:static void __attribute__((always_inline, optimize)) delay_us(uint16_t us) {__HAL_TIM_SET_COUNTER(&htim3, 0); // Set counter to 0while (__HAL_TIM_GET_COUNTER(&htim3) < us);}It should only produce a few inline instructions. But the drawback of getting more accurate timing in debug builds is you won't be able step through it with the debugger, although that's not particularly useful here.

author: westfw
author_rank: Super Contributor
timestamp: September 18, 2021, 06:01:12 am
content: QuoteIt gets very tricky on the newer, faster, ARM chips with caches and "flash accelerators" and shared buses and such.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 18, 2021, 04:39:52 pm
content: Using the CYCCNT register on ARM is the easiest way of doing this really. And it has the benefit of being accessed at the core clock's frequency, thus there is no extra synchronization latency compared to accessing a peripheral register through an APB.All you need is enable it (as was already shown). Which is even easier to do than setting up a peripheral timer.In any case, getting µs-accuracy with waiting loops obviously implies running the core at a frequency that is significantly higher than 1 MHz to make up for the waiting loop code overhead, even if it's just a couple instructions.It may still be hard to get accurate, very short delays, depending on cache considerations, branch prediction, and such, so you should also consider the underlying architecture.Thus using a timer or cycle counter will get you a delay of the form a.n + b (with a, the resolution, being much more accurate than with any ugly nop-based hack), but there will always be a b delay offset (software overhead) which becomes significant for very small a and n.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: September 18, 2021, 06:53:28 pm
content: And you can compensate that b by reducing the count limit by some fixed offset. Or just use that offset as the initial timer value instead of 0 - this way you’ll avoid subtraction (which could roll over if the input value is smaller than offset).

author: bson
author_rank: Unknown
timestamp: September 19, 2021, 11:17:59 pm
content: In general, just personally speaking, I never rely on bit banging accurate timing. When I do bit bang it's generally not important from a performance perspective, doesn't require super accurate timing, and the device doesn't care if the timing is stretched. Meaning if there's an interrupt in the middle of the delay loop that's fine and will just slow things down an itty bit once in a while. While you can count cycles and bang out accurate timing, you won't be doing much of anything else, like process data simultaneously.For accurate timing, I'd rely on timers, either to generate it to timestamp events (timer capture). For performance, a suitable bus - like FSMC, an LCD controller, DMA, carefully laying out memory on different AHBs to reduce memory contention, etc.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 20, 2021, 12:34:49 am
content: Quote from: abyrvalg on September 18, 2021, 06:53:28 pmProblem is, this offset, at least on some targets, is neither fixed nor really predictable. Trying to compensate it is thus pretty similar to implementing delays with cycle counting instructions. So your best bet is just to use your delays so that this offset is negligible.Very small *and* accurate delays are rarely useful anyway - if you use them, there's probably a better way of achieving the same thing using the appropriate peripherals.

author: viperidae
author_rank: Frequent Contributor
timestamp: September 20, 2021, 03:05:26 am
content: Are you trying to generate a single pulse to send to the ultrasonic module?The timers can be configured to single pulse mode and automatically do the pin toggling for you.For timing a response, that's exactly what the capture function of timers is for.You should be able to get all the timing critical stuff done with the peripheral without having to get the CPU involved

author: peter-h
author_rank: Super Contributor
timestamp: September 20, 2021, 11:28:18 am
content: If you want to generate a pulse of an accurate width then this is not possible unless you disable interrupts (and DMA if used) around the whole piece of code.And even then it won't work usefully because interrupts will generate a variable latency i.e. the point in time when the pulsestartswill move about.It could be made to work on a CPU which is not running anything else and just runs this tight loop. OTOH we don't know the OP's algorithm; it could be that the pulse needs to be generated in response to some input and a latency is not important but the width is.I would definitely try doing this with a timer, or even with external hardware.If one can do it with polled code, the CYCCNT method seems very solid and very accurate. See last post:https://www.eevblog.com/forum/microcontrollers/32f4-a-timer-without-interrupts-cyccnt/

author: westfw
author_rank: Super Contributor
timestamp: September 21, 2021, 04:24:01 am
content: QuoteOne has to be careful about defining "accurate."Accurate to within +/- 5 clock cycles: very difficult.Accurate to within +/- 100 clock cycles: not so difficult.(and keeping in mind that 100 clock cycles is about 1us on many current chips.)