Skip to content
Snippets Groups Projects
Verified Commit dbeec980 authored by rahix's avatar rahix
Browse files

fix(pycardium): Fix delay implementation


Rewrite the delay implementation to be more accurate and properly handle
incoming MicroPython interrupts (See issue #177).

For this, the delay is split into two functions:

- systick_delay_precise(): Delay with microsecond accuracy for delays
      less than 1 ms.
- systick_delay_sleep(): Sleep for a potentially very long time, using
      WFI to put the CPU to sleep.  Whenever the CPU wakes up, this
      function will poll the MicroPython scheduler so it can execute
      pending interrupts.

Ontop, this implementation allows for a future, fast, tick-based
monotonic time implementation (e.g. mp_hal_ticks_ms).

Signed-off-by: default avatarRahix <rahix@rahix.de>
parent d06941b8
No related branches found
No related tags found
No related merge requests found
......@@ -31,6 +31,11 @@ void pycardium_hal_init(void)
* a character becomes available.
*/
epic_interrupt_enable(EPIC_INT_UART_RX);
/*
* Configure SysTick timer for 1ms period.
*/
SysTick_Config(SystemCoreClock / 1000);
}
/******************************************************************************
......@@ -120,18 +125,148 @@ void mp_hal_set_interrupt_char(char c)
}
}
/******************************************************************************
* SysTick timer at 1000 Hz
*/
static volatile uint64_t systick_count = 0;
void SysTick_Handler(void)
{
systick_count += 1;
}
/*
* Get an absolute "timestamp" in microseconds.
*/
static uint64_t systick_get_us()
{
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;
__set_PRIMASK(irqsaved);
return us;
}
static void systick_delay_precise(uint32_t us)
{
/*
* Calculate how long the busy-spin needs to be. As the very first
* 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.
*/
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;
/*
* Calculate the final count for both paths. Marked as volatile so the
* compiler can't move this into the branches and screw up the timing.
*/
volatile uint32_t count_final_direct = count_to_overflow - delay_count;
volatile uint32_t count_final_underflow =
count_reload - (delay_count - count_to_overflow);
if (delay_count > count_to_overflow) {
/*
* Wait for the SysTick to underflow and then count down
* to the final value.
*/
while (SysTick->VAL <= count_to_overflow ||
SysTick->VAL > count_final_underflow) {
__NOP();
}
} else {
/*
* Wait for the SysTick to count down to the final value.
*/
while (SysTick->VAL > count_final_direct) {
__NOP();
}
}
}
static void systick_delay_sleep(uint32_t us)
{
uint64_t final_time = systick_get_us() + (uint64_t)us - 2;
while (1) {
uint64_t now = systick_get_us();
if (now >= 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.
*
* Use a critical section encompassing both the check and the
* WFI to prevent a race-condition where the interrupt happens
* just in between the check and WFI.
*/
uint32_t irqsaved = __get_PRIMASK();
__set_PRIMASK(0);
if ((now + 1000) < final_time) {
__WFI();
}
__set_PRIMASK(irqsaved);
/*
* Handle pending MicroPython 'interrupts'. This call could
* potentially not return here when a handler raises an
* exception. Those will propagate outwards and thus make the
* delay return early.
*
* One example of this happeing is the KeyboardInterrupt
* (CTRL+C) which will abort the running code and exit to REPL.
*/
mp_handle_pending();
}
}
static void systick_delay(uint32_t us)
{
if (us == 0)
return;
/*
* For very short delays, use the systick_delay_precise() function which
* delays with a microsecond accuracy. For anything >1ms, 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) {
systick_delay_precise(us);
} else {
systick_delay_sleep(us);
}
}
/******************************************************************************
* Time & Delay
*/
void mp_hal_delay_ms(mp_uint_t ms)
{
mxc_delay(ms * 1000);
systick_delay(ms * 1000);
}
void mp_hal_delay_us(mp_uint_t us)
{
mxc_delay(us);
systick_delay(us);
}
mp_uint_t mp_hal_ticks_ms(void)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment