Timebase for Everything: Ticker Real-Time
I covered the basics for making an embedded project which is also portable in the previous post Part1. Now it’s time to go one step further and implement a portable Ticker (a.k.a. system tick timer) for Porty. It will be the timebase of the system and may come in handy for basic time scheduling.
Features of the Ticker
Ticker is designed to be used with Porty and it has 2 use-cases. First, Ticker counts time in means of “ticks” which means the smallest time unit; second, it calls user functions in its interrupt that gives us a possibility to extend the usage. More importantly, it has to be totally MCU independent and can be used with any microcontroller wether or not it has a built-in SysTick functionality.
The mighty Ticker function runs in an interrupt (elevated) with a predefined period. The period is ideally 1ms and can be smaller when the MCU supports. The sub-millisecond part is defined in settings.h file.
1 |
#define TICKER_SUBMS_COUNT (4UL) ///< Number of ticks per [ms] (>=1) |
Project files
I separated the code in 2, ticker.c and ticker_hw.c in order to make it portable. The ticker_hw.c file initializes a hardware timer, which can also be SysTick in ARM, and runs the ISR routine in the real interrupt. Here TIM4 of STM32F100 is used for demonstration but the attached project also uses SysTick.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/** * @file ticker_hw.c * @author Atakan S. * @date 01/01/2018 * @version 1.0 * @brief Hardware implementation for ticker. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "ticker_hw.h" /** * @brief Ticker hardware init. */ void TickerHwInit(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; //-- TIM4 clock enable. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //-- Enable the TIM4 global Interrupt. NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = IRQPRIORITY_TIM4; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //-- Time base init. TIM_TimeBaseStructure.TIM_Period = TICKER_PERIOD_TICKS; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //-- TIM4 IT enable. TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //-- TIM2 enable counter. TIM_Cmd(TIM4, ENABLE); } /** * @brief Ticker hardware ISR, uses TIM4 IRQ handler. */ void TIM4_IRQHandler(void) { //-- Clear the interrupt flag. TIM_ClearITPendingBit(TIM4, TIM_IT_Update); // Call the virtual ISR and exit. TickerISR(); } |
Note that the interrupt priority is set in board.h file.
1 2 |
// IRQ priorities (HIGHEST 0..15 LOWEST). #define IRQPRIORITY_TIM4 1 |
The simple header ticker_hw.h is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/** * @file ticker_hw.h * @author Atakan S. * @date 01/01/2018 * @version 1.0 * @brief Hardware implementation for ticker. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef _H_TICKER_HW #define _H_TICKER_HW #include "porty.h" /** * @brief Ticker hardware init. */ void TickerHwInit(void); #endif |
The real abstraction takes place in ticker.c file. A fake ISR routine counts time in milliseconds as well as ticks and records into tTickerTime struct.
1 2 3 4 5 |
typedef struct{ uint32_t ms:32; ///< Counts ms. uint32_t sub:8; ///< Counts sub-ms. uint32_t ticks:24; ///< Counts every tick. }tTickerTime; |
Interface functions TickerRead_ms() and TickerRead_ticks() returns the time as milliseconds or ticks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
/** * @file ticker.c * @author Atakan S. * @date 01/01/2018 * @version 1.0 * @brief Implements abstraction routines for Ticker usage. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "ticker.h" #include "ticker_hw.h" // Type Definitions. typedef struct{ uint32_t ms:32; ///< Counts ms. uint32_t sub:8; ///< Counts sub-ms. uint32_t ticks:24; ///< Counts every tick. }tTickerTime; // Variables. tTickerTime TickerTime; ///< Holds the time. /** * @brief The virtual TickerISR. */ void TickerISR(void){ // Tick. TickerTime.ticks++; // Increase the sub and the ms counter. if((++TickerTime.sub) >= TICKER_SUBMS_COUNT){ TickerTime.sub = 0; TickerTime.ms++; // Hook for scheduled tasks. mTickerHook_ms(); } // Hook for scheduled tasks. mTickerHook_fast(); } /** * @brief Initializes Ticker. After calling this function, enable global interrupts manually. */ void TickerInit(void){ uint8_t wasIntEn; // Save state and disable the interrupts. wasIntEn = mIsIntEnabled(); mIntDisable(); // HW Init. TickerHwInit(); // Initialize the counters. TickerTime.ms = 0; TickerTime.sub = 0; TickerTime.ticks = 0; // Re-enable interrupts. if(wasIntEn){ mIntEnable(); } } /** * @brief Reads thread-safe time ticks. * @return 16-bit Ticker count. */ uint16_t TickerRead_ticks(void){ uint16_t ticks; do{ ticks = TickerTime.ticks; }while(ticks != TickerTime.ticks); return ticks; } /** * @brief Reads thread-safe time in ms. * @return 32-bit ms time, only relative-time usage. */ uint32_t TickerRead_ms(void){ uint32_t ms; do{ ms = TickerTime.ms; }while(ms != TickerTime.ms); return ms; } |
The header ticker.h implements 2 hook functions, mTickerHook_ms() and mTickerHook_fast(), that will be called on every tick or every millisecond.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/** * @file ticker.h * @author Atakan S. * @date 01/01/2018 * @version 1.0 * @brief Implements abstraction routines for Ticker usage. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef _H_TICKER #define _H_TICKER #include "porty.h" // Settings. #define TICKER_PERIOD_US (1000UL/TICKER_SUBMS_COUNT) ///< Period of a Ticker in [us] #define TICKER_PERIOD_TICKS (TICKER_PERIOD_US*CLOCK_MHz) ///< Period of a Ticker in [ticks] (CPU cycles). #define mTickerHook_ms() ///< General-purpose hook that is called inside ISR every ms. #define mTickerHook_fast() ///< General-purpose hook that is called inside ISR everytime. // Prototypes. void TickerInit(void); void TickerISR(void); uint16_t TickerRead_ticks(void); uint32_t TickerRead_ms(void); #endif |
Finally, we can replace our blinking delay function in main.c with a more precise one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
/** * @file main.c * @author Atakan S. * @date 01/01/2018 * @version 1.1 * @brief Porty project, MCU independent embedded code template. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "porty.h" // Precise blocking delay. void delay_ms(uint32_t ms) { // Read current time. uint32_t now = TickerRead_ms(); // Wait until the time becomes (now + ms). while((TickerRead_ms() - now) < ms); } // The main routine. int main(void){ // Init cpu. Board_Init(); // Init Ticker. TickerInit(); // Enable global interrupts. mIntEnable(); // The main loop. while(true){ bool led = mPinRead(LED_Green); mPinWrite(LED_Green, !led); delay_ms(1000); } } |
Now you can observe a perfect 1Hz blink on the Green LED of the Discovery board 😀
Download
Download link of the full project is here: Porty-0.1.1-Ticker (using TIM4 Porty-0.1.1-Ticker-TIM4).
For compilation check here.