Merge pull request #3127 from haukepetersen/add_samd21_pwm
cpu/samd21: added peripheral PWM driver
This commit is contained in:
commit
0bdf2a2222
@ -1,11 +1,15 @@
|
||||
FEATURES_PROVIDED += transceiver
|
||||
|
||||
FEATURES_PROVIDED += cpp
|
||||
|
||||
FEATURES_PROVIDED += periph_gpio
|
||||
FEATURES_PROVIDED += periph_spi
|
||||
FEATURES_PROVIDED += cpp
|
||||
FEATURES_PROVIDED += periph_timer
|
||||
FEATURES_PROVIDED += periph_uart
|
||||
FEATURES_PROVIDED += periph_i2c
|
||||
FEATURES_PROVIDED += periph_rtc
|
||||
FEATURES_PROVIDED += periph_rtt
|
||||
FEATURES_PROVIDED += periph_cpuid
|
||||
FEATURES_PROVIDED += periph_pwm
|
||||
|
||||
FEATURES_MCU_GROUP = cortex_m0
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Freie Universität Berlin
|
||||
* Copyright (C) 2014-2015 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
|
||||
@ -15,12 +15,16 @@
|
||||
* Pro board
|
||||
*
|
||||
* @author Thomas Eichinger <thomas.eichinger@fu-berlin.de>
|
||||
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>s
|
||||
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
||||
* @author Peter Kietzmann <peter.kietzmann@haw-hamburg.de>
|
||||
*/
|
||||
|
||||
#ifndef __PERIPH_CONF_H
|
||||
#define __PERIPH_CONF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cpu.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -112,6 +116,60 @@ extern "C" {
|
||||
#define UART_0_PINS (PORT_PA04 | PORT_PA05)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name PWM configuration
|
||||
* @{
|
||||
*/
|
||||
#define PWM_NUMOF (PWM_0_EN + PWM_1_EN)
|
||||
#define PWM_0_EN 1
|
||||
#define PWM_1_EN 1
|
||||
#define PWM_MAX_CHANNELS 2
|
||||
/* for compatibility with test application */
|
||||
#define PWM_0_CHANNELS PWM_MAX_CHANNELS
|
||||
#define PWM_1_CHANNELS PWM_MAX_CHANNELS
|
||||
|
||||
/**
|
||||
* @brief PWM channel configuration data structure
|
||||
*
|
||||
* TODO: this should be moved into the CPU folder
|
||||
*/
|
||||
typedef struct {
|
||||
PortGroup *port; /**< GPIO port */
|
||||
uint8_t pin; /**< GPIO pin */
|
||||
uint8_t fnct; /**< pin function multiplex value */
|
||||
uint8_t chan; /**< TCC channel to use */
|
||||
} pwm_conf_chan_t;
|
||||
|
||||
/**
|
||||
* @brief PWM device configuration data structure
|
||||
*
|
||||
* TODO: this should be moved into the CPU folder
|
||||
*/
|
||||
typedef struct {
|
||||
Tcc *dev; /*< TCC device to use */
|
||||
pwm_conf_chan_t chan[2]; /**< channel configuration */
|
||||
} pwm_conf_t;
|
||||
|
||||
/* PWM device configuration */
|
||||
#if PWM_NUMOF
|
||||
static const pwm_conf_t pwm_config[] = {
|
||||
#if PWM_0_EN
|
||||
{TCC1, {
|
||||
/* port , pin, AF, chan */
|
||||
{(PortGroup *)0x41004400, 6, 4, 0},
|
||||
{(PortGroup *)0x41004400, 7, 4, 1}
|
||||
}},
|
||||
#endif
|
||||
#if PWM_1_EN
|
||||
{TCC0, {
|
||||
/* port , pin, AF, chan */
|
||||
{(PortGroup *)0x41004400, 18, 5, 2},
|
||||
{(PortGroup *)0x41004400, 19, 5, 3}
|
||||
}},
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name SPI configuration
|
||||
@ -241,8 +299,8 @@ extern "C" {
|
||||
#define GPIO_2_PIN (15)
|
||||
#define GPIO_2_EXTINT (15)
|
||||
/* GPIO channel 3 config */
|
||||
#define GPIO_3_DEV PORT->Group[0]
|
||||
#define GPIO_3_PIN (19)
|
||||
#define GPIO_3_DEV PORT->Group[1]
|
||||
#define GPIO_3_PIN (3)
|
||||
#define GPIO_3_EXTINT (3)
|
||||
/* GPIO 4-7 Internal radio pins*/
|
||||
/* GPIO channel 4 config radio CS*/
|
||||
|
||||
202
cpu/samd21/periph/pwm.c
Normal file
202
cpu/samd21/periph/pwm.c
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hamburg University of Applied Sciences
|
||||
* 2015 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup cpu_samd21
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Low-level PWM driver implementation
|
||||
*
|
||||
* @author Peter Kietzmann <peter.kietzmann@haw-hamburg.de>
|
||||
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "cpu.h"
|
||||
#include "board.h"
|
||||
#include "periph/pwm.h"
|
||||
#include "periph_conf.h"
|
||||
|
||||
/* ignore file in case no PWM devices are defined */
|
||||
#if PWM_NUMOF
|
||||
|
||||
static inline int _num(pwm_t dev)
|
||||
{
|
||||
return ((int)(pwm_config[dev].dev) & 0xc00) >> 10;
|
||||
}
|
||||
|
||||
static inline Tcc *_tcc(pwm_t dev)
|
||||
{
|
||||
return pwm_config[dev].dev;
|
||||
}
|
||||
|
||||
static inline uint8_t _chan(pwm_t dev, int chan)
|
||||
{
|
||||
return pwm_config[dev].chan[chan].chan;
|
||||
}
|
||||
|
||||
static int _clk_id(pwm_t dev)
|
||||
{
|
||||
if (_num(dev) == 2) {
|
||||
return TCC2_GCLK_ID;
|
||||
}
|
||||
return TCC0_GCLK_ID;
|
||||
}
|
||||
|
||||
static uint8_t get_prescaler(unsigned int target, int *scale)
|
||||
{
|
||||
if (target == 0) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if (target >= 512) {
|
||||
*scale = 1024;
|
||||
return TCC_CTRLA_PRESCALER_DIV1024_Val;
|
||||
}
|
||||
if (target >= 128) {
|
||||
*scale = 256;
|
||||
return TCC_CTRLA_PRESCALER_DIV256_Val;
|
||||
}
|
||||
if (target >= 32) {
|
||||
*scale = 64;
|
||||
return TCC_CTRLA_PRESCALER_DIV64_Val;
|
||||
}
|
||||
if (target >= 12) {
|
||||
*scale = 16;
|
||||
return TCC_CTRLA_PRESCALER_DIV16_Val;
|
||||
}
|
||||
if (target >= 6) {
|
||||
*scale = 8;
|
||||
return TCC_CTRLA_PRESCALER_DIV8_Val;
|
||||
}
|
||||
if (target >= 3) {
|
||||
*scale = 4;
|
||||
return TCC_CTRLA_PRESCALER_DIV4_Val;
|
||||
}
|
||||
*scale = target;
|
||||
return target - 1;
|
||||
}
|
||||
|
||||
int pwm_init(pwm_t dev, pwm_mode_t mode,
|
||||
unsigned int frequency, unsigned int resolution)
|
||||
{
|
||||
uint8_t prescaler;
|
||||
int scale = 1;
|
||||
int f_real;
|
||||
|
||||
if (dev >= PWM_NUMOF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* calculate the closest possible clock presacler */
|
||||
prescaler = get_prescaler(F_CPU / (frequency * resolution), &scale);
|
||||
if (prescaler == 0xff) {
|
||||
return -2;
|
||||
}
|
||||
f_real = (F_CPU / (scale * resolution));
|
||||
|
||||
/* configure the used pins */
|
||||
for (int i = 0; i < PWM_MAX_CHANNELS; i++) {
|
||||
PortGroup *port = pwm_config[dev].chan[i].port;
|
||||
int pin = pwm_config[dev].chan[i].pin;
|
||||
int fnct = pwm_config[dev].chan[i].fnct;
|
||||
/* set pin as output and enable the MUX */
|
||||
port->DIRSET.reg = (1 << pin);
|
||||
port->PINCFG[pin].reg = (PORT_PINCFG_PMUXEN);
|
||||
port->PMUX[pin >> 1].reg &= ~(0xf << (4 * (pin & 0x1)));
|
||||
port->PMUX[pin >> 1].reg |= (fnct << (4 * (pin & 0x1)));
|
||||
}
|
||||
|
||||
/* power on the device */
|
||||
pwm_poweron(dev);
|
||||
/* configure generic clock 0 to feed the PWM */
|
||||
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN
|
||||
| GCLK_CLKCTRL_GEN_GCLK0
|
||||
| (_clk_id(dev) << GCLK_CLKCTRL_ID_Pos));
|
||||
while (GCLK->STATUS.bit.SYNCBUSY);
|
||||
/* reset TCC module */
|
||||
_tcc(dev)->CTRLA.reg = TCC_CTRLA_SWRST;
|
||||
while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_SWRST);
|
||||
/* set PWM mode */
|
||||
switch (mode) {
|
||||
case PWM_LEFT:
|
||||
_tcc(dev)->CTRLBCLR.reg = TCC_CTRLBCLR_DIR; /* count up */
|
||||
break;
|
||||
case PWM_RIGHT:
|
||||
_tcc(dev)->CTRLBSET.reg = TCC_CTRLBSET_DIR; /* count down */
|
||||
break;
|
||||
case PWM_CENTER: /* currently not supported */
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_CTRLB);
|
||||
|
||||
/* configure the TCC device */
|
||||
_tcc(dev)->CTRLA.reg = (TCC_CTRLA_PRESCSYNC_GCLK_Val
|
||||
| TCC_CTRLA_PRESCALER(prescaler));
|
||||
/* select the waveform generation mode -> normal PWM */
|
||||
_tcc(dev)->WAVE.reg = (TCC_WAVE_WAVEGEN_NPWM);
|
||||
while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_WAVE);
|
||||
/* set the selected period */
|
||||
_tcc(dev)->PER.reg = (resolution - 1);
|
||||
while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_PER);
|
||||
/* start PWM operation */
|
||||
pwm_start(dev);
|
||||
/* return the actual frequency the PWM is running at */
|
||||
return f_real;
|
||||
}
|
||||
|
||||
int pwm_set(pwm_t dev, int channel, unsigned int value)
|
||||
{
|
||||
if (channel >= PWM_MAX_CHANNELS) {
|
||||
return -1;
|
||||
}
|
||||
_tcc(dev)->CC[_chan(dev, channel)].reg = value;
|
||||
while (_tcc(dev)->SYNCBUSY.reg & (TCC_SYNCBUSY_CC0 << _chan(dev, channel)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pwm_start(pwm_t dev)
|
||||
{
|
||||
_tcc(dev)->CTRLA.reg |= (TCC_CTRLA_ENABLE);
|
||||
}
|
||||
|
||||
void pwm_stop(pwm_t dev)
|
||||
{
|
||||
_tcc(dev)->CTRLA.reg &= ~(TCC_CTRLA_ENABLE);
|
||||
}
|
||||
|
||||
void pwm_poweron(pwm_t dev)
|
||||
{
|
||||
int num = _num(dev);
|
||||
if (num < 0) {
|
||||
return;
|
||||
}
|
||||
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN
|
||||
| (_clk_id(dev) << GCLK_CLKCTRL_ID_Pos));
|
||||
PM->APBCMASK.reg |= (PM_APBCMASK_TCC0 << num);
|
||||
}
|
||||
|
||||
void pwm_poweroff(pwm_t dev)
|
||||
{
|
||||
int num = _num(dev);
|
||||
if (num < 0) {
|
||||
return;
|
||||
}
|
||||
GCLK->CLKCTRL.reg = ((_clk_id(dev) << GCLK_CLKCTRL_ID_Pos));
|
||||
PM->APBCMASK.reg &= ~(1 << num);
|
||||
}
|
||||
|
||||
#endif /* PWM_NUMOF */
|
||||
Loading…
x
Reference in New Issue
Block a user