Skip to content

Commit b2dae87

Browse files
committed
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) Add timer test module to check timer ranges, calculations and operation.
1 parent b484d6d commit b2dae87

File tree

19 files changed

+1490
-547
lines changed

19 files changed

+1490
-547
lines changed
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

Sming/Arch/Host/Components/esp_hal/timer_legacy.cpp

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,62 @@
1-
21
#include "include/esp_system.h"
32
#include "include/esp_timer_legacy.h"
43
#include <hostlib/threads.h>
4+
#include <driver/hw_timer.h>
5+
#include <muldiv.h>
6+
#include <assert.h>
57

68
static os_timer_t* timer_head;
79
static CMutex mutex;
810

9-
void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag)
11+
static void timer_insert(uint32_t expire, os_timer_t* ptimer)
1012
{
11-
os_timer_arm_us(ptimer, time * 1000UL, repeat_flag);
13+
os_timer_t* t_prev = nullptr;
14+
auto t = timer_head;
15+
while(t != nullptr) {
16+
if(int(t->timer_expire - expire) > 0) {
17+
break;
18+
}
19+
t_prev = t;
20+
t = t->timer_next;
21+
}
22+
if(t_prev == nullptr) {
23+
timer_head = ptimer;
24+
} else {
25+
t_prev->timer_next = ptimer;
26+
}
27+
ptimer->timer_next = t;
28+
ptimer->timer_expire = expire;
1229
}
1330

14-
void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag)
31+
void os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag)
1532
{
16-
os_timer_disarm(ptimer);
17-
ptimer->timer_next = nullptr;
18-
ptimer->timer_expire = system_get_time() + time;
19-
ptimer->timer_period = repeat_flag ? time : 0;
33+
assert(ptimer != nullptr);
34+
// assert(time <= MAX_OS_TIMER_INTERVAL_US);
2035

21-
// Append to list
36+
os_timer_disarm(ptimer);
37+
ptimer->timer_period = repeat_flag ? ticks : 0;
2238
mutex.lock();
23-
if(timer_head == nullptr) {
24-
timer_head = ptimer;
25-
} else {
26-
auto t = timer_head;
27-
while(t->timer_next != nullptr) {
28-
t = t->timer_next;
29-
}
30-
t->timer_next = ptimer;
31-
}
39+
timer_insert(hw_timer2_read() + ticks, ptimer);
3240
mutex.unlock();
3341
}
3442

43+
void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag)
44+
{
45+
using R = std::ratio<HW_TIMER2_CLK, 1000>;
46+
auto ticks = muldiv<R::num, R::den>(time);
47+
os_timer_arm_ticks(ptimer, ticks, repeat_flag);
48+
}
49+
50+
void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag)
51+
{
52+
using R = std::ratio<HW_TIMER2_CLK, 1000000>;
53+
auto ticks = muldiv<R::num, R::den>(time);
54+
os_timer_arm_ticks(ptimer, ticks, repeat_flag);
55+
}
56+
3557
void os_timer_disarm(struct os_timer_t* ptimer)
3658
{
37-
if(ptimer == nullptr) {
38-
return;
39-
}
59+
assert(ptimer != nullptr);
4060

4161
mutex.lock();
4262
if(timer_head != nullptr) {
@@ -53,6 +73,7 @@ void os_timer_disarm(struct os_timer_t* ptimer)
5373
t = t->timer_next;
5474
}
5575
}
76+
ptimer->timer_next = reinterpret_cast<os_timer_t*>(-1);
5677
}
5778
mutex.unlock();
5879
}
@@ -62,6 +83,7 @@ void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void*
6283
if(ptimer != nullptr) {
6384
ptimer->timer_func = pfunction;
6485
ptimer->timer_arg = parg;
86+
ptimer->timer_next = reinterpret_cast<os_timer_t*>(-1);
6587
}
6688
}
6789

@@ -72,23 +94,24 @@ static os_timer_t* find_expired_timer()
7294
return nullptr;
7395
}
7496

75-
auto time_now = system_get_time();
97+
auto ticks_now = hw_timer2_read();
7698
os_timer_t* t_prev = nullptr;
7799
for(auto t = timer_head; t != nullptr; t_prev = t, t = t->timer_next) {
78-
if(int(t->timer_expire - time_now) > 0) {
79-
continue;
100+
if(int(t->timer_expire - ticks_now) > 0) {
101+
// No timers due
102+
break;
80103
}
81104

82-
// Found an expired timer
83-
if(t->timer_period == 0) {
84-
// Non-repeating timer, remove now
85-
if(t == timer_head) {
86-
timer_head = nullptr;
87-
} else if(t_prev != nullptr) {
88-
t_prev->timer_next = t->timer_next;
89-
}
90-
} else {
91-
t->timer_expire = time_now + t->timer_period;
105+
// Found an expired timer, so remove from queue
106+
if(t == timer_head) {
107+
timer_head = t->timer_next;
108+
} else if(t_prev != nullptr) {
109+
t_prev->timer_next = t->timer_next;
110+
}
111+
112+
// Repeating timer?
113+
if(t->timer_period != 0) {
114+
timer_insert(t->timer_expire + t->timer_period, t);
92115
}
93116

94117
return t;

0 commit comments

Comments
 (0)