From fc926e57bf9175da1303341e6b49e905c3509c80 Mon Sep 17 00:00:00 2001 From: Joakim Gebart Date: Sat, 16 May 2015 01:10:16 +0200 Subject: [PATCH 1/3] pba-d-01-kw2x: Add PWM_MAX_VALUE --- boards/pba-d-01-kw2x/include/periph_conf.h | 1 + 1 file changed, 1 insertion(+) diff --git a/boards/pba-d-01-kw2x/include/periph_conf.h b/boards/pba-d-01-kw2x/include/periph_conf.h index ad1ca46dec..d6779f5254 100644 --- a/boards/pba-d-01-kw2x/include/periph_conf.h +++ b/boards/pba-d-01-kw2x/include/periph_conf.h @@ -171,6 +171,7 @@ extern "C" #define PWM_NUMOF (1U) #define PWM_0_EN 1 #define PWM_MAX_CHANNELS 4 +#define PWM_MAX_VALUE 0xffff /* PWM 0 device configuration */ #define PWM_0_DEV FTM0 From c85cc83a5a763feb94abe561a1fb743e8a8cdec4 Mon Sep 17 00:00:00 2001 From: Joakim Gebart Date: Sat, 16 May 2015 00:15:31 +0200 Subject: [PATCH 2/3] kinetis: Refactor PWM periph implementation - Set the proper prescaler value depending on requested frequency - Return the actual achieved frequency in pwm_init - Handle 1-8 channels depending on periph_conf.h instead of 4 fixed - Perform function argument verification before touching the hardware - Turn on PORT clock gate before touching PORT registers - Eliminate some magic numbers --- cpu/kinetis_common/pwm.c | 367 ++++++++++++++++++++++++++------------- 1 file changed, 243 insertions(+), 124 deletions(-) diff --git a/cpu/kinetis_common/pwm.c b/cpu/kinetis_common/pwm.c index 1b0bbbd966..f20ad77311 100644 --- a/cpu/kinetis_common/pwm.c +++ b/cpu/kinetis_common/pwm.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2014 Freie Universität Berlin * Copyright (C) 2014 PHYTEC Messtechnik GmbH + * Copyright (C) 2015 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 @@ -18,6 +19,7 @@ * @author Hauke Petersen * @author Johann Fischer * @author Jonas Remmert + * @author Joakim Gebart * * @} */ @@ -34,131 +36,188 @@ /* ignore file in case no PWM devices are defined */ #if PWM_NUMOF +/* FTM channel look up tables */ +#if PWM_0_EN +static const uint8_t ftm0chan[] = { +#if PWM_0_CHANNELS > 0 + PWM_0_FTMCHAN_CH0, +#endif +#if PWM_0_CHANNELS > 1 + PWM_0_FTMCHAN_CH1, +#endif +#if PWM_0_CHANNELS > 2 + PWM_0_FTMCHAN_CH2, +#endif +#if PWM_0_CHANNELS > 3 + PWM_0_FTMCHAN_CH3, +#endif +#if PWM_0_CHANNELS > 4 + PWM_0_FTMCHAN_CH4, +#endif +#if PWM_0_CHANNELS > 5 + PWM_0_FTMCHAN_CH5, +#endif +#if PWM_0_CHANNELS > 6 + PWM_0_FTMCHAN_CH6, +#endif +#if PWM_0_CHANNELS > 7 + PWM_0_FTMCHAN_CH7, +#endif +}; +#endif +#if PWM_1_EN +static const uint8_t ftm1chan[] = { +#if PWM_1_CHANNELS > 0 + PWM_1_FTMCHAN_CH0, +#endif +#if PWM_1_CHANNELS > 1 + PWM_1_FTMCHAN_CH1, +#endif +#if PWM_1_CHANNELS > 2 + PWM_1_FTMCHAN_CH2, +#endif +#if PWM_1_CHANNELS > 3 + PWM_1_FTMCHAN_CH3, +#endif +#if PWM_1_CHANNELS > 4 + PWM_1_FTMCHAN_CH4, +#endif +#if PWM_1_CHANNELS > 5 + PWM_1_FTMCHAN_CH5, +#endif +#if PWM_1_CHANNELS > 6 + PWM_1_FTMCHAN_CH6, +#endif +#if PWM_1_CHANNELS > 7 + PWM_1_FTMCHAN_CH7, +#endif +}; +#endif + int pwm_init(pwm_t dev, pwm_mode_t mode, unsigned int frequency, unsigned int resolution) { - FTM_Type *tim = NULL; - PORT_Type *port[PWM_MAX_CHANNELS]; - /* cppcheck-suppress unassignedVariable */ - uint8_t pins[PWM_MAX_CHANNELS]; - uint8_t af[PWM_MAX_CHANNELS]; - /* cppcheck-suppress unassignedVariable */ - uint8_t ftmchan[PWM_MAX_CHANNELS]; + FTM_Type *ftm; int channels = 0; uint32_t pwm_clk = 0; + const uint8_t *ftmchan = NULL; + switch (dev) { +#if PWM_0_EN + + 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 -1; + } + + switch (mode) { + case PWM_LEFT: + case PWM_RIGHT: + case PWM_CENTER: + break; + + default: + return -1; + } + + if (resolution > (PWM_MAX_VALUE + 1) || (resolution * frequency) > pwm_clk) { + return -2; + } + + /* 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) > (resolution * frequency)) { + ++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 -2; + } + } + /* 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: - tim = PWM_0_DEV; - port[0] = PWM_0_PORT_CH0; - port[1] = PWM_0_PORT_CH1; - port[2] = PWM_0_PORT_CH2; - port[3] = PWM_0_PORT_CH3; - pins[0] = PWM_0_PIN_CH0; - pins[1] = PWM_0_PIN_CH1; - pins[2] = PWM_0_PIN_CH2; - pins[3] = PWM_0_PIN_CH3; - ftmchan[0] = PWM_0_FTMCHAN_CH0; - ftmchan[1] = PWM_0_FTMCHAN_CH1; - ftmchan[2] = PWM_0_FTMCHAN_CH2; - ftmchan[3] = PWM_0_FTMCHAN_CH3; - af[0] = PWM_0_PIN_AF_CH0; - af[1] = PWM_0_PIN_AF_CH1; - af[2] = PWM_0_PIN_AF_CH2; - af[3] = PWM_0_PIN_AF_CH3; - channels = PWM_0_CHANNELS; - pwm_clk = PWM_0_CLK; PWM_0_PORT_CLKEN(); - break; -#endif - - default: - return -1; - } - - if (channels > PWM_MAX_CHANNELS) { - return -1; - } - - /* cppcheck-suppress nullPointer */ - tim->MODE = (1 << FTM_MODE_WPDIS_SHIFT); - - /* setup pins, reset timer match value */ - for (int i = 0; i < channels; i++) { - port[i]->PCR[pins[i]] = PORT_PCR_MUX(af[i]); - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[i].CnV = 0; - } - - /* reset timer configuration registers */ - /* cppcheck-suppress nullPointer */ - tim->COMBINE = 0; - - /* set prescale and mod registers to matching values for resolution and frequency */ - if (resolution > 0xffff || (resolution * frequency) > pwm_clk) { - return -2; - } - - /* cppcheck-suppress nullPointer */ - tim->SC = FTM_SC_PS((pwm_clk / (resolution * frequency)) - 1); - /* cppcheck-suppress nullPointer */ - tim->MOD = resolution; - - /* set PWM mode */ - switch (mode) { - case PWM_LEFT: - for (int i = 0; i < channels; i++) { - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[ftmchan[i]].CnSC = (1 << FTM_CnSC_MSB_SHIFT | - 1 << FTM_CnSC_ELSB_SHIFT); - } - - break; - - case PWM_RIGHT: - for (int i = 0; i < channels; i++) { - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[ftmchan[i]].CnSC = (1 << FTM_CnSC_MSB_SHIFT | - 1 << FTM_CnSC_ELSA_SHIFT); - } - - break; - - case PWM_CENTER: - for (int i = 0; i < channels; i++) { - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[ftmchan[i]].CnSC = (1 << FTM_CnSC_MSB_SHIFT); - } - - /* cppcheck-suppress nullPointer */ - tim->SC |= (1 << FTM_SC_CPWMS_SHIFT); - break; - } - - /* enable timer ergo the PWM generation */ - pwm_start(dev); - - return 0; -} - -int pwm_set(pwm_t dev, int channel, unsigned int value) -{ - FTM_Type *tim = NULL; - - switch (dev) { -#if PWM_0_EN - - case PWM_0: - tim = PWM_0_DEV; + #if PWM_0_CHANNELS > 0 + PWM_0_PORT_CH0->PCR[PWM_0_PIN_CH0] = PORT_PCR_MUX(PWM_0_PIN_AF_CH0); + #endif + #if PWM_0_CHANNELS > 1 + PWM_0_PORT_CH1->PCR[PWM_0_PIN_CH1] = PORT_PCR_MUX(PWM_0_PIN_AF_CH1); + #endif + #if PWM_0_CHANNELS > 2 + PWM_0_PORT_CH2->PCR[PWM_0_PIN_CH2] = PORT_PCR_MUX(PWM_0_PIN_AF_CH2); + #endif + #if PWM_0_CHANNELS > 3 + PWM_0_PORT_CH3->PCR[PWM_0_PIN_CH3] = PORT_PCR_MUX(PWM_0_PIN_AF_CH3); + #endif + #if PWM_0_CHANNELS > 4 + PWM_0_PORT_CH4->PCR[PWM_0_PIN_CH4] = PORT_PCR_MUX(PWM_0_PIN_AF_CH4); + #endif + #if PWM_0_CHANNELS > 5 + PWM_0_PORT_CH5->PCR[PWM_0_PIN_CH5] = PORT_PCR_MUX(PWM_0_PIN_AF_CH5); + #endif + #if PWM_0_CHANNELS > 6 + PWM_0_PORT_CH6->PCR[PWM_0_PIN_CH6] = PORT_PCR_MUX(PWM_0_PIN_AF_CH6); + #endif + #if PWM_0_CHANNELS > 7 + PWM_0_PORT_CH7->PCR[PWM_0_PIN_CH7] = PORT_PCR_MUX(PWM_0_PIN_AF_CH7); + #endif break; #endif #if PWM_1_EN case PWM_1: - tim = PWM_1_DEV; + PWM_1_PORT_CLKEN(); + #if PWM_1_CHANNELS > 0 + PWM_1_PORT_CH0->PCR[PWM_1_PIN_CH0] = PORT_PCR_MUX(PWM_1_PIN_AF_CH0); + #endif + #if PWM_1_CHANNELS > 1 + PWM_1_PORT_CH1->PCR[PWM_1_PIN_CH1] = PORT_PCR_MUX(PWM_1_PIN_AF_CH1); + #endif + #if PWM_1_CHANNELS > 2 + PWM_1_PORT_CH2->PCR[PWM_1_PIN_CH2] = PORT_PCR_MUX(PWM_1_PIN_AF_CH2); + #endif + #if PWM_1_CHANNELS > 3 + PWM_1_PORT_CH3->PCR[PWM_1_PIN_CH3] = PORT_PCR_MUX(PWM_1_PIN_AF_CH3); + #endif + #if PWM_1_CHANNELS > 4 + PWM_1_PORT_CH4->PCR[PWM_1_PIN_CH4] = PORT_PCR_MUX(PWM_1_PIN_AF_CH4); + #endif + #if PWM_1_CHANNELS > 5 + PWM_1_PORT_CH5->PCR[PWM_1_PIN_CH5] = PORT_PCR_MUX(PWM_1_PIN_AF_CH5); + #endif + #if PWM_1_CHANNELS > 6 + PWM_1_PORT_CH6->PCR[PWM_1_PIN_CH6] = PORT_PCR_MUX(PWM_1_PIN_AF_CH6); + #endif + #if PWM_1_CHANNELS > 7 + PWM_1_PORT_CH7->PCR[PWM_1_PIN_CH7] = PORT_PCR_MUX(PWM_1_PIN_AF_CH7); + #endif break; #endif @@ -166,36 +225,96 @@ int pwm_set(pwm_t dev, int channel, unsigned int value) return -1; } - /* norm value to maximum possible value */ - if (value > 0xffff) { - value = 0xffff; + /* disable write protect for changing settings */ + ftm->MODE = FTM_MODE_WPDIS_MASK; + + /* reset timer match value */ + for (int i = 0; i < channels; i++) { + ftm->CONTROLS[i].CnV = 0; } - switch (channel) { - case 0: - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[PWM_0_FTMCHAN_CH0].CnV = value; + /* 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 = resolution - 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 1: - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[PWM_0_FTMCHAN_CH1].CnV = value; + case PWM_RIGHT: + mode_mask = (1 << FTM_CnSC_MSB_SHIFT) | (1 << FTM_CnSC_ELSA_SHIFT); break; - case 2: - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[PWM_0_FTMCHAN_CH2].CnV = value; + 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; + } - case 3: - /* cppcheck-suppress nullPointer */ - tim->CONTROLS[PWM_0_FTMCHAN_CH3].CnV = value; + /* enable timer ergo the PWM generation */ + pwm_start(dev); + + /* Return actual frequency */ + return (pwm_clk / (1 << prescaler)) / resolution; +} + +int pwm_set(pwm_t dev, int channel, unsigned int value) +{ + FTM_Type *ftm; + const uint8_t *ftmchan = NULL; + + switch (dev) { +#if PWM_0_EN + + case PWM_0: + if (channel > PWM_0_CHANNELS) { + return -1; + } + ftm = PWM_0_DEV; + ftmchan = &ftm0chan[0]; break; +#endif +#if PWM_1_EN + + case PWM_1: + if (channel > PWM_1_CHANNELS) { + return -1; + } + ftm = PWM_1_DEV; + ftmchan = &ftm1chan[0]; + break; +#endif default: return -1; } + /* 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; + return 0; } From 964fdb5cf248fcac587bfb339221f71acbb3f9e0 Mon Sep 17 00:00:00 2001 From: Joakim Gebart Date: Sat, 16 May 2015 00:12:39 +0200 Subject: [PATCH 3/3] mulle: Add PWM configuration --- boards/mulle/Makefile.features | 1 + boards/mulle/include/periph_conf.h | 45 ++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/boards/mulle/Makefile.features b/boards/mulle/Makefile.features index 738e27b298..df0a7b0170 100644 --- a/boards/mulle/Makefile.features +++ b/boards/mulle/Makefile.features @@ -3,6 +3,7 @@ FEATURES_PROVIDED += periph_adc FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_i2c +FEATURES_PROVIDED += periph_pwm FEATURES_PROVIDED += periph_random FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_rtt diff --git a/boards/mulle/include/periph_conf.h b/boards/mulle/include/periph_conf.h index ce3c47c46c..78b300fe11 100644 --- a/boards/mulle/include/periph_conf.h +++ b/boards/mulle/include/periph_conf.h @@ -226,10 +226,51 @@ extern "C" * @name PWM configuration * @{ */ -#define PWM_NUMOF (0U) +#define PWM_NUMOF (2U) #define PWM_0_EN 1 #define PWM_1_EN 1 -#define PWM_MAX_CHANNELS 4 +#define PWM_MAX_CHANNELS 8 +#define PWM_MAX_VALUE 0xffff + +/* PWM 0 device configuration */ +#define PWM_0_DEV FTM0 +#define PWM_0_CHANNELS 2 +#define PWM_0_CLK (SystemBusClock) +#define PWM_0_CLKEN() (BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM0_SHIFT) = 1) +#define PWM_0_CLKDIS() (BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM0_SHIFT) = 0) + +/* PWM 0 pin configuration */ +#define PWM_0_PORT_CLKEN() (BITBAND_REG32(SIM->SCGC5, SIM_SCGC5_PORTC_SHIFT) = 1) + +#define PWM_0_PIN_CH0 1 +#define PWM_0_FTMCHAN_CH0 0 +#define PWM_0_PORT_CH0 PORTC +#define PWM_0_PIN_AF_CH0 4 + +#define PWM_0_PIN_CH1 2 +#define PWM_0_FTMCHAN_CH1 1 +#define PWM_0_PORT_CH1 PORTC +#define PWM_0_PIN_AF_CH1 4 + +/* PWM 1 device configuration */ +#define PWM_1_DEV FTM1 +#define PWM_1_CHANNELS 2 +#define PWM_1_CLK (SystemBusClock) +#define PWM_1_CLKEN() (BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM1_SHIFT) = 1) +#define PWM_1_CLKDIS() (BITBAND_REG32(SIM->SCGC6, SIM_SCGC6_FTM1_SHIFT) = 0) + +/* PWM 1 pin configuration */ +#define PWM_1_PORT_CLKEN() (BITBAND_REG32(SIM->SCGC5, SIM_SCGC5_PORTA_SHIFT) = 1) + +#define PWM_1_PIN_CH0 12 +#define PWM_1_FTMCHAN_CH0 0 +#define PWM_1_PORT_CH0 PORTA +#define PWM_1_PIN_AF_CH0 3 + +#define PWM_1_PIN_CH1 13 +#define PWM_1_FTMCHAN_CH1 1 +#define PWM_1_PORT_CH1 PORTA +#define PWM_1_PIN_AF_CH1 3 /** @} */