Delay function - Atmel SAM D21 without ASF - Page 1
Forum section: electronics / general

author: Franc
author_rank: Regular Contributor
timestamp: October 23, 2016, 08:12:39 pm
content: Hello everybodyI am needing to implement a general delay function in my code without ASF Atmel (Delay Service).As I don't know any thing about Assembly, what is the best way to get it with very good accuracy once I will need delays inmicroseconds.Could I do it with TC or only Assembly is the best way?Regards

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 09:49:11 pm
content: Something like this. Now tested, cut and modified from a working project.Code:void timer_init(void){PM->APBCMASK.reg |= PM_APBCMASK_TC4;GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(TC4_GCLK_ID) |GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);TC4->COUNT16.CTRLA.reg =TC_CTRLA_MODE(TC_CTRLA_MODE_COUNT16_Val) |TC_CTRLA_WAVEGEN(TC_CTRLA_WAVEGEN_MFRQ_Val) |TC_CTRLA_PRESCALER(TC_CTRLA_PRESCALER_DIV8_Val) |TC_CTRLA_PRESCSYNC(TC_CTRLA_PRESCSYNC_PRESC_Val);TC4->COUNT16.COUNT.reg = 0;TC4->COUNT16.CC.reg = TIMER_TOP;TC4->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;}void timer_delay(uint16_t us){uint16_t target = TC4->COUNT16.COUNT.reg + us;if (target > TIMER_TOP)target -= TIMER_TOP;TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_MC1;TC4->COUNT16.CC.reg = target;while (0 == TC4->COUNT16.INTFLAG.bit.MC1);}

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 09:52:36 pm
content: But you may get better accuracy by writing something like this:Code:for (int i = 0; i < NNN; i++)asm("nop");And calibrate the value of NNN to your desired delay. This delay will be affected by background interrupts, but if you are working with accurate us delays, then you should disable interrupts for the duration of the delay, of the whole operation that required this delay.

author: Franc
author_rank: Regular Contributor
timestamp: October 23, 2016, 10:22:47 pm
content: Dear Alex, thanks for always share your knowleadge.I found this routine below in assembly in other forum. I don't know how it works but could be used in any SAM DDo you think I will get a nice acurracy ? So I can save one TC and memory space.Code:#ifndef F_CPU#error CPU frequency not defined in sam_gpio.h // Taradov gpio definitions#endif#define _CyclesPerDelayLoop6#define _LoopCount_ms(ms)(uint32_t)(((F_CPU / 1000UL) * (ms)) / _CyclesPerDelayLoop)#define _LoopCount_us(us)(uint32_t)(((F_CPU / 1000000UL) * (us)) / _CyclesPerDelayLoop)#define ddDelay_ms(ms)_ddCycleDelay(_LoopCount_ms(ms))#define ddDelay_us(us)_ddCycleDelay(_LoopCount_us(us))inline void _ddCycleDelay(volatile uint32_t count) {asm volatile ("mov r0, %0 \n"// Get the loop count"loop%=: \n"// Loop start label"DMB \n"// Data Memory Barrier (3 cycles)"sub r0, #1 \n"// Decrement count (1 cycle)"bne loop%= \n"// Branch back to loop (2 cycles):// Nothing output from assembly: "r" (count)// Register input for count: "r0", "cc"// Clobber list);}

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 10:24:51 pm
content: That's a fancy way to write my second code in assembly. Same limitations about interrupts apply here as well.

author: Franc
author_rank: Regular Contributor
timestamp: October 23, 2016, 10:38:22 pm
content: QuoteSorry, I did not understand it totally. Do you have an example? I will miss accuracy if I use this delay together a NVIC or inside a NVIC?

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 10:41:59 pm
content: Quote from: Franc on October 23, 2016, 10:38:22 pmThis function counts number of cycles assuming that each cycle takes some time to execute (1 / F_CPU seconds per cycle).If interrupt happens while you are in this loop, counting will stop for the duration of the interrupt and will resume from the same point once interrupt routine exits. So delay performed by this function will be extended by the time it took to execute interrupt handler.

author: Franc
author_rank: Regular Contributor
timestamp: October 23, 2016, 10:48:59 pm
content: Ohh cool, I understood. Therefore there is not other way to do a secure delay routine once TC also is affected by NVIC?

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 10:57:59 pm
content: Quote from: Franc on October 23, 2016, 10:48:59 pmWhy do you think TC is affected by NVIC?TC code waits for the flag to be set. That waiting can be interrupted, as as long as this interruption is not longer that the actual delay.You need to temporarily block interrupts if you want exact intervals.

author: Franc
author_rank: Regular Contributor
timestamp: October 23, 2016, 11:24:16 pm
content: Quotein DS, page 48,'For the NVIC to activate the interrupt, itmust be enabled in the NVIC interrupt enable register (SETENA/CLRENA bits in ISER/ICER).'Maybe the best way to disabled is Assembly when the accuracy is very important? (maybe in some points of my firmware)

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 11:28:23 pm
content: Quote from: Franc on October 23, 2016, 11:24:16 pmJust use standard code for critical sections:Code:#define ATOMIC_SECTION_ENTER { register uint32_t __atomic; \__asm volatile ("mrs %0, primask" : "=r" (__atomic) ); \__asm volatile ("cpsid i");#define ATOMIC_SECTION_LEAVE __asm volatile ("msr primask, %0" : : "r" (__atomic) ); }Use:Code:ATOMIC_SECTION_ENTER// code that must not be interrupted goes hereATOMIC_SECTION_LEAVEAnd keep atomic sections as short as possible, of course. All pending interrupts will be served after the code leaves the atomic section.Quote from: Franc on October 23, 2016, 11:24:16 pmHos is assembly supposed to help with this? Assembly code is interrupted by interrupts as well.

author: Franc
author_rank: Regular Contributor
timestamp: October 23, 2016, 11:41:47 pm
content: Fantastic Alex....thanks again!! I will use as you recommended.QuoteBelow is assembly and I don't know anything in Assembly language. I need to buy one good book to learn it in ARM .Code:#define ATOMIC_SECTION_ENTER { register uint32_t __atomic; \__asm volatile ("mrs %0, primask" : "=r" (__atomic) ); \__asm volatile ("cpsid i");#define ATOMIC_SECTION_LEAVE __asm volatile ("msr primask, %0" : : "r" (__atomic) ); }

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2016, 11:44:35 pm
content: Quote from: Franc on October 23, 2016, 11:41:47 pmAll documentation is available from ARM. In this case you need to be looking for the following documents:DDI0419C_arm_architecture_v6m_reference_manual.pdf (instruction set and core registers)DDI0484C_cortex_m0p_r0p1_trm.pdf (actual core).Both are available from arm.com after registration.

author: westfw
author_rank: Super Contributor
timestamp: October 24, 2016, 12:13:00 am
content: For "arduino-like" delay() and delay_microseconds() on a SAMD10, I used the RTC peripheral, configured to count at 1MHz (microseconds) and interrupt at 1kHz (milliseconds.) This would give you delays accurate to within, say, +/- 2us, but probably not small numbers of microseconds with 1/10th microsecond accurate...Code is athttps://github.com/WestfW/SAMD10-experiments/blob/master/D10-LED_TOGGLE0/src/UserSource/ticker_rtc.cI don't know if this is a great way to do it; I had a horrible time with clock synchronization (and IIRC ataradov was a big help!)

author: Franc
author_rank: Regular Contributor
timestamp: October 26, 2016, 11:39:32 pm
content: Hi friends!Is possible to do a delay for nanoseconds ? I will get a nice accuracy?What is the best way, just changing the macro below or I defining the delay variable as volatile (delay_us(0.100))?In AVR forum I saw some people using the ASM 'NOP or defining the delay as volatile in delay_us() to get nano delays. This is a way in SAM?QuoteRegards!

author: dannyf
author_rank: Super Contributor
timestamp: October 26, 2016, 11:43:01 pm
content: QuoteIt may not be possible.For something that fast, using NOP() is likely the way to go.For longer delays, you may just loop around or using a hardware timer.

author: ataradov
author_rank: Super Contributor
timestamp: October 26, 2016, 11:43:28 pm
content: Quote from: Franc on October 26, 2016, 11:39:32 pmWell, if MCU runs at 48 MHz, then it takes more than 20 ns to execute one instruction (including NOP).Also, you need to run the code from RAM, since Flash has at least one wait state.

author: dannyf
author_rank: Super Contributor
timestamp: October 26, 2016, 11:48:02 pm
content: Quoteit works, unless SAM D has different instruction sets.Assemblies generally are not portable.QuoteIf that kind of approaches works for you, you don't need accurate delays.and the whole thing can take an entirely different direction.

author: Franc
author_rank: Regular Contributor
timestamp: October 26, 2016, 11:53:30 pm
content: So, if I need 100ns of delay, is possible I use 5 * NOP?regards!

author: ataradov
author_rank: Super Contributor
timestamp: October 26, 2016, 11:54:37 pm
content: Quote from: Franc on October 26, 2016, 11:53:30 pmYes, provided your code runs from RAM and MCU is clocked at 48 MHz.

author: rob42
author_rank: Newbie
timestamp: November 05, 2016, 07:51:11 am
content: Code:#define DELAY_LOOP_CYCLES 3.0 // cycles per loop#define DELAY_US_LOOPS(US) ((u32)((double)(US) * F_CPU / DELAY_LOOP_CYCLES / 1000000.0))#define DELAY_MS_LOOPS(MS) ((u32)((double)(MS) * F_CPU / DELAY_LOOP_CYCLES / 1000.0))#define DELAY_S_LOOPS (S) ((u32)((double)(S) * F_CPU / DELAY_LOOP_CYCLES))#define delay_us( US ) delay_loops( DELAY_US_LOOPS(US) )#define delay_ms( MS ) delay_loops( DELAY_MS_LOOPS(MS) )#define delay_s ( S ) delay_loops( DELAY_S_LOOPS(S) )void delay_loops(u32 loops) {asm(".syntax unified");asm volatile ("1: SUBS %, %, #1 \n"" BNE 1b \n":  "+r"(loops));asm(".syntax divided");}