7
7
*
8
8
* Modification history:
9
9
* 2014/5/1, v1.0 create this file.
10
- *
10
+ *
11
11
* Adapted for NodeMCU 2016
12
- *
12
+ *
13
13
* 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
17
17
* a small numeric value that is known not to clash.
18
18
*******************************************************************************/
19
+ #include "platform.h"
20
+ #include "c_stdio.h"
21
+ #include "c_stdlib.h"
19
22
#include "ets_sys.h"
20
23
#include "os_type.h"
21
24
#include "osapi.h"
22
25
23
26
#include "hw_timer.h"
24
27
28
+ //#define DEBUG_HW_TIMER
29
+ //#undef NODE_DBG
30
+ //#define NODE_DBG dbg_printf
31
+
25
32
#define FRC1_ENABLE_TIMER BIT7
26
33
#define FRC1_AUTO_LOAD BIT6
27
34
@@ -37,11 +44,227 @@ typedef enum { //timer interrupt mode
37
44
TM_EDGE_INT = 0 , //edge interrupt
38
45
} TIMER_INT_MODE ;
39
46
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
+ }
43
215
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
+ }
45
268
46
269
/******************************************************************************
47
270
* FunctionName : platform_hw_timer_arm_ticks
@@ -52,10 +275,23 @@ static void (* user_hw_timer_cb)(os_param_t);
52
275
*******************************************************************************/
53
276
bool ICACHE_RAM_ATTR platform_hw_timer_arm_ticks (os_param_t owner , uint32_t ticks )
54
277
{
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
+ }
57
283
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;
59
295
}
60
296
61
297
/******************************************************************************
@@ -72,10 +308,7 @@ bool ICACHE_RAM_ATTR platform_hw_timer_arm_ticks(os_param_t owner, uint32_t tick
72
308
*******************************************************************************/
73
309
bool ICACHE_RAM_ATTR platform_hw_timer_arm_us (os_param_t owner , uint32_t microseconds )
74
310
{
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 ));
79
312
}
80
313
81
314
/******************************************************************************
@@ -89,24 +322,67 @@ bool ICACHE_RAM_ATTR platform_hw_timer_arm_us(os_param_t owner, uint32_t microse
89
322
*******************************************************************************/
90
323
bool platform_hw_timer_set_func (os_param_t owner , void (* user_hw_timer_cb_set )(os_param_t ), os_param_t arg )
91
324
{
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;
96
333
}
97
334
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
+ */
98
342
static void ICACHE_RAM_ATTR hw_timer_isr_cb (void * arg )
99
343
{
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" );
102
380
}
103
381
}
104
382
105
383
static void ICACHE_RAM_ATTR hw_timer_nmi_cb (void )
106
384
{
107
- if (user_hw_timer_cb != NULL ) {
108
- (* (user_hw_timer_cb ))(callback_arg );
109
- }
385
+ hw_timer_isr_cb (NULL );
110
386
}
111
387
112
388
/******************************************************************************
@@ -117,9 +393,21 @@ static void ICACHE_RAM_ATTR hw_timer_nmi_cb(void)
117
393
*******************************************************************************/
118
394
uint32_t ICACHE_RAM_ATTR platform_hw_timer_get_delay_ticks (os_param_t owner )
119
395
{
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 ;
121
405
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 ;
123
411
}
124
412
125
413
/******************************************************************************
@@ -136,25 +424,34 @@ uint32_t ICACHE_RAM_ATTR platform_hw_timer_get_delay_ticks(os_param_t owner)
136
424
*******************************************************************************/
137
425
bool platform_hw_timer_init (os_param_t owner , FRC1_TIMER_SOURCE_TYPE source_type , bool autoload )
138
426
{
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 ;
146
436
}
147
437
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 );
151
443
ETS_FRC_TIMER1_INTR_ATTACH (hw_timer_isr_cb , NULL );
444
+
445
+ TM1_EDGE_INT_ENABLE ();
446
+ ETS_FRC1_INTR_ENABLE ();
152
447
}
153
448
154
- TM1_EDGE_INT_ENABLE ();
155
- ETS_FRC1_INTR_ENABLE ();
449
+ LOCK ();
450
+ tu -> next = inactive ;
451
+ inactive = tu ;
452
+ UNLOCK ();
156
453
157
- return 1 ;
454
+ return true ;
158
455
}
159
456
160
457
/******************************************************************************
@@ -165,19 +462,25 @@ bool platform_hw_timer_init(os_param_t owner, FRC1_TIMER_SOURCE_TYPE source_type
165
462
*******************************************************************************/
166
463
bool ICACHE_RAM_ATTR platform_hw_timer_close (os_param_t owner )
167
464
{
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 );
173
466
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
+ }
176
473
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 );
178
479
179
- the_owner = 0 ;
480
+ TM1_EDGE_INT_DISABLE ();
481
+ ETS_FRC1_INTR_DISABLE ();
482
+ }
180
483
181
- return 1 ;
484
+ return true ;
182
485
}
183
486
0 commit comments