Merge pull request #13902 from benpicco/periph_timer_periodic

periph/timer: add timer_set_periodic()
This commit is contained in:
benpicco 2020-05-28 18:03:32 +02:00 committed by GitHub
commit 49aef1b678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 429 additions and 17 deletions

View File

@ -1050,6 +1050,10 @@ ifneq (,$(filter periph_gpio_irq,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio FEATURES_REQUIRED += periph_gpio
endif endif
ifneq (,$(filter periph_timer_periodic,$(USEMODULE)))
FEATURES_REQUIRED += periph_timer
endif
ifneq (,$(filter devfs_hwrng,$(USEMODULE))) ifneq (,$(filter devfs_hwrng,$(USEMODULE)))
FEATURES_REQUIRED += periph_hwrng FEATURES_REQUIRED += periph_hwrng
endif endif

View File

@ -5,6 +5,7 @@ FEATURES_PROVIDED += periph_cpuid
FEATURES_PROVIDED += periph_eeprom FEATURES_PROVIDED += periph_eeprom
FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_gpio periph_gpio_irq
FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_pm
FEATURES_PROVIDED += periph_timer_periodic
FEATURES_PROVIDED += periph_wdt FEATURES_PROVIDED += periph_wdt
FEATURES_PROVIDED += puf_sram FEATURES_PROVIDED += puf_sram

View File

@ -74,6 +74,23 @@ static ctx_t ctx[] = {
#endif #endif
}; };
static unsigned _oneshot;
static inline void set_oneshot(tim_t tim, int chan)
{
_oneshot |= (1 << chan) << (TIMER_CHANNELS * tim);
}
static inline void clear_oneshot(tim_t tim, int chan)
{
_oneshot &= ~((1 << chan) << (TIMER_CHANNELS * tim));
}
static inline bool is_oneshot(tim_t tim, int chan)
{
return _oneshot & ((1 << chan) << (TIMER_CHANNELS * tim));
}
/** /**
* @brief Setup the given timer * @brief Setup the given timer
*/ */
@ -109,7 +126,7 @@ int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg)
} }
} }
if (pre == PRESCALE_NUMOF) { if (pre == PRESCALE_NUMOF) {
DEBUG("timer.c: prescaling failed!\n"); DEBUG("timer.c: prescaling from %lu Hz failed!\n", CLOCK_CORECLOCK);
return -1; return -1;
} }
@ -138,9 +155,12 @@ int timer_set_absolute(tim_t tim, int channel, unsigned int value)
} }
unsigned state = irq_disable(); unsigned state = irq_disable();
ctx[tim].dev->OCR[channel] = (uint16_t)value; ctx[tim].dev->OCR[channel] = (uint16_t)value;
*ctx[tim].flag &= ~(1 << (channel + OCF1A)); *ctx[tim].flag &= ~(1 << (channel + OCF1A));
*ctx[tim].mask |= (1 << (channel + OCIE1A)); *ctx[tim].mask |= (1 << (channel + OCIE1A));
set_oneshot(tim, channel);
irq_restore(state); irq_restore(state);
return 0; return 0;
@ -154,9 +174,11 @@ int timer_set(tim_t tim, int channel, unsigned int timeout)
unsigned state = irq_disable(); unsigned state = irq_disable();
unsigned absolute = ctx[tim].dev->CNT + timeout; unsigned absolute = ctx[tim].dev->CNT + timeout;
ctx[tim].dev->OCR[channel] = absolute; ctx[tim].dev->OCR[channel] = absolute;
uint8_t mask = 1 << (channel + OCIE1A); *ctx[tim].mask |= (1 << (channel + OCIE1A));
*ctx[tim].mask |= mask; set_oneshot(tim, channel);
if ((absolute - ctx[tim].dev->CNT) > timeout) { if ((absolute - ctx[tim].dev->CNT) > timeout) {
/* Timer already expired. Trigger the interrupt now and loop until it /* Timer already expired. Trigger the interrupt now and loop until it
* is triggered. * is triggered.
@ -164,13 +186,53 @@ int timer_set(tim_t tim, int channel, unsigned int timeout)
while (!(*ctx[tim].flag & (1 << (OCF1A + channel)))) { while (!(*ctx[tim].flag & (1 << (OCF1A + channel)))) {
ctx[tim].dev->OCR[channel] = ctx[tim].dev->CNT; ctx[tim].dev->OCR[channel] = ctx[tim].dev->CNT;
} }
} }
irq_restore(state); irq_restore(state);
return 0; return 0;
} }
int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags)
{
int res = 0;
if (channel >= TIMER_CHANNELS) {
return -1;
}
if (flags & TIM_FLAG_RESET_ON_SET) {
ctx[tim].dev->CNT = 0;
}
unsigned state = irq_disable();
ctx[tim].dev->OCR[channel] = (uint16_t)value;
*ctx[tim].flag &= ~(1 << (channel + OCF1A));
*ctx[tim].mask |= (1 << (channel + OCIE1A));
clear_oneshot(tim, channel);
/* only OCR0 can be use to set TOP */
if (channel == 0) {
if (flags & TIM_FLAG_RESET_ON_MATCH) {
/* enable CTC mode */
ctx[tim].dev->CRB |= (1 << 3);
} else {
/* disable CTC mode */
ctx[tim].dev->CRB &= (1 << 3);
}
} else {
assert((flags & TIM_FLAG_RESET_ON_MATCH) == 0);
res = -1;
}
irq_restore(state);
return res;
}
int timer_clear(tim_t tim, int channel) int timer_clear(tim_t tim, int channel)
{ {
if (channel >= TIMER_CHANNELS) { if (channel >= TIMER_CHANNELS) {
@ -216,7 +278,9 @@ static inline void _isr(tim_t tim, int chan)
atmega_enter_isr(); atmega_enter_isr();
*ctx[tim].mask &= ~(1 << (chan + OCIE1A)); if (is_oneshot(tim, chan)) {
*ctx[tim].mask &= ~(1 << (chan + OCIE1A));
}
ctx[tim].cb(ctx[tim].arg, chan); ctx[tim].cb(ctx[tim].arg, chan);
#if defined(DEBUG_TIMER_PORT) #if defined(DEBUG_TIMER_PORT)

View File

@ -2,5 +2,6 @@
FEATURES_PROVIDED += backup_ram FEATURES_PROVIDED += backup_ram
FEATURES_PROVIDED += periph_dac FEATURES_PROVIDED += periph_dac
FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_gpio periph_gpio_irq
FEATURES_PROVIDED += periph_timer_periodic
-include $(RIOTCPU)/arm7_common/Makefile.features -include $(RIOTCPU)/arm7_common/Makefile.features

View File

@ -126,7 +126,7 @@ typedef struct {
/** /**
* @brief Number of available timer channels * @brief Number of available timer channels
*/ */
#define TIMER_CHAN_NUMOF (4U) #define TIMER_CHANNELS (4U)
/** /**
* @brief Declare needed generic SPI functions * @brief Declare needed generic SPI functions

View File

@ -38,6 +38,23 @@
*/ */
static timer_isr_ctx_t isr_ctx[TIMER_NUMOF]; static timer_isr_ctx_t isr_ctx[TIMER_NUMOF];
static uint32_t _oneshot;
static inline void set_oneshot(tim_t tim, int chan)
{
_oneshot |= (1 << chan) << (TIMER_CHANNELS * tim);
}
static inline void clear_oneshot(tim_t tim, int chan)
{
_oneshot &= ~((1 << chan) << (TIMER_CHANNELS * tim));
}
static inline bool is_oneshot(tim_t tim, int chan)
{
return _oneshot & ((1 << chan) << (TIMER_CHANNELS * tim));
}
/** /**
* @brief Forward declarations for interrupt functions * @brief Forward declarations for interrupt functions
* @{ * @{
@ -139,19 +156,53 @@ int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg)
int timer_set_absolute(tim_t tim, int channel, unsigned int value) int timer_set_absolute(tim_t tim, int channel, unsigned int value)
{ {
if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHAN_NUMOF)) { if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHANNELS)) {
return -1; return -1;
} }
lpc23xx_timer_t *dev = get_dev(tim); lpc23xx_timer_t *dev = get_dev(tim);
set_oneshot(tim, channel);
dev->MR[channel] = value; dev->MR[channel] = value;
/* Match Interrupt */
dev->MCR |= (1 << (channel * 3)); dev->MCR |= (1 << (channel * 3));
return 0; return 0;
} }
int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags)
{
if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHANNELS)) {
return -1;
}
lpc23xx_timer_t *dev = get_dev(tim);
if (flags & TIM_FLAG_RESET_ON_SET) {
/* reset the timer */
dev->TCR = 2;
/* start the timer */
/* cppcheck-suppress redundantAssignment
* (reason: TCR is volatile control register.
Bit 2 will put the timer into Reset
Bit 1 will control if the timer is running) */
dev->TCR = 1;
}
clear_oneshot(tim, channel);
uint8_t cfg = (flags & TIM_FLAG_RESET_ON_MATCH)
? 0x3 /* Match Interrupt & Reset on Match */
: 0x1; /* Match Interrupt */
dev->MR[channel] = value;
dev->MCR |= (cfg << (channel * 3));
return 0;
}
int timer_clear(tim_t tim, int channel) int timer_clear(tim_t tim, int channel)
{ {
if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHAN_NUMOF)) { if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHANNELS)) {
return -1; return -1;
} }
get_dev(tim)->MCR &= ~(1 << (channel * 3)); get_dev(tim)->MCR &= ~(1 << (channel * 3));
@ -173,15 +224,28 @@ void timer_stop(tim_t tim)
get_dev(tim)->TCR = 0; get_dev(tim)->TCR = 0;
} }
static inline void chan_handler(lpc23xx_timer_t *dev, unsigned tim_num, unsigned chan_num)
{
const uint32_t mask = (1 << chan_num);
if ((dev->IR & mask) == 0) {
return;
}
dev->IR |= mask;
if (is_oneshot(tim_num, chan_num)) {
dev->MCR &= ~(1 << (chan_num * 3));
}
isr_ctx[tim_num].cb(isr_ctx[tim_num].arg, chan_num);
}
static inline void isr_handler(lpc23xx_timer_t *dev, int tim_num) static inline void isr_handler(lpc23xx_timer_t *dev, int tim_num)
{ {
for (unsigned i = 0; i < TIMER_CHAN_NUMOF; i++) { for (unsigned i = 0; i < TIMER_CHANNELS; ++i) {
if (dev->IR & (1 << i)) { chan_handler(dev, tim_num, i);
dev->IR |= (1 << i);
dev->MCR &= ~(1 << (i * 3));
isr_ctx[tim_num].cb(isr_ctx[tim_num].arg, i);
}
} }
/* we must not forget to acknowledge the handling of the interrupt */ /* we must not forget to acknowledge the handling of the interrupt */
VICVectAddr = 0; VICVectAddr = 0;
} }

View File

@ -5,6 +5,9 @@ endif
# All SAM0 based CPUs provide PM # All SAM0 based CPUs provide PM
USEMODULE += pm_layered USEMODULE += pm_layered
# the timer implements timer_set_periodic()
FEATURES_PROVIDED += periph_timer_periodic
# include sam0 common periph drivers # include sam0 common periph drivers
USEMODULE += sam0_common_periph USEMODULE += sam0_common_periph

View File

@ -343,6 +343,11 @@ typedef struct {
uint16_t flags; /**< flags for CTRA, e.g. TC_CTRLA_MODE_COUNT32 */ uint16_t flags; /**< flags for CTRA, e.g. TC_CTRLA_MODE_COUNT32 */
} tc32_conf_t; } tc32_conf_t;
/**
* @brief Number of available timer channels
*/
#define TIMER_CHANNELS (2)
/** /**
* @brief Set up alternate function (PMUX setting) for a PORT pin * @brief Set up alternate function (PMUX setting) for a PORT pin
* *

View File

@ -37,6 +37,23 @@
*/ */
static timer_isr_ctx_t config[TIMER_NUMOF]; static timer_isr_ctx_t config[TIMER_NUMOF];
static uint32_t _oneshot;
static inline void set_oneshot(tim_t tim, int chan)
{
_oneshot |= (1 << chan) << (TIMER_CHANNELS * tim);
}
static inline void clear_oneshot(tim_t tim, int chan)
{
_oneshot &= ~((1 << chan) << (TIMER_CHANNELS * tim));
}
static inline bool is_oneshot(tim_t tim, int chan)
{
return _oneshot & ((1 << chan) << (TIMER_CHANNELS * tim));
}
static inline TcCount32 *dev(tim_t tim) static inline TcCount32 *dev(tim_t tim)
{ {
return &timer_config[tim].dev->COUNT32; return &timer_config[tim].dev->COUNT32;
@ -89,6 +106,32 @@ static uint8_t _get_prescaler(unsigned long freq_out, unsigned long freq_in)
return scale; return scale;
} }
/* TOP value is CC0 */
static inline void _set_mfrq(tim_t tim)
{
timer_stop(tim);
wait_synchronization(tim);
#ifdef TC_WAVE_WAVEGEN_MFRQ
dev(tim)->WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;
#else
dev(tim)->CTRLA.bit.WAVEGEN = TC_CTRLA_WAVEGEN_MFRQ_Val;
#endif
timer_start(tim);
}
/* TOP value is MAX timer value */
static inline void _set_nfrq(tim_t tim)
{
timer_stop(tim);
wait_synchronization(tim);
#ifdef TC_WAVE_WAVEGEN_NFRQ
dev(tim)->WAVE.reg = TC_WAVE_WAVEGEN_NFRQ;
#else
dev(tim)->CTRLA.bit.WAVEGEN = TC_CTRLA_WAVEGEN_NFRQ_Val;
#endif
timer_start(tim);
}
/** /**
* @brief Setup the given timer * @brief Setup the given timer
*/ */
@ -187,7 +230,52 @@ int timer_set_absolute(tim_t tim, int channel, unsigned int value)
break; break;
default: default:
return -1; return -1;
} }
set_oneshot(tim, channel);
return 0;
}
int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags)
{
DEBUG("Setting timer %i channel %i to %i (repeating)\n", tim, channel, value);
/* set timeout value */
switch (channel) {
case 0:
dev(tim)->INTFLAG.reg = TC_INTFLAG_MC0;
if (flags & TIM_FLAG_RESET_ON_MATCH) {
_set_mfrq(tim);
} else {
_set_nfrq(tim);
}
_set_cc(tim, 0, value);
dev(tim)->INTENSET.reg = TC_INTENSET_MC0;
break;
case 1:
/* only CC0 can be used to set TOP */
if (flags & TIM_FLAG_RESET_ON_MATCH) {
assert(0);
return -1;
}
dev(tim)->INTFLAG.reg = TC_INTFLAG_MC1;
_set_cc(tim, 1, value);
dev(tim)->INTENSET.reg = TC_INTENSET_MC1;
break;
default:
return -1;
}
if (flags & TIM_FLAG_RESET_ON_SET) {
dev(tim)->COUNT.reg = 0;
}
clear_oneshot(tim, channel);
return 0; return 0;
} }
@ -255,13 +343,22 @@ static inline void timer_isr(tim_t tim)
tc->INTFLAG.reg = status; tc->INTFLAG.reg = status;
if ((status & TC_INTFLAG_MC0) && tc->INTENSET.bit.MC0) { if ((status & TC_INTFLAG_MC0) && tc->INTENSET.bit.MC0) {
tc->INTENCLR.reg = TC_INTENCLR_MC0;
if (is_oneshot(tim, 0)) {
tc->INTENCLR.reg = TC_INTENCLR_MC0;
}
if (config[tim].cb) { if (config[tim].cb) {
config[tim].cb(config[tim].arg, 0); config[tim].cb(config[tim].arg, 0);
} }
} }
if ((status & TC_INTFLAG_MC1) && tc->INTENSET.bit.MC1) { if ((status & TC_INTFLAG_MC1) && tc->INTENSET.bit.MC1) {
tc->INTENCLR.reg = TC_INTENCLR_MC1;
if (is_oneshot(tim, 1)) {
tc->INTENCLR.reg = TC_INTENCLR_MC1;
}
if (config[tim].cb) { if (config[tim].cb) {
config[tim].cb(config[tim].arg, 1); config[tim].cb(config[tim].arg, 1);
} }

View File

@ -34,6 +34,7 @@
#define PERIPH_TIMER_H #define PERIPH_TIMER_H
#include <limits.h> #include <limits.h>
#include <stdint.h>
#include "periph_cpu.h" #include "periph_cpu.h"
/** @todo remove dev_enums.h include once all platforms are ported to the updated periph interface */ /** @todo remove dev_enums.h include once all platforms are ported to the updated periph interface */
@ -69,6 +70,26 @@ extern "C" {
typedef unsigned int tim_t; typedef unsigned int tim_t;
#endif #endif
/**
* @brief Reset the timer when the set() function is called
*
* When set, calling the timer_set_periodic() function resets the timer count value.
*/
#ifndef TIM_FLAG_RESET_ON_SET
#define TIM_FLAG_RESET_ON_SET (0x01)
#endif
/**
* @brief Reset the timer on match
*
* When set, a match on this channel will reset the timer count value.
* When set on multiple channels, only the channel with the lowest match value
* will be reached.
*/
#ifndef TIM_FLAG_RESET_ON_MATCH
#define TIM_FLAG_RESET_ON_MATCH (0x02)
#endif
/** /**
* @brief Signature of event callback functions triggered from interrupts * @brief Signature of event callback functions triggered from interrupts
* *
@ -138,6 +159,21 @@ int timer_set(tim_t dev, int channel, unsigned int timeout);
*/ */
int timer_set_absolute(tim_t dev, int channel, unsigned int value); int timer_set_absolute(tim_t dev, int channel, unsigned int value);
/**
* @brief Set an absolute timeout value for the given channel of the given timer
* The timeout will be called periodically for each iteration
*
* @param[in] dev the timer device to set
* @param[in] channel the channel to set
* @param[in] value the absolute compare value when the callback will be
* triggered
* @param[in] flags options
*
* @return 0 on success
* @return -1 on error
*/
int timer_set_periodic(tim_t dev, int channel, unsigned int value, uint8_t flags);
/** /**
* @brief Clear the given channel of the given timer device * @brief Clear the given channel of the given timer device
* *

View File

@ -0,0 +1,5 @@
include ../Makefile.tests_common
FEATURES_REQUIRED = periph_timer_periodic
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2020 Beuth Hochschule für Technik 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 tests
* @{
*
* @file
* @brief Periodic timer test application
*
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
*
* @}
*/
#include <stdio.h>
#include <stdint.h>
#include "board.h"
#include "test_utils/expect.h"
#include "mutex.h"
#include "periph/timer.h"
#include "xtimer.h"
/* We use the timer used for xtimer with the frequency used by xtimer here
* to make sure we have a known valid timer configuration.
*
* DO NOT USE any low-level timer functions demonstrated here when xtimer
* is used with that timer!
* Configure a separate timer, XTIMER_DEV is usually 'owned' by xtimer, but
* as xtimer is not used in this test, we can use it and the fact that every board
* provides a configuration for it.
*/
#define TIMER_CYCL XTIMER_DEV
#define CYCLE_MS 100UL
#define CYCLES_MAX 10
static unsigned count[TIMER_CHANNELS];
static void cb(void *arg, int chan)
{
unsigned c = count[chan]++;
printf("[%d] tick\n", chan);
if (c > CYCLES_MAX) {
timer_stop(TIMER_CYCL);
mutex_unlock(arg);
}
}
static const char* _print_ok(int chan, bool *succeeded)
{
if (chan == 0 && count[chan] > 0) {
return "OK";
}
if (chan > 0 && count[chan] == 0) {
return "OK";
}
*succeeded = false;
return "ERROR";
}
int main(void)
{
mutex_t lock = MUTEX_INIT_LOCKED;
const unsigned long timer_hz = XTIMER_HZ;
const unsigned steps = (CYCLE_MS * timer_hz) / 1000;
printf("\nRunning Timer %d at %lu Hz.\n", TIMER_CYCL, timer_hz);
printf("One counter cycle is %u ticks or %lu ms\n", steps, CYCLE_MS);
puts("Will print 'tick' every cycle.\n");
expect(timer_init(TIMER_CYCL, timer_hz, cb, &lock) == 0);
puts("TEST START");
/* Only the first channel should trigger and reset the counter */
/* If subsequent channels trigger this is an error. */
timer_set_periodic(TIMER_CYCL, 1, 2 * steps, TIM_FLAG_RESET_ON_SET);
timer_set_periodic(TIMER_CYCL, 0, steps, TIM_FLAG_RESET_ON_MATCH);
mutex_lock(&lock);
puts("\nCycles:");
bool succeeded = true;
for (unsigned i = 0; i < TIMER_CHANNELS; ++i) {
printf("channel %u = %02u\t[%s]\n", i, count[i], _print_ok(i, &succeeded));
}
if (succeeded) {
puts("TEST SUCCEEDED");
} else {
puts("TEST FAILED");
}
return 0;
}

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
# Copyright (C) 2020 Benjamin Valentin <benpicco@beuth-hochschule.de>
#
# 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.
import sys
import time
from testrunner import run
def testfunc(child):
child.expect_exact('TEST START')
start = time.time()
child.expect_exact('TEST SUCCEEDED')
end = time.time()
# test should run 10 cycles with 100ms each
assert (end - start) > 1
assert (end - start) < 1.5
if __name__ == "__main__":
sys.exit(run(testfunc))