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

Merge pull request #20020 from gompper/periph/freqm

drivers/include/periph: add FREQM peripheral driver
This commit is contained in:
benpicco 2023-11-27 16:06:52 +00:00 committed by GitHub
commit c93a5b84a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 509 additions and 7 deletions

View File

@ -18,6 +18,7 @@ config BOARD_SAME54_XPRO
select HAS_PERIPH_RTC
select HAS_PERIPH_RTT
select HAS_PERIPH_PWM
select HAS_PERIPH_FREQM
select HAS_PERIPH_SDMMC
select HAS_PERIPH_SPI
select HAS_PERIPH_TIMER

View File

@ -14,6 +14,7 @@ FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_uart
FEATURES_PROVIDED += periph_adc
FEATURES_PROVIDED += periph_usbdev
FEATURES_PROVIDED += periph_freqm
# Put other features for this board (in alphabetical order)
FEATURES_PROVIDED += riotboot

View File

@ -3,4 +3,11 @@
# debugger.
TTY_BOARD_FILTER := --model 'EDBG CMSIS-DAP'
# Overwrite GCLK definitions, so that GCLK_IO[2..7] can be connected to GPIOs.
# This way the frequency of signals, connected to these pins, can be measured
# with the FREQM peripheral.
CFLAGS += -DSAM0_GCLK_TIMER=8
CFLAGS += -DSAM0_GCLK_PERIPH=9
CFLAGS += -DSAM0_GCLK_100MHZ=10
include $(RIOTMAKE)/boards/sam0.inc.mk

View File

@ -406,6 +406,18 @@ static const sam0_common_gmac_config_t sam_gmac_config[] = {
};
/** @} */
/**
* @name FREQM peripheral configuration
* @{
*/
static const freqm_config_t freqm_config[] = {
{
.pin = GPIO_PIN(PB, 17),
.gclk_src = SAM0_GCLK_32KHZ
}
};
/** @} */
#ifdef __cplusplus
}
#endif

View File

@ -945,6 +945,14 @@ typedef struct {
*/
#define WDT_HAS_INIT (1)
/**
* @brief Frequency meter configuration
*/
typedef struct {
gpio_t pin; /**< GPIO at which the frequency is to be measured */
uint8_t gclk_src; /**< GCLK source select for reference */
} freqm_config_t;
#if defined(REV_DMAC) || DOXYGEN
/**
* @name sam0 DMA peripheral

View File

@ -0,0 +1,259 @@
/*
* Copyright (C) 2023 ML!PA Consulting GmbH
*
* 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_sam0_common
* @ingroup drivers_periph_freqm
* @{
*
* @file freqm.c
* @brief Frequency meter driver implementation
*
* @author Urs Gompper <urs.gompper@ml-pa.com>
*
* @}
*/
#include "periph/freqm.h"
/* TODO: Remove defines when Microchip vendor files (which include these
* defines) get updated.
*/
/* FREQM_GCLK_ID_REF is defined in newer versions of vendor header files */
#ifndef FREQM_GCLK_ID_REF
#define FREQM_GCLK_ID_REF (FREQM_GCLK_ID_MSR + 1)
#endif
/* Channel Enable Mask */
#define GCLK_PCHCTRL_CHEN_Msk (_U_(0x1) << GCLK_PCHCTRL_CHEN_Pos)
/* Enable Mask */
#define FREQM_CTRLA_ENABLE_Msk (_U_(0x1) << FREQM_CTRLA_ENABLE_Pos)
/* Start Measurement Mask */
#define FREQM_CTRLB_START_Msk (_U_(0x1) << FREQM_CTRLB_START_Pos)
/* Measurement Done Interrupt Enable Mask */
#define FREQM_INTENSET_DONE_Msk (_U_(0x1) << FREQM_INTENSET_DONE_Pos)
/* Measurement Done Mask */
#define FREQM_INTFLAG_DONE_Msk (_U_(0x1) << FREQM_INTFLAG_DONE_Pos)
/* FREQM Status Mask */
#define FREQM_STATUS_BUSY_Msk (_U_(0x1) << FREQM_STATUS_BUSY_Pos)
/* Sticky Count Value Overflow Mask */
#define FREQM_STATUS_OVF_Msk (_U_(0x1) << FREQM_STATUS_OVF_Pos)
/* check if pin has peripheral function GCLK */
static int _freqm_pin(gpio_t pin)
{
for (unsigned i = 0; i < ARRAY_SIZE(gclk_io_pins); ++i) {
if (gclk_io_pins[i] == pin) {
return i;
}
}
return -1;
}
static void _gclk_connect(uint8_t id, uint8_t src, uint32_t flags)
{
GCLK->GENCTRL[id].reg = GCLK_GENCTRL_SRC(src) | GCLK_GENCTRL_GENEN | flags | GCLK_GENCTRL_IDC;
while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL(id)) {}
}
static int _freqm_gpio_init(gpio_t msr_gpio_src, uint8_t *gclk_io_id)
{
/* Check if selected pin has peripheral function GCLK */
int index = _freqm_pin(msr_gpio_src);
/* Fail assertion if pin has no peripheral function GCLK */
assert(index > 0);
/* Lookup which GCLK_IO[x] must be used */
*gclk_io_id = gclk_io_ids[index];
/* GCLK_IO[0] and GCLK_IO[1] can't be used here. They are associated with
GCLKGEN[0] and GCLKGEN[1] respectively. These in turn are used by
SAM0_GCLK_MAIN and SAM0_GCLK_32KHZ respectively */
assert(*gclk_io_id > 1);
/* Initialize GPIO as input */
gpio_init(msr_gpio_src, GPIO_IN);
/* Enable peripheral function GCLK/IO on GPIO */
gpio_init_mux(msr_gpio_src, GPIO_MUX_M);
/* Connect GCLK_IO[*gclk_io_id] with input pin */
_gclk_connect(*gclk_io_id, GCLK_SOURCE_GCLKIN, 0);
return 0;
}
static void _freqm_clock_init(uint8_t pin, uint8_t gclk_src)
{
/* Selection of the Generator and write Lock for FREQM_MSR */
GCLK->PCHCTRL[FREQM_GCLK_ID_MSR].reg = GCLK_PCHCTRL_GEN(pin) | GCLK_PCHCTRL_CHEN_Msk;
/* Wait for synchronization */
while ((GCLK->PCHCTRL[FREQM_GCLK_ID_MSR].reg & GCLK_PCHCTRL_CHEN_Msk) !=
GCLK_PCHCTRL_CHEN_Msk) {}
/* Selection of the Generator and write Lock for FREQM_REF */
GCLK->PCHCTRL[FREQM_GCLK_ID_REF].reg = GCLK_PCHCTRL_GEN(gclk_src) | GCLK_PCHCTRL_CHEN_Msk;
/* Wait for synchronization */
while ((GCLK->PCHCTRL[FREQM_GCLK_ID_REF].reg & GCLK_PCHCTRL_CHEN_Msk) !=
GCLK_PCHCTRL_CHEN_Msk) {}
}
static struct {
freqm_cb_t callback;
void *context;
freqm_t idx;
uint8_t period_cnt;
} freqm_obj;
struct _sync_ctx {
mutex_t lock; /**< Mutex for blocking till measurement is done */
uint32_t hz; /**< Measured frequency in Hz */
bool overflow; /**< Overflow in FREQM counter */
};
/**
* @brief Mutex for locking the FREQM device
*/
static mutex_t msr_lock = MUTEX_INIT;
static void _freqm_enable(uint8_t refnum)
{
mutex_lock(&msr_lock);
/* Save refnum for frequency calculation */
freqm_obj.period_cnt = refnum;
FREQM->CFGA.reg = (uint16_t)(FREQM_CFGA_REFNUM(refnum));
/* Enable DONE Interrupt */
FREQM->INTENSET.reg = FREQM_INTENSET_DONE_Msk;
/* Enable FREQM */
FREQM->CTRLA.reg = FREQM_CTRLA_ENABLE_Msk;
/* Wait for Sync */
while ((FREQM->SYNCBUSY.reg) != 0U) {}
}
static void _freqm_disable(void)
{
/* Disable DONE Interrupt */
FREQM->INTENCLR.reg = FREQM_INTENCLR_MASK;
/* Disable FREQM */
FREQM->CTRLA.reg &= ~FREQM_CTRLA_ENABLE_Msk;
/* Wait for Sync */
while ((FREQM->SYNCBUSY.reg) != 0U) {}
mutex_unlock(&msr_lock);
}
bool _freqm_get_measurement(uint32_t *result)
{
const freqm_config_t *config = &freqm_config[freqm_obj.idx];
/* Calculate measured frequency */
uint64_t result_tmp = FREQM->VALUE.reg * (uint64_t)(sam0_gclk_freq(config->gclk_src));
result_tmp = result_tmp / freqm_obj.period_cnt;
*result = (uint32_t)result_tmp;
/* Read overflow status */
bool overflow_condition = ((int)FREQM->STATUS.reg & FREQM_STATUS_OVF_Msk);
/* Clear overflow status */
FREQM->STATUS.reg = FREQM_STATUS_OVF_Msk;
return overflow_condition;
}
uint32_t _us_to_ref_clock_counts(uint32_t period_us, uint8_t clock_id)
{
uint64_t clk_cnt = (uint64_t)period_us * sam0_gclk_freq(clock_id) / US_PER_SEC;
if (clk_cnt > UINT8_MAX) {
return UINT8_MAX;
}
else if (clk_cnt == 0) {
return 1;
}
else {
return clk_cnt;
}
}
static void _sync_cb(uint32_t res, bool overflow, void *_ctx)
{
struct _sync_ctx *ctx = _ctx;
ctx->hz = res;
ctx->overflow = overflow;
mutex_unlock(&ctx->lock);
}
int freqm_frequency_get(freqm_t idx, uint32_t *result, uint32_t period_us)
{
struct _sync_ctx ctx = { .lock = MUTEX_INIT_LOCKED };
/* Invoke non-blocking FREQM measure function */
freqm_frequency_get_async(idx, _sync_cb, &ctx, period_us);
/* Block until measurement is done */
mutex_lock(&ctx.lock);
*result = ctx.hz;
return ctx.overflow ? -EOVERFLOW : 0;
}
void freqm_frequency_get_async(freqm_t idx, freqm_cb_t freqm_cb, void *context, uint32_t period_us)
{
const freqm_config_t *config = &freqm_config[idx];
uint8_t refnum = _us_to_ref_clock_counts(period_us, config->gclk_src);
_freqm_enable(refnum);
/* Register callback function */
freqm_obj.callback = freqm_cb;
freqm_obj.context = context;
freqm_obj.idx = idx;
/* Clear the Done Interrupt flag */
FREQM->INTFLAG.reg = FREQM_INTFLAG_DONE_Msk;
/* Start measurement */
FREQM->CTRLB.reg = FREQM_CTRLB_START_Msk;
}
void irq_freqm(void)
{
/* Clear the Done Interrupt flag */
FREQM->INTFLAG.reg = FREQM_INTFLAG_DONE_Msk;
uint32_t result = 0;
bool overflow_condition = _freqm_get_measurement(&result);
/* Invoke the callback function */
freqm_obj.callback(result, overflow_condition, freqm_obj.context);
_freqm_disable();
}
void freqm_init(freqm_t idx)
{
uint8_t gclk_io_id = 0;
const freqm_config_t *config = &freqm_config[idx];
/* Sanity check configuration */
assert(config->gclk_src <= GCLK_GEN_NUM_MSB);
_freqm_gpio_init(config->pin, &gclk_io_id);
_freqm_clock_init(gclk_io_id, config->gclk_src);
/* Enable interrupt */
NVIC_EnableIRQ(FREQM_IRQn);
}

View File

@ -325,6 +325,9 @@ void cpu_init(void)
#ifdef MODULE_PERIPH_PM
| MCLK_APBAMASK_PM
#endif
#ifdef MODULE_PERIPH_FREQM
| MCLK_APBAMASK_FREQM
#endif
#ifdef MODULE_PERIPH_GPIO_IRQ
| MCLK_APBAMASK_EIC
#endif

View File

@ -70,13 +70,19 @@ enum {
* @name SAMD5x GCLK definitions
* @{
*/
enum {
SAM0_GCLK_MAIN = 0, /**< 120 MHz main clock */
SAM0_GCLK_32KHZ, /**< 32 kHz clock */
SAM0_GCLK_TIMER, /**< 4-8 MHz clock for xTimer */
SAM0_GCLK_PERIPH, /**< 12-48 MHz (DFLL) clock */
SAM0_GCLK_100MHZ, /**< 100MHz FDPLL clock */
};
#define SAM0_GCLK_MAIN 0 /**< 120 MHz main clock */
#ifndef SAM0_GCLK_32KHZ
#define SAM0_GCLK_32KHZ 1 /**< 32 kHz clock */
#endif
#ifndef SAM0_GCLK_TIMER
#define SAM0_GCLK_TIMER 2 /**< 4-8 MHz clock for xTimer */
#endif
#ifndef SAM0_GCLK_PERIPH
#define SAM0_GCLK_PERIPH 3 /**< 12-48 MHz (DFLL) clock */
#endif
#ifndef SAM0_GCLK_100MHZ
#define SAM0_GCLK_100MHZ 4 /**< 100MHz FDPLL clock */
#endif
/** @} */
/**
@ -198,6 +204,28 @@ static const gpio_t rtc_tamper_pins[RTC_NUM_OF_TAMPERS] = {
GPIO_PIN(PC, 0), GPIO_PIN(PC, 1)
};
/**
* @brief Pins that have peripheral function GCLK
*/
static const gpio_t gclk_io_pins[] = {
GPIO_PIN(PA, 10), GPIO_PIN(PA, 11), GPIO_PIN(PA, 14),
GPIO_PIN(PA, 15), GPIO_PIN(PA, 16), GPIO_PIN(PA, 17),
GPIO_PIN(PA, 27), GPIO_PIN(PA, 30), GPIO_PIN(PB, 10),
GPIO_PIN(PB, 11), GPIO_PIN(PB, 12), GPIO_PIN(PB, 13),
GPIO_PIN(PB, 14), GPIO_PIN(PB, 15), GPIO_PIN(PB, 16),
GPIO_PIN(PB, 17), GPIO_PIN(PB, 18), GPIO_PIN(PB, 19),
GPIO_PIN(PB, 20), GPIO_PIN(PB, 21), GPIO_PIN(PB, 22),
GPIO_PIN(PB, 23)
};
/**
* @brief GCLK IDs of pins that have peripheral function GCLK - This maps
* directly to gclk_io_pins.
*/
static const uint8_t gclk_io_ids[] = {
4, 5, 0, 1, 2, 3, 1, 0, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1
};
/**
* @brief NVM User Page Mapping - Dedicated Entries
* Config values will be applied at power-on.

View File

@ -20,6 +20,7 @@ warning: Member EPD_BW_SPI_DISPLAY_UPDATE_OPTION_[A-Z0-9_]* \(macro definition\)
warning: Member EPD_BW_SPI_WAIT_[A-Z0-9_]* \(macro definition\) of
warning: Member F_CPU \(macro definition\) of
warning: Member F_RC_OSCILLATOR \(macro definition\) of
warning: Member freqm_config\[\] \(variable\) of
warning: Member FXOS8700_PARAM_ADDR \(macro definition\) of
warning: Member FXOS8700_PARAM_I2C \(macro definition\) of
warning: Member FXOS8700_PARAM_RENEW_INTERVAL \(macro definition\) of

View File

@ -0,0 +1,100 @@
/*
* Copyright (C) 2023 ML!PA Consulting GmbH
*
* 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.
*/
/**
* @defgroup drivers_periph_freqm FREQM
* @ingroup drivers_periph
* @brief FREQM peripheral driver interface
*
* This interface allows to configure and use the Frequency Meter (FREQM)
* peripheral.
*
* The Frequency Meter uses the frequency of a known reference clock to
* determine the frequency of a signal connected via GPIO.
*
* @{
*
* @file
* @brief FREQM peripheral driver interface definitions
*
* @author Urs Gompper <urs.gompper@ml-pa.com>
*/
#ifndef PERIPH_FREQM_H
#define PERIPH_FREQM_H
#include <errno.h>
#include <mutex.h>
#include "periph_cpu.h"
#include "periph/gpio.h"
#include "time_units.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Frequency meter callback function.
* When a measurement is done the callbackfunction is called.
*
* @param result measured frequency in hz
* @param overflow overflow in sticky counter
* @param context pointer to user defined context data
*/
typedef void (*freqm_cb_t)(uint32_t result, bool overflow, void *context);
/**
* @brief Define default Frequency meter type identifier
*/
#ifndef HAVE_FREQM_T
typedef uint_fast8_t freqm_t;
#endif
/**
* @brief Initialize the frequency meter
*
* @param[in] idx index of the configuration
*/
void freqm_init(freqm_t idx);
/**
* @brief Read number of periods of measured clock and calculate its frequency
*
* This function returns after triggering the measurement and calls
* @p freqm_callback , with the calculated result and @p context , when the
* measurement is done.
*
* @param[in] idx index of the configuration
* @param[in] freqm_cb callback function when measurement is ready
* @param[in] context context for the callback function
* @param[in] period_us measurement duration in microseconds
*/
void freqm_frequency_get_async(freqm_t idx, freqm_cb_t freqm_cb, void *context,
uint32_t period_us);
/**
* @brief Read number of periods of measured clock and calculate its frequency
*
* This function uses a blocking mutex to wait for the measurement to finish.
*
* @param[in] idx index of the configuration
* @param[out] result calculated frequency
* @param[in] period_us measurement duration in microseconds
*
* @return -EOVERFLOW if FREQM sticky counter has an overflow
* @return 0 on success
*/
int freqm_frequency_get(freqm_t idx, uint32_t *result, uint32_t period_us);
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* PERIPH_FREQM_H */

View File

@ -139,6 +139,10 @@ config MODULE_PERIPH_RTT
depends on HAS_PERIPH_RTT
select MODULE_PERIPH_COMMON
config MODULE_PERIPH_FREQM
bool "Frequency Meter driver"
depends on HAS_PERIPH_FREQM
config MODULE_PERIPH_RTT_SET_COUNTER
bool "rtc_set_counter() implementation in the RTT peripheral driver"
depends on HAS_PERIPH_RTT_SET_COUNTER && MODULE_PERIPH_RTT

View File

@ -263,6 +263,11 @@ config HAS_PERIPH_FLASHPAGE_RWEE
help
Indicates that the Flashpage peripheral is of the Read While Write.
config HAS_PERIPH_FREQM
bool
help
Indicates that a Frequency Meter peripheral is present.
config HAS_PERIPH_GPIO
bool
help

View File

@ -0,0 +1,7 @@
BOARD ?= same54-xpro
include ../Makefile.periph_common
USEMODULE += periph_freqm
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,14 @@
Peripheral FREQM Test Application
=====================================
This application tests the frequency meter (FREQM) functionality. This is done
by measuring the frequency of a clock, connected to a GPIO, with an internal
clock as reference.
Expected Output on Success
--------------------------
main(): This is RIOT! (Version: <INSERT VERSION HERE>)
FREQM peripheral driver test
Measured clock frequency: <MEASURED CLOCK FREQUENCY> Hz
Test run finished.

52
tests/periph/freqm/main.c Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 ML!PA Consulting GmbH
*
* 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 Application to test functionality of the frequency meter
* peripheral
*
* @author Urs Gompper <urs.gompper@ml-pa.com>
* @}
*/
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "periph/freqm.h"
int main(void)
{
puts("FREQM peripheral driver test");
/* Initialize frequency meter peripheral */
freqm_init(0);
uint32_t period_us = UINT32_MAX;
uint32_t freq_hz = 0;
/* Measure in blocking mode */
if (!freqm_frequency_get(0, &freq_hz, period_us)) {
printf("Measured Clock Frequency: %ld Hz\n", freq_hz);
}
else {
puts("Overflow occurred to the FREQM value counter!");
return EXIT_FAILURE;
}
puts("Test run finished.");
/* main thread exits */
return 0;
}