Skip to content

Commit d758304

Browse files
pjsgmarcelstoer
authored andcommittedFeb 16, 2019
Initial version of code to support multiple hardware timers (#2497)
* Initial version of code to support multiple hardware timers * MAde the time sinca last tick work again * Add some documentation to the code
1 parent dcc1ea2 commit d758304

File tree

2 files changed

+355
-52
lines changed

2 files changed

+355
-52
lines changed
 

‎app/modules/gpio_pulse.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include "pin_map.h"
1111
#include "driver/gpio16.h"
1212

13-
#define TIMER_OWNER 'P'
13+
#define TIMER_OWNER (('P' << 8) + 'U')
1414

1515
#define xstr(s) str(s)
1616
#define str(s) #s
@@ -434,7 +434,7 @@ static int gpio_pulse_start(lua_State *L) {
434434
pulser->next_adjust = initial_adjust;
435435

436436
// Now start things up
437-
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) {
437+
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, FALSE)) {
438438
// Failed to init the timer
439439
luaL_error(L, "Unable to initialize timer");
440440
}

‎app/platform/hw_timer.c

+353-50
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,28 @@
77
*
88
* Modification history:
99
* 2014/5/1, v1.0 create this file.
10-
*
10+
*
1111
* Adapted for NodeMCU 2016
12-
*
12+
*
1313
* The owner parameter should be a unique value per module using this API
14-
* It could be a pointer to a bit of data or code
15-
* e.g. #define OWNER ((os_param_t) module_init)
16-
* where module_init is a function. For builtin modules, it might be
14+
* It could be a pointer to a bit of data or code
15+
* e.g. #define OWNER ((os_param_t) module_init)
16+
* where module_init is a function. For builtin modules, it might be
1717
* a small numeric value that is known not to clash.
1818
*******************************************************************************/
19+
#include "platform.h"
20+
#include "c_stdio.h"
21+
#include "c_stdlib.h"
1922
#include "ets_sys.h"
2023
#include "os_type.h"
2124
#include "osapi.h"
2225

2326
#include "hw_timer.h"
2427

28+
//#define DEBUG_HW_TIMER
29+
//#undef NODE_DBG
30+
//#define NODE_DBG dbg_printf
31+
2532
#define FRC1_ENABLE_TIMER BIT7
2633
#define FRC1_AUTO_LOAD BIT6
2734

@@ -37,11 +44,227 @@ typedef enum { //timer interrupt mode
3744
TM_EDGE_INT = 0, //edge interrupt
3845
} TIMER_INT_MODE;
3946

40-
static os_param_t the_owner;
41-
static os_param_t callback_arg;
42-
static void (* user_hw_timer_cb)(os_param_t);
47+
/*
48+
* This represents a single user of the timer functionality. It is keyed by the owner
49+
* field.
50+
*/
51+
typedef struct _timer_user {
52+
struct _timer_user *next;
53+
bool autoload;
54+
int32_t delay; // once on the active list, this is difference in delay from the preceding element
55+
int32_t autoload_delay;
56+
uint32_t expected_interrupt_time;
57+
os_param_t owner;
58+
os_param_t callback_arg;
59+
void (* user_hw_timer_cb)(os_param_t);
60+
#ifdef DEBUG_HW_TIMER
61+
int cb_count;
62+
#endif
63+
} timer_user;
64+
65+
/*
66+
* There are two lists of timer_user blocks. The active list are those which are waiting
67+
* for timeouts to happen, and the inactive list contains idle blocks. Unfortunately
68+
* there isn't a way to clean up the inactive blocks as some modules call the
69+
* close method from interrupt level.
70+
*/
71+
static timer_user *active;
72+
static timer_user *inactive;
73+
74+
/*
75+
* There are a fair number of places when interrupts need to be disabled as many of
76+
* the methods can be called from interrupt level. The lock/unlock calls support
77+
* multiple LOCKs and then the same number of UNLOCKs are required to re-enable
78+
* interrupts. This is imolemeted by counting the number of times that lock is called.
79+
*/
80+
static uint8_t lock_count;
81+
static uint8_t timer_running;
82+
83+
static uint32_t time_next_expiry;
84+
static int32_t last_timer_load;
85+
86+
#define LOCK() do { ets_intr_lock(); lock_count++; } while (0)
87+
#define UNLOCK() if (--lock_count == 0) ets_intr_unlock()
88+
89+
/*
90+
* To start a timer, you write to FRCI_LOAD_ADDRESS, and that starts the counting
91+
* down. When it reaches zero, the interrupt fires -- but the counting continues.
92+
* The counter is 23 bits wide. The current value of the counter can be read
93+
* at FRC1_COUNT_ADDRESS. The unit is 200ns, and so it takes somewhat over a second
94+
* to wrap the counter.
95+
*/
96+
97+
#ifdef DEBUG_HW_TIMER
98+
void ICACHE_RAM_ATTR hw_timer_debug() {
99+
dbg_printf("timer_running=%d\n", timer_running);
100+
timer_user *tu;
101+
for (tu = active; tu; tu = tu->next) {
102+
dbg_printf("Owner: 0x%x, delay=%d, autoload=%d, autoload_delay=%d, cb_count=%d\n",
103+
tu->owner, tu->delay, tu->autoload, tu->autoload_delay, tu->cb_count);
104+
}
105+
}
106+
#endif
107+
108+
static void ICACHE_RAM_ATTR set_timer(int delay, const char *caller) {
109+
if (delay < 1) {
110+
delay = 1;
111+
}
112+
int32_t time_left = (RTC_REG_READ(FRC1_COUNT_ADDRESS)) & ((1 << 23) - 1);
113+
RTC_REG_WRITE(FRC1_LOAD_ADDRESS, delay);
114+
115+
if (time_left > last_timer_load) {
116+
// We have missed the interrupt
117+
time_left -= 1 << 23;
118+
}
119+
NODE_DBG("%s(%x): time_next=%d, left=%d (load=%d), delay=%d => %d\n", caller, active->owner, time_next_expiry, time_left, last_timer_load, delay, time_next_expiry - time_left + delay);
120+
time_next_expiry = time_next_expiry - time_left + delay;
121+
last_timer_load = delay;
122+
123+
timer_running = 1;
124+
}
125+
126+
static void ICACHE_RAM_ATTR adjust_root() {
127+
// Can only ge called with interrupts disabled
128+
// change the initial active delay so that relative stuff still works
129+
// Also, set the last_timer_load to be now
130+
int32_t time_left = (RTC_REG_READ(FRC1_COUNT_ADDRESS)) & ((1 << 23) - 1);
131+
if (time_left > last_timer_load) {
132+
// We have missed the interrupt
133+
time_left -= 1 << 23;
134+
}
135+
136+
if (active && timer_running) {
137+
active->delay = time_left;
138+
}
139+
140+
if (active) {
141+
NODE_DBG("adjust(%x): time_left=%d (last_load=%d)\n", active->owner, time_left, last_timer_load);
142+
} else {
143+
NODE_DBG("adjust: time_left=%d (last_load=%d)\n", time_left, last_timer_load);
144+
}
145+
146+
last_timer_load = time_left;
147+
}
148+
/*
149+
* Find the timer_user block for this owner. This just returns
150+
* a pointer to the block, or NULL.
151+
*/
152+
static timer_user * ICACHE_RAM_ATTR find_tu(os_param_t owner) {
153+
// Try the inactive chain first
154+
timer_user **p;
155+
156+
LOCK();
157+
158+
for (p = &inactive; *p; p = &((*p)->next)) {
159+
if ((*p)->owner == owner) {
160+
timer_user *result = *p;
161+
162+
UNLOCK();
163+
164+
return result;
165+
}
166+
}
167+
168+
for (p = &active; *p; p = &((*p)->next)) {
169+
if ((*p)->owner == owner) {
170+
timer_user *result = *p;
171+
172+
UNLOCK();
173+
174+
return result;
175+
}
176+
}
177+
178+
UNLOCK();
179+
return NULL;
180+
}
181+
182+
/*
183+
* Find the timer_user block for this owner. This just returns
184+
* a pointer to the block, or NULL. If it finds the block, then it is
185+
* removed from whichever chain it is on. Note that this may require
186+
* triggering a timer.
187+
*/
188+
static timer_user * ICACHE_RAM_ATTR find_tu_and_remove(os_param_t owner) {
189+
// Try the inactive chain first
190+
timer_user **p;
191+
192+
LOCK();
193+
194+
for (p = &inactive; *p; p = &((*p)->next)) {
195+
if ((*p)->owner == owner) {
196+
timer_user *result = *p;
197+
*p = result->next;
198+
result->next = NULL;
199+
200+
UNLOCK();
201+
202+
return result;
203+
}
204+
}
205+
206+
for (p = &active; *p; p = &((*p)->next)) {
207+
if ((*p)->owner == owner) {
208+
timer_user *result = *p;
209+
210+
bool need_to_reset = (result == active) && result->next;
211+
212+
if (need_to_reset) {
213+
adjust_root();
214+
}
43215

44-
#define VERIFY_OWNER(owner) if (owner != the_owner) { if (the_owner) { return 0; } the_owner = owner; }
216+
// Increase the delay on the next element
217+
if (result->next) {
218+
result->next->delay += result->delay;
219+
}
220+
221+
// Cut out of chain
222+
*p = result->next;
223+
result->next = NULL;
224+
225+
if (need_to_reset) {
226+
set_timer(active->delay, "find_tu");
227+
}
228+
229+
UNLOCK();
230+
return result;
231+
}
232+
}
233+
234+
UNLOCK();
235+
return NULL;
236+
}
237+
238+
/*
239+
* This inserts a timer_user block into the active chain. This is a sightly
240+
* complex process as it can involve triggering a timer load.
241+
*/
242+
static void ICACHE_RAM_ATTR insert_active_tu(timer_user *tu) {
243+
timer_user **p;
244+
245+
LOCK();
246+
247+
tu->expected_interrupt_time = time_next_expiry - last_timer_load + tu->delay;
248+
249+
for (p = &active; *p; p = &((*p)->next)) {
250+
if ((*p)->delay >= tu->delay) {
251+
break;
252+
}
253+
tu->delay -= (*p)->delay;
254+
}
255+
256+
if (*p) {
257+
(*p)->delay -= tu->delay;
258+
}
259+
tu->next = *p;
260+
*p = tu;
261+
262+
if (tu == active) {
263+
// We have a new leader
264+
set_timer(active->delay, "insert_active");
265+
}
266+
UNLOCK();
267+
}
45268

46269
/******************************************************************************
47270
* FunctionName : platform_hw_timer_arm_ticks
@@ -52,10 +275,23 @@ static void (* user_hw_timer_cb)(os_param_t);
52275
*******************************************************************************/
53276
bool ICACHE_RAM_ATTR platform_hw_timer_arm_ticks(os_param_t owner, uint32_t ticks)
54277
{
55-
VERIFY_OWNER(owner);
56-
RTC_REG_WRITE(FRC1_LOAD_ADDRESS, ticks);
278+
timer_user *tu = find_tu_and_remove(owner);
279+
280+
if (!tu) {
281+
return false;
282+
}
57283

58-
return 1;
284+
tu->delay = ticks;
285+
tu->autoload_delay = ticks;
286+
287+
NODE_DBG("arm(%x): ticks=%d\n", owner, ticks);
288+
289+
LOCK();
290+
adjust_root();
291+
insert_active_tu(tu);
292+
UNLOCK();
293+
294+
return true;
59295
}
60296

61297
/******************************************************************************
@@ -72,10 +308,7 @@ bool ICACHE_RAM_ATTR platform_hw_timer_arm_ticks(os_param_t owner, uint32_t tick
72308
*******************************************************************************/
73309
bool ICACHE_RAM_ATTR platform_hw_timer_arm_us(os_param_t owner, uint32_t microseconds)
74310
{
75-
VERIFY_OWNER(owner);
76-
RTC_REG_WRITE(FRC1_LOAD_ADDRESS, US_TO_RTC_TIMER_TICKS(microseconds));
77-
78-
return 1;
311+
return platform_hw_timer_arm_ticks(owner, US_TO_RTC_TIMER_TICKS(microseconds));
79312
}
80313

81314
/******************************************************************************
@@ -89,24 +322,67 @@ bool ICACHE_RAM_ATTR platform_hw_timer_arm_us(os_param_t owner, uint32_t microse
89322
*******************************************************************************/
90323
bool platform_hw_timer_set_func(os_param_t owner, void (* user_hw_timer_cb_set)(os_param_t), os_param_t arg)
91324
{
92-
VERIFY_OWNER(owner);
93-
callback_arg = arg;
94-
user_hw_timer_cb = user_hw_timer_cb_set;
95-
return 1;
325+
timer_user *tu = find_tu(owner);
326+
if (!tu) {
327+
return false;
328+
}
329+
tu->callback_arg = arg;
330+
tu->user_hw_timer_cb = user_hw_timer_cb_set;
331+
NODE_DBG("set-CB(%x): %x, %x\n", tu->owner, tu->user_hw_timer_cb, tu->callback_arg);
332+
return true;
96333
}
97334

335+
/*
336+
* This is the timer ISR. It has to find the timer that was running and trigger the callback
337+
* for that timer. By this stage, the next timer may have expired as well, and so the process
338+
* iterates. Note that if there is an autoload timer, then it should be restarted immediately.
339+
* Also, the callbacks typically do re-arm the timer, so we have to be careful not to
340+
* assume that nothing changes during the callback.
341+
*/
98342
static void ICACHE_RAM_ATTR hw_timer_isr_cb(void *arg)
99343
{
100-
if (user_hw_timer_cb != NULL) {
101-
(*(user_hw_timer_cb))(callback_arg);
344+
bool keep_going = true;
345+
adjust_root();
346+
timer_running = 0;
347+
348+
while (keep_going && active) {
349+
keep_going = false;
350+
351+
timer_user *fired = active;
352+
active = fired->next;
353+
if (fired->autoload) {
354+
fired->expected_interrupt_time += fired->autoload_delay;
355+
fired->delay = fired->expected_interrupt_time - (time_next_expiry - last_timer_load);
356+
insert_active_tu(fired);
357+
if (active->delay <= 0) {
358+
keep_going = true;
359+
}
360+
} else {
361+
fired->next = inactive;
362+
inactive = fired;
363+
if (active) {
364+
active->delay += fired->delay;
365+
if (active->delay <= 0) {
366+
keep_going = true;
367+
}
368+
}
369+
}
370+
if (fired->user_hw_timer_cb) {
371+
#ifdef DEBUG_HW_TIMER
372+
fired->cb_count++;
373+
#endif
374+
NODE_DBG("CB(%x): %x, %x\n", fired->owner, fired->user_hw_timer_cb, fired->callback_arg);
375+
(*(fired->user_hw_timer_cb))(fired->callback_arg);
376+
}
377+
}
378+
if (active && !timer_running) {
379+
set_timer(active->delay, "isr");
102380
}
103381
}
104382

105383
static void ICACHE_RAM_ATTR hw_timer_nmi_cb(void)
106384
{
107-
if (user_hw_timer_cb != NULL) {
108-
(*(user_hw_timer_cb))(callback_arg);
109-
}
385+
hw_timer_isr_cb(NULL);
110386
}
111387

112388
/******************************************************************************
@@ -117,9 +393,21 @@ static void ICACHE_RAM_ATTR hw_timer_nmi_cb(void)
117393
*******************************************************************************/
118394
uint32_t ICACHE_RAM_ATTR platform_hw_timer_get_delay_ticks(os_param_t owner)
119395
{
120-
VERIFY_OWNER(owner);
396+
timer_user *tu = find_tu(owner);
397+
if (!tu) {
398+
return 0;
399+
}
400+
401+
LOCK();
402+
adjust_root();
403+
UNLOCK();
404+
int ret = (time_next_expiry - last_timer_load) - tu->expected_interrupt_time;
121405

122-
return (- RTC_REG_READ(FRC1_COUNT_ADDRESS)) & ((1 << 23) - 1);
406+
if (ret < 0) {
407+
NODE_DBG("delay ticks = %d, last_timer_load=%d, tu->expected_int=%d, next_exp=%d\n", ret, last_timer_load, tu->expected_interrupt_time, time_next_expiry);
408+
}
409+
410+
return ret < 0 ? 0 : ret;
123411
}
124412

125413
/******************************************************************************
@@ -136,25 +424,34 @@ uint32_t ICACHE_RAM_ATTR platform_hw_timer_get_delay_ticks(os_param_t owner)
136424
*******************************************************************************/
137425
bool platform_hw_timer_init(os_param_t owner, FRC1_TIMER_SOURCE_TYPE source_type, bool autoload)
138426
{
139-
VERIFY_OWNER(owner);
140-
if (autoload) {
141-
RTC_REG_WRITE(FRC1_CTRL_ADDRESS,
142-
FRC1_AUTO_LOAD | DIVIDED_BY_16 | FRC1_ENABLE_TIMER | TM_EDGE_INT);
143-
} else {
144-
RTC_REG_WRITE(FRC1_CTRL_ADDRESS,
145-
DIVIDED_BY_16 | FRC1_ENABLE_TIMER | TM_EDGE_INT);
427+
timer_user *tu = find_tu_and_remove(owner);
428+
429+
if (!tu) {
430+
tu = (timer_user *) c_malloc(sizeof(*tu));
431+
if (!tu) {
432+
return false;
433+
}
434+
memset(tu, 0, sizeof(*tu));
435+
tu->owner = owner;
146436
}
147437

148-
if (source_type == NMI_SOURCE) {
149-
ETS_FRC_TIMER1_NMI_INTR_ATTACH(hw_timer_nmi_cb);
150-
} else {
438+
tu->autoload = autoload;
439+
440+
if (!active && !inactive) {
441+
RTC_REG_WRITE(FRC1_CTRL_ADDRESS,
442+
DIVIDED_BY_16 | FRC1_ENABLE_TIMER | TM_EDGE_INT);
151443
ETS_FRC_TIMER1_INTR_ATTACH(hw_timer_isr_cb, NULL);
444+
445+
TM1_EDGE_INT_ENABLE();
446+
ETS_FRC1_INTR_ENABLE();
152447
}
153448

154-
TM1_EDGE_INT_ENABLE();
155-
ETS_FRC1_INTR_ENABLE();
449+
LOCK();
450+
tu->next = inactive;
451+
inactive = tu;
452+
UNLOCK();
156453

157-
return 1;
454+
return true;
158455
}
159456

160457
/******************************************************************************
@@ -165,19 +462,25 @@ bool platform_hw_timer_init(os_param_t owner, FRC1_TIMER_SOURCE_TYPE source_type
165462
*******************************************************************************/
166463
bool ICACHE_RAM_ATTR platform_hw_timer_close(os_param_t owner)
167464
{
168-
VERIFY_OWNER(owner);
169-
170-
/* Set no reload mode */
171-
RTC_REG_WRITE(FRC1_CTRL_ADDRESS,
172-
DIVIDED_BY_16 | TM_EDGE_INT);
465+
timer_user *tu = find_tu_and_remove(owner);
173466

174-
TM1_EDGE_INT_DISABLE();
175-
ETS_FRC1_INTR_DISABLE();
467+
if (tu) {
468+
LOCK();
469+
tu->next = inactive;
470+
inactive = tu;
471+
UNLOCK();
472+
}
176473

177-
user_hw_timer_cb = NULL;
474+
// This will never actually run....
475+
if (!active && !inactive) {
476+
/* Set no reload mode */
477+
RTC_REG_WRITE(FRC1_CTRL_ADDRESS,
478+
DIVIDED_BY_16 | TM_EDGE_INT);
178479

179-
the_owner = 0;
480+
TM1_EDGE_INT_DISABLE();
481+
ETS_FRC1_INTR_DISABLE();
482+
}
180483

181-
return 1;
484+
return true;
182485
}
183486

0 commit comments

Comments
 (0)
Please sign in to comment.