diff --git a/pycardium/mphalport.c b/pycardium/mphalport.c index 49fbf9232d5c93527cdfe21f7948b7e7f9fa8419..ab3e0857c7d132f67c4c9b5f08530f06563d9a3d 100644 --- a/pycardium/mphalport.c +++ b/pycardium/mphalport.c @@ -20,6 +20,30 @@ #include <stdio.h> #include <string.h> +// Smallest interval which can be reached exactly +#define SYSTICK_INTERVAL_US 15625ULL +#define SYSTICK_FREQ_HZ (1000000 / SYSTICK_INTERVAL_US) + +/* + * Copied from core_cm4.h and modified to select the + * 32768 Hz RTC crystal as the clock source */ +static uint32_t systick_config(uint32_t ticks) +{ + if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) + return (1); /* Reload value impossible */ + + SysTick->LOAD = ticks - 1; /* set reload register */ + NVIC_SetPriority( + SysTick_IRQn, + (1 << __NVIC_PRIO_BITS) - + 1); /* set Priority for Systick Interrupt */ + SysTick->VAL = 0; /* Load the SysTick Counter Value */ + SysTick->CTRL = + SysTick_CTRL_TICKINT_Msk | + SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ + return (0); /* Function successful */ +} + /* Initialize everything for MicroPython */ void pycardium_hal_init(void) { @@ -33,9 +57,9 @@ void pycardium_hal_init(void) epic_interrupt_enable(EPIC_INT_UART_RX); /* - * Configure SysTick timer for 1ms period. + * Configure SysTick timer for SYSTICK_INTERVAL_US period. */ - SysTick_Config(SystemCoreClock / 1000); + systick_config(32768 / SYSTICK_FREQ_HZ); } /****************************************************************************** @@ -141,14 +165,21 @@ void SysTick_Handler(void) */ static uint64_t systick_get_us() { + uint32_t val, count; uint32_t irqsaved = __get_PRIMASK(); - __set_PRIMASK(0); - uint64_t counts_per_us = SystemCoreClock / 1000000; - uint64_t us = systick_count * 1000 + - (SysTick->LOAD - SysTick->VAL) / counts_per_us; + /* The asynchronous/slow clocking of the systick means that + * its value can jump to 0 before the interrupt is triggered. + * Simply wait until it is not 0 and then read the count. */ + do { + __set_PRIMASK(0); + val = SysTick->VAL; + count = systick_count; + __set_PRIMASK(irqsaved); + } while (val == 0); - __set_PRIMASK(irqsaved); + uint64_t us = count * SYSTICK_INTERVAL_US + + (SysTick->LOAD - val) * 1000000ULL / 32768; return us; } @@ -160,14 +191,12 @@ static void systick_delay_precise(uint32_t us) * instruction, read the current timer value to ensure as little skew as * possible. * - * Subtract 0.3us (constant_offset) to account for the duration of the - * calculations. + * Accuracy is about 30 us (due to the 32 kHz systick) */ uint32_t count_to_overflow = SysTick->VAL; uint32_t count_reload = SysTick->LOAD; - uint32_t clocks_per_us = SystemCoreClock / 1000000; - uint32_t constant_offset = clocks_per_us * 3 / 10; - uint32_t delay_count = us * clocks_per_us - constant_offset; + + uint32_t delay_count = us * 32768 / 1000000; /* * Calculate the final count for both paths. Marked as volatile so the @@ -203,14 +232,14 @@ static void systick_delay_sleep(uint32_t us) while (1) { uint64_t now = systick_get_us(); - if (now >= final_time) { + if ((now + SYSTICK_INTERVAL_US) > final_time) { break; } /* - * Sleep with WFI if more than 1ms of delay is remaining. The - * SysTick interrupt is guaranteed to happen within any timespan - * of 1ms. + * Sleep with WFI if more than SYSTICK_INTERVAL_US of delay + * is remaining. The SysTick interrupt is guaranteed to + * happen within any timespan of SYSTICK_INTERVAL_US. * * Use a critical section encompassing both the check and the * WFI to prevent a race-condition where the interrupt happens @@ -218,7 +247,7 @@ static void systick_delay_sleep(uint32_t us) */ uint32_t irqsaved = __get_PRIMASK(); __set_PRIMASK(0); - if ((now + 1000) < final_time) { + if ((now + SYSTICK_INTERVAL_US) < final_time) { __WFI(); } __set_PRIMASK(irqsaved); @@ -234,6 +263,11 @@ static void systick_delay_sleep(uint32_t us) */ mp_handle_pending(true); } + + uint64_t now = systick_get_us(); + if (now < final_time) { + systick_delay_precise(final_time - now); + } } static void systick_delay(uint32_t us) @@ -243,12 +277,12 @@ static void systick_delay(uint32_t us) /* * For very short delays, use the systick_delay_precise() function which - * delays with a microsecond accuracy. For anything >1ms, use + * delays with a microsecond accuracy. For anything >SYSTICK_INTERVAL_US, use * systick_delay_sleep() which puts the CPU to sleep when nothing is * happening and also checks for MicroPython interrupts every now and * then. */ - if (us < 1000) { + if (us < SYSTICK_INTERVAL_US) { systick_delay_precise(us); } else { systick_delay_sleep(us);