-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cpu/nrf51/pwm: initial implementation
- Loading branch information
Semjon Kerner
committed
Apr 25, 2018
1 parent
8d6f5dd
commit ad99326
Showing
2 changed files
with
254 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |