1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-18 12:52:44 +01:00
RIOT/cpu/stm32/periph/timer.c
Joshua DeWeese eeb359e80c cpu/stm32/periph/timer: fix execution flow
The implmentation of `timer_set_absolute()` has The following problems.
First, it attempts to restore the auto reload register (ARR) to it's
default if the ARR was previosly set by `timer_set_periodic()` by
comparing it to the channel's capture compare (CC) register _after_ it
has already set the CC register. Secondly, it clears spurious IRQs
_after_ the CC register has been set. If the value being set is equal to
the timer's current count (or the two become equal before the supurios
IRQ clearing happens), this could cause a legitimate IRQ to be cleared.

The implmentation of `timer_set()` has the same error in handling the
ARR as described above.

This patch reorders the operations of both functions to do:

1. handle ARR
2. clear spurious IRQs
3. set channel's CC
4. enable IRQ

Additionally, the calulation of `value` in `timer_set()` is moved
earlier in the function's exec path as a pedantic measure.
2023-03-07 11:52:16 -05:00

330 lines
6.9 KiB
C

/*
* Copyright (C) 2014-2016 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_stm32
* @ingroup drivers_periph_timer
* @{
*
* @file
* @brief Low-level timer driver implementation
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Thomas Eichinger <thomas.eichinger@fu-berlin.de>
*
* @}
*/
#include "cpu.h"
#include "periph/timer.h"
/**
* @brief Interrupt context for each configured timer
*/
static timer_isr_ctx_t isr_ctx[TIMER_NUMOF];
/**
* @brief Get the timer device
*/
static inline TIM_TypeDef *dev(tim_t tim)
{
return timer_config[tim].dev;
}
#ifdef MODULE_PERIPH_TIMER_PERIODIC
/**
* @brief Helper macro to get channel bit in timer/channel bitmap
*/
#define CHAN_BIT(tim, chan) (1 << chan) << (TIMER_CHANNEL_NUMOF * (tim & 1))
/**
* @brief Bitmap for compare channel disable after match
*/
static uint8_t _oneshot[(TIMER_NUMOF+1)/2];
/**
* @brief Clear interrupt enable after the interrupt has fired
*/
static inline void set_oneshot(tim_t tim, int chan)
{
_oneshot[tim >> 1] |= CHAN_BIT(tim, chan);
}
/**
* @brief Enable interrupt with every wrap-around of the timer
*/
static inline void clear_oneshot(tim_t tim, int chan)
{
_oneshot[tim >> 1] &= ~CHAN_BIT(tim, chan);
}
static inline bool is_oneshot(tim_t tim, int chan)
{
return _oneshot[tim >> 1] & CHAN_BIT(tim, chan);
}
#else /* !MODULE_PERIPH_TIMER_PERIODIC */
static inline void set_oneshot(tim_t tim, int chan)
{
(void)tim;
(void)chan;
}
static inline bool is_oneshot(tim_t tim, int chan)
{
(void)tim;
(void)chan;
return true;
}
#endif /* MODULE_PERIPH_TIMER_PERIODIC */
int timer_init(tim_t tim, uint32_t freq, timer_cb_t cb, void *arg)
{
/* check if device is valid */
if (tim >= TIMER_NUMOF) {
return -1;
}
/* remember the interrupt context */
isr_ctx[tim].cb = cb;
isr_ctx[tim].arg = arg;
/* enable the peripheral clock */
periph_clk_en(timer_config[tim].bus, timer_config[tim].rcc_mask);
/* configure the timer as upcounter in continuous mode */
dev(tim)->CR1 = 0;
dev(tim)->CR2 = 0;
dev(tim)->ARR = timer_config[tim].max;
/* set prescaler */
dev(tim)->PSC = ((periph_timer_clk(timer_config[tim].bus) / freq) - 1);
/* generate an update event to apply our configuration */
dev(tim)->EGR = TIM_EGR_UG;
/* enable the timer's interrupt */
NVIC_EnableIRQ(timer_config[tim].irqn);
/* reset the counter and start the timer */
timer_start(tim);
return 0;
}
int timer_set_absolute(tim_t tim, int channel, unsigned int value)
{
if (channel >= (int)TIMER_CHANNEL_NUMOF) {
return -1;
}
unsigned irqstate = irq_disable();
set_oneshot(tim, channel);
#ifdef MODULE_PERIPH_TIMER_PERIODIC
if (dev(tim)->ARR == TIM_CHAN(tim, channel)) {
dev(tim)->ARR = timer_config[tim].max;
}
#endif
/* clear spurious IRQs */
dev(tim)->SR &= ~(TIM_SR_CC1IF << channel);
TIM_CHAN(tim, channel) = (value & timer_config[tim].max);
/* enable IRQ */
dev(tim)->DIER |= (TIM_DIER_CC1IE << channel);
irq_restore(irqstate);
return 0;
}
int timer_set(tim_t tim, int channel, unsigned int timeout)
{
unsigned value = (dev(tim)->CNT + timeout) & timer_config[tim].max;
if (channel >= (int)TIMER_CHANNEL_NUMOF) {
return -1;
}
unsigned irqstate = irq_disable();
set_oneshot(tim, channel);
#ifdef MODULE_PERIPH_TIMER_PERIODIC
if (dev(tim)->ARR == TIM_CHAN(tim, channel)) {
dev(tim)->ARR = timer_config[tim].max;
}
#endif
/* clear spurious IRQs */
dev(tim)->SR &= ~(TIM_SR_CC1IF << channel);
TIM_CHAN(tim, channel) = value;
/* enable IRQ */
dev(tim)->DIER |= (TIM_DIER_CC1IE << channel);
/* calculate time till timeout */
value = (value - dev(tim)->CNT) & timer_config[tim].max;
if (value > timeout) {
/* time till timeout is larger than requested --> timer already expired
* ==> let's make sure we have an IRQ pending :) */
dev(tim)->EGR |= (TIM_EGR_CC1G << channel);
}
irq_restore(irqstate);
return 0;
}
#ifdef MODULE_PERIPH_TIMER_PERIODIC
int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags)
{
if (channel >= (int)TIMER_CHANNEL_NUMOF) {
return -1;
}
unsigned irqstate = irq_disable();
clear_oneshot(tim, channel);
if (flags & TIM_FLAG_SET_STOPPED) {
timer_stop(tim);
}
if (flags & TIM_FLAG_RESET_ON_SET) {
/* setting COUNT gives us an interrupt on all channels */
dev(tim)->CNT = 0;
/* wait for the interrupt & clear it */
while(dev(tim)->SR == 0) {}
dev(tim)->SR = 0;
}
TIM_CHAN(tim, channel) = value;
/* clear spurious IRQs */
dev(tim)->SR &= ~(TIM_SR_CC1IF << channel);
/* enable IRQ */
dev(tim)->DIER |= (TIM_DIER_CC1IE << channel);
if (flags & TIM_FLAG_RESET_ON_MATCH) {
dev(tim)->ARR = value;
}
irq_restore(irqstate);
return 0;
}
#endif /* MODULE_PERIPH_TIMER_PERIODIC */
int timer_clear(tim_t tim, int channel)
{
if (channel >= (int)TIMER_CHANNEL_NUMOF) {
return -1;
}
unsigned irqstate = irq_disable();
dev(tim)->DIER &= ~(TIM_DIER_CC1IE << channel);
irq_restore(irqstate);
#ifdef MODULE_PERIPH_TIMER_PERIODIC
if (dev(tim)->ARR == TIM_CHAN(tim, channel)) {
dev(tim)->ARR = timer_config[tim].max;
}
#endif
return 0;
}
unsigned int timer_read(tim_t tim)
{
return (unsigned int)dev(tim)->CNT;
}
void timer_start(tim_t tim)
{
unsigned irqstate = irq_disable();
dev(tim)->CR1 |= TIM_CR1_CEN;
irq_restore(irqstate);
}
void timer_stop(tim_t tim)
{
unsigned irqstate = irq_disable();
dev(tim)->CR1 &= ~(TIM_CR1_CEN);
irq_restore(irqstate);
}
static inline void irq_handler(tim_t tim)
{
uint32_t top = dev(tim)->ARR;
uint32_t status = dev(tim)->SR & dev(tim)->DIER;
dev(tim)->SR = 0;
for (unsigned int i = 0; status; i++) {
uint32_t msk = TIM_SR_CC1IF << i;
/* check if interrupt flag is set */
if ((status & msk) == 0) {
continue;
}
status &= ~msk;
/* interrupt flag gets set for all channels > ARR */
if (TIM_CHAN(tim, i) > top) {
continue;
}
/* disable Interrupt */
if (is_oneshot(tim, i)) {
dev(tim)->DIER &= ~msk;
}
isr_ctx[tim].cb(isr_ctx[tim].arg, i);
}
cortexm_isr_end();
}
#ifdef TIMER_0_ISR
void TIMER_0_ISR(void)
{
irq_handler(0);
}
#endif
#ifdef TIMER_1_ISR
void TIMER_1_ISR(void)
{
irq_handler(1);
}
#endif
#ifdef TIMER_2_ISR
void TIMER_2_ISR(void)
{
irq_handler(2);
}
#endif
#ifdef TIMER_3_ISR
void TIMER_3_ISR(void)
{
irq_handler(3);
}
#endif
#ifdef TIMER_4_ISR
void TIMER_4_ISR(void)
{
irq_handler(4);
}
#endif