From 14a4980ea1b345ad298a6afbed75c7a89bc67eb7 Mon Sep 17 00:00:00 2001 From: Nico Tonnhofer Date: Sat, 21 Nov 2015 14:17:21 +0100 Subject: [PATCH] STM32F411: implement the stepper interrupt. --- timer-stm32.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 8 deletions(-) diff --git a/timer-stm32.c b/timer-stm32.c index 21f7bea..b26a86c 100644 --- a/timer-stm32.c +++ b/timer-stm32.c @@ -9,7 +9,8 @@ #include "cmsis-core_cm4.h" #include "clock.h" - +#include "pinio.h" +#include "dda_queue.h" /** Timer initialisation. @@ -18,6 +19,13 @@ For the system clock, we use SysTickTimer. This timer is made for exactly such purposes. + + Other than AVRs, Cortex-M doesn't allow reentry of interupts. To deal with + this we use another interrupt, PendSV, with slightly lower priority and + without an interrupt source. The possibly lengthy operation in dda_clock() + goes into there and at the end of the SysTick interrupt we simply set PendSV + pending. This way PendSV is executed right after SysTick or, if PendSV is + already running, ignored. */ void timer_init() { @@ -29,8 +37,17 @@ void timer_init() { Register name mapping from STM32F4xx + SYST_CSR SysTick->CTRL System Timer Control and status register. + SYST_RVR SysTick->LOAD System Timer Reload value register. + SYST_CVR SysTick->VAL System Timer Current value register. + SYST_CALIB SysTick->CALIB System Timer Calibration value register. */ - NVIC_SetPriority(SysTick_IRQn, 0); // Highest priority. + + NVIC_SetPriorityGrouping(1); + NVIC_SetPriority(SysTick_IRQn, + NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 1, 0)); + + //NVIC_SetPriority(SysTick_IRQn, 2); // Highest priority. // SysTick defined in cmsis-core_cm4.h. SysTick->LOAD = TICK_TIME - 1; // set reload register @@ -39,19 +56,85 @@ void timer_init() { SysTick->CTRL = SysTick_CTRL_ENABLE_Msk // Enable the ticker. | SysTick_CTRL_TICKINT_Msk // Enable interrupt. | SysTick_CTRL_CLKSOURCE_Msk; // Run at full CPU clock. + + /** + Initialise PendSV for dda_clock(). + */ + NVIC_SetPriority(PendSV_IRQn, + NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0)); // Almost highest priority. + + /** + Initialise the stepper timer. On ARM we have the comfort of hardware + 32-bit timers, so we don't have to artifically extend the timer to this + size. We use match register 1 of the first 32-bit timer, TIM5. + + We run the timer all the time, just turn the interrupt on and off, to + allow interrupts equally spaced, independent from processing time. See + also description of timer_set(). + */ + RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; // Turn on TIM5 power. + + TIM5->CR1 &= ~(0x03FF); // clear register + TIM5->CR1 |= TIM_CR1_CEN; // Enable counter. + + NVIC_SetPriority(TIM5_IRQn, + NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0)); // Also highest priority. + NVIC_EnableIRQ(TIM5_IRQn); // Enable interrupt generally. } /** System clock interrupt. Happens every TICK_TIME. Must have the same name as in - cmsis-startup_lpc11xx.s + cmsis-startup_stm32f411xe.s */ void SysTick_Handler(void) { clock_tick(); - #ifndef __ARMEL_NOTYET__ + + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; // Trigger PendSV_Handler(). + +} + +/** System clock interrupt, slow part. + + Here we do potentially lengthy calculations. Must have the same name as in + cmsis-startup_stm32f411xe.s +*/ +void PendSV_Handler(void) { dda_clock(); - #endif /* __ARMEL_NOTYET__ */ +} + +/** Step interrupt. + + Happens for every stepper motor step. Must have the same name as in + cmsis-startup_stm32f411xe.s + + TIM2 and TIM5 are 32bit timers. We use TIM5, because depending pins have + an alternative timer for pwm. +*/ +void TIM5_IRQHandler(void) { + + #ifdef DEBUG_LED_PIN + WRITE(DEBUG_LED_PIN, 1); + #endif + + /** + Turn off interrupt generation, timer counter continues. As this interrupt + is the only use of this timer, there's no need for a read-modify-write. + */ + TIM5->DIER = 0; + + /** + We have to "reset" this interrupt, else it'll be triggered over and over + again. + */ + TIM5->SR = 0; + + queue_step(); + + #ifdef DEBUG_LED_PIN + WRITE(DEBUG_LED_PIN, 0); + #endif } /** Specify how long until the step timer should fire. @@ -64,9 +147,7 @@ void SysTick_Handler(void) { usually wants to handle this case. Calls from elsewhere should set it to 0. In this case a timer - interrupt is always scheduled. At the risk that this scheduling - doesn't delay the requested time, but up to a full timer counter - overflow ( = 65536 / F_CPU = 3 to 4 milliseconds). + interrupt is always scheduled. \return A flag whether the requested time was too short to allow scheduling an interrupt. This is meaningful for ACCELERATION_TEMPORAL, where @@ -86,11 +167,56 @@ void SysTick_Handler(void) { So, if you use it from inside the step interrupt, make sure to do so as late as possible. If you use it from outside the step interrupt, do a sei() after it to make the interrupt actually fire. + + On ARM we have the comfort of hardware 32-bit timers, so we don't have to + artifically extend the timer to this size. We use match register 1 of the + first 32-bit timer, TIM5. */ uint8_t timer_set(int32_t delay, uint8_t check_short) { + #ifdef ACCELERATION_TEMPORAL + if (check_short) { + /** + 100 = safe number of cpu cycles after current_time to allow a new + interrupt happening. This is mostly the time needed to complete the + current interrupt. + + TIM5->CNT = timer counter = current time. + TIM5->CCR1 = last capture compare = time of the last step. + */ + if ((TIM5->CNT - TIM5->CCR1) + 100 > delay) + return 1; + } + #endif /* ACCELERATION_TEMPORAL */ + + /** + Still here? Then we can schedule the next step. Off of the previous step. + If there is no previous step, CNT and CCR1 should have been reset to zero + by calling timer_reset() shortly before we arrive here. + */ + TIM5->CCR1 += delay; + + /** + Turn on the stepper interrupt. As this interrupt is the only use of this + timer, there's no need for a read-modify-write. + */ + TIM5->DIER = TIM_DIER_CC1IE; // Interrupt on MR0 match. + return 0; } +/** Timer reset. + + Reset the timer, so step interrupts scheduled at an arbitrary point in time + don't lead to a full round through the timer counter. + + On ARM we actually do something, such a full round through the timer is + 2^32 / F_CPU = 22 to 44 seconds. +*/ +void timer_reset() { + TIM5->CNT = 0; + TIM5->CCR1 = 0; +} + /** Stop timers. This means to be an emergency stop.