How do you code your delays and waits  - Page 1
Forum section: electronics / general

author: jancumps
author_rank: Unknown
timestamp: March 23, 2014, 11:25:04 am
content: I'm curious about the options to wait/delay that you use (for instance, if the spec of a device says to wait 10msec after a command before it is ready to receive the next command).Do you use a loop, a timer/counter, an interrupt, something else?

author: han
author_rank: Frequent Contributor
timestamp: March 23, 2014, 11:39:56 am
content: loop delay in low end 8 bit micro controller because it's simpleinterrupt/timer for complex device since loop delay is bad when using complex device (e.g computer)

author: jancumps
author_rank: Unknown
timestamp: March 23, 2014, 11:46:51 am
content: Quote from: AhhhChoo on March 23, 2014, 11:44:49 amThat's what I'm interested in. These different ways you are mentioning.

author: dannyf
author_rank: Super Contributor
timestamp: March 23, 2014, 11:48:30 am
content: It depends on the actual application. I use both. For start-ups, I tend to initialize the slow devices first and then do software delays (looping around).Once the code is running, I typically use a flag to maintain timing - this allows the mcu to take care of other tasks while the slow process is running.

author: madires
author_rank: Super Contributor
timestamp: March 23, 2014, 12:08:50 pm
content: I use whatever works best in a given situation. Another method is to create a stacked or cascaded wait with calls and nops in assembler.

author: 8086
author_rank: Super Contributor
timestamp: March 23, 2014, 12:10:50 pm
content: Depends on:Type of microClock frequencyLanguage usedLength of delay

author: han
author_rank: Frequent Contributor
timestamp: March 23, 2014, 12:29:30 pm
content: loop delay:pro:- simple easy to use- not much things to wrongcon:- 100% cpu time when not optimized- cpu cannot do anything else (except its threaded operation)- waste on computing powertypical uses : -simple program-your program didn't have anything else to do-if you lazy to learn how to use interrupt properlyinterrupt/timer/counter:pro: - cpu can do someting else- power save mode can be done- most precise timingcon: - more complex programming- too much interrupt can ruin your day

author: Psi
author_rank: Super Contributor
timestamp: March 23, 2014, 12:48:22 pm
content: I usually dedicate one hardware timer to delays. I enable its overflow interrupt and write code to increment many variables inside the interrupt handler. These variables are reset to zero when they reach a set point which stops the incrementing.So my clk_100Hz variable might count 50 interrupt events then reset where as my clk_200Hz variable might count 100 etc.. then reset to zeroInside the main program loop i then check these 'flag variables' for a zero condition and run any code that i want repeating at that rate. (Then i set it back to 1 so it starts counting again).Although you can divide any clock down to as slow as you want you should configure the timer so the overflow interrupt happens as slow as you can get away with so it doesn't take up CPU time needlessly. This can be done with a combination of changing the timers clock setting and updating the timers count register to a value which will overflow sooner. The timers clock settings give you course control and the counter register gives you fine control to get the interrupt running at the right speed so your delay numbers make sense, eg, 10 count = 1 msI wrote a page in the wiki with an example a while backHere

author: dannyf
author_rank: Super Contributor
timestamp: March 23, 2014, 01:13:13 pm
content: QuoteTry to write one with awareness of cpu speed and works under all optimization levels.

author: han
author_rank: Frequent Contributor
timestamp: March 23, 2014, 01:41:02 pm
content: Quote from: dannyf on March 23, 2014, 01:13:13 pmthe only problem i ever get is only wrong delay period (e.g longer / shorter delay)using interrupt blindly can cause greater headache

author: jaxbird
author_rank: Frequent Contributor
timestamp: March 23, 2014, 01:43:04 pm
content: Highly depends on the situation and what you are programming in.Some suggestions (some already mentioned)a simple NOP loop if you got nothing better to do.Put the mcu to sleep for the duration if you are on battery power.One or more timer interrupt based counters.If you go full assembly, write your main loop with a constant cycle count independent of the code path to keep track of timings.Etc, ect, lots of room for creativity here if you need to squeeze more cycles out of your mcu or save a few bytes of memory.

author: dannyf
author_rank: Super Contributor
timestamp: March 23, 2014, 01:53:13 pm
content: QuoteUsing anything blindly can cause greater headache.QuoteYeah. creating delays() is one of those simple yet highly challenging things due to the large number of approaches that can be deployed.

author: jaxbird
author_rank: Frequent Contributor
timestamp: March 23, 2014, 02:30:35 pm
content: Quote from: dannyf on March 23, 2014, 01:53:13 pmAgree, it really depends on the accuracy, frequency and environment required plus what else needs to be taken care of at the same time. Priority etc, no best practice to recommended without very specific details.

author: dannyf
author_rank: Super Contributor
timestamp: March 23, 2014, 05:35:53 pm
content: It's not difficult at all to implement a systick-styled timer. The issues are resource and cycle drain.a low end mcu may have 2-3 timers and dedicating one to time keeping can be expensive.not to mention that you likely need to keep time over a few seconds at a minimum so a 32-bit type is needed. That means isr and reading and writing 32-bit types frequently in a 8-bitter.so it is doable, but not wise to do, in my view.

author: Rick Law
author_rank: Super Contributor
timestamp: March 24, 2014, 03:19:51 am
content: With MCU's, I am only familiar with the ATMega using the Arduino boot loader.Everything that must be done or are done in the start(), I would use delay(). Stuff done in the loop(), I use an elapse timer unless it is in the single-digit millisecond range.

author: SL4P
author_rank: Super Contributor
timestamp: March 24, 2014, 06:55:33 am
content: What I usually do is setup a timer interrupt at the highest tick rate I'm going to need (e.g. 100mS), and simply increment a global variable with each tick. Use an unsigned int or long as the counter, and reset those counters for each time domain as needed* - e.g. user interface events are cleared when a button is released so that new events will time-up from that.In main() code, I test when the tick count reaches my next desired trigger point, an execute the required code.You can test several times for different tasks, or maintain 'separate' tickers at divisors of the main tick count.Remember to put as little as you can within the interrupt routinbe to keep the time-critical takk on-time, and called when 'you' want them.You can also perform relative timing - e.g. button down + 1500mS represents a long-press event.Remember to arrange your timed 'ifs' in order that the most important calls are made first !!The possibilities are endless. Or as others have mentioned - simple while() loops are OK in single-task code... as long as there is no chance of getting stuck in the loop!. WDT are made to protect you from this (* e.g. above)!

author: dannyf
author_rank: Super Contributor
timestamp: March 24, 2014, 11:11:11 am
content: -reset those counters for each time domain as needed* -I wouldn't reset it - just keep it running. Local timing events will keep their timing info locally. With this approach, multiple timing events can be kept concurrently and independently.an almost true systick implementation.

author: Kjelt
author_rank: Super Contributor
timestamp: March 24, 2014, 11:33:50 am
content: Quote from: dannyf on March 23, 2014, 05:35:53 pmI generally use one HW timer to generate an universal 1ms tick. Not only for timekeeping but also for other tasks that have to wait for an event for instance (not extremely timecritical stuff).That 1ms tick I can subscribe 8 or 16 bit timers. Indeed I never use 32 bit timers on 8 bit uC, instead I sub-subscribe a second timer on a primary timer, for instance one 16bit 60000 count (for one second) on the 1ms and then another 16 bits on the 1s . So I cascade timers.

author: SL4P
author_rank: Super Contributor
timestamp: March 25, 2014, 01:22:45 am
content: Quote from: dannyf on March 24, 2014, 11:11:11 amIf your tick timing is critical, or can have problems with interrupts too close together - then reset the counter(s) on and to - a known value ! Allowing the counter to wrap around is OK, but not predictable if you are using a preset value other than zero as a counter calibration start point.e.g. if you reset the tick-counter arbitrarily, then then next interrupt event may occur 'too soon' r 'too late' if you are using it as a constant (e.g. time-of-day), or your LED blinky may randomly give a double-blink, and you wonder why !

author: ve7xen
author_rank: Super Contributor
timestamp: March 25, 2014, 05:19:53 am
content: I often do 1ms ticks, the ISR simply incrementing the current time. In main, I then have a scheduler queue (simple) which contains the target time and either a function pointer or index for a switch block to do the work, sorted by target time. Each loop pass just needs to check if the target time of the queue head is reached or not, and pop/execute it if so. For delays shorter than 1ms like satisfying setup times, busy wait is usually fine.Timing precision isn't guaranteed (worst case is probably if your 1ms turns into 0ms, I think), but execution order is, it's pretty easy to extend (e.g. add priority) and manage, and you're not blocking interrupts for very long. Likewise, I usually try to have my ISRs just collect data or set pre-processed outputs/settings and submit an event to the queue for processing if necessary.

author: zapta
author_rank: Super Contributor
timestamp: March 25, 2014, 01:05:17 pm
content: In my last project I used a 16 bit counter with 4usec ticks. For short critical ticks (e.g. half a start bit with serial bit banging) I busy loop that polls the timer. For longer time period I extend it in the main loop() to a global uint32 time in millis, no interrupts needed. Then I used multiple instances of a simple class that tracks time in millis based on the global uin32 time in millishttps://github.com/zapta/linbus/blob/master/prototype/arduino/passive_timer.h

author: dannyf
author_rank: Super Contributor
timestamp: March 25, 2014, 01:06:05 pm
content: QuoteYou can still get sub 1ms timing resolution (for example, down to 1us on a 1Mhz mcu) even if your timer overflows every 1ms. It just increases the overhead when you read that value, and / or you are willing to live with non-atomicity.

author: Unknown
author_rank: Unknown
timestamp: March 25, 2014, 11:51:25 pm
content: For PIC projects with very small delays I setup some #DEFS to some NOPs; for longer non-critical I sometimes do loops on NOP's; but if I need longer still then instead of loops on loops of NOPs I use the internal timers; a while back I created a simple c# application to create the code for delays using timers on PICs.ISR "Tickers", as mentioned are very handy to ensure you widget does everything it needs to at the right time.

author: Psi
author_rank: Super Contributor
timestamp: March 26, 2014, 12:02:58 am
content: On the subject of timing. If anyone else decides to use a raw 2-4khz piezo beeper with a <20mhz mcu make sure you put it on a timer output pin and don't try to pulse it in software.I had a mistake on one of my PCBs where the beeper ended up on an input compare pin instead of an output compare pin so i couldn't drive it in hardware. I had to resort to interrupt driven software toggling the pin at 4khz.It was quite hard to get a 'pure' sound out of it. All other active interrupts randomly delayed execution of the beeper interrupt and caused a 'dirty tone'

author: Rerouter
author_rank: Super Contributor
timestamp: March 26, 2014, 01:26:41 am
content: As my recent project have been lower power, i put the processor into sleep and have a watchdog wake it up close to the required delay (I'm not doing mission critical stuff, e.g. waiting greater than 400ms for a sensor to wake up)other than that, it comes down to why your waiting, if its just wait 500ms to prevent registering a button press twice, then use a time stamp compare and remove the delay altogether,my other normal way is doing a time compare and return system, and it can work well even for large code, though i will admit it makes it a little less maintainable,

author: Psi
author_rank: Super Contributor
timestamp: March 26, 2014, 01:37:29 am
content: Does anyone have tips/tricks for dealing with wrap-around on global tick variables?

author: mazurov
author_rank: Frequent Contributor
timestamp: March 26, 2014, 01:40:28 am
content: Some time ago I wrote a code to drive HD44780-compatible display. The application for which it was written is very sensitive to blocking so loop/nop delays are not possible. I have a queue to feed the display consumed by a timer interrupt. Depending on the data, the interrupt reloads its own (timer's) period register - there are two commands requiring longer delays. Here's the link ->https://www.circuitsathome.com/mcu/driving-a-character-lcd-using-pic24-enhanced-parallel-master-port

author: miguelvp
author_rank: Super Contributor
timestamp: March 26, 2014, 02:23:18 am
content: Quote from: Psi on March 26, 2014, 01:37:29 amI usually ignore them because what matters is deltas, so when doing the math on unsigned values it should wrap around naturally and the deltas would be preservedso for unsigned shorts:2 - 33 = 655050x00020x00210xFFE1Code:unsigned short val1, val2, val3;val1 = 2;val2 = 33;val3 = val1 - val2;So as long as you don't wrap around more than once per measurement you'll be fine.Edit: to explain a bit better2 - 33 = 65505 is the same as (65536)+2-33, where the 65536 is all the values that a 16 bit unsigned integer can hold (including 0).So the rule of thumb will be (2^x)+val1-val2 = delta if val1 is less than val2, where x is the bit count for your unsigned register. And the 2^x is implied and doesn't need to be coded because your are using deltas.

author: miguelvp
author_rank: Super Contributor
timestamp: March 26, 2014, 02:44:56 am
content: I should have used better names for my variables so replying againCode:unsigned short beginTime, endTime, deltaTime;beginTime = 33;endTime = 2; // after warp-arounddeltaTime = endTime - beginTime;

author: ecat
author_rank: Frequent Contributor
timestamp: March 26, 2014, 02:46:30 am
content: Quote from: Psi on March 26, 2014, 01:37:29 amWhat are you trying to achieve? What are you trying to avoid? Why is wrap-around a problem?What CPU are you using? What size is its native integer? How does the compiler handle addition when using larger than native integer types?tl;drIt depends

author: Psi
author_rank: Super Contributor
timestamp: March 26, 2014, 03:01:12 am
content: I thought it was pretty obvious.You have a variable that ticks (incremented from interrupt/hardware timer).Eventually it's going to wrap-around and start from zero again.If you try and use that for timestamping things then you have to deal with the situation of the wrap occuring during two timestamps.You can detect the wrap around and remove it but then you have to consider that the wrap may occur multiple times etc..Are there any coding tips/tricks to handle this in a simple way.

author: miguelvp
author_rank: Super Contributor
timestamp: March 26, 2014, 03:19:36 am
content: Quote from: Psi on March 26, 2014, 03:01:12 amNot sure if you saw my answer.If you use deltas then there is no problem with unsigned computer math, doesn't get simpler than that.If you want to keep a time stamp, use the deltas to update a bigger number (like an unsigned 64bit counter) provided you call the update within twice the wrap around time.If using an interrupt then you can update the bigger number there making it available to the system.If hardware counter (usually at least 32bits) or 4294967296, you have that many ticks per check So you might get in trouble if you don't update it once a second on a 4.29 GHz processor.Edit: actually if you don't update it once in 2 seconds on a 4.29 GHz processor because it won't wrap around twice until then

author: Psi
author_rank: Super Contributor
timestamp: March 26, 2014, 03:44:10 am
content: Quote from: miguelvp on March 26, 2014, 02:23:18 amthanks, that makes sense

author: ecat
author_rank: Frequent Contributor
timestamp: March 26, 2014, 03:45:52 am
content: Quote from: Psi on March 26, 2014, 03:01:12 amThe simple answer is 'use a bigger integer'.You can count the wrap arounds in the interrupt routine, maybe you can count them in you main loop - both methods are effectively making a bigger integer.If you are constrained by code size, data size, execution time or the need to ensure your interrupt routine always executes in a fixed number of cycles then you may need something a little more imaginative than changing a uint16 to either a unit32 or something involving a couple of 'if' statements. Obviously

author: miguelvp
author_rank: Super Contributor
timestamp: March 26, 2014, 03:56:47 am
content: Quote from: Psi on March 26, 2014, 03:44:10 amJust make sure you use unsigned types if possible. Signed types will work as well but it's harder to wrap your head around it (pun intended).But the problem with signed types is that it doesn't allow you to wait for two times the wrapping since some values will result on negative numbers, so stick to unsigned types.Edit: And you have to measure within the wrap around not twice like I mentioned before I just tried it and it will only allow you up to 2^x-1 delta, but on a 32bit counter that is once a second in a 4.29GHz system.i.e. maximum delta time possible is (2^x)-1 or 4294967295 on a 32 bit unsigned counter

author: dannyf
author_rank: Super Contributor
timestamp: March 26, 2014, 10:58:38 am
content: QuoteThere is no cure for that (within the confines of the polling construct that we are discussing here) other than using a sufficiently large data type so it doesn't happen.

author: jancumps
author_rank: Unknown
timestamp: March 30, 2014, 07:39:47 pm
content: Quote from: mazurov on March 26, 2014, 01:40:28 amI'm going to check your library and see if I can make it work on the TI TMS570 ARM Cortex that I have running here.