Skip to content

Commit d5c7409

Browse files
mikee47slaff
authored andcommitted
Unified Callback Timer API (#1831)
* 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
1 parent 1f77fe5 commit d5c7409

File tree

22 files changed

+1556
-584
lines changed

22 files changed

+1556
-584
lines changed

Sming/Arch/Esp8266/Compiler/ld/common.ld

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,55 @@ SECTIONS
8282
_data_end = ABSOLUTE(.);
8383
} >dram0_0_seg :dram0_0_phdr
8484

85+
86+
/*
87+
IRAM is split into .text and .text1 to allow for moving specific
88+
functions into IRAM that would be matched by the irom0.text matcher
89+
*/
90+
.text : ALIGN(4)
91+
{
92+
_stext = .;
93+
_text_start = ABSOLUTE(.);
94+
*(.UserEnter.text)
95+
. = ALIGN(16);
96+
*(.DebugExceptionVector.text)
97+
. = ALIGN(16);
98+
*(.NMIExceptionVector.text)
99+
. = ALIGN(16);
100+
*(.KernelExceptionVector.text)
101+
LONG(0)
102+
LONG(0)
103+
LONG(0)
104+
LONG(0)
105+
. = ALIGN(16);
106+
*(.UserExceptionVector.text)
107+
LONG(0)
108+
LONG(0)
109+
LONG(0)
110+
LONG(0)
111+
. = ALIGN(16);
112+
*(.DoubleExceptionVector.text)
113+
LONG(0)
114+
LONG(0)
115+
LONG(0)
116+
LONG(0)
117+
. = ALIGN (16);
118+
*(.entry.text)
119+
*(.init.literal)
120+
*(.init)
121+
122+
*(.iram.literal .iram.text.literal .iram.text .iram.text.*)
123+
124+
/*
125+
GCC silently ignores section attributes on templated code.
126+
The only practical workaround is enforcing sections in the linker script.
127+
*/
128+
*(.literal._ZN13CallbackTimer*)
129+
*(.text._ZN13CallbackTimer*)
130+
*(.text._ZNKSt8functionIF*EE*) /* std::function<any(...)>::operator()() const */
131+
132+
} >iram1_0_seg :iram1_0_phdr
133+
85134
.irom0.text : ALIGN(4)
86135
{
87136
_irom0_text_start = ABSOLUTE(.);
@@ -128,39 +177,12 @@ SECTIONS
128177
_flash_code_end = ABSOLUTE(.);
129178
} >irom0_0_seg :irom0_0_phdr
130179

131-
.text : ALIGN(4)
180+
181+
/* Pick up any remaining IRAM objects and close the text segment */
182+
.text1 : ALIGN(4)
132183
{
133-
_stext = .;
134-
_text_start = ABSOLUTE(.);
135-
*(.UserEnter.text)
136-
. = ALIGN(16);
137-
*(.DebugExceptionVector.text)
138-
. = ALIGN(16);
139-
*(.NMIExceptionVector.text)
140-
. = ALIGN(16);
141-
*(.KernelExceptionVector.text)
142-
LONG(0)
143-
LONG(0)
144-
LONG(0)
145-
LONG(0)
146-
. = ALIGN(16);
147-
*(.UserExceptionVector.text)
148-
LONG(0)
149-
LONG(0)
150-
LONG(0)
151-
LONG(0)
152-
. = ALIGN(16);
153-
*(.DoubleExceptionVector.text)
154-
LONG(0)
155-
LONG(0)
156-
LONG(0)
157-
LONG(0)
158-
. = ALIGN (16);
159-
*(.entry.text)
160-
*(.init.literal)
161-
*(.init)
162184
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
163-
*(.iram.literal .iram.text.literal .iram.text .iram.text.*)
185+
164186
*(.fini.literal)
165187
*(.fini)
166188
*(.gnu.version)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/****
2+
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
3+
* Created 2015 by Skurydin Alexey
4+
* http://github.com/SmingHub/Sming
5+
* All files of the Sming Core are provided under the LGPL v3 license.
6+
*
7+
* os_timer.h
8+
*
9+
* @author: 13 August 2018 - mikee47 <[email protected]>
10+
*
11+
* An alternative method for setting software timers based on the tick count.
12+
*
13+
*/
14+
15+
#pragma once
16+
17+
#include <esp_systemapi.h>
18+
19+
#ifdef __cplusplus
20+
extern "C" {
21+
#endif
22+
23+
/**
24+
* @brief Set a software timer using the Timer2 tick value
25+
* @param ptimer Timer structure
26+
* @param ticks Tick count duration for the timer
27+
* @param repeat_flag true if timer will automatically repeat
28+
*/
29+
void IRAM_ATTR os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag);
30+
31+
#ifdef __cplusplus
32+
}
33+
#endif
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/****
2+
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
3+
* Created 2015 by Skurydin Alexey
4+
* http://github.com/SmingHub/Sming
5+
* All files of the Sming Core are provided under the LGPL v3 license.
6+
*
7+
* os_timer.cpp
8+
*
9+
* @author: 13 August 2018 - mikee47 <[email protected]>
10+
*
11+
* An alternative method for setting software timers based on the tick count.
12+
*
13+
* Technical notes
14+
* ---------------
15+
*
16+
* This information was obtained by examining the SDK timer function assembly code
17+
* from SDK versions 1.5.4, 2.2 and 3.0.
18+
*
19+
* Software timers for the ESP8266 are defined by an `os_timer_t` structure.
20+
* When armed, the structure is contained in a queue ordered by expiry time.
21+
* Thus, the first timer is the next one to expire and the expiry time is programmed
22+
* into the Timer2 alarm register (counter compare). The timer alarm interrupt simply
23+
* queues a task to handle the event.
24+
*
25+
* The timer task dequeues the timer (setting `timer_next` to -1) and invokes the
26+
* user-provided callback routine. If it is a repeating timer then it is re-queued.
27+
* The queue is implemented as a linked list so adding and removing items is very efficient
28+
* and requires no additional memory allocation.
29+
*
30+
* Because the Sming Clock API handles all time/tick conversions, a new `os_timer_arm_ticks()`
31+
* function is used which replaces the existing `ets_timer_arm_new()` function. This makes
32+
* timer operation more transparent, faster.
33+
*
34+
* `ets_timer_arm_new`
35+
* -------------------
36+
*
37+
* This is the SDK function which implements `os_timer_arm_us` and `os_timer_arm`.
38+
*
39+
* With ms_flag = false, the maximum value for `time` is 428496729us. The SDK documentation
40+
* for `os_timer_arm_us` states a maximum value of 0x0FFFFFFF, which is incorrect; it probably
41+
* applies to earlier SDK releases.
42+
*
43+
* Note: If `system_timer_reinit()` hasn't been called then calling with `ms_flag = false` will fail.
44+
*
45+
* This figure can be derived as follows, where 0x7FFFFFFF is the maximum tick range
46+
* (signed comparison) and 5000000 is the Timer2 frequency with /16 prescale:
47+
*
48+
* 0x7FFFFFFF / 5000000 = 429496729.4us = 0' 7" 9.5s
49+
*
50+
* With ms_flag = true, the limit is 428496ms which agrees with the value stated in the SDK documentation.
51+
*
52+
* Timer2 frequencies for two prescaler settings are:
53+
* Prescale Frequency Period Range (0x7FFFFFFF ticks)
54+
* -------- --------- ------ -------------------------
55+
* - /1 80000000 12.5ns 0' 0" 26.84s
56+
* - /16 5000000 200ns 0' 7" 9.5s
57+
* - /256 312500 3.2us 1' 54" 31.95s
58+
*
59+
* @see See also `drivers/hw_timer.h`
60+
*
61+
*/
62+
63+
#include "include/driver/os_timer.h"
64+
#include <driver/hw_timer.h>
65+
66+
/*
67+
* This variable points to the first timer in the queue.
68+
* It's a global variable defined in the Espressif SDK.
69+
*/
70+
extern "C" os_timer_t* timer_list;
71+
72+
/**
73+
* @brief Insert a timer into the queue
74+
* @param ptimer The timer to insert
75+
* @param expire The Timer2 tick value when this timer is due
76+
* @note Timer is inserted into queue according to its expiry time, and _after_ any
77+
* existing timers with the same expiry time. If it's inserted at the head of the
78+
* queue (i.e. it's the new value for `timer_list`) then the Timer2 alarm register
79+
* is updated.
80+
*/
81+
static void IRAM_ATTR timer_insert(os_timer_t* ptimer, uint32_t expire)
82+
{
83+
os_timer_t* t_prev = nullptr;
84+
auto t = timer_list;
85+
while(t != nullptr) {
86+
if(int(t->timer_expire - expire) > 0) {
87+
break;
88+
}
89+
t_prev = t;
90+
t = t->timer_next;
91+
}
92+
if(t_prev == nullptr) {
93+
timer_list = ptimer;
94+
hw_timer2_set_alarm(expire);
95+
} else {
96+
t_prev->timer_next = ptimer;
97+
}
98+
ptimer->timer_next = t;
99+
ptimer->timer_expire = expire;
100+
}
101+
102+
void os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag)
103+
{
104+
os_timer_disarm(ptimer);
105+
ptimer->timer_period = repeat_flag ? ticks : 0;
106+
ets_intr_lock();
107+
timer_insert(ptimer, hw_timer2_read() + ticks);
108+
ets_intr_unlock();
109+
}

Sming/Arch/Host/Components/esp_hal/include/esp_timer_legacy.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/*
2+
* This implementation mimics the behaviour of the ESP8266 Non-OS SDK timers,
3+
* using Timer2 as the reference (which is _not_ in microseconds!)
4+
*
5+
* The ESP32 IDF contains more sophisticated timer implementations, but also
6+
* this same API which it refers to as the 'legacy' timer API.
7+
*/
8+
19
#pragma once
210
#include "c_types.h"
311

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

816
typedef void os_timer_func_t(void* timer_arg);
917

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

38+
void os_timer_arm_ticks(struct os_timer_t* ptimer, uint32_t ticks, bool repeat_flag);
39+
1840
void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag);
1941
void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag);
42+
2043
void os_timer_disarm(struct os_timer_t* ptimer);
2144
void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void* parg);
2245

0 commit comments

Comments
 (0)