From 38c7d11f8c2982fd4058fae493841e38e9bee7fc Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Tue, 13 Dec 2016 15:49:57 +0100 Subject: [PATCH 1/3] cpu/sam3: add gpio_init_mux() function --- cpu/sam3/include/periph_cpu.h | 8 ++++++++ cpu/sam3/periph/gpio.c | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/cpu/sam3/include/periph_cpu.h b/cpu/sam3/include/periph_cpu.h index ffdabe961b..a584a8635a 100644 --- a/cpu/sam3/include/periph_cpu.h +++ b/cpu/sam3/include/periph_cpu.h @@ -149,6 +149,14 @@ typedef struct { uint8_t irqn; /**< interrupt number of the device */ } uart_conf_t; +/** + * @brief Configure the given GPIO pin to be used with the given MUX setting + * + * @param[in] pin GPIO pin to configure + * @param[in] mux MUX setting to use + */ +void gpio_init_mux(gpio_t pin, gpio_mux_t mux); + #ifdef __cplusplus } #endif diff --git a/cpu/sam3/periph/gpio.c b/cpu/sam3/periph/gpio.c index 7043054b46..bf79bfd494 100644 --- a/cpu/sam3/periph/gpio.c +++ b/cpu/sam3/periph/gpio.c @@ -242,6 +242,17 @@ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, return 0; } +void gpio_init_mux(gpio_t pin, gpio_mux_t mux) +{ + /* power on the corresponding port */ + PMC->PMC_PCER0 = (1 << (_port_num(pin) + 11)); + /* give peripheral control over the pin */ + _port(pin)->PIO_PDR = (1 << _pin_num(pin)); + /* and configure the MUX */ + _port(pin)->PIO_ABSR &= ~(1 << _pin_num(pin)); + _port(pin)->PIO_ABSR |= (mux << _pin_num(pin)); +} + void gpio_irq_enable(gpio_t pin) { NVIC_EnableIRQ((1 << (_port_num(pin) + PIOA_IRQn))); From adfd201a20b76fdad60322d56b9bd8c03433f3f6 Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Tue, 13 Dec 2016 15:50:22 +0100 Subject: [PATCH 2/3] cpu/sam3: reworked PWM driver implementation --- cpu/sam3/include/periph_cpu.h | 8 + cpu/sam3/periph/pwm.c | 292 ++++++++++------------------------ 2 files changed, 88 insertions(+), 212 deletions(-) diff --git a/cpu/sam3/include/periph_cpu.h b/cpu/sam3/include/periph_cpu.h index a584a8635a..65d3a84c8d 100644 --- a/cpu/sam3/include/periph_cpu.h +++ b/cpu/sam3/include/periph_cpu.h @@ -149,6 +149,14 @@ typedef struct { uint8_t irqn; /**< interrupt number of the device */ } uart_conf_t; +/** + * @brief PWM channel configuration data + */ +typedef struct { + gpio_t pin; /**< GPIO pin connected to the channel */ + uint8_t hwchan; /**< the HW channel used for a logical channel */ +} pwm_chan_conf_t; + /** * @brief Configure the given GPIO pin to be used with the given MUX setting * diff --git a/cpu/sam3/periph/pwm.c b/cpu/sam3/periph/pwm.c index 1fd22848a6..af694c4174 100644 --- a/cpu/sam3/periph/pwm.c +++ b/cpu/sam3/periph/pwm.c @@ -1,9 +1,10 @@ /* * Copyright (C) 2015 Hamburg University of Applied Sciences + * 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 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. */ /** @@ -13,268 +14,135 @@ * @file * @brief CPU specific low-level PWM driver implementation for the SAM3X8E * + * The SAM3 has only support for a single PWM device, so we accept only + * PWM_DEV(0) for this driver. + * * @author Andreas "Paul" Pauli + * @author Hauke Petersen * * @} */ -#include -#include "board.h" -#include "periph_conf.h" + +#include "cpu.h" +#include "assert.h" +#include "periph/pwm.h" +#include "periph/gpio.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" /* guard file in case no PWM device is defined */ -#if (PWM_0_EN || PWM_1_EN) +#if defined(PWM_NUMOF) && defined(PWM_CHAN_NUMOF) -/* pull the PWM header inside the guards for now. Guards will be removed on - * adapting this driver implementation... */ -#include "periph/pwm.h" +#define PREA_MAX (10U) -#define MCK_DIV_LB_MAX (10U) +static uint16_t pwm_period; +static uint8_t pwm_chan_mask; uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res) { - uint32_t pwm_clk = 0; /* Desired/real pwm_clock */ + uint32_t pwm_clk = freq * res; /* Desired/real pwm_clock */ uint32_t diva = 1; /* Candidate for 8bit divider */ uint32_t prea = 0; /* Candidate for clock select */ - if (dev != PWM_0) { + /* check for valid device and mode (left-aligned PWM only so far) */ + if ((dev != PWM_DEV(0)) || (mode != PWM_LEFT)) { return 0; } - /* - * Mode check. - * Only PW_LEFT -which is hw default- supported for now. + /* check if target frequency and resolution is applicable */ + if (pwm_clk > CLOCK_CORECLOCK) { + return 0; + } + + /* calculate pre-scalers for targeted frequency and resolution: + * clk = CORECLOCK / (2 ^ prea) / diva + * width prea := [0, 10] and diva [1, 255] */ - switch (mode) { - case PWM_LEFT: - break; - - case PWM_RIGHT: - case PWM_CENTER: - default: - return 0; + while ((diva = (CLOCK_CORECLOCK / pwm_clk / (1 << prea))) > 255) { + ++prea; + } + /* make sure PREA does not exceed its limit */ + if (prea > PREA_MAX) { + return 0; } - /* Should check if "|log_2 frequency|+|log_2 resolution| <= 32" */ - pwm_clk = freq * res; - - /* - * The pwm provides 11 prescaled clocks with (MCK/2^prea | prea=[0,10]) - * and a divider (diva) with a denominator range [1,255] in line. - */ - if (CLOCK_CORECLOCK < pwm_clk) { /* Have to cut down resulting frequency. */ - freq = CLOCK_CORECLOCK / res; - } - else { /* Estimate prescaler and divider. */ - diva = CLOCK_CORECLOCK / pwm_clk; - - while ((prea < MCK_DIV_LB_MAX) && (~0xff & diva)) { - prea = prea + 1; - diva = diva >> 1; - } - - freq = CLOCK_CORECLOCK / ((res * diva) << prea); - } - - /* Activate PWM block by enabling it's clock. */ + /* activate PWM block by enabling it's clock. */ PMC->PMC_PCER1 = PMC_PCER1_PID36; + /* disable all channels to allow CPRD updates. */ + PWM->PWM_DIS = 0xff; + /* configure clock generator */ + PWM->PWM_CLK = PWM_CLK_PREA(prea) | PWM_CLK_DIVA(diva); + /* remember the used resolution (for cropping inputs later) */ + pwm_period = res - 1; - /* Unlock User Interface */ - PWM_0_DEV->PWM_WPCR = PWM_ENA_CHID0; + /* setup the configured channels */ + pwm_chan_mask = 0; + for (unsigned i = 0; i < PWM_CHAN_NUMOF; i++) { + /* configure the use pin */ + gpio_init_mux(pwm_chan[i].pin, GPIO_MUX_B); + /* and setup the channel */ + pwm_chan_mask |= (1 << pwm_chan[i].hwchan); + PWM->PWM_CH_NUM[pwm_chan[i].hwchan].PWM_CMR = PWM_CMR_CPRE_CLKA; + PWM->PWM_CH_NUM[pwm_chan[i].hwchan].PWM_CPRD = pwm_period; + PWM->PWM_CH_NUM[pwm_chan[i].hwchan].PWM_CDTY = 0; + } - /* Disable all channels to allow CPRD updates. */ - PWM_0_DEV->PWM_DIS = 0xff; + /* enable all configured channels */ + PWM->PWM_ENA = pwm_chan_mask; - /* Step 2. Configure clock generator */ - PWM_0_DEV->PWM_CLK = PWM_CLK_PREA(prea) | PWM_CLK_DIVA(diva) | - (~(PWM_CLK_PREA_Msk | PWM_CLK_DIVA_Msk) & - PWM_0_DEV->PWM_CLK); - - /* +++++++++++ Configure and init channels +++++++++++++++ */ - - /* Set clock source, resolution, duty-cycle and enable */ -#if PWM_0_CHANNELS > 0 - PWM_0_DEV_CH0->PWM_CMR = PWM_CMR_CPRE_CLKA; - PWM_0_DEV_CH0->PWM_CPRD = res - 1; - PWM_0_DEV_CH0->PWM_CDTY = 0; - PWM_0_DEV->PWM_ENA = PWM_0_ENA_CH0; -#endif -#if PWM_0_CHANNELS > 1 - PWM_0_DEV_CH1->PWM_CMR = PWM_CMR_CPRE_CLKA; - PWM_0_DEV_CH1->PWM_CPRD = res - 1; - PWM_0_DEV_CH1->PWM_CDTY = 0; - PWM_0_DEV->PWM_ENA = PWM_0_ENA_CH1; -#endif -#if PWM_0_CHANNELS > 2 - PWM_0_DEV_CH2->PWM_CMR = PWM_CMR_CPRE_CLKA; - PWM_0_DEV_CH2->PWM_CPRD = res - 1; - PWM_0_DEV_CH2->PWM_CDTY = 0; - PWM_0_DEV->PWM_ENA = PWM_0_ENA_CH2; -#endif -#if PWM_0_CHANNELS > 3 - PWM_0_DEV_CH3->PWM_CMR = PWM_CMR_CPRE_CLKA; - PWM_0_DEV_CH3->PWM_CPRD = res - 1; - PWM_0_DEV_CH3->PWM_CDTY = 0; - PWM_0_DEV->PWM_ENA = PWM_0_ENA_CH3; -#endif - - /* +++++++++++ Configure and init channels ready++++++++++ */ - - - /* - * Disable GPIO and set peripheral A/B ("0/1") for pwm channel pins. - */ -#if PWM_0_CHANNELS > 0 - PWM_0_PORT_CH0->PIO_PDR |= PWM_0_PIN_CH0; - PWM_0_PORT_CH0->PIO_ABSR |= PWM_0_PIN_CH0; -#endif -#if PWM_0_CHANNELS > 1 - PWM_0_PORT_CH1->PIO_PDR |= PWM_0_PIN_CH1; - PWM_0_PORT_CH1->PIO_ABSR |= PWM_0_PIN_CH1; -#endif -#if PWM_0_CHANNELS > 2 - PWM_0_PORT_CH2->PIO_PDR |= PWM_0_PIN_CH2; - PWM_0_PORT_CH2->PIO_ABSR |= PWM_0_PIN_CH2; -#endif -#if PWM_0_CHANNELS > 3 - PWM_0_PORT_CH3->PIO_PDR |= PWM_0_PIN_CH3; - PWM_0_PORT_CH3->PIO_ABSR |= PWM_0_PIN_CH3; -#endif - - return freq; + /* and return the actual configured frequency */ + return (CLOCK_CORECLOCK / (1 << prea) / diva / res); } -uint8_t pwm_channels(pwm_t dev) +uint8_t pwm_channels(pwm_t pwm) { - if (dev == 0) { - return PWM_0_CHANNELS; - } - return 0; + assert(pwm == PWM_DEV(0)); + return (uint8_t)PWM_CHAN_NUMOF; } /* * Update duty-cycle in channel with value. * If value is larger than resolution set by pwm_init() it is cropped. */ -void pwm_set(pwm_t dev, uint8_t channel, uint16_t value) +void pwm_set(pwm_t pwm, uint8_t channel, uint16_t value) { + assert((pwm == PWM_DEV(0)) && (channel < PWM_CHAN_NUMOF)); - uint32_t period = 0; /* Store pwm period */ - PwmCh_num *chan = (void *)0; /* Addressed channel. */ - - switch (dev) { -#if PWM_0_EN - - case PWM_0: - break; -#endif - - default: - return; - } - - - switch (channel) { -#if PWM_0_CHANNELS > 0 - - case 0: - chan = PWM_0_DEV_CH0; - break; -#endif -#if PWM_0_CHANNELS > 1 - - case 1: - chan = PWM_0_DEV_CH1; - break; -#endif -#if PWM_0_CHANNELS > 2 - - case 2: - chan = PWM_0_DEV_CH2; - break; -#endif -#if PWM_0_CHANNELS > 3 - - case 3: - chan = PWM_0_DEV_CH3; - break; -#endif - - default: - return; - } - - if (chan) { - period = chan->PWM_CPRD & PWM_CPRD_CPRD_Msk; - - - if (value < period) { - chan->PWM_CDTYUPD = value; - } - else { /* Value Out of range. Clip silent as required by interface. */ - chan->PWM_CDTYUPD = period; - } - } + /* clip and set new value */ + value = (value > pwm_period) ? pwm_period : value; + PWM->PWM_CH_NUM[pwm_chan[channel].hwchan].PWM_CDTYUPD = value; } -/* - * Continue operation. - */ -void pwm_start(pwm_t dev) +void pwm_start(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - - case PWM_0: - PMC->PMC_PCER1 |= PMC_PCER1_PID36; - break; -#endif - } + assert(pwm == PWM_DEV(0)); + PWM->PWM_ENA = pwm_chan_mask; } -/* - * Stop operation and set output to 0. - */ -void pwm_stop(pwm_t dev) +void pwm_stop(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - - case PWM_0: - PMC->PMC_PCDR1 |= PMC_PCDR1_PID36; - break; -#endif - } + assert(pwm == PWM_DEV(0)); + PWM->PWM_ENA = 0; } /* * The device is reactivated by by clocking the device block. * Operation continues where it has been stopped by poweroff. */ -void pwm_poweron(pwm_t dev) +void pwm_poweron(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - - case PWM_0: - PMC->PMC_PCER1 |= PMC_PCER1_PID36; - break; -#endif - } + assert(pwm == PWM_DEV(0)); + PMC->PMC_PCER1 = PMC_PCDR1_PID36; } /* * The device is set to power saving mode by disabling the clock. */ -void pwm_poweroff(pwm_t dev) +void pwm_poweroff(pwm_t pwm) { - switch (dev) { -#if PWM_0_EN - - case PWM_0: - PMC->PMC_PCDR1 |= PMC_PCDR1_PID36; - break; -#endif - } + assert(pwm == PWM_DEV(0)); + PMC->PMC_PCDR1 = PMC_PCDR1_PID36; } -#endif /* (PWM_0_EN || PWM_1_EN) */ +#endif /* PWM_NUMOF && PWM_CHAN_NUMOF */ From 08f350093441a2d70f54e63eba31376e1cd94eec Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Tue, 13 Dec 2016 15:51:47 +0100 Subject: [PATCH 3/3] boards/arduino-due: adaped PWM configuration --- boards/arduino-due/include/periph_conf.h | 34 +++++++----------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/boards/arduino-due/include/periph_conf.h b/boards/arduino-due/include/periph_conf.h index cef749f238..bfdfa13945 100644 --- a/boards/arduino-due/include/periph_conf.h +++ b/boards/arduino-due/include/periph_conf.h @@ -112,34 +112,18 @@ static const uart_conf_t uart_config[] = { /** @} */ /** - * @name PWM configuration + * @brief PWM configuration * @{ */ -#define PWM_NUMOF (1U) -#define PWM_0_EN (1) -#define PWM_MAX_VALUE (0xffff) -#define PWM_MAX_CHANNELS (4U) +static const pwm_chan_conf_t pwm_chan[] = { + { .pin = GPIO_PIN(PC, 21), .hwchan = 4, }, + { .pin = GPIO_PIN(PC, 22), .hwchan = 5, }, + { .pin = GPIO_PIN(PC, 23), .hwchan = 6, }, + { .pin = GPIO_PIN(PC, 24), .hwchan = 7, } +}; -/* PWM_0 configuration */ -#define PWM_0_DEV PWM -#define PWM_0_PID ID_PWM -#define PWM_0_CHANNELS (4U) -#define PWM_0_DEV_CH0 (&(PWM_0_DEV->PWM_CH_NUM[4])) -#define PWM_0_ENA_CH0 PWM_ENA_CHID4 -#define PWM_0_PORT_CH0 PIOC -#define PWM_0_PIN_CH0 PIO_PC21B_PWML4 -#define PWM_0_DEV_CH1 (&(PWM_0_DEV->PWM_CH_NUM[5])) -#define PWM_0_ENA_CH1 PWM_ENA_CHID5 -#define PWM_0_PORT_CH1 PIOC -#define PWM_0_PIN_CH1 PIO_PC22B_PWML5 -#define PWM_0_DEV_CH2 (&(PWM_0_DEV->PWM_CH_NUM[6])) -#define PWM_0_ENA_CH2 PWM_ENA_CHID6 -#define PWM_0_PORT_CH2 PIOC -#define PWM_0_PIN_CH2 PIO_PC23B_PWML6 -#define PWM_0_DEV_CH3 (&(PWM_0_DEV->PWM_CH_NUM[7])) -#define PWM_0_ENA_CH3 PWM_ENA_CHID7 -#define PWM_0_PORT_CH3 PIOC -#define PWM_0_PIN_CH3 PIO_PC24B_PWML7 +#define PWM_NUMOF (1U) +#define PWM_CHAN_NUMOF (sizeof(pwm_chan) / sizeof(pwm_chan[0])) /** @} */ #ifdef __cplusplus