ARM Kinetis, alternative to the millis() function. - Page 1
Forum section: electronics / general

author: luiHS
author_rank: Frequent Contributor
timestamp: January 24, 2020, 06:20:14 am
content: Hi.I need to control the time between events in a program with an ARM Kinetis MK66 microcontroller. In Arduino we have the functions micros() and millis(), but these are not in the MCUXpresso environment.I've been looking for information, but I'm not sure. The problem is that in my program several interrupts are already being used to read the status of several GPIO ports, and using a timer linked to an interruption to create something similar to the millis() might affect the rest of the interruptions.What would be a good alternative to the millis() of Arduino, to implement it in an ARM Kinetis MK66 using MCUxpresso?Best Regards.

author: ataradov
author_rank: Super Contributor
timestamp: January 24, 2020, 06:28:25 am
content: If you don't need precise delay, then simple stuff like this works magic:Code:static inline void delay_ms(int ms){uint32_t cycles = ms * F_CPU / 4 / 1000;asm volatile ("1: subs %, %, #1 \n"" nop \n"" bne 1b \n":  "+r"(cycles));}static inline void delay_cycles(int cycles){cycles /= 4;asm volatile ("1: subs %, %, #1 \n"" nop \n"" bne 1b \n":  "+l"(cycles));}Other than this, a more precise way it to use a timer, but don't use interrupts. Just poll for a flag. You are busy-waiting anyway, interrupts don't buy you anything.

author: luiHS
author_rank: Frequent Contributor
timestamp: January 24, 2020, 08:04:25 am
content: Thanks ataradovHowever, I think the examples you've put using assembler are for delay routines. In my case it is not about the program waiting for a certain time in that routine, but what I need is to constantly measure the time between events.Specifically, in my program, I send a command via serial port to a Raspberry to play a video. My program stays in a while (1) loop doing other tasks, such as reading GPIO ports for interruptions and depending on the state of those ports, doing other things. Within the while (1) loop I want to control how much time has elapsed since I sent the command through a serial port, to know when finish a video and be able to send another, each video has a different duration.On the other hand I was watching the use of the Systick, and in fact the routine was already set in the libraries that my program uses to handle the SD card, which is being used equally and is already set to 1ms.What I have found is that when trying to use the g_eventTimeMilliseconds variable that is incremented in the SysTick_Handler routine, the compiler gave me an error, indicating that the variable is not defined. It is a rare thing, because when I hover over the variable in my source it is recognized and I can go to the source fsl_sdmmc_event.c where it is defined.void SDMMCEVENT_InitTimer(void){/* Set systick reload value to generate 1ms interrupt */SysTick_Config(CLOCK_GetFreq(kCLOCK_CoreSysClk) / 1000U);}void SysTick_Handler(void){g_eventTimeMilliseconds++;}What I have done is declare it in my source asexterng_eventTimeMilliseconds and so it has compiled me, but I don't know if this is correct and it will work.extern g_eventTimeMilliseconds;// Calculo tiempo transcurrido en milisegundostiempo_transcurrido = g_eventTimeMilliseconds - tiempo_inicial;tiempo_inicial = g_eventTimeMilliseconds;

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 24, 2020, 08:17:33 am
content: Kinetis K66 does have a cycle counter in the DWT (exposed usually asDWT_CYCCNT, although the DWT needs to be enabled); I would use that. It does not involve any sort of interrupt or overhead, it is just a hardware cycle counter.Here is an example:Code:typedef struct {uint32_t core_freq;uint32_t cyccnt_start;uint32_t cyccnt_stop;} duration;static inline void duration_init(duration *d){/* TODO: Enable DWT */d->core_freq = CLOCK_GetFreq(kCLOCK_CoreSysClk);d->cyccnt_start = d->cyccnt_stop = DWT_CYCCNT;}static inline void duration_start(duration *d){d->cyccnt_start = DWT_CYCCNT;}static inline void duration_mark(duration *d){d->cyccnt_stop = DWT_CYCCNT;}static inline uint32_t elapsed_ms(const duration *d){return ((uint64_t)((uint32_t)(d->cyccnt_stop - d->cyccnt_start)) * 1000) / (uint64_t)d->core_freq);}static inline uint32_t elapsed_us(const duration *d){return ((uint64_t)((uint32_t)(d->cyccnt_stop - d->cyccnt_start)) * 1000000) / (uint64_t)d->core_freq);}The idea is that you callduration_init()to initialize the structure, capturing the current core clock frequency.Then, you callduration_start()to capture the start moment.Whenever you want to know the elapsed time from the moment you calledduration_start(), callduration_mark(), and obtain the elapsed time in milliseconds usingelapsed_ms()or in microseconds usingelapsed_us().The problem is, of course, that the cycle counter overruns in less than 24 seconds at 180 MHz, and in just over 7 seconds at 600 MHz.There are workarounds for these, as long as you call a specific update function every few seconds (before the cycle counter overruns).You can even adjust for a changing clock frequency, if you call another pair of functions every time the clock frequency is modified (before and after the core clock frequency modification). This way you can get a monotonic millisecond and microsecond counters that properly reflect core frequency changes.

author: lucazader
author_rank: Regular Contributor
timestamp: January 24, 2020, 08:18:28 am
content: You will probably find that there is some sort of function like "HAL_GetTick" or similar (that particular name is what stm32's use in their HAL)This function will return the value in the g_eventTimeMilliseconds variable.It is much safer to access this variable using a function call than to use that variable as an extern.

author: westfw
author_rank: Super Contributor
timestamp: January 24, 2020, 08:52:43 am
content: Note that at 180MHz, the 32bit CYCCNT register will overflow in about 22 seconds.Do you really not already use the SysTick interrupt? for all practical purposes, the Arduino functions just increment a millisecond counter from some SysTick timer interrupt equivalent...It looks like the K66 also has an RTC module (RTC implementations vary a lot, but are usually good for timing larger intervals.) I didn't read very carefully, but it sounds like the K66 RTC can count 64bits worth at up to 32768kHz (good enough for millis(). but perhaps not for micros() (about 30us per tick.)

author: cv007
author_rank: Frequent Contributor
timestamp: January 24, 2020, 09:02:42 am
content: Looking at a imxrt1010 which probably is not too far from a K66 in its sdk code, it has a microseconds.h,microseconds_pit.c,microseconds_sysclk.c buried in the middleware folder. If you already use the systick, then it seems you would choose the microseconds_pit.c as the source (otherwise end up with a complaint about the duplicate systick isr function).It looks like they have blocking and non-blocking delays, although the non-blocking only has one counter so if you need more than 1 you have to use some of its functions to keep your own time variable(s) and duplicate the microseconds_timeout function.Or I guess you can add another var to the current SysTick_Handler for your own use. If g_eventTimeMilliseconds is only incremented in the isr you should be ok with using it, but if the code using it resets it or does any other modifications to it then can just create your own milliseconds var.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 24, 2020, 12:12:41 pm
content: If you call the followingwallclock_ms()at least once every 4294967/kHz (i.e., at least once every period when the 32-bit cycle counter wraps around ), you can use it to measure rough wall clock time elapsed, still use the DWT cycle counter for more accurate measurements, and even change the core system clock at runtime (as long as you callwallclock_pause()before andwallclock_resume(core_kHz)immediately afterwards):Code:#define K66_COREDEBUG_DEMCR (*(volatile uint32_t *)0xE000EDFC)#define K66_COREDEBUG_DEMCR_MASK_TRCENA (1<<24)#define K66_DWT_CTRL (*(volatile uint32_t *)0xE0001000)#define K66_DWT_CTRL_MASK_CYCCNT_ENABLE (1<<0)#define K66_DWT_CYCCNT (*(volatile uint32_t *)0xE0001004)static volatile uint32_t wallclock_millisecs;static volatile uint32_t wallclock_kHz;static volatile uint32_t wallclock_cyccnt;/* Initialize wall clock.*/void wallclock_init(const uint32_t kHz){/* Reset wallclock. */wallclock_millisecs = 0;wallclock_kHz = kHz;wallclock_cyccnt = 0;/* Enable access to DWT. */K66_COREDEBUG_DEMCR |= K66_COREDEBUG_DEMCR_MASK_TRCENA;/* DWT cycle counter should be cleared before being enabled. */K66_DWT_CYCCNT = 0;/* Enable DWT cycle counter. */K66_DWT_CTRL |= K66_DWT_CTRL_MASK_CYCCNT_ENABLE;}/* Internal update function. Is only called with interrupts disabled.wallclock_update(wallclock_kHz) or wallclock_ms() must be called at leastonce before DWT_CYCCNT wraps, ie. at least once per 4294967/f seconds,where f is the core system clock frequency in kHz.*/static inline void wallclock_update(const uint32_t kHz){if (kHz) {uint32_t cycles, ms;/* Exact number of cycles since last update. */cycles = (uint32_t)(K66_DWT_CYCCNT - wallclock_cyccnt);/* Milliseconds since last update; and the equivalent in cycles. */ms = cycles / kHz;cycles = ms * kHz; /* or substract the remainder of the division from cycles *//* Update the wallclock to reflect the elapsed time. */wallclock_millisecs += ms;wallclock_cyccnt += cycles;}}/* millisecond clock derived from the core system clock.Calls wallclock_update().*/uint32_t wallclock_ms(void){uint32_t ms;int enabled;enabled = __disable_irq();wallclock_update(wallclock_kHz);ms = wallclock_millisecs;if (enabled)__enable_irq();return ms;}/* Pause and resume functions.When changing core frequency:1. Optionally disable interrupts2. Call wallclock_pause()3. Change core system clock frequency4. Call wallclock_resume()5. Enable interrupts if disabled at step 1.*/void wallclock_pause(void){int enabled;enabled = __disable_irq();wallclock_update(wallclock_kHz);wallclock_kHz = 0;if (enabled)__enable_irq();}void wallclock_resume(const uint32_t kHz){int enabled;enabled = __disable_irq();wallclock_kHz = kHz;wallclock_cyccnt = K66_DWT_CYCCNT;if (enabled)__enable_irq();}This does disable interrupts for a very short duration (some accesses, one division, and one multiplication operation, which on a K66 are all fast hardware operations, so just for a handful of cycles), using__disable_irq()(which should return nonzero if interrupts were enabled prior to the call, and zero if interrupts were already disabled) and__enable_irq().I would call this acceptable for all sorts of timeouts and such, but not for accurate real-world measurements. For example, to simplify the update calculation (during which interrupts must be disabled), the core system clock frequency is only specified at kiloherz precision. If the core system clock is at least 8 MHz, then the error due to that is less than 0.0125%.The way the above is written,wallclock_ms()is safe to call in an interrupt handler. (Assuming you do not directly callwallclock_update()from your own code without interrupts being disabled.)Although the cycle counter wraps around every few seconds, the value returned bywallclock_ms()counts up to 232-1 in a monotonic manner. If you want, you could make it 64-bit, even.If you need a more precise wall clock timer for timeouts and delays, I would use the DWT cycle counter directly, and calculate the limiting number of cycles instead. For example, ifCLOCK_GetFreq(kCLOCK_CoreSysClk)yields the current core system clock frequency in Hertz:Code:static inline uint32_t milliseconds_to_cycles(uint32_t ms){return (uint64_t)us * (uint64_t)(CLOCK_GetFreq(kCLOCK_CoreSysClk)) / 1000;}static inline uint32_t microseconds_to_cycles(uint32_t us){return (uint64_t)us * (uint64_t)(CLOCK_GetFreq(kCLOCK_CoreSysClk)) / 1000000;}static inline uint32_t nanoseconds_to_cycles(uint32_t ns){return (uint64_t)ns * (uint64_t)(CLOCK_GetFreq(kCLOCK_CoreSysClk)) / 1000000000;}With these, when DWT cycle counter is enabled, you can implement e.g. delays thus:Code:static inline void delay_cycles(const uint32_t cycles){const uint32_t started = K66_DWT_CYCCNT;while ((uint32_t)(K66_DWT_CYCCNT - started) < cycles) {/* Do nothing */}}void delay_ms(const uint32_t ms) { delay_cycles(milliseconds_to_cycles(ms)); }void delay_us(const uint32_t us) { delay_cycles(microseconds_to_cycles(us)); }void delay_ns(const uint32_t ns) { delay_cycles(nanoseconds_to_cycles(ns)); }Note that this works just fine with the wall clock stuff; they do not interfere with each others operation.

author: ataradov
author_rank: Super Contributor
timestamp: January 24, 2020, 05:17:37 pm
content: Quote from: luiHS on January 24, 2020, 08:04:25 amYes, my bad, brainfarted.In case of time measurement if you only need ms resolution, then the best approach is to use a regular timer, but prescale its clock to 1 kHz or as low as you can get it. Even 16-bit timer will give you 65 seconds before the overflow, and another 65 seconds after the overflow, which can still be wrapped into one measured time interval. This system is also flexible enough to let you adjust the resolution sacrificing the maximum duration without polling.And even if you want to go with interrupt based approach, do not do the most obvious thing - interrupts every ms. Let interrupts only happen on counter overflow, and calculate how much time passed in the millis() function itself. That function will take the value of the software counter that is incremented in the interrupts and the current value in the SysTick counter.

author: ejeffrey
author_rank: Super Contributor
timestamp: January 24, 2020, 06:29:22 pm
content: The Kinetis microcontrollers PIT peripheral has two timers that can be daisy chained to make a 64 bit counter by calling PIT_SetTimerChainMode. Then you can have a global uptime that "never" rolls over. Even though it is the "programmable interrupt timer" you don't have to enable timer interrupts to read the value.The Kinetis "L" devices (based on the ARM Cortex M0+ core) actually have a chained lifetime counter which allows you to read the combined 64 bit counter value atomically. The K series do not have that, but it is easy enough to detect rollover. The basic idea is: read the "high" word, then read the low word. If the low word is <= N, re-read the high word. N should be large enough to account for a possible interrupt unless you disable interrupts while reading the counters.

author: ataradov
author_rank: Super Contributor
timestamp: January 24, 2020, 06:35:32 pm
content: For atomic reads of chained things like this that don't need an explicit N, I do something like this:Code:uint32_t high, low1, low2;do{low1 = read_low_register();high = read_high_register();low2 = read_low_register();} while (low1 > low2);uint64_t full_value = ((uint64_t)high << 32) | low2;This code assumes that counters count up and overflow to 0.Realistically the code will loop at most twice. But in the worst case of heavy interrupt load it will loop until the correct values are read.

author: ejeffrey
author_rank: Super Contributor
timestamp: January 24, 2020, 06:54:44 pm
content: If interrupts are taking long enough that it loops more than once then it could be wrong anyway. In that case you want:Code:uint32_t high, low1, low2;do{high1 = read_high_register();low = read_low_register();high2 = read_high_register();} while (high1 != high2);uint64_t full_value = ((uint64_t)high2 << 32) | low;Which should always be correct.

author: ataradov
author_rank: Super Contributor
timestamp: January 24, 2020, 06:56:44 pm
content: Correct, I mistyped from memory. But the principle stands.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 24, 2020, 10:16:06 pm
content: If you have atomic 32-bit access and a 32-bit nondecreasing counter (except at wraparound), you can trivially extend it to 62 bits:Code:#define CYCLES_COUNT (K66_DWT_CYCCNT)#define CYCLES_MASK_HIGH ((uint32_t)0xC0000000)#define CYCLES_MASK_LOW ((uint32_t)0x3FFFFFFF)/* Bits 31, 30, 62, 61, 60, .., 33, 32 of 62-bit cycle counter: */static volatile uint32_t cycles_state;/* Return current 62-bit cycle counter value */uint64_t cycles(void){uint32_t state, count, count_high;/* We must read state first, */state = cycles_state;/* cycle count second. */count = CYCLES_COUNT;/* Highest two bits of count. */count_high = count & CYCLES_MASK_HIGH;/* Increment state if count has wrapped around since the last update. */state += count_high < (state & CYCLES_MASK_HIGH);/* Ignore those two highest bits of state otherwise. */state &= CYCLES_MASK_LOW;/* Note: this may store a slightly stale state, but not so stale to cause an error;count_high is never as old/stale as state. */cycles_state = state | count_high;/* Return the combined 62-bit value. */return ((uint64_t)state << 32) | count;}This should work even if it gets nested/re-entered (because it was interrupted by a function that usescycles()), as long as the interval between outermost calls tocycles()is somewhat less than 231cycles; try to keep it under 230.It is one of those functions that looks deceptively simple, like locking; but make even an innocuous-looking change, and it may break.The way it works is that two of the most significant bits incycles_stateduplicate the two most significant bits of the 32-bit counter. The upper 30 bits get incremented only when the cycle counter has wrapped around with respect to the counter valuecycles_statewas based on.If the calls happen to get nested,cycles_statemay become too stale, but never by three quadrants or more. This means that the nextcycles()call will restore the stale value (although it may remain stale by less than two quadrants), including the cycle counter wraparound increment. Because the more recent pair of the shared bits (always incounter) are used (and increment done wrt. this value), the staleness does not cause any error in the result.Even the 62-bit overflow case is handled correctly (the wraparound increment does not overflow into the two shared highest bits).At 1 GHz, 230cycles is just over one second, and the 62-bit counter wraps around every 146 years or so.(I should probably verify this code first before posting it, but it's Friday. If you have any reason to suspect it shouldn't, or should fail in some corner case I haven't thought about, I can put the code through a corner-case stress test to verify.)

author: westfw
author_rank: Super Contributor
timestamp: January 25, 2020, 05:09:11 am
content: QuoteBe careful that the math to convert ticks to millisecondsIsn't too expensive.doesn't cause weird behavior when the math overflows at a different place than the tick counter... (IIRC, Arduino code used to have this problem!)

author: iMo
author_rank: Super Contributor
timestamp: January 26, 2020, 12:30:46 am
content: The standard approach is to use SysTick - a 24bit down counter (runs at cpu clock) you may find in all cortexes. The SysTick could be automatically preloaded (SysTick->Load) and it fires an interrupt at zero. Thus you get 1ms tick for millis(). And because 1ms is derived from SysTick (you may read SysTick->VAL), you get micros() easily too.PS: in your above case for example (assuming your g_eventTimeMilliseconds variable is global)inline uint32_t millis() {return g_eventTimeMilliseconds;}inline void delay(uint32_t milliseconds) {uint32_t tstart = g_eventTimeMilliseconds ;while((g_eventTimeMilliseconds - tstart) < milliseconds) {yield();}}You may test it like:uint32_t start = millis();delay (1234);uint32_t elapsed =millis() - start;

author: luiHS
author_rank: Frequent Contributor
timestamp: January 29, 2020, 11:51:35 am
content: Quote from: imoThank you, it has been very useful, I have already programmed it that way and it works perfectly.A question.What is yield() for?I had never seen or used it, and even though I am looking for information, I am still not sure what this function is for. I have only seen one useful case, which would be to restart the Watchdog in a delay.

author: ejeffrey
author_rank: Super Contributor
timestamp: January 29, 2020, 06:35:08 pm
content: yield() is a call in cooperative multitasking systems that tell the scheduler you aren't doing anything important and that other tasks can be activated. If you don't include it then all other non-interrupt driven tasks on the CPU will be blocked while waiting for the timeout. Some preemptive (interrupt drive) schedulers also allow you to yield explicitly in an attempt to get better overall performance but whether that does anything useful is highly variable (for instance, linux sched_yield() is only intended to be used with realtime processes)If you aren't using an RTOS with multiple tasks you can just omit yield and it will be a blocking delay and only interrupts will be able to execute (assuming they are not disabled, such as if you are already running in an interrupt handler).

author: westfw
author_rank: Super Contributor
timestamp: January 30, 2020, 06:51:44 am
content: QuoteIn the Arduino run time environment, yield() is an empty function or no-op (probably eliminated at compile-time) on most platforms, but will occasionally go off and run things like wireless support code on ESP8266 or ESP32 boards...