1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/kinetis_common/periph/pwm.c

399 lines
9.2 KiB
C

/*
* Copyright (C) 2014 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.
*/
/**
* @ingroup cpu_kinetis_common_pwm
*
* @{
*
* @file
* @brief Low-level PWM driver implementation
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Johann Fischer <j.fischer@phytec.de>
* @author Jonas Remmert <j.remmert@phytec.de>
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*
* @}
*/
#include <stdint.h>
#include <string.h>
#include "cpu.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
uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res)
{
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 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) {
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;
}
}
/* 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;
}
/* 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;
}
/* 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;
}
/* enable timer ergo the PWM generation */
pwm_start(dev);
/* Return actual frequency */
return (pwm_clk / (1 << prescaler)) / res;
}
uint8_t pwm_channels(pwm_t dev)
{
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;
}
}
void pwm_set(pwm_t dev, 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;
}
void pwm_start(pwm_t dev)
{
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
}
}
void pwm_stop(pwm_t dev)
{
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
}
}
void pwm_poweron(pwm_t dev)
{
switch (dev) {
#if PWM_0_EN
case PWM_0:
PWM_0_CLKEN();
break;
#endif
#if PWM_1_EN
case PWM_1:
PWM_1_CLKEN();
break;
#endif
}
}
void pwm_poweroff(pwm_t dev)
{
switch (dev) {
#if PWM_0_EN
case PWM_0:
PWM_0_CLKDIS();
break;
#endif
#if PWM_1_EN
case PWM_1:
PWM_1_CLKDIS();
break;
#endif
}
}