1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 14:33:52 +01:00

Merge pull request #10498 from smlng/pr/adapt/7542

atmega: add periph_pwm (taken from #7542)
This commit is contained in:
Kevin "Bear Puncher" Weiss 2018-11-29 15:54:51 +01:00 committed by GitHub
commit 153759b926
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 291 additions and 0 deletions

View File

@ -9,6 +9,7 @@ FEATURES_PROVIDED += periph_uart
# Various other features (if any)
ifeq (,$(filter jiminy-mega256rfr2,$(BOARD)))
FEATURES_PROVIDED += arduino
FEATURES_PROVIDED += periph_pwm
endif
# The board MPU family (used for grouping by the CI system)

View File

@ -24,6 +24,8 @@
#ifndef PERIPH_CONF_H
#define PERIPH_CONF_H
#include "periph_cpu.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -165,6 +167,46 @@ extern "C" {
#endif
/** @} */
/**
* @name PWM configuration
*
* The current implementation supports only 8-bit timers for PWM generation.
* These timers are typically timer 0 and timer 2 in Atmega2560/1281/328p.
*
* Setting the first channel to GPIO_UNDEF allows multiple resolutions for the
* PWM channel. Otherwise the resolution is fixed to 256, allowing duty cycle
* values ranging from 0 to 255.
*
* @{
*/
#if defined(CPU_ATMEGA328P)
#define PWM_PINS_CH0 { GPIO_PIN(PORT_D, 6), GPIO_PIN(PORT_D, 5) }
#define PWM_PINS_CH1 { GPIO_PIN(PORT_B, 3), GPIO_PIN(PORT_D, 3) }
#elif defined(CPU_ATMEGA2560)
#define PWM_PINS_CH0 { GPIO_PIN(PORT_B, 7), GPIO_PIN(PORT_G, 5) }
#define PWM_PINS_CH1 { GPIO_PIN(PORT_B, 4), GPIO_PIN(PORT_H, 6) }
#elif defined(CPU_ATMEGA1281)
#define PWM_PINS_CH0 { GPIO_PIN(PORT_B, 7), GPIO_PIN(PORT_G, 5) }
#define PWM_PINS_CH1 { GPIO_PIN(PORT_B, 4), GPIO_UNDEF }
#endif
static const pwm_conf_t pwm_conf[] = {
{
.dev = MINI_TIMER0,
.pin_ch = PWM_PINS_CH0,
.div = MINI_TIMER0_DIV,
},
{
.dev = MINI_TIMER2,
.pin_ch = PWM_PINS_CH1,
.div = MINI_TIMER2_DIV,
}
};
#define PWM_NUMOF (sizeof(pwm_conf) / sizeof(pwm_conf[0]))
/** @} */
#ifdef __cplusplus
}
#endif

View File

@ -49,6 +49,16 @@ typedef struct {
REG16 OCR[3]; /**< output compare */
} mega_timer_t;
/**
* @brief 8-bit timer register map
*/
typedef struct {
REG8 CRA; /**< control A */
REG8 CRB; /**< control B */
REG8 CNT; /**< counter */
REG8 OCR[2]; /**< output compare */
} mini_timer_t;
/**
* @brief UART register map
*/
@ -65,11 +75,21 @@ typedef struct {
* @brief Timer register definitions and instances
* @{
*/
#if defined(TCCR0A)
#define MINI_TIMER0 ((mini_timer_t *)(uint16_t *)(&TCCR0A))
#define MINI_TIMER0_DIV TIMER_DIV1_8_64_128_1024
#endif
#if defined(TCCR1A)
#define MEGA_TIMER1_BASE (uint16_t *)(&TCCR1A)
#define MEGA_TIMER1 ((mega_timer_t *)MEGA_TIMER1_BASE)
#endif
#if defined(TCCR2A)
#define MINI_TIMER2 ((mini_timer_t *)(uint16_t *)(&TCCR2A))
#define MINI_TIMER2_DIV TIMER_DIV1_8_32_64_128_256_1024
#endif
#if defined(TCCR3A)
#define MEGA_TIMER3_BASE (uint16_t *)(&TCCR3A)
#define MEGA_TIMER3 ((mega_timer_t *)MEGA_TIMER3_BASE)

View File

@ -23,10 +23,27 @@
#ifndef PERIPH_CPU_COMMON_H
#define PERIPH_CPU_COMMON_H
#include "cpu.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef DOXYGEN
/**
* @brief Overwrite the default gpio_t type definition
* @{
*/
#define HAVE_GPIO_T
typedef uint8_t gpio_t;
/** @} */
#endif
/**
* @brief Definition of a fitting UNDEF value
*/
#define GPIO_UNDEF (0xff)
/**
* @brief Define a CPU specific GPIO pin generator macro
*/
@ -108,6 +125,27 @@ typedef enum {
} spi_clk_t;
/** @} */
/**
* @brief Bitmasks indicating which are the possible dividers for a timer
* @{
*/
typedef enum {
TIMER_DIV1_8_64_128_1024 = 0x549, /**< 1/{1,8,64,128,1024} */
TIMER_DIV1_8_32_64_128_256_1024 = 0x5E9, /**< 1/{1,8,32,64,128,256,1024} */
} timer_div_t;
/** @} */
/**
* @brief PWM configuration
* @{
*/
typedef struct {
mini_timer_t *dev; /**< Timer used */
gpio_t pin_ch[2]; /**< Output Pins */
timer_div_t div; /**< Timer divider mask */
} pwm_conf_t;
/** @} */
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,188 @@
/*
* Copyright (C) 2017 Víctor Ariño
*
* 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_atmega_common
* @ingroup drivers_periph_pwm
* @{
*
* @file
* @brief Low-level PWM driver implementation
*
* @author Víctor Ariño <victor@lebrush.org>
*
* @}
*/
#include "cpu.h"
#include "assert.h"
#include "periph/pwm.h"
#include "periph/gpio.h"
#ifdef PWM_NUMOF
#define WGM0 0
#define WGM1 1
#define WGM2 3
#define COMA0 6
#define COMA1 7
static struct {
uint8_t CRA;
uint8_t CRB;
uint8_t res;
} state[PWM_NUMOF];
static inline unsigned get_prescaler(pwm_t dev, uint32_t *scale)
{
uint16_t divmask = pwm_conf[dev].div;
uint32_t target = *scale;
uint8_t div, pre = 0;
for (div = 0; divmask > 0; div++) {
if (divmask & 0x01) {
pre++;
*scale = (uint32_t)(1 << div);
if (*scale >= target) {
break;
}
}
divmask >>= 1;
}
return pre;
}
uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res)
{
/* only left implemented, max resolution 256 */
assert(dev < PWM_NUMOF && mode == PWM_LEFT && res <= 256);
/* resolution != 256 only valid if ch0 not used */
assert(!(res != 256 && pwm_conf[dev].pin_ch[0] != GPIO_UNDEF));
/* disable PWM */
pwm_conf[dev].dev->CRA = 0x00;
pwm_conf[dev].dev->CRB = 0x00;
pwm_conf[dev].dev->OCR[0] = 0;
pwm_conf[dev].dev->OCR[1] = 0;
/* disable power reduction */
if (dev) {
power_timer2_enable();
}
else {
power_timer0_enable();
}
/* find out prescaler */
uint32_t scale = (CLOCK_CORECLOCK / (freq * (uint32_t)res));
unsigned pre = get_prescaler(dev, &scale);
freq = (CLOCK_CORECLOCK / (scale * (uint32_t)res));
/* compute register values and enable pins */
uint8_t cra = _BV(WGM1) | _BV(WGM0);
uint8_t crb = 0;
res -= 1;
/* configure pins and resolution. Output must be low at initialization.
* Force the pin low to avoid flickering. */
if (pwm_conf[dev].pin_ch[0] != GPIO_UNDEF) {
gpio_init(pwm_conf[dev].pin_ch[0], GPIO_OUT);
gpio_clear(pwm_conf[dev].pin_ch[0]);
}
else {
crb |= _BV(WGM2);
pwm_conf[dev].dev->OCR[0] = (uint8_t)res;
}
if (pwm_conf[dev].pin_ch[1] != GPIO_UNDEF) {
gpio_init(pwm_conf[dev].pin_ch[1], GPIO_OUT);
gpio_clear(pwm_conf[dev].pin_ch[1]);
}
pwm_conf[dev].dev->CRA = cra;
pwm_conf[dev].dev->CRB = crb | (pre);
state[dev].CRA = cra;
state[dev].CRB = crb | (pre);
state[dev].res = res;
/* return real frequency */
return freq;
}
uint8_t pwm_channels(pwm_t dev)
{
assert(dev < PWM_NUMOF);
/* a pwm with no channels enabled makes no sense. Assume at least one is
* enabled */
if (pwm_conf[dev].pin_ch[0] == GPIO_UNDEF ||
pwm_conf[dev].pin_ch[1] == GPIO_UNDEF) {
return 1;
}
return 2;
}
void pwm_set(pwm_t dev, uint8_t ch, uint16_t value)
{
assert(dev < PWM_NUMOF && ch <= 1 && pwm_conf[dev].pin_ch[ch] != GPIO_UNDEF);
/* output flickers when duty cycle is 0 or 100%. Simply force the pin
* low or high respectively to have a clean output. */
uint8_t bit = (_BV(COMA1) >> (ch << 1));
if (value >= state[dev].res) {
pwm_conf[dev].dev->CRA &= ~bit;
gpio_set(pwm_conf[dev].pin_ch[ch]);
}
else if (value == 0) {
pwm_conf[dev].dev->CRA &= ~bit;
gpio_clear(pwm_conf[dev].pin_ch[ch]);
}
else {
pwm_conf[dev].dev->OCR[ch] = value;
pwm_conf[dev].dev->CRA |= bit;
}
}
void pwm_poweron(pwm_t dev)
{
assert(dev < PWM_NUMOF);
/* disable power reduction */
if (dev) {
power_timer2_enable();
}
else {
power_timer0_enable();
}
pwm_conf[dev].dev->CRA = state[dev].CRA;
pwm_conf[dev].dev->CRB = state[dev].CRB;
}
void pwm_poweroff(pwm_t dev)
{
assert(dev < PWM_NUMOF);
pwm_conf[dev].dev->CRA = 0x00;
pwm_conf[dev].dev->CRB = 0x00;
/* disable power reduction */
if (dev) {
power_timer2_disable();
}
else {
power_timer0_disable();
}
if (pwm_conf[dev].pin_ch[0] != GPIO_UNDEF) {
gpio_clear(pwm_conf[dev].pin_ch[0]);
}
if (pwm_conf[dev].pin_ch[1] != GPIO_UNDEF) {
gpio_clear(pwm_conf[dev].pin_ch[1]);
}
}
#endif /* PWM_NUMOF */

View File

@ -1,6 +1,8 @@
BOARD ?= samr21-xpro
include ../Makefile.tests_common
BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-uno
FEATURES_REQUIRED = periph_pwm
USEMODULE += xtimer