2
\$\begingroup\$

So I am using an nRF24 radio module and I probably need a 10us delay after enabling chip select. But the thing is I can't use HAL_Delay() since the least it provides is 1ms delay (SCK/1000 is hardcoded in the HAL function).

I was suggested to not use SysTick timer for too small of a delay anyways and rather go for hardware timers. I read up on Timers and to me, it looks more like they're mainly used for tasks where you need to do a function at a specific interval i.e blinking LED every 4 seconds or something by changing the prescaler and counter period and I did try it out as well -- and it's nonblocking as opposed to a delay. So every time a counter hits the specific counter period, it generates an event/IRQ

Am I interpreting it incorrectly? Is there a way around it?

I'm using STM32F401RE.

\$\endgroup\$
7
  • \$\begingroup\$ Systick IS a hardware timer but may be inappropriate nonetheless \$\endgroup\$
    – DKNguyen
    Jul 9, 2020 at 14:22
  • \$\begingroup\$ ah, true. how is it different than GP/advanced/basic timers? \$\endgroup\$
    – xyf
    Jul 9, 2020 at 14:24
  • 1
    \$\begingroup\$ Systick is part of the ARM core itself rather than a peripheral so is "standard". Intended for OS timing interrupts that might run on ARM processors of different manufacturers since it's always there and always the same. That's why it is accessed by ARM standard commands rather than registers like all your other peripherals. \$\endgroup\$
    – DKNguyen
    Jul 9, 2020 at 14:25
  • \$\begingroup\$ i see. yeah I checked its implementation in the cortex file and I see it basically gets the current ticks, and polls it till the desired time has elapsed. but still can you get a delay out of it in us? \$\endgroup\$
    – xyf
    Jul 9, 2020 at 14:32
  • \$\begingroup\$ There is nothing stopping you from making systick trigger faster, but it will affect things already coded to use it (like parts of HAL which may or may not matter). If you work from scratch you can do whatever with it. \$\endgroup\$
    – DKNguyen
    Jul 9, 2020 at 14:32

6 Answers 6

2
\$\begingroup\$

You can start a timer and read the count until it is large enough. Or you can poll the overflow flag when it rolls over. No need for interrupts. You can also sit in a while or for loop for enough counts to reach 10us.

\$\endgroup\$
7
  • \$\begingroup\$ static variables will be helpful here (encapsulate in a function) if you do not want to go with a global variable to remember when the start time was \$\endgroup\$
    – DKNguyen
    Jul 9, 2020 at 14:55
  • \$\begingroup\$ So assuming I have a 16MHz clk with a prescaler of 1, the counter goes up by 1 every 62.5ns? meaning it will take ~160 counts to reach 10us? \$\endgroup\$
    – xyf
    Jul 9, 2020 at 15:53
  • \$\begingroup\$ It depends on the clock that the timer runs. It may be different from core or buses. But yes, if the timer runs at 16 MHz, 160 counts is 10us. \$\endgroup\$
    – Justme
    Jul 9, 2020 at 16:06
  • \$\begingroup\$ right. there's no dedicated register to get the count value? i'm looking at TIM2 registers and nothing stands out. also checked the hal tim source file to no avail. \$\endgroup\$
    – xyf
    Jul 9, 2020 at 16:59
  • \$\begingroup\$ Timer has a count register you can get the count value. Or do you mean the frequency it runs at? A timer gets its frequency source from bus it is on, but you need to know how the system clock is divided down for the buses before it reaches the timer. \$\endgroup\$
    – Justme
    Jul 9, 2020 at 17:13
1
\$\begingroup\$

For a delay of at least some amount, You could set a flag in the main loop and enable a timer interrupt that resets the flag. The main loop polls the flag in sequence while running other tasks and skips the function that executes the second half while it is set. You may need another flag to indicate that the second half is armed to run. (Or just poll the timer counter directly like what justme said. Much simpler if nothing else needs to happen right when the delay is over).

An interrupt of exactly some amount requires running things in the interrupt which is bad practice if not absolutely necessary.

\$\endgroup\$
1
\$\begingroup\$

Timers are great at making accurate time intervals. But if you just need a one time delay of some period longer than 10us, when the chip is enabled and don't want to hassle with configuring a timer, then a simple for loop with a NOP might be the most expeditious path. Add a pragma to it so that it doesn't change with optimization levels and you are good to go.

\$\endgroup\$
6
  • \$\begingroup\$ well I need a delay of 10us. by a for loop with a NOP you mean just an empty loop? how are you ensuring the 10us elapse? \$\endgroup\$
    – xyf
    Jul 9, 2020 at 16:57
  • \$\begingroup\$ @xyf There are several ways, empirically measure it via a pin toggle, or count the number of cycles to execute a nop instruction (it's in the datasheet for the processor), then multiply by the clock period, just like for a timer, then loop over that x times to get to 10us. The loop overhead will put you a little beyond 10us. \$\endgroup\$
    – Aaron
    Jul 9, 2020 at 20:00
  • \$\begingroup\$ @xyf If you want to get closer to 10us, you can count the incrementor, comparison and the jump instruction. \$\endgroup\$
    – Aaron
    Jul 9, 2020 at 20:02
  • \$\begingroup\$ sorry not sure I fully understood your last point. what I did now is set the prescalar to 1 and the period to 160 which should generate the delay of 10us, but how do I go about validating it since it's too short of a period \$\endgroup\$
    – xyf
    Jul 9, 2020 at 20:31
  • \$\begingroup\$ If you have a scope or logic analyzer, toggle a pin! \$\endgroup\$
    – Aaron
    Jul 9, 2020 at 20:42
1
\$\begingroup\$

I was suggested to not use SysTick timer for too small of a delay anyways and rather go for hardware timers.

Yes, this is the sensible approach. 10us is a pretty "hard" real-time constraint.

I read up on Timers and to me, it looks more like they're mainly used for tasks where you need to do a function at a specific interval

Not necessarily. They are supposed to be general enough to be used in a lots of different ways. Periodic interrupts, one-time interrupts, PWM generation, input capture, output generation etc etc.

and it's nonblocking as opposed to a delay

Indeed, this is often a big advantage over fishy busy-delays.

Is there a way around it?

Set up the pre-scaler, enable the timer interrupt, start the timer, from the ISR that triggers 10us later simply disable the timer interrupt, then set a application-specific flag. Preferably wrap this whole functionality including the ISR inside some manner of HAL.

You could have something like this in the caller code:

tim_init(10);
...

tim_enable(timer_n);

while(!tim_done(timer_n))
{
  /* optionally do other stuff here while you wait */
}
\$\endgroup\$
1
  • \$\begingroup\$ but can you even achieve 10us with SysTick given the HAL code has 1000 hardcoded? okay. why interrupt in particular? it's not just one time using the ISR - at least that's what I interpret from your comment about disabling it. I just wanna use it at specific instance of the program like I would HAL_Delay() for instance which just polls. Or maybe I misunderstood your point... \$\endgroup\$
    – xyf
    Jul 10, 2020 at 18:56
0
\$\begingroup\$

Or you might use the 74LS121 one-shot delay IC.

\$\endgroup\$
0
\$\begingroup\$

Here is the code on how ST implements a delay in one of their example projects (also as another user has suggested here):

for (volatile uint8_t i=0; i!=0xFF; i++);

Change the value of 0xFF to the max count representing the max time delay you would like based on the system clock of your device. Assuming that you use STM32CubeMX to generate your project template, this system clock is the same SYSCLK (MHz) clock under 'Clock Configuration'.

Example: if you use a max count of 8000 on a system clock of 8MHz, the delay would be 1ms.

\$\endgroup\$
10
  • \$\begingroup\$ right. it's similar to the idea of a timer I guess? but what's the guarantee that each iteration occurs at a clock cycle? for a timer, a tick increments by one every clock cycle till it reaches the max count after which it rolls over to 0 \$\endgroup\$
    – xyf
    Jul 10, 2020 at 4:28
  • \$\begingroup\$ This is blocking though, and it's very inaccurate. \$\endgroup\$
    – Lundin
    Jul 10, 2020 at 8:50
  • \$\begingroup\$ @lundin yes blocking, and yes accurate and repeatable. The OP said it's when the chip is activated, so no interrupts. However the OP also said at least 10us, which means if there were an interrupt it still meets the time requirement. \$\endgroup\$
    – Aaron
    Jul 10, 2020 at 15:01
  • \$\begingroup\$ @xyf the guarantee is that the CPU will execute the instructions in the specified number of clock cycles every time as per the instruction datasheet. If it didn't,then nothing we do in code would work well. \$\endgroup\$
    – Aaron
    Jul 10, 2020 at 15:05
  • \$\begingroup\$ @xyf An interrupt might make this inaccurate. You can also verify if each iteration occurs at a clock cycle through the assembly language translation. I believe the volatile keyword also makes sure that the iteration happens regardless of the optimization level \$\endgroup\$
    – Cimory
    Jul 10, 2020 at 20:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.