1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/msp430: improve periph_timer

- add support for multiple timers
- add support for selecting clock source in the board's `periph_conf.h`
- add support for the prescaler
- implement `periph_timer_query_freqs`
- add a second timer to all MSP430 boards
    - the first timer is fast ticking, high-power
    - the second is slow ticking, low-power
This commit is contained in:
Marian Buschsieweke 2023-12-06 20:33:41 +01:00
parent aa045d540f
commit 7044699388
No known key found for this signature in database
GPG Key ID: 77AA882EC78084E6
12 changed files with 370 additions and 103 deletions

View File

@ -48,11 +48,24 @@ static const msp430_clock_params_t clock_params = {
* @name Timer configuration
* @{
*/
#define TIMER_NUMOF (1U)
#define TIMER_BASE (&TIMER_A)
#define TIMER_CHAN (3)
#define TIMER_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER_ISR_CCX (TIMERA1_VECTOR)
static const timer_conf_t timer_conf[] = {
{
.timer = &TIMER_A,
.irq_flags = &TIMER_A_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK,
},
{
.timer = &TIMER_B,
.irq_flags = &TIMER_B_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK,
}
};
#define TIMER_NUMOF ARRAY_SIZE(timer_conf)
#define TIMER0_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER0_ISR_CCX (TIMERA1_VECTOR)
#define TIMER1_ISR_CC0 (TIMERB0_VECTOR)
#define TIMER1_ISR_CCX (TIMERB1_VECTOR)
/** @} */
/**

View File

@ -47,11 +47,24 @@ static const msp430_clock_params_t clock_params = {
* @name Timer configuration
* @{
*/
#define TIMER_NUMOF (1U)
#define TIMER_BASE (&TIMER_A)
#define TIMER_CHAN (3)
#define TIMER_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER_ISR_CCX (TIMERA1_VECTOR)
static const timer_conf_t timer_conf[] = {
{
.timer = &TIMER_A,
.irq_flags = &TIMER_A_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK,
},
{
.timer = &TIMER_B,
.irq_flags = &TIMER_B_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK,
}
};
#define TIMER_NUMOF ARRAY_SIZE(timer_conf)
#define TIMER0_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER0_ISR_CCX (TIMERA1_VECTOR)
#define TIMER1_ISR_CC0 (TIMERB0_VECTOR)
#define TIMER1_ISR_CCX (TIMERB1_VECTOR)
/** @} */
/**

View File

@ -47,11 +47,24 @@ static const msp430_clock_params_t clock_params = {
* @name Timer configuration
* @{
*/
#define TIMER_NUMOF (1U)
#define TIMER_BASE (&TIMER_A)
#define TIMER_CHAN (3)
#define TIMER_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER_ISR_CCX (TIMERA1_VECTOR)
static const timer_conf_t timer_conf[] = {
{
.timer = &TIMER_A,
.irq_flags = &TIMER_A_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK,
},
{
.timer = &TIMER_B,
.irq_flags = &TIMER_B_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK,
}
};
#define TIMER_NUMOF ARRAY_SIZE(timer_conf)
#define TIMER0_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER0_ISR_CCX (TIMERA1_VECTOR)
#define TIMER1_ISR_CC0 (TIMERB0_VECTOR)
#define TIMER1_ISR_CCX (TIMERB1_VECTOR)
/** @} */
/**

View File

@ -46,11 +46,24 @@ static const msp430_clock_params_t clock_params = {
* @name Timer configuration
* @{
*/
#define TIMER_NUMOF (1U)
#define TIMER_BASE (&TIMER_A)
#define TIMER_CHAN (3)
#define TIMER_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER_ISR_CCX (TIMERA1_VECTOR)
static const timer_conf_t timer_conf[] = {
{
.timer = &TIMER_A,
.irq_flags = &TIMER_A_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK,
},
{
.timer = &TIMER_B,
.irq_flags = &TIMER_B_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK,
}
};
#define TIMER_NUMOF ARRAY_SIZE(timer_conf)
#define TIMER0_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER0_ISR_CCX (TIMERA1_VECTOR)
#define TIMER1_ISR_CC0 (TIMERB0_VECTOR)
#define TIMER1_ISR_CCX (TIMERB1_VECTOR)
/** @} */
/**

View File

@ -47,11 +47,24 @@ static const msp430_clock_params_t clock_params = {
* @name Timer configuration
* @{
*/
#define TIMER_NUMOF (1U)
#define TIMER_BASE (&TIMER_A)
#define TIMER_CHAN (3)
#define TIMER_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER_ISR_CCX (TIMERA1_VECTOR)
static const timer_conf_t timer_conf[] = {
{
.timer = &TIMER_A,
.irq_flags = &TIMER_A_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK,
},
{
.timer = &TIMER_B,
.irq_flags = &TIMER_B_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK,
}
};
#define TIMER_NUMOF ARRAY_SIZE(timer_conf)
#define TIMER0_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER0_ISR_CCX (TIMERA1_VECTOR)
#define TIMER1_ISR_CC0 (TIMERB0_VECTOR)
#define TIMER1_ISR_CCX (TIMERB1_VECTOR)
/** @} */
/**

View File

@ -47,11 +47,24 @@ static const msp430_clock_params_t clock_params = {
* @name Timer configuration
* @{
*/
#define TIMER_NUMOF (1U)
#define TIMER_BASE (&TIMER_A)
#define TIMER_CHAN (3)
#define TIMER_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER_ISR_CCX (TIMERA1_VECTOR)
static const timer_conf_t timer_conf[] = {
{
.timer = &TIMER_A,
.irq_flags = &TIMER_A_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK,
},
{
.timer = &TIMER_B,
.irq_flags = &TIMER_B_IRQFLAGS,
.clock_source = TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK,
}
};
#define TIMER_NUMOF ARRAY_SIZE(timer_conf)
#define TIMER0_ISR_CC0 (TIMERA0_VECTOR)
#define TIMER0_ISR_CCX (TIMERA1_VECTOR)
#define TIMER1_ISR_CC0 (TIMERB0_VECTOR)
#define TIMER1_ISR_CCX (TIMERB1_VECTOR)
/** @} */
/**

View File

@ -18,6 +18,7 @@ config CPU_ARCH_MSP430
select HAS_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE
select HAS_PERIPH_FLASHPAGE_PAGEWISE
select HAS_PERIPH_PM
select HAS_PERIPH_TIMER_QUERY_FREQS
select MODULE_MALLOC_THREAD_SAFE if TEST_KCONFIG
config HAS_CPU_MSP430

View File

@ -18,3 +18,4 @@ FEATURES_PROVIDED += periph_flashpage
FEATURES_PROVIDED += periph_flashpage_in_address_space
FEATURES_PROVIDED += periph_flashpage_pagewise
FEATURES_PROVIDED += periph_pm
FEATURES_PROVIDED += periph_timer_query_freqs

View File

@ -57,13 +57,16 @@ extern "C" {
* @name Timer Input Divider Values
*
* @details The vendor header macros are again non-obvious in their naming, so
* provide better alies names.
* provide better alias names.
* @{
*/
#define TXID_DIV_1 ID_0 /**< Input Divider: Divide by 1 */
#define TXID_DIV_2 ID_1 /**< Input Divider: Divide by 2 */
#define TXID_DIV_4 ID_2 /**< Input Divider: Divide by 4 */
#define TXID_DIV_8 ID_3 /**< Input Divider: Divide by 4 */
#define TXID_DIV_8 ID_3 /**< Input Divider: Divide by 8 */
#define TXID_DIV_Msk ID_3 /**< Mask to get the TXID field */
#define TXID_DIV_Pos 6U /**< Position of the TXID field */
#define TXID_DIV_MAX 3 /**< Maximum configuration value in the TXID field */
/** @} */
/**
@ -108,18 +111,11 @@ typedef struct {
REG8 SEL; /**< alternative function select */
} msp430_port_p3_p6_t;
/**
* @brief Timer interrupt status registers
*/
typedef struct {
REG16 TBIV; /**< TIMER_A interrupt status */
REG16 reserved[7];/**< reserved */
REG16 TAIV; /**< TIMER_B interrupt status */
} msp430_timer_ivec_t;
/**
* @brief Timer module registers
* @brief Timer peripheral registers
*
* @note The TIMER_A timer only has 3 CC channels instead of the 8 channels
* the TIMER_B has, the memory layout is the same nonetheless.
*/
typedef struct {
REG16 CTL; /**< timer control */
@ -160,16 +156,29 @@ extern msp430_port_p3_p6_t PORT_5;
*/
extern msp430_port_p3_p6_t PORT_6;
/**
* @brief Register map of the timer interrupt control registers
*/
extern msp430_timer_ivec_t TIMER_IVEC;
/**
* @brief Register map of the timer A control registers
*/
extern msp430_timer_t TIMER_A;
/**
* @brief IRQ flags for TIMER_A
*
* Called TAIV in the data sheet / vendor files. This shallow alias
* makes the name more readable and does impedance matching for the type
* (`volatile uint16_t` vs `volatile short`).
*/
extern REG16 TIMER_A_IRQFLAGS;
/**
* @brief IRQ flags for TIMER_B
*
* Called TBIV in the data sheet / vendor files. This shallow alias
* makes the name more readable and does impedance matching for the type
* (`volatile uint16_t` vs `volatile short`).
*/
extern REG16 TIMER_B_IRQFLAGS;
/**
* @brief Register map of the timer B control registers
*/

View File

@ -22,6 +22,7 @@
#include <stdbool.h>
#include "bitarithm.h"
#include "compiler_hints.h"
#include "cpu.h"
#include "msp430_regs.h"
@ -52,6 +53,17 @@ typedef uint16_t gpio_t;
*/
#define SPI_HWCS(x) (SPI_CS_UNDEF)
/**
* @brief The MSP430 timer peripheral can have up to 8 channels
*
* @note The actual number of channels should be queried per timer, as
* timers have either 7 or 3 capture/compare channels; typically both
* variants are present in the same MCU. This is the highest number
* of channels supported, e.g. useful for "worst case" static memory
* allocation.
*/
#define TIMER_CHANNEL_NUMOF 7
/**
* @name Override flank selection values
* @{
@ -289,6 +301,31 @@ typedef struct {
bool has_xt2;
} msp430_clock_params_t;
/**
* @brief Enumeration of possible clock sources for a timer
*/
typedef enum {
TIMER_CLOCK_SOURCE_TXCLK = TXSSEL_TXCLK, /**< External TxCLK as clock source */
TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK = TXSSEL_ACLK, /**< Auxiliary clock as clock source */
TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK = TXSSEL_SMCLK, /**< Sub-system master clock as clock source */
TIMER_CLOCK_SOURCE_INCLK = TXSSEL_INCLK, /**< External INCLK as clock source */
} msp430_timer_clock_source_t;
/**
* @brief Timer configuration on an MSP430 timer
*/
typedef struct {
msp430_timer_t *timer; /**< Hardware timer to use */
/**
* @brief "Timer interrupt vector" register
*
* Use `&TIMER_A_IRQFLAGS` for `TIMER_A` or
* `&TIMER_B_IRQFLAGS` for `TIMER_B`.
*/
REG16 *irq_flags;
msp430_timer_clock_source_t clock_source; /**< Clock source to use */
} timer_conf_t;
/**
* @brief Initialize the basic clock system to provide the main clock,
* the subsystem clock, and the auxiliary clock.

View File

@ -24,16 +24,15 @@ SECTIONS
/* provide address for register maps by taking the address of the first
* register (as provided by the vendor files) */
PROVIDE(PORT_1 = P1IN);
PROVIDE(PORT_2 = P2IN);
PROVIDE(PORT_3 = P3IN);
PROVIDE(PORT_3 = P3IN);
PROVIDE(PORT_4 = P4IN);
PROVIDE(PORT_5 = P5IN);
PROVIDE(PORT_6 = P6IN);
PROVIDE(PORT_1 = P1IN);
PROVIDE(PORT_2 = P2IN);
PROVIDE(PORT_3 = P3IN);
PROVIDE(PORT_3 = P3IN);
PROVIDE(PORT_4 = P4IN);
PROVIDE(PORT_5 = P5IN);
PROVIDE(PORT_6 = P6IN);
/* no typo: TBIV indeed comes before TAIV in memory, see msp430_timer_ivec_t */
PROVIDE(TIMER_IVEC = TBIV);
PROVIDE(TIMER_A = TACTL);
PROVIDE(TIMER_B = TBCTL);
PROVIDE(TIMER_A = TACTL);
PROVIDE(TIMER_B = TBCTL);
PROVIDE(TIMER_A_IRQFLAGS = TAIV);
PROVIDE(TIMER_B_IRQFLAGS = TBIV);

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Freie Universität Berlin
* 2023 Otto-von-Guericke-Universität Magdeburg
*
* 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
@ -21,107 +22,248 @@
* through the board's `periph_conf.h`
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Marian Buschsieweke <marian.buschsieweke@posteo.net>
*
* @}
*/
#include "compiler_hints.h"
#include "cpu.h"
#include "periph_cpu.h"
#include "periph_conf.h"
#include "periph/timer.h"
#include "periph_conf.h"
#include "periph_cpu.h"
/**
* @brief Save reference to the timer callback
* @brief Interrupt context for each configured timer
*/
static timer_cb_t isr_cb;
static timer_isr_ctx_t isr_ctx[TIMER_NUMOF];
/**
* @brief Save argument for the ISR callback
*/
static void *isr_arg;
/* Hack to count the number of ISR vectors provided by the board */
enum {
#ifdef TIMER0_ISR_CC0
TIMER_ISR_COUNT_HELPER_0,
#endif
#ifdef TIMER1_ISR_CC0
TIMER_ISR_COUNT_HELPER_1,
#endif
TIMER_ISR_NUMOF
};
static_assert((TXID_DIV_MAX << TXID_DIV_Pos) == TXID_DIV_Msk, "TXID_DIV_MAX or TXID_DIV_Pos is incorrect.");
static_assert(TIMER_ISR_NUMOF == TIMER_NUMOF,
"For each provided timer a corresponding IRQ number needs to be provided by the board.");
static uint32_t abs_diff(uint32_t a, uint32_t b)
{
if (a >= b) {
return a - b;
}
return b - a;
}
static uint16_t prescale(msp430_timer_clock_source_t clock_source, uint32_t freq)
{
uint32_t clock_freq;
assume((clock_source == TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK) ||
(clock_source == TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK));
switch (clock_source) {
default:
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
clock_freq = msp430_auxiliary_clock_freq();
break;
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
clock_freq = msp430_submain_clock_freq();
break;
}
unsigned prescaler = 0;
uint32_t best_diff = UINT32_MAX;
for (unsigned i = 0; i <= TXID_DIV_MAX; i++) {
uint32_t prescaled_freq = clock_freq >> i;
uint32_t off = abs_diff(freq, prescaled_freq);
if (off < best_diff) {
best_diff = off;
prescaler = i;
}
}
return clock_source | (prescaler << TXID_DIV_Pos);
}
int timer_init(tim_t dev, uint32_t freq, timer_cb_t cb, void *arg)
{
/* using fixed TIMER_BASE for now */
if (dev != 0) {
return -1;
}
/* TODO: configure time-base depending on freq value */
if (freq != 1000000ul) {
return -1;
}
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
/* reset the timer A configuration */
TIMER_BASE->CTL = TACLR;
msptimer->CTL = TACLR;
/* save callback */
isr_cb = cb;
isr_arg = arg;
/* configure timer to use the SMCLK with prescaler of 8 */
TIMER_BASE->CTL = (TXSSEL_SMCLK | TXID_DIV_8);
isr_ctx[dev].cb = cb;
isr_ctx[dev].arg = arg;
/* compute prescaler */
uint16_t ctl = prescale(timer_conf[dev].clock_source, freq);
msptimer->CTL = ctl;
/* configure CC channels */
for (int i = 0; i < TIMER_CHAN; i++) {
TIMER_BASE->CCTL[i] = 0;
for (unsigned i = 0; i < timer_query_channel_numof(dev); i++) {
msptimer->CCTL[i] = 0;
}
/* start the timer in continuous mode */
TIMER_BASE->CTL |= TXMC_CONT;
msptimer->CTL = ctl | TXMC_CONT;
return 0;
}
int timer_set_absolute(tim_t dev, int channel, unsigned int value)
{
if (dev != 0 || channel >= TIMER_CHAN) {
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
if ((unsigned)channel >= timer_query_channel_numof(dev)) {
return -1;
}
TIMER_BASE->CCR[channel] = value;
TIMER_BASE->CCTL[channel] &= ~(CCIFG);
TIMER_BASE->CCTL[channel] |= CCIE;
msptimer->CCR[channel] = value;
msptimer->CCTL[channel] &= ~(CCIFG);
msptimer->CCTL[channel] |= CCIE;
return 0;
}
int timer_clear(tim_t dev, int channel)
{
if (dev != 0 || channel >= TIMER_CHAN) {
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
if ((unsigned)channel >= timer_query_channel_numof(dev)) {
return -1;
}
TIMER_BASE->CCTL[channel] &= ~(CCIE);
msptimer->CCTL[channel] &= ~(CCIE);
return 0;
}
unsigned int timer_read(tim_t dev)
{
(void)dev;
return (unsigned int)TIMER_BASE->R;
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
return msptimer->R;
}
void timer_start(tim_t dev)
{
(void)dev;
TIMER_BASE->CTL |= TXMC_CONT;
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
msptimer->CTL |= TXMC_CONT;
}
void timer_stop(tim_t dev)
{
(void)dev;
TIMER_BASE->CTL &= ~(TXMC_MASK);
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
msptimer->CTL &= ~(TXMC_MASK);
}
ISR(TIMER_ISR_CC0, isr_timer_a_cc0)
__attribute__((pure))
uword_t timer_query_freqs_numof(tim_t dev)
{
assume((unsigned)dev < TIMER_NUMOF);
/* Smallest div value is 0, so number is max + 1 */
return TXID_DIV_MAX + 1;
}
__attribute__((pure))
uword_t timer_query_channel_numof(tim_t dev)
{
assume((unsigned)dev < TIMER_NUMOF);
if (timer_conf[dev].timer == &TIMER_A) {
return 3;
}
return 7;
}
uint32_t timer_query_freqs(tim_t dev, uword_t index)
{
assume((unsigned)dev < TIMER_NUMOF);
const msp430_timer_clock_source_t clock_source = timer_conf[dev].clock_source;
assume((clock_source == TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK) ||
(clock_source == TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK));
uint32_t clock_freq;
switch (clock_source) {
default:
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
clock_freq = msp430_auxiliary_clock_freq();
break;
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
clock_freq = msp430_submain_clock_freq();
break;
}
return clock_freq >> index;
}
static void timer_isr(tim_t dev, unsigned chan)
{
assume((unsigned)dev < TIMER_NUMOF);
msp430_timer_t *msptimer = timer_conf[dev].timer;
/* disable IRQ */
msptimer->CCTL[chan] &= ~(CCIE);
isr_ctx[dev].cb(isr_ctx[dev].arg, chan);
}
static void timer_isr_cc0(tim_t dev)
{
assume((unsigned)dev < TIMER_NUMOF);
timer_isr(dev, 0);
}
static void timer_isr_ccx(tim_t dev)
{
assume((unsigned)dev < TIMER_NUMOF);
unsigned chan = *timer_conf[dev].irq_flags >> 1U;
if (chan >= timer_query_channel_numof(dev)) {
/* timer overflown */
}
else {
/* CC matched */
timer_isr(dev, chan);
}
}
ISR(TIMER0_ISR_CC0, isr_timer0_cc0)
{
__enter_isr();
TIMER_BASE->CCTL[0] &= ~(CCIE);
isr_cb(isr_arg, 0);
timer_isr_cc0(0);
__exit_isr();
}
ISR(TIMER_ISR_CCX, isr_timer_a_ccx)
ISR(TIMER0_ISR_CCX, isr_timer0_ccx)
{
__enter_isr();
int chan = (int)(TIMER_IVEC.TAIV >> 1);
TIMER_BASE->CCTL[chan] &= ~(CCIE);
isr_cb(isr_arg, chan);
timer_isr_ccx(0);
__exit_isr();
}
#ifdef TIMER1_ISR_CC0
ISR(TIMER1_ISR_CC0, isr_timer1_cc0)
{
__enter_isr();
timer_isr_cc0(1);
__exit_isr();
}
ISR(TIMER1_ISR_CCX, isr_timer1_ccx)
{
__enter_isr();
timer_isr_ccx(1);
__exit_isr();
}
#endif