Skip to content

Commit f0859ac

Browse files
committed
Switch SAMD21 ticks to PER event
The EVSYS is used to generate an interrupt from the event. This simplifies timing used in pulseio that conflicted with the auto-reload countdown. Fixes #3890
1 parent f805e63 commit f0859ac

File tree

4 files changed

+126
-95
lines changed

4 files changed

+126
-95
lines changed

lib/utils/pyexec.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
149149
mp_hal_stdout_tx_strn("\x04", 1);
150150
}
151151
// check for SystemExit
152-
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
152+
if (mp_obj_is_subclass_fast(mp_obj_get_type((mp_obj_t)nlr.ret_val), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
153153
// at the moment, the value of SystemExit is unused
154154
ret = pyexec_system_exit;
155155
#if CIRCUITPY_ALARM

ports/atmel-samd/audio_dma.c

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,13 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
265265

266266
#ifdef SAM_D5X_E5X
267267
int irq = dma->event_channel < 4 ? EVSYS_0_IRQn + dma->event_channel : EVSYS_4_IRQn;
268+
// Only disable and clear on SAMD51 because the SAMD21 shares EVSYS with ticks.
269+
NVIC_DisableIRQ(irq);
270+
NVIC_ClearPendingIRQ(irq);
268271
#else
269272
int irq = EVSYS_IRQn;
270273
#endif
271274

272-
NVIC_DisableIRQ(irq);
273-
NVIC_ClearPendingIRQ(irq);
274-
275275
DmacDescriptor *first_descriptor = dma_descriptor(dma_channel);
276276
setup_audio_descriptor(first_descriptor, dma->beat_size, output_spacing, output_register_address);
277277
if (single_buffer) {
@@ -366,7 +366,7 @@ STATIC void dma_callback_fun(void *arg) {
366366
audio_dma_load_next_block(dma);
367367
}
368368

369-
void evsyshandler_common(void) {
369+
void audio_evsys_handler(void) {
370370
for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
371371
audio_dma_t *dma = audio_dma_state[i];
372372
if (dma == NULL) {
@@ -380,26 +380,4 @@ void evsyshandler_common(void) {
380380
}
381381
}
382382

383-
#ifdef SAM_D5X_E5X
384-
void EVSYS_0_Handler(void) {
385-
evsyshandler_common();
386-
}
387-
void EVSYS_1_Handler(void) {
388-
evsyshandler_common();
389-
}
390-
void EVSYS_2_Handler(void) {
391-
evsyshandler_common();
392-
}
393-
void EVSYS_3_Handler(void) {
394-
evsyshandler_common();
395-
}
396-
void EVSYS_4_Handler(void) {
397-
evsyshandler_common();
398-
}
399-
#else
400-
void EVSYS_Handler(void) {
401-
evsyshandler_common();
402-
}
403-
#endif
404-
405383
#endif

ports/atmel-samd/audio_dma.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,6 @@ void audio_dma_background(void);
9999

100100
uint8_t find_sync_event_channel_raise(void);
101101

102+
void audio_evsys_handler(void);
103+
102104
#endif // MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H

ports/atmel-samd/supervisor/port.c

Lines changed: 119 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -91,26 +91,19 @@
9191
#if CIRCUITPY_PEW
9292
#include "common-hal/_pew/PewPew.h"
9393
#endif
94-
volatile bool hold_interrupt = false;
94+
static volatile bool sleep_ok = true;
9595
#ifdef SAMD21
96-
static void rtc_set_continuous(bool continuous) {
97-
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
98-
;
99-
}
100-
RTC->MODE0.READREQ.reg = (continuous ? RTC_READREQ_RCONT : 0) | 0x0010;
101-
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
102-
;
103-
}
104-
}
96+
static uint8_t _tick_event_channel = 0;
10597

98+
// Sleeping requires a register write that can stall interrupt handling. Turning
99+
// off sleeps allows for more accurate interrupt timing. (Python still thinks
100+
// it is sleeping though.)
106101
void rtc_start_pulse(void) {
107-
rtc_set_continuous(true);
108-
hold_interrupt = true;
102+
sleep_ok = false;
109103
}
110104

111105
void rtc_end_pulse(void) {
112-
hold_interrupt = false;
113-
rtc_set_continuous(false);
106+
sleep_ok = true;
114107
}
115108
#endif
116109

@@ -161,16 +154,38 @@ static void save_usb_clock_calibration(void) {
161154
}
162155
#endif
163156

157+
static void rtc_continuous_mode(void) {
158+
#ifdef SAMD21
159+
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
160+
}
161+
RTC->MODE0.READREQ.reg = RTC_READREQ_RCONT | 0x0010;
162+
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
163+
}
164+
// Do the first request and wait for it.
165+
RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010;
166+
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
167+
}
168+
#endif
169+
}
170+
164171
static void rtc_init(void) {
165172
#ifdef SAMD21
166173
_gclk_enable_channel(RTC_GCLK_ID, GCLK_CLKCTRL_GEN_GCLK2_Val);
167174
RTC->MODE0.CTRL.bit.SWRST = true;
168175
while (RTC->MODE0.CTRL.bit.SWRST != 0) {
169176
}
170177

178+
// Turn on periodic events to use as tick. We control whether it interrupts
179+
// us with the EVSYS INTEN register.
180+
RTC->MODE0.EVCTRL.reg = RTC_MODE0_EVCTRL_PEREO2;
181+
171182
RTC->MODE0.CTRL.reg = RTC_MODE0_CTRL_ENABLE |
172183
RTC_MODE0_CTRL_MODE_COUNT32 |
173184
RTC_MODE0_CTRL_PRESCALER_DIV2;
185+
186+
// Turn on continuous sync of the count register. This will speed up all
187+
// tick reads.
188+
rtc_continuous_mode();
174189
#endif
175190
#ifdef SAM_D5X_E5X
176191
hri_mclk_set_APBAMASK_RTC_bit(MCLK);
@@ -363,6 +378,9 @@ void reset_port(void) {
363378
#endif
364379

365380
reset_event_system();
381+
#ifdef SAMD21
382+
_tick_event_channel = EVSYS_SYNCH_NUM;
383+
#endif
366384

367385
reset_all_pins();
368386

@@ -430,21 +448,14 @@ uint32_t port_get_saved_word(void) {
430448
// TODO: Move this to an RTC backup register so we can preserve it when only the BACKUP power domain
431449
// is enabled.
432450
static volatile uint64_t overflowed_ticks = 0;
433-
#ifdef SAMD21
434-
static volatile bool _ticks_enabled = false;
435-
#endif
436451

437452
static uint32_t _get_count(uint64_t *overflow_count) {
438453
#ifdef SAM_D5X_E5X
439454
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT)) != 0) {
440455
}
441456
#endif
442-
#ifdef SAMD21
443-
// Request a read so we don't stall the bus later. See section 14.3.1.5 Read Request
444-
RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | 0x0010;
445-
while (RTC->MODE0.STATUS.bit.SYNCBUSY != 0) {
446-
}
447-
#endif
457+
// SAMD21 does continuous sync so we don't need to wait here.
458+
448459
// Disable interrupts so we can grab the count and the overflow.
449460
common_hal_mcu_disable_interrupts();
450461
uint32_t count = RTC->MODE0.COUNT.reg;
@@ -458,29 +469,6 @@ static uint32_t _get_count(uint64_t *overflow_count) {
458469

459470
volatile bool _woken_up;
460471

461-
static void _port_interrupt_after_ticks(uint32_t ticks) {
462-
uint32_t current_ticks = _get_count(NULL);
463-
if (ticks > 1 << 28) {
464-
// We'll interrupt sooner with an overflow.
465-
return;
466-
}
467-
#ifdef SAMD21
468-
if (hold_interrupt) {
469-
return;
470-
}
471-
#endif
472-
uint32_t target = current_ticks + (ticks << 4);
473-
RTC->MODE0.COMP[0].reg = target;
474-
#ifdef SAM_D5X_E5X
475-
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COMP0)) != 0) {
476-
}
477-
#endif
478-
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
479-
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP0;
480-
current_ticks = _get_count(NULL);
481-
_woken_up = current_ticks >= target;
482-
}
483-
484472
void RTC_Handler(void) {
485473
uint32_t intflag = RTC->MODE0.INTFLAG.reg;
486474
if (intflag & RTC_MODE0_INTFLAG_OVF) {
@@ -497,19 +485,10 @@ void RTC_Handler(void) {
497485
}
498486
#endif
499487
if (intflag & RTC_MODE0_INTFLAG_CMP0) {
500-
// Clear the interrupt because we may have hit a sleep and _ticks_enabled
488+
// Clear the interrupt because we may have hit a sleep
501489
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
502490
_woken_up = true;
503-
#ifdef SAMD21
504-
if (_ticks_enabled) {
505-
// Do things common to all ports when the tick occurs.
506-
supervisor_tick();
507-
// Check _ticks_enabled again because a tick handler may have turned it off.
508-
if (_ticks_enabled) {
509-
_port_interrupt_after_ticks(1);
510-
}
511-
}
512-
#endif
491+
// SAMD21 ticks are handled by EVSYS
513492
#ifdef SAM_D5X_E5X
514493
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0;
515494
#endif
@@ -526,16 +505,63 @@ uint64_t port_get_raw_ticks(uint8_t *subticks) {
526505
return overflow_count + current_ticks / 16;
527506
}
528507

508+
void evsyshandler_common(void) {
509+
#ifdef SAMD21
510+
if (_tick_event_channel < EVSYS_SYNCH_NUM && event_interrupt_active(_tick_event_channel)) {
511+
supervisor_tick();
512+
}
513+
#endif
514+
#if CIRCUITPY_AUDIOIO || CIRCUITPY_AUDIOBUSIO
515+
audio_evsys_handler();
516+
#endif
517+
}
518+
519+
#ifdef SAM_D5X_E5X
520+
void EVSYS_0_Handler(void) {
521+
evsyshandler_common();
522+
}
523+
void EVSYS_1_Handler(void) {
524+
evsyshandler_common();
525+
}
526+
void EVSYS_2_Handler(void) {
527+
evsyshandler_common();
528+
}
529+
void EVSYS_3_Handler(void) {
530+
evsyshandler_common();
531+
}
532+
void EVSYS_4_Handler(void) {
533+
evsyshandler_common();
534+
}
535+
#else
536+
void EVSYS_Handler(void) {
537+
evsyshandler_common();
538+
}
539+
#endif
540+
529541
// Enable 1/1024 second tick.
530542
void port_enable_tick(void) {
531543
#ifdef SAM_D5X_E5X
532544
// PER2 will generate an interrupt every 32 ticks of the source 32.768 clock.
533545
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_PER2;
534546
#endif
535547
#ifdef SAMD21
536-
// TODO: Switch to using the PER *event* from the RTC to generate an interrupt via EVSYS.
537-
_ticks_enabled = true;
538-
_port_interrupt_after_ticks(1);
548+
// SAMD21 ticks won't survive port_reset(). This *should* be ok since it'll
549+
// be triggered by ticks and no Python will be running.
550+
if (_tick_event_channel >= EVSYS_SYNCH_NUM) {
551+
turn_on_event_system();
552+
_tick_event_channel = find_sync_event_channel();
553+
}
554+
// This turns on both the event detected interrupt (EVD) and overflow (OVR).
555+
init_event_channel_interrupt(_tick_event_channel, CORE_GCLK, EVSYS_ID_GEN_RTC_PER_2);
556+
// Disable overflow interrupt because we ignore it.
557+
if (_tick_event_channel >= 8) {
558+
uint8_t value = 1 << (_tick_event_channel - 8);
559+
EVSYS->INTENCLR.reg = EVSYS_INTENSET_OVRp8(value);
560+
} else {
561+
uint8_t value = 1 << _tick_event_channel;
562+
EVSYS->INTENCLR.reg = EVSYS_INTENSET_OVR(value);
563+
}
564+
NVIC_EnableIRQ(EVSYS_IRQn);
539565
#endif
540566
}
541567

@@ -545,21 +571,46 @@ void port_disable_tick(void) {
545571
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_PER2;
546572
#endif
547573
#ifdef SAMD21
548-
_ticks_enabled = false;
549-
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0;
574+
if (_tick_event_channel >= 8) {
575+
uint8_t value = 1 << (_tick_event_channel - 8);
576+
EVSYS->INTENCLR.reg = EVSYS_INTENSET_EVDp8(value);
577+
} else {
578+
uint8_t value = 1 << _tick_event_channel;
579+
EVSYS->INTENCLR.reg = EVSYS_INTENSET_EVD(value);
580+
}
550581
#endif
551582
}
552583

553-
// This is called by sleep, we ignore it when our ticks are enabled because
554-
// they'll wake us up earlier. If we don't, we'll mess up ticks by overwriting
555-
// the next RTC wake up time.
556584
void port_interrupt_after_ticks(uint32_t ticks) {
585+
uint32_t current_ticks = _get_count(NULL);
586+
if (ticks > 1 << 28) {
587+
// We'll interrupt sooner with an overflow.
588+
return;
589+
}
557590
#ifdef SAMD21
558-
if (_ticks_enabled) {
591+
if (!sleep_ok) {
559592
return;
560593
}
561594
#endif
562-
_port_interrupt_after_ticks(ticks);
595+
596+
uint32_t target = current_ticks + (ticks << 4);
597+
// Try and avoid a bus stall when writing COMP by checking for an obvious
598+
// existing sync.
599+
while (RTC->MODE0.STATUS.bit.SYNCBUSY == 1) {
600+
}
601+
// Writing the COMP register can take up to 180us to synchronize. During
602+
// this time, the bus will stall and no interrupts will be serviced.
603+
RTC->MODE0.COMP[0].reg = target;
604+
#ifdef SAM_D5X_E5X
605+
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COMP0)) != 0) {
606+
}
607+
#endif
608+
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
609+
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP0;
610+
// Set continuous mode again because setting COMP may disable it.
611+
rtc_continuous_mode();
612+
current_ticks = _get_count(NULL);
613+
_woken_up = current_ticks >= target;
563614
}
564615

565616
void port_idle_until_interrupt(void) {
@@ -571,7 +622,7 @@ void port_idle_until_interrupt(void) {
571622
}
572623
#endif
573624
common_hal_mcu_disable_interrupts();
574-
if (!tud_task_event_ready() && !hold_interrupt && !_woken_up) {
625+
if (!tud_task_event_ready() && sleep_ok && !_woken_up) {
575626
__DSB();
576627
__WFI();
577628
}

0 commit comments

Comments
 (0)