mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 06:32:45 +01:00
dea25437c7
The STM32 periph_timer driver reads the timer's status flags, then clears them all. It is possible that a timer interrupt could occur between reading the flag and clearing it. This would lead to a lost interrupt. The timer's status flags can be cleared by software, but can only be set by the hardware. This patch takes advantage of this by only clearing the flags it knows are set. The rest of the flags are set, which doesn't actually change their state.
335 lines
7.2 KiB
C
335 lines
7.2 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;
|
|
|
|
/* clear interrupts which we are about to service */
|
|
/* Note, the flags in the SR register can be cleared by software, but
|
|
* setting them has no effect on the register. Only the hardware can set
|
|
* them. */
|
|
dev(tim)->SR = ~status;
|
|
|
|
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
|