mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #6207 from haukepetersen/opt_sam3_pwm
cpu/sam3: reworked PWM driver
This commit is contained in:
commit
4520d87121
@ -112,34 +112,18 @@ static const uart_conf_t uart_config[] = {
|
|||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name PWM configuration
|
* @brief PWM configuration
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
#define PWM_NUMOF (1U)
|
static const pwm_chan_conf_t pwm_chan[] = {
|
||||||
#define PWM_0_EN (1)
|
{ .pin = GPIO_PIN(PC, 21), .hwchan = 4, },
|
||||||
#define PWM_MAX_VALUE (0xffff)
|
{ .pin = GPIO_PIN(PC, 22), .hwchan = 5, },
|
||||||
#define PWM_MAX_CHANNELS (4U)
|
{ .pin = GPIO_PIN(PC, 23), .hwchan = 6, },
|
||||||
|
{ .pin = GPIO_PIN(PC, 24), .hwchan = 7, }
|
||||||
|
};
|
||||||
|
|
||||||
/* PWM_0 configuration */
|
#define PWM_NUMOF (1U)
|
||||||
#define PWM_0_DEV PWM
|
#define PWM_CHAN_NUMOF (sizeof(pwm_chan) / sizeof(pwm_chan[0]))
|
||||||
#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
|
#ifdef __cplusplus
|
||||||
|
@ -149,6 +149,22 @@ typedef struct {
|
|||||||
uint8_t irqn; /**< interrupt number of the device */
|
uint8_t irqn; /**< interrupt number of the device */
|
||||||
} uart_conf_t;
|
} 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
|
||||||
|
*
|
||||||
|
* @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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -242,6 +242,17 @@ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
|
|||||||
return 0;
|
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)
|
void gpio_irq_enable(gpio_t pin)
|
||||||
{
|
{
|
||||||
NVIC_EnableIRQ((1 << (_port_num(pin) + PIOA_IRQn)));
|
NVIC_EnableIRQ((1 << (_port_num(pin) + PIOA_IRQn)));
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Hamburg University of Applied Sciences
|
* 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
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
* Public License v2.1. See the file LICENSE in the top level directory for more
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
* details.
|
* directory for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,268 +14,135 @@
|
|||||||
* @file
|
* @file
|
||||||
* @brief CPU specific low-level PWM driver implementation for the SAM3X8E
|
* @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 <andreas.pauli@haw-hamburg.de>
|
* @author Andreas "Paul" Pauli <andreas.pauli@haw-hamburg.de>
|
||||||
|
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
||||||
*
|
*
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
#include <stdint.h>
|
|
||||||
#include "board.h"
|
#include "cpu.h"
|
||||||
#include "periph_conf.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 */
|
/* 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
|
#define PREA_MAX (10U)
|
||||||
* adapting this driver implementation... */
|
|
||||||
#include "periph/pwm.h"
|
|
||||||
|
|
||||||
#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_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 diva = 1; /* Candidate for 8bit divider */
|
||||||
uint32_t prea = 0; /* Candidate for clock select */
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* check if target frequency and resolution is applicable */
|
||||||
* Mode check.
|
if (pwm_clk > CLOCK_CORECLOCK) {
|
||||||
* Only PW_LEFT -which is hw default- supported for now.
|
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) {
|
while ((diva = (CLOCK_CORECLOCK / pwm_clk / (1 << prea))) > 255) {
|
||||||
case PWM_LEFT:
|
++prea;
|
||||||
break;
|
}
|
||||||
|
/* make sure PREA does not exceed its limit */
|
||||||
case PWM_RIGHT:
|
if (prea > PREA_MAX) {
|
||||||
case PWM_CENTER:
|
return 0;
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Should check if "|log_2 frequency|+|log_2 resolution| <= 32" */
|
/* activate PWM block by enabling it's clock. */
|
||||||
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. */
|
|
||||||
PMC->PMC_PCER1 = PMC_PCER1_PID36;
|
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 */
|
/* setup the configured channels */
|
||||||
PWM_0_DEV->PWM_WPCR = PWM_ENA_CHID0;
|
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. */
|
/* enable all configured channels */
|
||||||
PWM_0_DEV->PWM_DIS = 0xff;
|
PWM->PWM_ENA = pwm_chan_mask;
|
||||||
|
|
||||||
/* Step 2. Configure clock generator */
|
/* and return the actual configured frequency */
|
||||||
PWM_0_DEV->PWM_CLK = PWM_CLK_PREA(prea) | PWM_CLK_DIVA(diva) |
|
return (CLOCK_CORECLOCK / (1 << prea) / diva / res);
|
||||||
(~(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t pwm_channels(pwm_t dev)
|
uint8_t pwm_channels(pwm_t pwm)
|
||||||
{
|
{
|
||||||
if (dev == 0) {
|
assert(pwm == PWM_DEV(0));
|
||||||
return PWM_0_CHANNELS;
|
return (uint8_t)PWM_CHAN_NUMOF;
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update duty-cycle in channel with value.
|
* Update duty-cycle in channel with value.
|
||||||
* If value is larger than resolution set by pwm_init() it is cropped.
|
* 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 */
|
/* clip and set new value */
|
||||||
PwmCh_num *chan = (void *)0; /* Addressed channel. */
|
value = (value > pwm_period) ? pwm_period : value;
|
||||||
|
PWM->PWM_CH_NUM[pwm_chan[channel].hwchan].PWM_CDTYUPD = value;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void pwm_start(pwm_t pwm)
|
||||||
* Continue operation.
|
|
||||||
*/
|
|
||||||
void pwm_start(pwm_t dev)
|
|
||||||
{
|
{
|
||||||
switch (dev) {
|
assert(pwm == PWM_DEV(0));
|
||||||
#if PWM_0_EN
|
PWM->PWM_ENA = pwm_chan_mask;
|
||||||
|
|
||||||
case PWM_0:
|
|
||||||
PMC->PMC_PCER1 |= PMC_PCER1_PID36;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void pwm_stop(pwm_t pwm)
|
||||||
* Stop operation and set output to 0.
|
|
||||||
*/
|
|
||||||
void pwm_stop(pwm_t dev)
|
|
||||||
{
|
{
|
||||||
switch (dev) {
|
assert(pwm == PWM_DEV(0));
|
||||||
#if PWM_0_EN
|
PWM->PWM_ENA = 0;
|
||||||
|
|
||||||
case PWM_0:
|
|
||||||
PMC->PMC_PCDR1 |= PMC_PCDR1_PID36;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The device is reactivated by by clocking the device block.
|
* The device is reactivated by by clocking the device block.
|
||||||
* Operation continues where it has been stopped by poweroff.
|
* Operation continues where it has been stopped by poweroff.
|
||||||
*/
|
*/
|
||||||
void pwm_poweron(pwm_t dev)
|
void pwm_poweron(pwm_t pwm)
|
||||||
{
|
{
|
||||||
switch (dev) {
|
assert(pwm == PWM_DEV(0));
|
||||||
#if PWM_0_EN
|
PMC->PMC_PCER1 = PMC_PCDR1_PID36;
|
||||||
|
|
||||||
case PWM_0:
|
|
||||||
PMC->PMC_PCER1 |= PMC_PCER1_PID36;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The device is set to power saving mode by disabling the clock.
|
* 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) {
|
assert(pwm == PWM_DEV(0));
|
||||||
#if PWM_0_EN
|
PMC->PMC_PCDR1 = PMC_PCDR1_PID36;
|
||||||
|
|
||||||
case PWM_0:
|
|
||||||
PMC->PMC_PCDR1 |= PMC_PCDR1_PID36;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* (PWM_0_EN || PWM_1_EN) */
|
#endif /* PWM_NUMOF && PWM_CHAN_NUMOF */
|
||||||
|
Loading…
Reference in New Issue
Block a user