mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #8747 from SemjonKerner/driver_periph_pwm_nrf51_calliope
cpu/nrf51: add PWM implementation
This commit is contained in:
commit
ded2a44c77
@ -4,6 +4,7 @@ FEATURES_PROVIDED += periph_i2c
|
||||
FEATURES_PROVIDED += periph_rtt
|
||||
FEATURES_PROVIDED += periph_timer
|
||||
FEATURES_PROVIDED += periph_uart
|
||||
FEATURES_PROVIDED += periph_pwm
|
||||
|
||||
# Various other features (if any)
|
||||
FEATURES_PROVIDED += radio_nrfmin
|
||||
|
@ -57,18 +57,11 @@ static const timer_conf_t timer_config[] = {
|
||||
.channels = 3,
|
||||
.bitmode = TIMER_BITMODE_BITMODE_16Bit,
|
||||
.irqn = TIMER1_IRQn
|
||||
},
|
||||
{
|
||||
.dev = NRF_TIMER2,
|
||||
.channels = 3,
|
||||
.bitmode = TIMER_BITMODE_BITMODE_16Bit,
|
||||
.irqn = TIMER2_IRQn
|
||||
}
|
||||
};
|
||||
|
||||
#define TIMER_0_ISR isr_timer0
|
||||
#define TIMER_1_ISR isr_timer1
|
||||
#define TIMER_2_ISR isr_timer2
|
||||
|
||||
#define TIMER_NUMOF (sizeof(timer_config) / sizeof(timer_config[0]))
|
||||
/** @} */
|
||||
@ -129,6 +122,15 @@ static const i2c_conf_t i2c_config[] = {
|
||||
#define RADIO_IRQ_PRIO 1
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name PWM configuration
|
||||
* @{
|
||||
*/
|
||||
#define PWM_NUMOF (1U)
|
||||
#define PWM_TIMER NRF_TIMER2
|
||||
#define PWM_PIN (0U)
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -59,6 +59,15 @@ extern "C" {
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief CPU specific PWM configuration
|
||||
* @{
|
||||
*/
|
||||
#define PWM_GPIOTE_CH (2U)
|
||||
#define PWM_PPI_A (0U)
|
||||
#define PWM_PPI_B (1U)
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
245
cpu/nrf51/periph/pwm.c
Normal file
245
cpu/nrf51/periph/pwm.c
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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_nrf51
|
||||
* @ingroup drivers_periph_pwm
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Low-level PWM driver implementation
|
||||
*
|
||||
* @author Semjon Kerner <semjon.kerner@fu-berlin.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/pwm.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
#define PWM_PS_MAX (9U)
|
||||
#define PWM_PPI_CHANNELS ((1 << PWM_PPI_A) | (1 << PWM_PPI_B))
|
||||
#ifndef PWM_PERCENT_VAL
|
||||
#define PWM_PERCENT_VAL (1U)
|
||||
#endif
|
||||
|
||||
static const uint32_t divtable[10] = {
|
||||
(CLOCK_CORECLOCK >> 0),
|
||||
(CLOCK_CORECLOCK >> 1),
|
||||
(CLOCK_CORECLOCK >> 2),
|
||||
(CLOCK_CORECLOCK >> 3),
|
||||
(CLOCK_CORECLOCK >> 4),
|
||||
(CLOCK_CORECLOCK >> 5),
|
||||
(CLOCK_CORECLOCK >> 6),
|
||||
(CLOCK_CORECLOCK >> 7),
|
||||
(CLOCK_CORECLOCK >> 8),
|
||||
(CLOCK_CORECLOCK >> 9),
|
||||
};
|
||||
|
||||
static uint32_t init_data[2];
|
||||
|
||||
uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res)
|
||||
{
|
||||
assert(dev == 0 && ((mode == PWM_LEFT) || (mode == PWM_RIGHT)));
|
||||
|
||||
/* reset and configure the timer */
|
||||
PWM_TIMER->POWER = 1;
|
||||
PWM_TIMER->TASKS_STOP = 1;
|
||||
PWM_TIMER->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
|
||||
PWM_TIMER->MODE = TIMER_MODE_MODE_Timer;
|
||||
PWM_TIMER->TASKS_CLEAR = 1;
|
||||
|
||||
/* calculate and set prescaler */
|
||||
uint32_t timer_freq = freq * res;
|
||||
uint32_t lower = (timer_freq - (PWM_PERCENT_VAL * (timer_freq / 100)));
|
||||
uint32_t upper = (timer_freq + (PWM_PERCENT_VAL * (timer_freq / 100)));
|
||||
for (uint32_t ps = 0; ps <= (PWM_PS_MAX + 1); ps++) {
|
||||
if (ps == (PWM_PS_MAX + 1)) {
|
||||
DEBUG("[pwm] init error: resolution or frequency not supported\n");
|
||||
return 0;
|
||||
}
|
||||
if ((divtable[ps] < upper) && (divtable[ps] > lower)) {
|
||||
PWM_TIMER->PRESCALER = ps;
|
||||
timer_freq = divtable[ps];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* reset timer compare events */
|
||||
PWM_TIMER->EVENTS_COMPARE[0] = 0;
|
||||
PWM_TIMER->EVENTS_COMPARE[1] = 0;
|
||||
/* init timer compare values */
|
||||
PWM_TIMER->CC[0] = 1;
|
||||
PWM_TIMER->CC[1] = res;
|
||||
|
||||
/* configure PPI Event (set compare values and pwm width) */
|
||||
if (mode == PWM_LEFT) {
|
||||
NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] = (GPIOTE_CONFIG_MODE_Task |
|
||||
(PWM_PIN << 8) |
|
||||
GPIOTE_CONFIG_POLARITY_Msk |
|
||||
GPIOTE_CONFIG_OUTINIT_Msk);
|
||||
}
|
||||
else if (mode == PWM_RIGHT) {
|
||||
NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] = (GPIOTE_CONFIG_MODE_Task |
|
||||
(PWM_PIN << 8) |
|
||||
GPIOTE_CONFIG_POLARITY_Msk);
|
||||
}
|
||||
|
||||
/* configure PPI Channels (connect compare-event and gpiote-task) */
|
||||
NRF_PPI->CH[PWM_PPI_A].EEP = (uint32_t)(&PWM_TIMER->EVENTS_COMPARE[0]);
|
||||
NRF_PPI->CH[PWM_PPI_B].EEP = (uint32_t)(&PWM_TIMER->EVENTS_COMPARE[1]);
|
||||
|
||||
NRF_PPI->CH[PWM_PPI_A].TEP =
|
||||
(uint32_t)(&NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH]);
|
||||
NRF_PPI->CH[PWM_PPI_B].TEP =
|
||||
(uint32_t)(&NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH]);
|
||||
|
||||
/* enable configured PPI Channels */
|
||||
NRF_PPI->CHENSET = PWM_PPI_CHANNELS;
|
||||
|
||||
/* shortcut to reset Counter after CC[1] event */
|
||||
PWM_TIMER->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Msk;
|
||||
|
||||
/* start pwm with value '0' */
|
||||
pwm_set(dev, 0, 0);
|
||||
|
||||
DEBUG("Timer frequency is set to %ld\n", timer_freq);
|
||||
|
||||
return (uint32_t)(timer_freq / res);
|
||||
}
|
||||
|
||||
void pwm_set(pwm_t dev, uint8_t channel, uint16_t value)
|
||||
{
|
||||
assert((dev == 0) && (channel == 0));
|
||||
|
||||
/*
|
||||
* make sure duty cycle is set at the beggining of each period
|
||||
* ensure to stop the timer as soon as possible
|
||||
*/
|
||||
PWM_TIMER->TASKS_STOP = 1;
|
||||
PWM_TIMER->EVENTS_COMPARE[1] = 0;
|
||||
PWM_TIMER->SHORTS = TIMER_SHORTS_COMPARE1_STOP_Msk;
|
||||
PWM_TIMER->TASKS_START = 1;
|
||||
|
||||
/*
|
||||
* waiting for the timer to stop
|
||||
* This loop generates heavy load. This is not optimal therefore a local
|
||||
* sleep function should be implemented.
|
||||
*/
|
||||
while (PWM_TIMER->EVENTS_COMPARE[1] == 0) {};
|
||||
|
||||
/*
|
||||
* checking pwm alignment first
|
||||
* and guarding if duty cycle is 0% / 100%
|
||||
*/
|
||||
if (NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] & GPIOTE_CONFIG_OUTINIT_Msk) {
|
||||
if (value == 0) {
|
||||
if (PWM_TIMER->CC[0] != 0) {
|
||||
NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
|
||||
}
|
||||
|
||||
NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
|
||||
PWM_TIMER->CC[0] = 0;
|
||||
} else {
|
||||
if (PWM_TIMER->CC[0] == 0) {
|
||||
NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
|
||||
}
|
||||
if (value >= PWM_TIMER->CC[1]) {
|
||||
NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
|
||||
PWM_TIMER->CC[0] = PWM_TIMER->CC[1];
|
||||
} else {
|
||||
NRF_PPI->CHENSET = PWM_PPI_CHANNELS;
|
||||
PWM_TIMER->CC[0] = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value >= PWM_TIMER->CC[1]) {
|
||||
if (PWM_TIMER->CC[0] != PWM_TIMER->CC[1]) {
|
||||
NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
|
||||
}
|
||||
NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
|
||||
PWM_TIMER->CC[0] = PWM_TIMER->CC[1];
|
||||
} else {
|
||||
if (PWM_TIMER->CC[0] == PWM_TIMER->CC[1]) {
|
||||
NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
|
||||
}
|
||||
if (value == 0) {
|
||||
NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
|
||||
PWM_TIMER->CC[0] = 0;
|
||||
} else {
|
||||
NRF_PPI->CHENSET = PWM_PPI_CHANNELS;
|
||||
PWM_TIMER->CC[0] = PWM_TIMER->CC[1] - value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* reconfigure pwm to standard mode */
|
||||
PWM_TIMER->TASKS_CLEAR = 1;
|
||||
PWM_TIMER->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Msk;
|
||||
PWM_TIMER->TASKS_START = 1;
|
||||
}
|
||||
|
||||
uint8_t pwm_channels(pwm_t dev)
|
||||
{
|
||||
assert(dev == 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void pwm_poweron(pwm_t dev)
|
||||
{
|
||||
assert(dev == 0);
|
||||
|
||||
/*
|
||||
* reinit pwm with correct alignment
|
||||
*/
|
||||
if (NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] & GPIOTE_CONFIG_OUTINIT_Msk) {
|
||||
pwm_init(dev, PWM_LEFT, init_data[1], (init_data[0] >> 16));
|
||||
} else {
|
||||
pwm_init(dev, PWM_RIGHT, init_data[1], (init_data[0] >> 16));
|
||||
}
|
||||
|
||||
/*
|
||||
* reset dutycycle
|
||||
*/
|
||||
pwm_set(dev, 0, (init_data[0] & 0xffff));
|
||||
|
||||
}
|
||||
|
||||
void pwm_poweroff(pwm_t dev)
|
||||
{
|
||||
assert(dev == 0);
|
||||
|
||||
PWM_TIMER->TASKS_STOP = 1;
|
||||
|
||||
/*
|
||||
* power off function ensures that the inverted CC[0] is cached correctly
|
||||
* when right aligned
|
||||
*/
|
||||
if (((NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] & GPIOTE_CONFIG_OUTINIT_Msk) == 0) &
|
||||
(PWM_TIMER->CC[1] != PWM_TIMER->CC[0]) &
|
||||
(PWM_TIMER->CC[0] != 0)) {
|
||||
init_data[0] = ((PWM_TIMER->CC[1] << 16) |
|
||||
(PWM_TIMER->CC[1] - PWM_TIMER->CC[0]));
|
||||
} else {
|
||||
init_data[0] = ((PWM_TIMER->CC[1] << 16) | PWM_TIMER->CC[0]);
|
||||
}
|
||||
|
||||
init_data[1] = (divtable[PWM_TIMER->PRESCALER] / PWM_TIMER->CC[1]);
|
||||
|
||||
/*
|
||||
* make sure the gpio is set to '0' while power is off
|
||||
*/
|
||||
pwm_set(dev, 0, 0);
|
||||
|
||||
PWM_TIMER->POWER = 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user