Skip to content

Commit

Permalink
cpu/nrf51/pwm: initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Semjon Kerner committed Apr 25, 2018
1 parent 8d6f5dd commit ad99326
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 0 deletions.
9 changes: 9 additions & 0 deletions cpu/nrf51/include/cpu_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
245 changes: 245 additions & 0 deletions cpu/nrf51/periph/pwm.c
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit ad99326

Please sign in to comment.