From ad993263e95116a2ffde6489d9c5c9ba02ca1e94 Mon Sep 17 00:00:00 2001 From: Semjon Kerner Date: Wed, 11 Apr 2018 17:05:02 +0200 Subject: [PATCH 1/2] cpu/nrf51/pwm: initial implementation --- cpu/nrf51/include/cpu_conf.h | 9 ++ cpu/nrf51/periph/pwm.c | 245 +++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 cpu/nrf51/periph/pwm.c diff --git a/cpu/nrf51/include/cpu_conf.h b/cpu/nrf51/include/cpu_conf.h index 6e36da701f..b5948685a1 100644 --- a/cpu/nrf51/include/cpu_conf.h +++ b/cpu/nrf51/include/cpu_conf.h @@ -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 diff --git a/cpu/nrf51/periph/pwm.c b/cpu/nrf51/periph/pwm.c new file mode 100644 index 0000000000..5749dec504 --- /dev/null +++ b/cpu/nrf51/periph/pwm.c @@ -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 + * @} + */ + +#include +#include + +#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; +} From f37825a1bac35820cd376f04177b57f9e5bc9b88 Mon Sep 17 00:00:00 2001 From: Semjon Kerner Date: Wed, 11 Apr 2018 17:05:53 +0200 Subject: [PATCH 2/2] boards/calliope-mini: configure pwm --- boards/calliope-mini/Makefile.features | 1 + boards/calliope-mini/include/periph_conf.h | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/boards/calliope-mini/Makefile.features b/boards/calliope-mini/Makefile.features index 9a75beba68..eb4cb8ead4 100644 --- a/boards/calliope-mini/Makefile.features +++ b/boards/calliope-mini/Makefile.features @@ -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 diff --git a/boards/calliope-mini/include/periph_conf.h b/boards/calliope-mini/include/periph_conf.h index 2d7e5458da..e3a033b826 100644 --- a/boards/calliope-mini/include/periph_conf.h +++ b/boards/calliope-mini/include/periph_conf.h @@ -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