STM32F411: implement the stepper interrupt.

This commit is contained in:
Nico Tonnhofer 2015-11-21 14:17:21 +01:00
parent 42b26d9c20
commit 14a4980ea1
1 changed files with 134 additions and 8 deletions

View File

@ -9,7 +9,8 @@
#include "cmsis-core_cm4.h" #include "cmsis-core_cm4.h"
#include "clock.h" #include "clock.h"
#include "pinio.h"
#include "dda_queue.h"
/** Timer initialisation. /** Timer initialisation.
@ -18,6 +19,13 @@
For the system clock, we use SysTickTimer. This timer is made for exactly For the system clock, we use SysTickTimer. This timer is made for exactly
such purposes. 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() { void timer_init() {
@ -29,8 +37,17 @@ void timer_init() {
Register name mapping from STM32F4xx 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 defined in cmsis-core_cm4.h.
SysTick->LOAD = TICK_TIME - 1; // set reload register 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 = SysTick_CTRL_ENABLE_Msk // Enable the ticker.
| SysTick_CTRL_TICKINT_Msk // Enable interrupt. | SysTick_CTRL_TICKINT_Msk // Enable interrupt.
| SysTick_CTRL_CLKSOURCE_Msk; // Run at full CPU clock. | 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. /** System clock interrupt.
Happens every TICK_TIME. Must have the same name as in Happens every TICK_TIME. Must have the same name as in
cmsis-startup_lpc11xx.s cmsis-startup_stm32f411xe.s
*/ */
void SysTick_Handler(void) { void SysTick_Handler(void) {
clock_tick(); 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(); 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. /** Specify how long until the step timer should fire.
@ -64,9 +147,7 @@ void SysTick_Handler(void) {
usually wants to handle this case. usually wants to handle this case.
Calls from elsewhere should set it to 0. In this case a timer Calls from elsewhere should set it to 0. In this case a timer
interrupt is always scheduled. At the risk that this scheduling interrupt is always scheduled.
doesn't delay the requested time, but up to a full timer counter
overflow ( = 65536 / F_CPU = 3 to 4 milliseconds).
\return A flag whether the requested time was too short to allow scheduling \return A flag whether the requested time was too short to allow scheduling
an interrupt. This is meaningful for ACCELERATION_TEMPORAL, where 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 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, as late as possible. If you use it from outside the step interrupt,
do a sei() after it to make the interrupt actually fire. 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) { 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; 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. /** Stop timers.
This means to be an emergency stop. This means to be an emergency stop.