From ba66a3d63fe54f592bede26ebb8c58e129ab0af6 Mon Sep 17 00:00:00 2001 From: "Andreas \"Paul\" Pauli" <paul@localhost> Date: Mon, 1 Jun 2015 16:07:00 +0200 Subject: [PATCH] cpu/sam3x8e: add pwm peripheral driver --- boards/arduino-due/Makefile.features | 1 + boards/arduino-due/include/periph_conf.h | 32 +++ cpu/sam3x8e/periph/pwm.c | 291 +++++++++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 cpu/sam3x8e/periph/pwm.c diff --git a/boards/arduino-due/Makefile.features b/boards/arduino-due/Makefile.features index e75ff321ac..8acd3cf9b5 100644 --- a/boards/arduino-due/Makefile.features +++ b/boards/arduino-due/Makefile.features @@ -1,6 +1,7 @@ FEATURES_PROVIDED += cpp FEATURES_PROVIDED += periph_uart FEATURES_PROVIDED += periph_gpio +FEATURES_PROVIDED += periph_pwm FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_random FEATURES_MCU_GROUP = cortex_m3_1 diff --git a/boards/arduino-due/include/periph_conf.h b/boards/arduino-due/include/periph_conf.h index bf80529e77..6d1fa45f28 100644 --- a/boards/arduino-due/include/periph_conf.h +++ b/boards/arduino-due/include/periph_conf.h @@ -15,6 +15,7 @@ * * @author Hauke Petersen <hauke.petersen@fu-berlin.de> * @author Peter Kietzmann <peter.kietzmann@haw-hamburg.de> + * @author Andreas "Paul" Pauli <andreas.pauli@haw-hamburg.de> */ #ifndef __PERIPH_CONF_H @@ -342,6 +343,37 @@ extern "C" { #define GPIO_B21_MAP 31 /** @} */ +/** + * @name PWM configuration + * @{ + */ +#define PWM_NUMOF (1U) +#define PWM_0_EN (1) +#define PWM_MAX_VALUE (0xffff) +#define PWM_MAX_CHANNELS (4U) + +/* 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 +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/sam3x8e/periph/pwm.c b/cpu/sam3x8e/periph/pwm.c new file mode 100644 index 0000000000..22572620f5 --- /dev/null +++ b/cpu/sam3x8e/periph/pwm.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences + * + * 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_sam3x8e + * @{ + * + * @file + * @brief CPU specific low-level PWM driver implementation for the SAM3X8E + * + * @author Andreas "Paul" Pauli <andreas.pauli@haw-hamburg.de> + * + * @} + */ +#include <stdint.h> +#include "board.h" +#include "periph/pwm.h" + +/* + * guard file in case no PWM device is defined, + */ +#if PWM_NUMOF && PWM_0_EN + +#define ERR_INIT_MODE (-1) +#define ERR_INIT_BWTH (-2) +#define ERR_SET_CHAN (-1) +#define MCK_DIV_LB_MAX (10U) + +int pwm_init(pwm_t dev, pwm_mode_t mode, + unsigned int frequency, + unsigned int resolution) +{ + + int32_t retval = ERR_INIT_MODE; /* Worst/First case */ + uint32_t pwm_clk = 0; /* Desired/real pwm_clock */ + uint32_t diva = 1; /* Candidate for 8bit divider */ + uint32_t prea = 0; /* Candidate for clock select */ + + switch (dev) { +#if PWM_0_EN + + case PWM_0: + break; +#endif + + default: + return ERR_INIT_MODE; + } + + /* + * Mode check. + * Only PW_LEFT -which is hw default- supported for now. + */ + switch (mode) { + case PWM_LEFT: + break; + + case PWM_RIGHT: + case PWM_CENTER: + default: + return ERR_INIT_MODE; + } + + /* Should check if "|log_2 frequency|+|log_2 resolution| <= 32" */ + pwm_clk = frequency * resolution; + + /* + * 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 (F_CPU < pwm_clk) { /* Have to cut down resulting frequency. */ + frequency = F_CPU / resolution; + } + else { /* Estimate prescaler and divider. */ + diva = F_CPU / pwm_clk; + + while ((prea < MCK_DIV_LB_MAX) && (~0xff & diva)) { + prea = prea + 1; + diva = diva >> 1; + } + + frequency = F_CPU / ((resolution * diva) << prea); + } + + retval = frequency; + + /* Activate PWM block by enabling it's clock. */ + PMC->PMC_PCER1 = PMC_PCER1_PID36; + + /* Unlock User Interface */ + PWM_0_DEV->PWM_WPCR = PWM_ENA_CHID0; + + /* Disable all channels to allow CPRD updates. */ + PWM_0_DEV->PWM_DIS = 0xff; + + /* 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 = resolution - 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 = resolution - 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 = resolution - 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 = resolution - 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 retval; +} + +/* + * Update duty-cycle in channel with value. + * If value is larger than resolution set by pwm_init() it is cropped. + */ +int pwm_set(pwm_t dev, int channel, unsigned int value) +{ + + int retval = ERR_SET_CHAN; /* Worst case */ + 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 ERR_SET_CHAN; + } + + + 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: + retval = ERR_SET_CHAN; + } + + 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; + } + + retval = 0; + } + + return retval; +} + +/* + * Continue operation. + */ +void pwm_start(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + + case PWM_0: + PMC->PMC_PCER1 |= PMC_PCER1_PID36; + break; +#endif + } +} + +/* + * Stop operation and set output to 0. + */ +void pwm_stop(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + + case PWM_0: + PMC->PMC_PCDR1 |= PMC_PCDR1_PID36; + break; +#endif + } +} + +/* + * 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) +{ + switch (dev) { +#if PWM_0_EN + + case PWM_0: + PMC->PMC_PCER1 |= PMC_PCER1_PID36; + break; +#endif + } +} + +/* + * The device is set to power saving mode by disabling the clock. + */ +void pwm_poweroff(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + + case PWM_0: + PMC->PMC_PCDR1 |= PMC_PCDR1_PID36; + break; +#endif + } +} + +#endif /* PWM_NUMOF */ -- GitLab