13
13
* See the License for the specific language governing permissions and
14
14
* limitations under the License.
15
15
*/
16
-
16
+
17
17
#include "lp_ticker_api.h"
18
18
19
19
#if DEVICE_LOWPOWERTIMER
20
20
21
21
#include "sleep_api.h"
22
+ #include "mbed_wait_api.h"
23
+ #include "mbed_assert.h"
22
24
#include "nu_modutil.h"
23
25
#include "nu_miscutil.h"
24
- #include "mbed_critical.h"
25
-
26
- // lp_ticker tick = us = timestamp
27
- #define US_PER_TICK (1)
28
- #define US_PER_SEC (1000 * 1000)
29
26
30
- #define US_PER_TMR2_INT (US_PER_SEC * 10)
31
- #define TMR2_CLK_PER_SEC (__LXT)
32
- #define TMR2_CLK_PER_TMR2_INT ((uint32_t) ((uint64_t) US_PER_TMR2_INT * TMR2_CLK_PER_SEC / US_PER_SEC))
33
- #define TMR3_CLK_PER_SEC (__LXT)
27
+ /* Micro seconds per second */
28
+ #define NU_US_PER_SEC 1000000
29
+ /* Timer clock per lp_ticker tick */
30
+ #define NU_TMRCLK_PER_TICK 1
31
+ /* Timer clock per second */
32
+ #define NU_TMRCLK_PER_SEC (__LXT)
33
+ /* Timer max counter bit size */
34
+ #define NU_TMR_MAXCNT_BITSIZE 24
35
+ /* Timer max counter */
36
+ #define NU_TMR_MAXCNT ((1 << NU_TMR_MAXCNT_BITSIZE) - 1)
34
37
35
38
static void tmr2_vec (void );
36
39
static void tmr3_vec (void );
37
- static void lp_ticker_arm_cd (void );
40
+ /* Configure scheduled alarm */
41
+ static void arm_alarm (uint32_t cd_clk );
38
42
39
- static int lp_ticker_inited = 0 ;
40
- static volatile uint32_t counter_major = 0 ;
41
- static volatile uint32_t cd_major_minor_clks = 0 ;
42
- static volatile uint32_t cd_minor_clks = 0 ;
43
- static volatile uint32_t wakeup_tick = (uint32_t ) -1 ;
43
+ static int ticker_inited = 0 ;
44
+ static uint32_t ticker_last_read_clk = 0 ;
44
45
45
- // NOTE: To wake the system from power down mode, timer clock source must be ether LXT or LIRC.
46
- // NOTE: TIMER_2 for normal counting and TIMER_3 for scheduled wakeup
46
+ /* NOTE: To wake the system from power down mode, timer clock source must be ether LXT or LIRC. */
47
+ /* NOTE: TIMER_2 for normal counting and TIMER_3 for scheduled alarm */
47
48
static const struct nu_modinit_s timer2_modinit = {TIMER_2 , TMR2_MODULE , CLK_CLKSEL1_TMR2SEL_LXT , 0 , TMR2_RST , TMR2_IRQn , (void * ) tmr2_vec };
48
49
static const struct nu_modinit_s timer3_modinit = {TIMER_3 , TMR3_MODULE , CLK_CLKSEL1_TMR3SEL_LXT , 0 , TMR3_RST , TMR3_IRQn , (void * ) tmr3_vec };
49
50
@@ -52,20 +53,17 @@ static const struct nu_modinit_s timer3_modinit = {TIMER_3, TMR3_MODULE, CLK_CLK
52
53
53
54
void lp_ticker_init (void )
54
55
{
55
- if (lp_ticker_inited ) {
56
+ if (ticker_inited ) {
56
57
return ;
57
58
}
58
- lp_ticker_inited = 1 ;
59
-
60
- counter_major = 0 ;
61
- cd_major_minor_clks = 0 ;
62
- cd_minor_clks = 0 ;
63
- wakeup_tick = (uint32_t ) -1 ;
59
+ ticker_inited = 1 ;
60
+
61
+ ticker_last_read_clk = 0 ;
64
62
65
63
// Reset module
66
64
SYS_ResetModule (timer2_modinit .rsetidx );
67
65
SYS_ResetModule (timer3_modinit .rsetidx );
68
-
66
+
69
67
// Select IP clock source
70
68
CLK_SetModuleClock (timer2_modinit .clkidx , timer2_modinit .clksrc , timer2_modinit .clkdiv );
71
69
CLK_SetModuleClock (timer3_modinit .clkidx , timer3_modinit .clksrc , timer3_modinit .clkdiv );
@@ -75,93 +73,114 @@ void lp_ticker_init(void)
75
73
76
74
// Configure clock
77
75
uint32_t clk_timer2 = TIMER_GetModuleClock ((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ));
78
- uint32_t prescale_timer2 = clk_timer2 / TMR2_CLK_PER_SEC - 1 ;
76
+ uint32_t prescale_timer2 = clk_timer2 / NU_TMRCLK_PER_SEC - 1 ;
79
77
MBED_ASSERT ((prescale_timer2 != (uint32_t ) -1 ) && prescale_timer2 <= 127 );
80
- MBED_ASSERT ((clk_timer2 % TMR2_CLK_PER_SEC ) == 0 );
81
- uint32_t cmp_timer2 = TMR2_CLK_PER_TMR2_INT ;
78
+ MBED_ASSERT ((clk_timer2 % NU_TMRCLK_PER_SEC ) == 0 );
79
+ uint32_t cmp_timer2 = TMR_CMP_MAX ;
82
80
MBED_ASSERT (cmp_timer2 >= TMR_CMP_MIN && cmp_timer2 <= TMR_CMP_MAX );
83
81
// Continuous mode
84
82
// NOTE: TIMER_CTL_CNTDATEN_Msk exists in NUC472, but not in M451. In M451, TIMER_CNT is updated continuously by default.
85
83
((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ))-> CTL = TIMER_PERIODIC_MODE | prescale_timer2 /* | TIMER_CTL_CNTDATEN_Msk*/ ;
86
84
((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ))-> CMP = cmp_timer2 ;
87
-
85
+
88
86
// Set vector
89
87
NVIC_SetVector (timer2_modinit .irq_n , (uint32_t ) timer2_modinit .var );
90
88
NVIC_SetVector (timer3_modinit .irq_n , (uint32_t ) timer3_modinit .var );
91
-
89
+
92
90
NVIC_EnableIRQ (timer2_modinit .irq_n );
93
91
NVIC_EnableIRQ (timer3_modinit .irq_n );
94
-
92
+
95
93
TIMER_EnableInt ((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ));
96
94
TIMER_EnableWakeup ((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ));
97
-
98
- // NOTE: TIMER_Start() first and then lp_ticker_set_interrupt(); otherwise, we may get stuck in lp_ticker_read() because
99
- // timer is not running.
100
-
101
- // Start timer
95
+ /* NOTE: When engine is clocked by low power clock source (LXT/LIRC), we need to wait for 3 engine clocks. */
96
+ wait_us ((NU_US_PER_SEC / NU_TMRCLK_PER_SEC ) * 3 );
102
97
TIMER_Start ((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ));
103
-
104
- // Schedule wakeup to match semantics of lp_ticker_get_compare_match()
105
- lp_ticker_set_interrupt (wakeup_tick );
106
98
}
107
99
108
100
timestamp_t lp_ticker_read ()
109
- {
110
- if (! lp_ticker_inited ) {
101
+ {
102
+ if (! ticker_inited ) {
111
103
lp_ticker_init ();
112
104
}
113
-
105
+
114
106
TIMER_T * timer2_base = (TIMER_T * ) NU_MODBASE (timer2_modinit .modname );
115
-
116
- do {
117
- uint64_t major_minor_clks ;
118
- uint32_t minor_clks ;
119
-
120
- // NOTE: As TIMER_CNT = TIMER_CMP and counter_major has increased by one, TIMER_CNT doesn't change to 0 for one tick time.
121
- // NOTE: As TIMER_CNT = TIMER_CMP or TIMER_CNT = 0, counter_major (ISR) may not sync with TIMER_CNT. So skip and fetch stable one at the cost of 1 clock delay on this read.
122
- do {
123
- core_util_critical_section_enter ();
124
-
125
- // NOTE: Order of reading minor_us/carry here is significant.
126
- minor_clks = TIMER_GetCounter (timer2_base );
127
- uint32_t carry = (timer2_base -> INTSTS & TIMER_INTSTS_TIF_Msk ) ? 1 : 0 ;
128
- // When TIMER_CNT approaches TIMER_CMP and will wrap soon, we may get carry but TIMER_CNT not wrapped. Hanlde carefully carry == 1 && TIMER_CNT is near TIMER_CMP.
129
- if (carry && minor_clks > (TMR2_CLK_PER_TMR2_INT / 2 )) {
130
- major_minor_clks = (counter_major + 1 ) * TMR2_CLK_PER_TMR2_INT ;
131
- }
132
- else {
133
- major_minor_clks = (counter_major + carry ) * TMR2_CLK_PER_TMR2_INT + minor_clks ;
134
- }
135
-
136
- core_util_critical_section_exit ();
137
- }
138
- while (minor_clks == 0 || minor_clks == TMR2_CLK_PER_TMR2_INT );
139
-
140
- // Add power-down compensation
141
- return ((uint64_t ) major_minor_clks * US_PER_SEC / TMR2_CLK_PER_SEC / US_PER_TICK );
142
- }
143
- while (0 );
107
+
108
+ ticker_last_read_clk = TIMER_GetCounter (timer2_base );
109
+ return (ticker_last_read_clk / NU_TMRCLK_PER_TICK );
144
110
}
145
111
146
112
void lp_ticker_set_interrupt (timestamp_t timestamp )
147
113
{
148
- uint32_t delta = timestamp - lp_ticker_read ();
149
- wakeup_tick = timestamp ;
150
-
151
114
TIMER_Stop ((TIMER_T * ) NU_MODBASE (timer3_modinit .modname ));
115
+
116
+ /* We need to get alarm interval from alarm timestamp `timestamp` to configure H/W timer.
117
+ *
118
+ * Because both `timestamp` and xx_ticker_read() would wrap around, we have difficulties in distinguishing
119
+ * long future event and past event. To distinguish them, we need `tick_last_read` against which
120
+ * `timestamp` is calculated out. In timeline, we would always have below after fixing wrap-around:
121
+ * (1) tick_last_read <= present_clk
122
+ * (2) tick_last_read <= alarm_ts_clk
123
+ *
124
+ *
125
+ * 1. Future event case:
126
+ *
127
+ * tick_last_read present_clk alarm_ts_clk
128
+ * | | |
129
+ * --------------------------------------------------------
130
+ * |-alarm_intvl1_clk-|
131
+ * |-------------------alarm_intvl2_clk-------------------|
132
+ *
133
+ * 2. Past event case:
134
+ *
135
+ * tick_last_read alarm_ts_clk present_clk
136
+ * | | |
137
+ * --------------------------------------------------------
138
+ * |-------------------alarm_intvl1_clk-------------------|
139
+ * |-alarm_intvl2_clk-|
140
+ *
141
+ * Unfortunately, `tick_last_read` is not passed along the xx_ticker_set_interrupt() call. To solve it, we
142
+ * assume that `tick_last_read` tick is exactly the one returned by the last xx_ticker_read() call before
143
+ * xx_ticker_set_interrupt() is invoked. With this assumption, we can hold it via `xx_ticker_last_read_clk`
144
+ * in xx_ticker_read().
145
+ */
146
+
147
+ /* ticker_last_read_clk will update in lp_ticker_read(). Keep it beforehand. */
148
+ uint32_t last_read_clk = ticker_last_read_clk ;
149
+ uint32_t present_clk = lp_ticker_read () * NU_TMRCLK_PER_TICK ;
150
+ uint32_t alarm_ts_clk = timestamp * NU_TMRCLK_PER_TICK ;
151
+ uint32_t alarm_intvl1_clk , alarm_intvl2_clk ;
152
152
153
- cd_major_minor_clks = (uint64_t ) delta * US_PER_TICK * TMR3_CLK_PER_SEC / US_PER_SEC ;
154
- lp_ticker_arm_cd ();
155
- }
153
+ /* alarm_intvl1_clk = present_clk - last_read_clk
154
+ *
155
+ * NOTE: Don't miss the `=` sign here. Otherwise, we would get the wrong result.
156
+ */
157
+ if (present_clk >= last_read_clk ) {
158
+ alarm_intvl1_clk = present_clk - last_read_clk ;
159
+ } else {
160
+ alarm_intvl1_clk = (uint32_t ) (((uint64_t ) NU_TMR_MAXCNT ) + 1 + present_clk - last_read_clk );
161
+ }
156
162
157
- void lp_ticker_fire_interrupt (void )
158
- {
159
- cd_major_minor_clks = cd_minor_clks = 0 ;
160
- /**
161
- * This event was in the past. Set the interrupt as pending, but don't process it here.
162
- * This prevents a recurive loop under heavy load which can lead to a stack overflow.
163
- */
164
- NVIC_SetPendingIRQ (timer3_modinit .irq_n );
163
+ /* alarm_intvl2_clk = alarm_ts_clk - last_read_clk
164
+ *
165
+ * NOTE: Don't miss the `=` sign here. Otherwise, we would get the wrong result.
166
+ */
167
+ if (alarm_ts_clk >= last_read_clk ) {
168
+ alarm_intvl2_clk = alarm_ts_clk - last_read_clk ;
169
+ } else {
170
+ alarm_intvl2_clk = (uint32_t ) (((uint64_t ) NU_TMR_MAXCNT ) + 1 + alarm_ts_clk - last_read_clk );
171
+ }
172
+
173
+ /* Distinguish (long) future event and past event
174
+ *
175
+ * NOTE: No '=' sign here. Alarm should go off immediately in equal case.
176
+ */
177
+ if (alarm_intvl2_clk > alarm_intvl1_clk ) {
178
+ /* Schedule for future event */
179
+ arm_alarm (alarm_intvl2_clk - alarm_intvl1_clk );
180
+ } else {
181
+ /* Go off immediately for past event, including equal case */
182
+ lp_ticker_fire_interrupt ();
183
+ }
165
184
}
166
185
167
186
void lp_ticker_disable_interrupt (void )
@@ -174,48 +193,64 @@ void lp_ticker_clear_interrupt(void)
174
193
TIMER_ClearIntFlag ((TIMER_T * ) NU_MODBASE (timer3_modinit .modname ));
175
194
}
176
195
196
+ void lp_ticker_fire_interrupt (void )
197
+ {
198
+ // NOTE: This event was in the past. Set the interrupt as pending, but don't process it here.
199
+ // This prevents a recursive loop under heavy load which can lead to a stack overflow.
200
+ NVIC_SetPendingIRQ (timer3_modinit .irq_n );
201
+ }
202
+
177
203
static void tmr2_vec (void )
178
204
{
179
205
TIMER_ClearIntFlag ((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ));
180
206
TIMER_ClearWakeupFlag ((TIMER_T * ) NU_MODBASE (timer2_modinit .modname ));
181
- counter_major ++ ;
182
207
}
183
208
184
209
static void tmr3_vec (void )
185
210
{
186
211
TIMER_ClearIntFlag ((TIMER_T * ) NU_MODBASE (timer3_modinit .modname ));
187
212
TIMER_ClearWakeupFlag ((TIMER_T * ) NU_MODBASE (timer3_modinit .modname ));
188
- cd_major_minor_clks = (cd_major_minor_clks > cd_minor_clks ) ? (cd_major_minor_clks - cd_minor_clks ) : 0 ;
189
- if (cd_major_minor_clks == 0 ) {
190
- // NOTE: lp_ticker_set_interrupt() may get called in lp_ticker_irq_handler();
191
- lp_ticker_irq_handler ();
192
- }
193
- else {
194
- lp_ticker_arm_cd ();
195
- }
213
+
214
+ // NOTE: lp_ticker_set_interrupt() may get called in lp_ticker_irq_handler();
215
+ lp_ticker_irq_handler ();
216
+
196
217
}
197
218
198
- static void lp_ticker_arm_cd ( void )
219
+ static void arm_alarm ( uint32_t cd_clk )
199
220
{
200
221
TIMER_T * timer3_base = (TIMER_T * ) NU_MODBASE (timer3_modinit .modname );
201
-
222
+
202
223
// Reset 8-bit PSC counter, 24-bit up counter value and CNTEN bit
203
224
timer3_base -> CTL |= TIMER_CTL_RSTCNT_Msk ;
204
225
// One-shot mode, Clock = 1 KHz
205
226
uint32_t clk_timer3 = TIMER_GetModuleClock ((TIMER_T * ) NU_MODBASE (timer3_modinit .modname ));
206
- uint32_t prescale_timer3 = clk_timer3 / TMR3_CLK_PER_SEC - 1 ;
227
+ uint32_t prescale_timer3 = clk_timer3 / NU_TMRCLK_PER_SEC - 1 ;
207
228
MBED_ASSERT ((prescale_timer3 != (uint32_t ) -1 ) && prescale_timer3 <= 127 );
208
- MBED_ASSERT ((clk_timer3 % TMR3_CLK_PER_SEC ) == 0 );
229
+ MBED_ASSERT ((clk_timer3 % NU_TMRCLK_PER_SEC ) == 0 );
209
230
// NOTE: TIMER_CTL_CNTDATEN_Msk exists in NUC472, but not in M451. In M451, TIMER_CNT is updated continuously by default.
210
231
timer3_base -> CTL &= ~(TIMER_CTL_OPMODE_Msk | TIMER_CTL_PSC_Msk /* | TIMER_CTL_CNTDATEN_Msk*/ );
211
232
timer3_base -> CTL |= TIMER_ONESHOT_MODE | prescale_timer3 /* | TIMER_CTL_CNTDATEN_Msk*/ ;
212
-
213
- cd_minor_clks = cd_major_minor_clks ;
214
- cd_minor_clks = NU_CLAMP (cd_minor_clks , TMR_CMP_MIN , TMR_CMP_MAX );
215
- timer3_base -> CMP = cd_minor_clks ;
216
-
233
+
234
+ /* NOTE: Because H/W timer requests min compare value, our implementation would have alarm delay of
235
+ * (TMR_CMP_MIN - interval_clk) clocks when interval_clk is between [1, TMR_CMP_MIN). */
236
+ uint32_t cmp_timer3 = cd_clk ;
237
+ cmp_timer3 = NU_CLAMP (cmp_timer3 , TMR_CMP_MIN , TMR_CMP_MAX );
238
+ timer3_base -> CMP = cmp_timer3 ;
239
+
217
240
TIMER_EnableInt (timer3_base );
218
241
TIMER_EnableWakeup ((TIMER_T * ) NU_MODBASE (timer3_modinit .modname ));
242
+ /* NOTE: When engine is clocked by low power clock source (LXT/LIRC), we need to wait for 3 engine clocks. */
243
+ wait_us ((NU_US_PER_SEC / NU_TMRCLK_PER_SEC ) * 3 );
219
244
TIMER_Start (timer3_base );
220
245
}
246
+
247
+ const ticker_info_t * lp_ticker_get_info ()
248
+ {
249
+ static const ticker_info_t info = {
250
+ NU_TMRCLK_PER_SEC / NU_TMRCLK_PER_TICK ,
251
+ NU_TMR_MAXCNT_BITSIZE
252
+ };
253
+ return & info ;
254
+ }
255
+
221
256
#endif
0 commit comments