eLua platform interface - timers

Overview

This part of the platform interface groups functions related to the timers of the MCU. It also makes provisions for using virtual timers on any platform, see this section for details. Keep in mind that in the following paragraphs a timer id can refer to both a hardware timer or a virtual timer.

Data structures, constants and types

typedef u64/u32 timer_data_type;
// Timer operations
enum
{
  PLATFORM_TIMER_OP_START,
  PLATFORM_TIMER_OP_READ,
  PLATFORM_TIMER_OP_SET_CLOCK,
  PLATFORM_TIMER_OP_GET_CLOCK,
  PLATFORM_TIMER_OP_GET_MAX_DELAY,
  PLATFORM_TIMER_OP_GET_MIN_DELAY,
  PLATFORM_TIMER_OP_GET_MAX_CNT
};

This enum lists all the operations that can be executed on a given timer.

Functions

int platform_timer_exists( unsigned id );

Checks if the platform has the timer specified as argument. Implemented in src/common.c, it uses the NUM_TIMER macro that must be defined in the platform's platform_conf.h file (see here for details) and the virtual timer configuration (here for details). For example:

#define NUM_TIMER   2      // The platform has 2 hardware timers

Arguments: id - the timer ID

Returns: 1 if the timer exists, 0 otherwise

void platform_s_timer_delay( unsigned id, timer_data_type delay_us );

This function is identical in functionality to platform_timer_delay, but this is the function that must actually be implemented by a platform port and it must never handle virtual timer IDs or the system timer ID, only hardware timer IDs. It has the same limitations as platform_timer_delay.

Arguments:

  • id - the timer ID
  • delay_us - the delay time (in microseconds)

Returns: nothing.

timer_data_type platform_timer_op( unsigned id, int op, timer_data_type data );

Executes an operation on a timer. This function is "split" in two parts: a platform-independent part implemented in src/common_tmr.c (that handles virtual timers and the system timer) and a platform-dependent part that must be implemented by each platform in a function named platform_s_timer_op. This function handles both hardware timer IDs and virtual timer IDs.

Arguments:

  • id - the timer ID
  • op - the operation. op can take any value from the this enum, as follows:
    • PLATFORM_TIMER_OP_START: start the specified timer by setting its counter register to a predefined value.
    • PLATFORM_TIMER_OP_READ: get the value of the specified timer's counter register.
    • PLATFORM_TIMER_SET_CLOCK: set the clock of the specified timer to data (in hertz). You can never set the clock of a virtual timer, which is set at compile time.
    • PLATFORM_TIMER_GET_CLOCK: get the clock of the specified timer.
    • PLATFORM_TIMER_OP_GET_MAX_DELAY: get the maximum achievable timeout on the specified timer (in us).
    • PLATFORM_TIMER_OP_GET_MIN_DELAY: get the minimum achievable timeout on the specified timer (in us).
    • PLATFORM_TIMER_OP_GET_MAX_CNT: get the maximum value of the timer's counter register.
  • data - used to specify the timer clock value when op = PLATFORM_TIMER_SET_CLOCK, ignored otherwise

Returns:

  • the predefined value used when starting the clock if op = PLATFORM_TIMER_OP_START
  • the timer's counter register if op = PLATFORM_TIMER_OP_READ
  • the actual clock set on the timer, which might be different than the request clock depending on the hardware if op = PLATFORM_TIMER_SET_CLOCK
  • the timer clock if op = PLATFORM_TIMER_GET_CLOCK
  • the maximum achievable delay (in microseconds) if op = PLATFORM_TIMER_OP_GET_MAX_DELAY
  • the minimum achievable delay (in microseconds) if op = PLATFORM_TIMER_OP_GET_MIN_DELAY
  • the maximum value of the timer's coutner register if op == PLATFORM_TIMER_OP_GET_MAX_CNT

timer_data_type platform_s_timer_op( unsigned id, int op, timer_data_type data );

This function is identical in functionality to platform_timer_op, but this is the function that must actually be implemented by a platform port and it must never handle virtual timer IDs or the system timer, only hardware timer IDs.

Arguments:

  • id - the timer ID
  • op - the operation. op can take any value from the this enum, as follows:
    • PLATFORM_TIMER_OP_START: start the specified timer by setting its counter register to a predefined value.
    • PLATFORM_TIMER_OP_READ: get the value of the specified timer's counter register.
    • PLATFORM_TIMER_SET_CLOCK: set the clock of the specified timer to data (in hertz). You can never set the clock of a virtual timer, which is set at compile time.
    • PLATFORM_TIMER_GET_CLOCK: get the clock of the specified timer.
    • PLATFORM_TIMER_OP_GET_MAX_DELAY: get the maximum achievable timeout on the specified timer (in us).
    • PLATFORM_TIMER_OP_GET_MIN_DELAY: get the minimum achievable timeout on the specified timer (in us).
    • PLATFORM_TIMER_OP_GET_MAX_CNT: get the maximum value of the timer's counter register.
  • data - used to specify the timer clock value when op = PLATFORM_TIMER_SET_CLOCK, ignored otherwise

Returns:

  • the predefined value used when starting the clock if op = PLATFORM_TIMER_OP_START
  • the timer's counter register if op = PLATFORM_TIMER_OP_READ
  • the actual clock set on the timer, which might be different than the request clock depending on the hardware if op = PLATFORM_TIMER_SET_CLOCK
  • the timer clock if op = PLATFORM_TIMER_GET_CLOCK
  • the maximum achievable delay (in microseconds) if op = PLATFORM_TIMER_OP_GET_MAX_DELAY
  • the minimum achievable delay (in microseconds) if op = PLATFORM_TIMER_OP_GET_MIN_DELAY
  • the maximum value of the timer's coutner register if op == PLATFORM_TIMER_OP_GET_MAX_CNT

timer_data_type platform_timer_get_diff_us( unsigned id, timer_data_type start, timer_data_type end );

Return the time difference (in us) between two timer values (as returned by calling platform_timer_op with PLATFORM_TIMER_OP_READ or PLATFORM_TIMER_OP_START. This function is generic, thus it is implemented in src/common.c. NOTE: the order of start and end is important. end must correspond to a moment in time which came after start. The function knows how to deal with a single timer overflow condition (end is less than start); if the timer overflowed 2 or more times between start and end the result of this function will be incorrect.

Arguments:

  • id - the timer ID
  • start - the initial counter value.
  • end - the final counter value.

Returns: the time difference (in microseconds)

int platform_timer_set_match_int( unsigned id, timer_data_type period_us, int type );

Setup the timer match interrupt. Only available if interrupt support is enabled, check here for details.This function is "split" in two parts: a platform-independent part implemented in src/common_tmr.c (that handles virtual timers and the system timer) and a platform-dependent part that must be implemented by each platform in a function named platform_s_timer_set_match_int. This function handles both hardware timer IDs and virtual timer IDs. NOTE: the system timer can't generate interrupts.

Arguments:

  • id - the timer ID
  • period_us - the period (in microseconds) of the timer interrupt. Setting this to 0 disables the timer match interrupt.
  • type - PLATFORM_TIMER_INT_ONESHOT for an interrupt that occurs only once after period_us microseconds, or PLATFORM_TIMER_INT_CYCLIC for an interrupt that occurs every period_us microseconds

Returns:

  • PLATFORM_TIMER_INT_OK if the operation was successful.
  • PLATFORM_TIMER_INT_TOO_SHORT if the specified period is too short.
  • PLATFORM_TIMER_INT_TOO_LONG if the specified period is too long.
  • PLATFORM_TIMER_INT_INVALID_ID if the specified timer cannot handle this operation.

int platform_s_timer_set_match_int( unsigned id, timer_data_type period_us, int type );

This function is identical in functionality to platform_timer_set_match_int, but this is the function that must actually be implemented by a platform port and it must never handle virtual timer IDs or the system timer, only hardware timer IDs.

Arguments:

  • id - the timer ID
  • period_us - the period (in microseconds) of the timer interrupt. Setting this to 0 disables the timer match interrupt.
  • type - PLATFORM_TIMER_INT_ONESHOT for an interrupt that occurs only once after period_us microseconds, or PLATFORM_TIMER_INT_CYCLIC for an interrupt that occurs every period_us microseconds

Returns:

  • PLATFORM_TIMER_INT_OK if the operation was successful.
  • PLATFORM_TIMER_INT_TOO_SHORT if the specified period is too short.
  • PLATFORM_TIMER_INT_TOO_LONG if the specified period is too long.
  • PLATFORM_TIMER_INT_INVALID_ID if the specified timer cannot handle this operation.

timer_data_type platform_timer_read_sys();

Returns the current value of the system timer, see here for more details.

Arguments: none.

Returns: The current value of the system timer.

Virtual timers

Virtual timers were added to eLua to overcome some limitations:

  • there are generally few hardware timers available, some of which might be dedicated (thus not usable directly by eLua).
  • many times it is difficult to share a hardware timer between different parts of an application because of conflicting requirements. Generally it's not possible to have timers that can achieve long delays and high accuracy at the same time (this is especially true for systems that have 16 bit or even smaller timers).

In this respect, virtual timers are a set of timers that share a single hardware timer. It is possible, in this way, to have a hardware timer that can implement 4, 8 or more virtual/software timers. There are a few drawbacks to this approach:

  • the hardware timer used to implement the virtual timers must generally be dedicated. In fact it can still be used in "read only mode", which means that the only operations that can be executed on it are PLATFORM_TIMER_OP_READ, PLATFORM_TIMER_GET_CLOCK, PLATFORM_TIMER_OP_GET_MAX_DELAY and PLATFORM_TIMER_OP_GET_MIN_DELAY. However, since the "read only mode" is not enforced by the code, it is advisable to treat this timer as a dedicated resource and thus make it invisible to eLua by not associating it with an ID.
  • the number of virtual timers and their base frequency are fixed at compile time.
  • virtual timers are generally used for large delays with low accuracy, since their base frequency should be fairly low (see below).

To enable virtual timers:

  1. edit platform_conf.h (see here for details) and set VTMR_NUM_TIMERS to the number of desired virtual timers and VTMR_FREQ_HZ to the base frequency of the virtual timers (in hertz). For example:
    #define VTMR_NUM_TIMERS       4 // we need 4 virtual timers
    #define VTMR_FREQ_HZ          4 // the base clock for the virtual timers is 4Hz
  2. in your platform port setup a hardware timer to fire an interrupt at VTMR_FREQ_HZ and call the cmn_virtual_timer_cb function (defined in src/common.c) in the timer interrupt handler. For example, if the the interrupt handler is called timer_int_handler, do this:
    void timer_int_handler( void )
    {
      // add code to clear the timer interrupt flag here if needed
      cmn_virtual_timer_cb();
    }

Note that because of step 2 above you are limited by practical constraints on the value of VTMR_FREQ_HZ. If set too high, the timer interrupt will fire too often, thus taking too much CPU time. The maximum value depends largely on the hardware and the desired behaviour of the virtual timers, but in practice values larger than 10 might visibly change the behaviour of your system.

To use a virtual timer, identify it with the constant VTMR_FIRST_ID (defined in inc/common.h) plus an offset. For example, VTMR_FIRST_ID+0 (or simply VTMR_FIRST_ID) is the ID of the first virtual timer in the system, and VTMR_FIRST_ID+2 is the ID of the third virtual timer in the system.

Virtual timers are capable of generating timer match interrupts just like regular timers, check here for details.

The system timer

The system timer was introduced in eLua 0.9 as a simpler alternative to the traditional eLua timers. Working with regular timers in eLua might be challenging for a number of reasons:

  • depending on the hardware, the timers might have a limited range. Because of this, they might not be able to timeout in the interval requested by the user.
  • the timers might have different ranges even on the same platform (they might have a different base clock, for example). The problem is further aggravated when switching platforms.
  • the timers might be shared with other hardware resources (for example PWMs or ADC triggers) so using them might have unexpected side effects.
  • manual timer management is error prone. The user needs to keep into account the timers he's using, their base frequencies and wether they are shared or not with the C code.

The virtual timers can fix some of the above problems, but their resolution is fairly low and they still require manual management.

The system timer attemps to fix (at least partially) these issues. It is a timer with fixed resolution (1us) on all platforms and large counters:

  • if eLua is compiled in floating point mode (default) the counter is 52 bits wide. It will overflow after more than 142 years.
  • if eLua is compiled in 32 bit integer-only mode (lualong) the counter is 32 bits wide. It will overflow after about one hour.
  • if eLua is compiled in 64 bit integer-only mode (lualonglong, new in 0.9) the counter is again 52 bits wide and it will also overflow after more than 142 years.

The eLua API was partially modified to take full advantage of this new timer:

  • all the functions that can operate with a timeout (for example uart.read or net.accept) will default to the system timer is a timer ID is not specified explicitly.
  • all the function in the timer module will default to the system timer if a timer ID is not specified explicitly.
  • timeouts are specified in a more unified manner across the eLua modules as a [timeout], [timer_id] pair:
    timeout timer_id Result
    not specified any value infinite timeout (the function blocks until it completes).
    0 any value no timeout (the function returns immediately).
    a positive value not specified the system timer will be used to measure the function's timeout.
    a positive value a timer ID the specified timer will be used to measure the function's timeout.

Using the system timer as much as possible is also encouraged with C code that uses the eLua C api, not only with Lua programs. The C code can use the system timer by specifying PLATFORM_TIMER_SYS_ID as the timer ID.

From an implementation stand point, the system timer is built around a hardware timer with a base clock of at least 1MHz that can generate an interrupt when the timer counter overflows or when it reaches a certain value. The interrupt handler updates the upper part of the system timer counter (basically an overflow counter). eLua has a generic mechanism that can be used to implement a system timer on any platform using this method. To take advantage of this mechanism follow the steps below:

  1. define the PLATFORM_HAS_SYSTIMER macro in your platform_conf.h file.
  2. implement platform_timer_sys_raw_read, platform_timer_sys_enable_int and platform_timer_sys_disable_int.
  3. include the common.h header.
  4. setup your hardware timer and its associated interrupt. This should happen an initialization time (for example in platform_init).
  5. call cmn_systimer_set_base_freq with the base frequency of your timer in Hz.
  6. call cmn_systimer_set_interrupt_freq with the frequency of the timer's overflow/match interrupt in Hz. Alternatively you can call cmn_systimer_set_interrupt_period_us to set the timer's overflow/match interrupt period (in microseconds) instead of its frequency. Use the latter form if the frequency is not an integer.
  7. call cmn_systimer_periodic from your timer's overflow interrupt handler.
  8. use this implementation for platform_timer_read_sys:
    timer_data_type platform_timer_read_sys()
    {
      return cmn_systimer_get();
    }

Note that the above mechanism is optional. A platform might have a different method to implement the system timer; this is OK as long as the system timer requirements are respected.

IMPORTANT NOTE: although system timer support in eLua is optional, implementing the system timer is highly recommended. As already specified, all the timer IDs in various eLua modules default to the system timer. This means that any code that was written under the assumption that a system timer is present (which is a fair assumption) will fail on platforms that don't actually have a system timer. Check here for a list of platforms that implement the system timer. If your platform doesn't implement the system timer, you'll get this warning at compile time:

#warning This platform does not have a system timer. Your eLua image might not work as expected.