aboutsummaryrefslogtreecommitdiffstats
path: root/src/stm32f1/timer.c
diff options
context:
space:
mode:
authorGrigori Goronzy <greg@chown.ath.cx>2018-03-31 15:34:59 +0200
committerKevinOConnor <kevin@koconnor.net>2018-04-09 18:08:29 -0400
commit75d57372115eb70ac22d16240c5c887df8ae1da8 (patch)
treed31b31d32f80916c8406fc663994260a1acaa786 /src/stm32f1/timer.c
parente097b085209e4737abc71dbb0fc06408a6e1f6d1 (diff)
downloadkutter-75d57372115eb70ac22d16240c5c887df8ae1da8.tar.gz
kutter-75d57372115eb70ac22d16240c5c887df8ae1da8.tar.xz
kutter-75d57372115eb70ac22d16240c5c887df8ae1da8.zip
Add STM32F103 port
Add a fully functional STM32F1 port, currently mostly targeting STM32F103 microcontrollers. This requires an 8 MHz XTAL. The maximum possible step rate is around 282K steps per second. This uses stm32flash to burn the firmware. The bootloader needs to be started by setting BOOT0 to 1 and resetting the MCU. There is no automatic bootloader, unlike on Arduino. Signed-off-by: Grigori Goronzy <greg@kinoho.net>
Diffstat (limited to 'src/stm32f1/timer.c')
-rw-r--r--src/stm32f1/timer.c188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/stm32f1/timer.c b/src/stm32f1/timer.c
new file mode 100644
index 00000000..76b683b3
--- /dev/null
+++ b/src/stm32f1/timer.c
@@ -0,0 +1,188 @@
+// STM32F1 timer interrupt scheduling code.
+//
+// Copyright (C) 2018 Grigori Goronzy <greg@kinoho.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include "autoconf.h"
+#include "board/misc.h" // timer_from_us
+#include "stm32f1xx.h"
+#include "stm32f1xx.h"
+#include "stm32f1xx_ll_bus.h"
+#include "stm32f1xx_ll_tim.h"
+#include "command.h" // shutdown
+#include "board/irq.h" // irq_save
+#include "sched.h" // sched_timer_dispatch
+
+
+/****************************************************************
+ * Low level timer code
+ ****************************************************************/
+
+DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
+
+union u32_u {
+ struct { uint8_t b0, b1, b2, b3; };
+ struct { uint16_t lo, hi; };
+ uint32_t val;
+};
+
+static inline uint16_t
+timer_get(void)
+{
+ return LL_TIM_GetCounter(TIM2);
+}
+
+static inline void
+timer_set(uint16_t next)
+{
+ LL_TIM_OC_SetCompareCH1(TIM2, next);
+}
+
+static inline void
+timer_repeat_set(uint16_t next)
+{
+ LL_TIM_OC_SetCompareCH2(TIM2, next);
+ LL_TIM_ClearFlag_CC2(TIM2);
+}
+
+// Activate timer dispatch as soon as possible
+void
+timer_kick(void)
+{
+ timer_set(timer_get() + 50);
+ LL_TIM_ClearFlag_CC1(TIM2);
+}
+
+static struct timer wrap_timer;
+
+void
+timer_reset(void)
+{
+ sched_add_timer(&wrap_timer);
+}
+DECL_SHUTDOWN(timer_reset);
+
+void
+timer_init(void)
+{
+ irqstatus_t flag = irq_save();
+ LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
+ LL_TIM_SetPrescaler(TIM2, __LL_TIM_CALC_PSC(SystemCoreClock, CONFIG_CLOCK_FREQ));
+ LL_TIM_SetCounter(TIM2, 0);
+ LL_TIM_SetAutoReload(TIM2, 0xFFFF);
+ LL_TIM_EnableIT_CC1(TIM2);
+ LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH1);
+ LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2);
+ NVIC_EnableIRQ(TIM2_IRQn);
+ NVIC_SetPriority(TIM2_IRQn, 0);
+ timer_kick();
+ timer_repeat_set(timer_get() + 50);
+ timer_reset();
+ LL_TIM_EnableCounter(TIM2);
+ irq_restore(flag);
+}
+DECL_INIT(timer_init);
+
+
+/****************************************************************
+ * 32bit timer wrappers
+ ****************************************************************/
+
+static uint16_t timer_high;
+
+// Return the current time (in absolute clock ticks).
+uint32_t
+timer_read_time(void)
+{
+ irqstatus_t flag = irq_save();
+ union u32_u calc = { .val = timer_get() };
+ calc.hi = timer_high;
+ if (unlikely(LL_TIM_IsActiveFlag_UPDATE(TIM2))) {
+ irq_restore(flag);
+ if (calc.b1 < 0xff)
+ calc.hi++;
+ return calc.val;
+ }
+ irq_restore(flag);
+ return calc.val;
+}
+
+// Timer that runs every cycle - allows 16bit comparison optimizations
+static uint_fast8_t
+timer_event(struct timer *t)
+{
+ union u32_u *nextwake = (void*)&wrap_timer.waketime;
+ if (LL_TIM_IsActiveFlag_UPDATE(TIM2)) {
+ // Hardware timer has overflowed - update overflow counter
+ LL_TIM_ClearFlag_UPDATE(TIM2);
+ timer_high++;
+ *nextwake = (union u32_u){ .hi = timer_high, .lo = 0x8000 };
+ } else {
+ *nextwake = (union u32_u){ .hi = timer_high + 1, .lo = 0x0000 };
+ }
+ return SF_RESCHEDULE;
+}
+static struct timer wrap_timer = {
+ .func = timer_event,
+ .waketime = 0x8000,
+};
+
+#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
+#define TIMER_REPEAT_TICKS timer_from_us(100)
+
+#define TIMER_MIN_TRY_TICKS timer_from_us(1)
+#define TIMER_DEFER_REPEAT_TICKS timer_from_us(5)
+
+// Hardware timer IRQ handler - dispatch software timers
+void __visible __aligned(16)
+TIM2_IRQHandler(void)
+{
+ irq_disable();
+ LL_TIM_ClearFlag_CC1(TIM2);
+ uint16_t next;
+ for (;;) {
+ // Run the next software timer
+ next = sched_timer_dispatch();
+
+ for (;;) {
+ int16_t diff = timer_get() - next;
+ if (likely(diff >= 0)) {
+ // Another timer is pending - briefly allow irqs and then run it
+ irq_enable();
+ if (unlikely(LL_TIM_IsActiveFlag_CC2(TIM2)))
+ goto check_defer;
+ irq_disable();
+ break;
+ }
+
+ if (likely(diff <= -TIMER_MIN_TRY_TICKS))
+ // Schedule next timer normally
+ goto done;
+
+ irq_enable();
+ if (unlikely(LL_TIM_IsActiveFlag_CC2(TIM2)))
+ goto check_defer;
+ irq_disable();
+ continue;
+
+ check_defer:
+ // Check if there are too many repeat timers
+ irq_disable();
+ uint16_t now = timer_get();
+ if ((int16_t)(next - now) < (int16_t)(-timer_from_us(1000)))
+ try_shutdown("Rescheduled timer in the past");
+ if (sched_tasks_busy()) {
+ timer_repeat_set(now + TIMER_REPEAT_TICKS);
+ next = now + TIMER_DEFER_REPEAT_TICKS;
+ goto done;
+ }
+ timer_repeat_set(now + TIMER_IDLE_REPEAT_TICKS);
+ timer_set(now);
+ }
+ }
+
+done:
+ timer_set(next);
+ irq_enable();
+}