diff --git a/cpu/kinetis_common/include/periph_cpu.h b/cpu/kinetis_common/include/periph_cpu.h index 83a29694db..ac138ab8d7 100644 --- a/cpu/kinetis_common/include/periph_cpu.h +++ b/cpu/kinetis_common/include/periph_cpu.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Freie Universität Berlin + * Copyright (C) 2015-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 @@ -61,6 +61,11 @@ typedef uint16_t gpio_t; */ #define GPIO_MODE(pu, pe, od, out) (pu | (pe << 1) | (od << 5) | (out << 7)) +/** + * @brief Define the maximum number of PWM channels that can be configured + */ +#define PWM_CHAN_MAX (4U) + #ifndef DOXYGEN /** * @brief Override GPIO modes @@ -142,6 +147,18 @@ typedef enum { ADC_RES_16BIT = ADC_CFG1_MODE(3) /**< ADC resolution: 16 bit */ } adc_res_t; /** @} */ + +/** + * @brief Override default PWM mode configuration + * @{ + */ +#define HAVE_PWM_MODE_T +typedef enum { + PWM_LEFT = (FTM_CnSC_MSB_MASK | FTM_CnSC_ELSB_MASK), /**< left aligned */ + PWM_RIGHT = (FTM_CnSC_MSB_MASK | FTM_CnSC_ELSA_MASK), /**< right aligned */ + PWM_CENTER = (FTM_CnSC_MSB_MASK) /**< center aligned */ +} pwm_mode_t; +/** @} */ #endif /* ndef DOXYGEN */ /** @@ -186,12 +203,26 @@ typedef struct { uint8_t index; } lptmr_conf_t; +/** + * @brief PWM configuration structure + */ +typedef struct { + FTM_Type* ftm; /**< used FTM */ + struct { /**< logical channel configuration */ + gpio_t pin; /**< GPIO pin used, set to GPIO_UNDEF */ + uint8_t af; /**< alternate function mapping */ + uint8_t ftm_chan; /**< the actual FTM channel used */ + } chan[PWM_CHAN_MAX]; + uint8_t chan_numof; /**< number of actually configured channels */ + uint8_t ftm_num; /**< FTM number used */ +} pwm_conf_t; + /** * @brief Possible timer module types */ enum { - TIMER_PIT, - TIMER_LPTMR, + TIMER_PIT, /**< PIT */ + TIMER_LPTMR, /**< LPTMR */ }; /** diff --git a/cpu/kinetis_common/periph/pwm.c b/cpu/kinetis_common/periph/pwm.c index 75da1da4f7..f6166a1963 100644 --- a/cpu/kinetis_common/periph/pwm.c +++ b/cpu/kinetis_common/periph/pwm.c @@ -1,11 +1,11 @@ /* - * Copyright (C) 2014 Freie Universität Berlin + * Copyright (C) 2014-2016 Freie Universität Berlin * Copyright (C) 2014 PHYTEC Messtechnik GmbH * Copyright (C) 2015-2016 Eistec AB * - * 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. + * 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. */ /** @@ -24,375 +24,130 @@ * @} */ -#include -#include - #include "cpu.h" +#include "assert.h" #include "periph/pwm.h" -#include "periph_conf.h" -/* FTM channel look up tables */ -#if PWM_0_EN -static const uint8_t ftm0chan[] = { -#if PWM_0_CHANNELS > 0 - PWM_0_CH0_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 1 - PWM_0_CH1_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 2 - PWM_0_CH2_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 3 - PWM_0_CH3_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 4 - PWM_0_CH4_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 5 - PWM_0_CH5_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 6 - PWM_0_CH6_FTMCHAN, -#endif -#if PWM_0_CHANNELS > 7 - PWM_0_CH7_FTMCHAN, -#endif -}; -#endif -#if PWM_1_EN -static const uint8_t ftm1chan[] = { -#if PWM_1_CHANNELS > 0 - PWM_1_CH0_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 1 - PWM_1_CH1_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 2 - PWM_1_CH2_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 3 - PWM_1_CH3_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 4 - PWM_1_CH4_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 5 - PWM_1_CH5_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 6 - PWM_1_CH6_FTMCHAN, -#endif -#if PWM_1_CHANNELS > 7 - PWM_1_CH7_FTMCHAN, -#endif -}; -#endif +#define PRESCALER_MAX (7U) -uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res) +static inline FTM_Type *ftm(pwm_t pwm) { - FTM_Type *ftm; - int channels = 0; - uint32_t pwm_clk = 0; - const uint8_t *ftmchan = NULL; + return pwm_config[pwm].ftm; +} - switch (dev) { -#if PWM_0_EN +uint32_t pwm_init(pwm_t pwm, pwm_mode_t mode, uint32_t freq, uint16_t res) +{ + uint8_t pre = 0; - case PWM_0: - channels = PWM_0_CHANNELS; - pwm_clk = PWM_0_CLK; - ftm = PWM_0_DEV; - ftmchan = &ftm0chan[0]; - break; -#endif -#if PWM_1_EN - - case PWM_1: - channels = PWM_1_CHANNELS; - pwm_clk = PWM_1_CLK; - ftm = PWM_1_DEV; - ftmchan = &ftm1chan[0]; - break; -#endif - - default: - return 0; - } - - switch (mode) { - case PWM_LEFT: - case PWM_RIGHT: - case PWM_CENTER: - break; - - default: - return 0; - } - - if ((unsigned int)res > (PWM_MAX_VALUE + 1) || (res * freq) > pwm_clk) { + if (pwm >= PWM_NUMOF || ((res * freq) > CLOCK_BUSCLOCK)) { return 0; } - /* Try to find a good prescaler value */ - /* The prescaler divides the module clock by a power of two, between 2^0 and 2^7 */ - uint8_t prescaler = 0; - /* (resolution * frequency) is the number of timer ticks per second */ - while ((pwm_clk >> prescaler) > (res * freq)) { - ++prescaler; - if (prescaler > 7) { - /* Module clock is too fast to reach the requested frequency using the - * hardware supported prescaler values */ - /* Note: The frequency might be reachable if the requested resolution - * is increased. */ - return 0; - } + /* figure out the clock settings + * the resulting frequency is calculated by + * ticks := BUS_CLK / 2 ^ pre + * where `ticks` is `freq * res` + * and `pre` must be between [0, 7]. + * + * The resulting prescaler yields a timer frequency, which is the closest + * possible frequency requested. */ + while ((CLOCK_BUSCLOCK >> pre) > (res * freq)) { + ++pre; } - /* The chosen prescaler yields a timer frequency which is the - * nearest possible frequency less than the requested frequency */ - - /* Turn on the peripheral */ - pwm_poweron(dev); - - switch (dev) { -#if PWM_0_EN - - case PWM_0: - #if PWM_0_CHANNELS > 0 - gpio_init_port(PWM_0_CH0_GPIO, PORT_PCR_MUX(PWM_0_CH0_AF)); - #endif - #if PWM_0_CHANNELS > 1 - gpio_init_port(PWM_0_CH1_GPIO, PORT_PCR_MUX(PWM_0_CH1_AF)); - #endif - #if PWM_0_CHANNELS > 2 - gpio_init_port(PWM_0_CH2_GPIO, PORT_PCR_MUX(PWM_0_CH2_AF)); - #endif - #if PWM_0_CHANNELS > 3 - gpio_init_port(PWM_0_CH3_GPIO, PORT_PCR_MUX(PWM_0_CH3_AF)); - #endif - #if PWM_0_CHANNELS > 4 - gpio_init_port(PWM_0_CH4_GPIO, PORT_PCR_MUX(PWM_0_CH4_AF)); - #endif - #if PWM_0_CHANNELS > 5 - gpio_init_port(PWM_0_CH5_GPIO, PORT_PCR_MUX(PWM_0_CH5_AF)); - #endif - #if PWM_0_CHANNELS > 6 - gpio_init_port(PWM_0_CH6_GPIO, PORT_PCR_MUX(PWM_0_CH6_AF)); - #endif - #if PWM_0_CHANNELS > 7 - gpio_init_port(PWM_0_CH7_GPIO, PORT_PCR_MUX(PWM_0_CH7_AF)); - #endif - break; -#endif -#if PWM_1_EN - - case PWM_1: - #if PWM_1_CHANNELS > 0 - gpio_init_port(PWM_1_CH0_GPIO, PORT_PCR_MUX(PWM_1_CH0_AF)); - #endif - #if PWM_1_CHANNELS > 1 - gpio_init_port(PWM_1_CH1_GPIO, PORT_PCR_MUX(PWM_1_CH1_AF)); - #endif - #if PWM_1_CHANNELS > 2 - gpio_init_port(PWM_1_CH2_GPIO, PORT_PCR_MUX(PWM_1_CH2_AF)); - #endif - #if PWM_1_CHANNELS > 3 - gpio_init_port(PWM_1_CH3_GPIO, PORT_PCR_MUX(PWM_1_CH3_AF)); - #endif - #if PWM_1_CHANNELS > 4 - gpio_init_port(PWM_1_CH4_GPIO, PORT_PCR_MUX(PWM_1_CH4_AF)); - #endif - #if PWM_1_CHANNELS > 5 - gpio_init_port(PWM_1_CH5_GPIO, PORT_PCR_MUX(PWM_1_CH5_AF)); - #endif - #if PWM_1_CHANNELS > 6 - gpio_init_port(PWM_1_CH6_GPIO, PORT_PCR_MUX(PWM_1_CH6_AF)); - #endif - #if PWM_1_CHANNELS > 7 - gpio_init_port(PWM_1_CH7_GPIO, PORT_PCR_MUX(PWM_1_CH7_AF)); - #endif - break; -#endif - - default: - return 0; + /* make sure the calculated prescaler is valid, else return */ + if (pre > PRESCALER_MAX) { + return 0; } + /* configure the used timer */ + pwm_poweron(pwm); /* disable write protect for changing settings */ - ftm->MODE = FTM_MODE_WPDIS_MASK; + ftm(pwm)->MODE = FTM_MODE_WPDIS_MASK; + /* clear any existing configuration */ + ftm(pwm)->COMBINE = 0; + ftm(pwm)->CNTIN = 0; + ftm(pwm)->SWOCTRL = 0; + /* apply prescaler and set resolution */ + ftm(pwm)->SC = FTM_SC_PS(pre); + ftm(pwm)->MOD = (res - 1); - /* reset timer match value */ - for (int i = 0; i < channels; i++) { - ftm->CONTROLS[i].CnV = 0; + /* set CPWMS bit in the SC register in case of center aligned mode */ + if (mode == PWM_CENTER) { + BITBAND_REG32(ftm(pwm)->SC, FTM_SC_CPWMS_SHIFT) = 1; } - /* reset timer configuration registers */ - ftm->COMBINE = 0; - ftm->CNTIN = 0; - ftm->SWOCTRL = 0; - - /* set prescale and mod registers to matching values for resolution and frequency */ - ftm->SC = FTM_SC_PS(prescaler); - ftm->MOD = res - 1; - - /* set PWM mode */ - uint32_t mode_mask = 0; - switch (mode) { - case PWM_LEFT: - mode_mask = (1 << FTM_CnSC_MSB_SHIFT) | (1 << FTM_CnSC_ELSB_SHIFT); - break; - - case PWM_RIGHT: - mode_mask = (1 << FTM_CnSC_MSB_SHIFT) | (1 << FTM_CnSC_ELSA_SHIFT); - break; - - case PWM_CENTER: - mode_mask = (1 << FTM_CnSC_MSB_SHIFT); - ftm->SC |= (1 << FTM_SC_CPWMS_SHIFT); - break; - } - for (int i = 0; i < channels; i++) { - /* cppcheck thinks ftmchan may be NULL here, but the variable is - * assigned in all non-returning branches of the switch at the top of - * this function. */ - /* cppcheck-suppress nullPointer ftmchan */ - ftm->CONTROLS[ftmchan[i]].CnSC = mode_mask; + /* setup the configured channels */ + for (int i = 0; i < (int)pwm_config[pwm].chan_numof; i++) { + /* configure the used pin */ + gpio_init_port(pwm_config[pwm].chan[i].pin, + PORT_PCR_MUX(pwm_config[pwm].chan[i].af)); + /* set the given mode */ + ftm(pwm)->CONTROLS[pwm_config[pwm].chan[i].ftm_chan].CnSC = mode; + /* and reset the PWM to 0% duty cycle */ + ftm(pwm)->CONTROLS[pwm_config[pwm].chan[i].ftm_chan].CnV = 0; } - /* enable timer ergo the PWM generation */ - pwm_start(dev); + /* and now we start the actual waveform generation */ + pwm_start(pwm); - /* Return actual frequency */ - return (pwm_clk / (1 << prescaler)) / res; + /* finally we need to return the actual applied PWM frequency */ + return (CLOCK_BUSCLOCK >> pre) / res; } -uint8_t pwm_channels(pwm_t dev) +uint8_t pwm_channels(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - case PWM_0: - return PWM_0_CHANNELS; -#endif -#if PWM_1_EN - case PWM_1: - return PWM_1_CHANNELS; -#endif - default: - return 0; - } + assert(pwm < PWM_NUMOF); + return pwm_config[pwm].chan_numof; } -void pwm_set(pwm_t dev, uint8_t channel, uint16_t value) +void pwm_set(pwm_t pwm, uint8_t channel, uint16_t value) { - FTM_Type *ftm; - const uint8_t *ftmchan = NULL; - - switch (dev) { -#if PWM_0_EN - - case PWM_0: - if (channel > PWM_0_CHANNELS) { - return; - } - ftm = PWM_0_DEV; - ftmchan = &ftm0chan[0]; - break; -#endif -#if PWM_1_EN - - case PWM_1: - if (channel > PWM_1_CHANNELS) { - return; - } - ftm = PWM_1_DEV; - ftmchan = &ftm1chan[0]; - break; -#endif - - default: - return; - } - - /* clamp value to maximum possible value */ - if (value > PWM_MAX_VALUE) { - value = PWM_MAX_VALUE; - } - - /* cppcheck thinks ftmchan may be NULL here, but the variable is - * assigned in all non-returning branches of the switch at the top of - * this function. */ - /* cppcheck-suppress nullPointer */ - ftm->CONTROLS[ftmchan[channel]].CnV = value; + assert((pwm < PWM_NUMOF) && (channel < pwm_config[pwm].chan_numof)); + ftm(pwm)->CONTROLS[pwm_config[pwm].chan[channel].ftm_chan].CnV = value; } -void pwm_start(pwm_t dev) +void pwm_start(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - - case PWM_0: - PWM_0_DEV->SC |= FTM_SC_CLKS(1); - break; -#endif -#if PWM_1_EN - - case PWM_1: - PWM_1_DEV->SC |= FTM_SC_CLKS(1); - break; -#endif - } + assert(pwm < PWM_NUMOF); + ftm(pwm)->SC |= FTM_SC_CLKS(1); } -void pwm_stop(pwm_t dev) +void pwm_stop(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - - case PWM_0: - PWM_0_DEV->SC &= ~FTM_SC_CLKS_MASK; - break; -#endif -#if PWM_1_EN - - case PWM_1: - PWM_1_DEV->SC &= ~FTM_SC_CLKS_MASK; - break; -#endif - } + assert(pwm < PWM_NUMOF); + ftm(pwm)->SC &= ~(FTM_SC_CLKS_MASK); } -void pwm_poweron(pwm_t dev) +void pwm_poweron(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN + assert(pwm < PWM_NUMOF); + int ftm = pwm_config[pwm].ftm_num; - case PWM_0: - PWM_0_CLKEN(); - break; -#endif -#if PWM_1_EN - - case PWM_1: - PWM_1_CLKEN(); - break; -#endif +#ifdef SIM_SCGC6_FTM2_SHIFT + BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM0_SHIFT + ftm) = 1; +#else + if (ftm < 2) { + BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM0_SHIFT + ftm) = 1; } + else if (ftm == 2) { + BITBAND_REG32(SIM->SCGC3, SIM_SCGC3_FTM2_SHIFT) = 1; + } +#endif } -void pwm_poweroff(pwm_t dev) +void pwm_poweroff(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN + assert(pwm < PWM_NUMOF); + int ftm = pwm_config[pwm].ftm_num; - case PWM_0: - PWM_0_CLKDIS(); - break; -#endif -#if PWM_1_EN - - case PWM_1: - PWM_1_CLKDIS(); - break; -#endif +#ifdef SIM_SCGC6_FTM2_SHIFT + BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM0_SHIFT + ftm) = 0; +#else + if (ftm < 2) { + BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM0_SHIFT + ftm) = 0; } + else if (ftm == 2) { + BITBAND_REG32(SIM->SCGC3, SIM_SCGC3_FTM2_SHIFT) = 0; + } +#endif }