Skip to content

Commit

Permalink
Unified Callback Timer API (#1831)
Browse files Browse the repository at this point in the history
* Add `CallbackTimer` class template to support HardwareTimer, SimpleTimer and Timer.

- Contains common logic and checks for all callback timer types
- Templated for best performance (no VMT)
- Added `TimerCallback` supporting void* arg parameter (in addition to existing `InterruptCallback`)
- Templated methods added with compile-time checks on interval (so code won't compile if out of range)
- Added methods to support setting/checking/querying intervals directly in timer ticksn
- Timer intervals stored internally in timer ticks, so querying the value confirms the actual time in use accounting for rounding, etc.

Note that delegate callbacks are supported only by the Timer class, which now also has an AutoDelete variant.

Host timer queue implementation improved, handles multiple timers properly (fixes bug where all timers get cancelled instead of just one).

Add `os_timer_arm_ticks()` function so software timers are programmed in ticks instead of microseconds or milliseconds

- Used instead of `os_timer_arm_us` provides simpler and more flexible timer interface.
- Avoids un-necessary (and potentially inaccurate) time conversions
- Timers can be used with `USE_US_TIMER=0` for 3.2us tick resolution, providing basic range of 1'54" (with SimpleTimer)
- Default is still `USE_US_TIMER=1` for 0.2us tick resolution and reduced 0'7"9s range (without using Timer class)

* Update samples to use improved timer API and add timers module to HostTests

* Templates ignore section attributes so requires entries in linker script for IRAM code.

In the source code you'll see quite a bit of `__forceinline` as well as `IRAM_ATTR`, which is mainly for safety
and to indicate functions/methods may be used from interrupt context.

Unfortunately, marking templated code with `IRAM_ATTR` is not sufficient to get it into IRAM:

https://stackoverflow.com/questions/36279162/section-attribute-of-a-function-template-is-silently-ignored-in-gcc

So the linker script needs to be updated to catch all such instances. To do this requires splitting the `.text` output
segment into two parts, called `.text` and `.text1`. The rBoot script also needs to know about both segments
so they both end up in the ROM image it creates.

Using `CallbackTimer` as an example, most of the code gets inlined anyway and as it's called from task context
this actually uses very little IRAM.

More details here:

esp8266/Arduino#5922
  • Loading branch information
mikee47 authored and slaff committed Sep 15, 2019
1 parent 1f77fe5 commit d5c7409
Show file tree
Hide file tree
Showing 22 changed files with 1,556 additions and 584 deletions.
84 changes: 53 additions & 31 deletions Sming/Arch/Esp8266/Compiler/ld/common.ld
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,55 @@ SECTIONS
_data_end = ABSOLUTE(.);
} >dram0_0_seg :dram0_0_phdr


/*
IRAM is split into .text and .text1 to allow for moving specific
functions into IRAM that would be matched by the irom0.text matcher
*/
.text : ALIGN(4)
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.UserEnter.text)
. = ALIGN(16);
*(.DebugExceptionVector.text)
. = ALIGN(16);
*(.NMIExceptionVector.text)
. = ALIGN(16);
*(.KernelExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN(16);
*(.UserExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN(16);
*(.DoubleExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)

*(.iram.literal .iram.text.literal .iram.text .iram.text.*)

/*
GCC silently ignores section attributes on templated code.
The only practical workaround is enforcing sections in the linker script.
*/
*(.literal._ZN13CallbackTimer*)
*(.text._ZN13CallbackTimer*)
*(.text._ZNKSt8functionIF*EE*) /* std::function<any(...)>::operator()() const */

} >iram1_0_seg :iram1_0_phdr

.irom0.text : ALIGN(4)
{
_irom0_text_start = ABSOLUTE(.);
Expand Down Expand Up @@ -128,39 +177,12 @@ SECTIONS
_flash_code_end = ABSOLUTE(.);
} >irom0_0_seg :irom0_0_phdr

.text : ALIGN(4)

/* Pick up any remaining IRAM objects and close the text segment */
.text1 : ALIGN(4)
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.UserEnter.text)
. = ALIGN(16);
*(.DebugExceptionVector.text)
. = ALIGN(16);
*(.NMIExceptionVector.text)
. = ALIGN(16);
*(.KernelExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN(16);
*(.UserExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN(16);
*(.DoubleExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.iram.literal .iram.text.literal .iram.text .iram.text.*)

*(.fini.literal)
*(.fini)
*(.gnu.version)
Expand Down
33 changes: 33 additions & 0 deletions Sming/Arch/Esp8266/Components/driver/include/driver/os_timer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* os_timer.h
*
* @author: 13 August 2018 - mikee47 <[email protected]>
*
* An alternative method for setting software timers based on the tick count.
*
*/

#pragma once

#include <esp_systemapi.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Set a software timer using the Timer2 tick value
* @param ptimer Timer structure
* @param ticks Tick count duration for the timer
* @param repeat_flag true if timer will automatically repeat
*/
void IRAM_ATTR os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag);

#ifdef __cplusplus
}
#endif
109 changes: 109 additions & 0 deletions Sming/Arch/Esp8266/Components/driver/os_timer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* os_timer.cpp
*
* @author: 13 August 2018 - mikee47 <[email protected]>
*
* An alternative method for setting software timers based on the tick count.
*
* Technical notes
* ---------------
*
* This information was obtained by examining the SDK timer function assembly code
* from SDK versions 1.5.4, 2.2 and 3.0.
*
* Software timers for the ESP8266 are defined by an `os_timer_t` structure.
* When armed, the structure is contained in a queue ordered by expiry time.
* Thus, the first timer is the next one to expire and the expiry time is programmed
* into the Timer2 alarm register (counter compare). The timer alarm interrupt simply
* queues a task to handle the event.
*
* The timer task dequeues the timer (setting `timer_next` to -1) and invokes the
* user-provided callback routine. If it is a repeating timer then it is re-queued.
* The queue is implemented as a linked list so adding and removing items is very efficient
* and requires no additional memory allocation.
*
* Because the Sming Clock API handles all time/tick conversions, a new `os_timer_arm_ticks()`
* function is used which replaces the existing `ets_timer_arm_new()` function. This makes
* timer operation more transparent, faster.
*
* `ets_timer_arm_new`
* -------------------
*
* This is the SDK function which implements `os_timer_arm_us` and `os_timer_arm`.
*
* With ms_flag = false, the maximum value for `time` is 428496729us. The SDK documentation
* for `os_timer_arm_us` states a maximum value of 0x0FFFFFFF, which is incorrect; it probably
* applies to earlier SDK releases.
*
* Note: If `system_timer_reinit()` hasn't been called then calling with `ms_flag = false` will fail.
*
* This figure can be derived as follows, where 0x7FFFFFFF is the maximum tick range
* (signed comparison) and 5000000 is the Timer2 frequency with /16 prescale:
*
* 0x7FFFFFFF / 5000000 = 429496729.4us = 0' 7" 9.5s
*
* With ms_flag = true, the limit is 428496ms which agrees with the value stated in the SDK documentation.
*
* Timer2 frequencies for two prescaler settings are:
* Prescale Frequency Period Range (0x7FFFFFFF ticks)
* -------- --------- ------ -------------------------
* - /1 80000000 12.5ns 0' 0" 26.84s
* - /16 5000000 200ns 0' 7" 9.5s
* - /256 312500 3.2us 1' 54" 31.95s
*
* @see See also `drivers/hw_timer.h`
*
*/

#include "include/driver/os_timer.h"
#include <driver/hw_timer.h>

/*
* This variable points to the first timer in the queue.
* It's a global variable defined in the Espressif SDK.
*/
extern "C" os_timer_t* timer_list;

/**
* @brief Insert a timer into the queue
* @param ptimer The timer to insert
* @param expire The Timer2 tick value when this timer is due
* @note Timer is inserted into queue according to its expiry time, and _after_ any
* existing timers with the same expiry time. If it's inserted at the head of the
* queue (i.e. it's the new value for `timer_list`) then the Timer2 alarm register
* is updated.
*/
static void IRAM_ATTR timer_insert(os_timer_t* ptimer, uint32_t expire)
{
os_timer_t* t_prev = nullptr;
auto t = timer_list;
while(t != nullptr) {
if(int(t->timer_expire - expire) > 0) {
break;
}
t_prev = t;
t = t->timer_next;
}
if(t_prev == nullptr) {
timer_list = ptimer;
hw_timer2_set_alarm(expire);
} else {
t_prev->timer_next = ptimer;
}
ptimer->timer_next = t;
ptimer->timer_expire = expire;
}

void os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag)
{
os_timer_disarm(ptimer);
ptimer->timer_period = repeat_flag ? ticks : 0;
ets_intr_lock();
timer_insert(ptimer, hw_timer2_read() + ticks);
ets_intr_unlock();
}
23 changes: 23 additions & 0 deletions Sming/Arch/Host/Components/esp_hal/include/esp_timer_legacy.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/*
* This implementation mimics the behaviour of the ESP8266 Non-OS SDK timers,
* using Timer2 as the reference (which is _not_ in microseconds!)
*
* The ESP32 IDF contains more sophisticated timer implementations, but also
* this same API which it refers to as the 'legacy' timer API.
*/

#pragma once
#include "c_types.h"

Expand All @@ -7,16 +15,31 @@ extern "C" {

typedef void os_timer_func_t(void* timer_arg);

/**
* @brief This is the structure used by the Espressif timer API
* @note This is used as an element in a linked list
* The Espressif implementation orders the list according to next expiry time.
* os_timer_setfn and os_timer_disarm set timer_next to -1
* When expired, timer_next is 0
*/
struct os_timer_t {
/// If disarmed, set to -1, otherwise points to the next queued timer (or NULL if last in the list)
struct os_timer_t* timer_next;
/// Set to the next Timer2 count value when the timer will expire
uint32_t timer_expire;
/// 0 if this is a one-shot timer, otherwise defines the interval in Timer2 ticks
uint32_t timer_period;
/// User-provided callback function pointer
os_timer_func_t* timer_func;
/// Argument passed to the callback function
void* timer_arg;
};

void os_timer_arm_ticks(struct os_timer_t* ptimer, uint32_t ticks, bool repeat_flag);

void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag);
void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag);

void os_timer_disarm(struct os_timer_t* ptimer);
void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void* parg);

Expand Down
Loading

0 comments on commit d5c7409

Please sign in to comment.