From 74f23221320296dd600e41c044780b4d12e2f93c Mon Sep 17 00:00:00 2001 From: Stephane D'Alu Date: Thu, 5 May 2016 20:41:51 +0200 Subject: PWM driver --- os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c | 365 ++++++++++++++++++++++++++++++ os/hal/ports/NRF51/NRF51822/hal_pwm_lld.h | 276 ++++++++++++++++++++++ os/hal/ports/NRF51/NRF51822/platform.mk | 6 +- 3 files changed, 646 insertions(+), 1 deletion(-) create mode 100644 os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c create mode 100644 os/hal/ports/NRF51/NRF51822/hal_pwm_lld.h (limited to 'os/hal/ports') diff --git a/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c b/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c new file mode 100644 index 0000000..6952fcb --- /dev/null +++ b/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c @@ -0,0 +1,365 @@ +/* + ChibiOS/HAL - Copyright (C) 2016 Stéphane D'Alu + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file hal_pwm_lld.c + * @brief NRF51 PWM subsystem low level driver source. + * + * @addtogroup PWM + * @{ + */ + +#include "hal.h" + +#if HAL_USE_PWM || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief PWMD1 driver identifier. + * @note The driver PWMD1 allocates the timer TIMER0 when enabled. + */ +#if NRF51_PWM_USE_TIMER0 || defined(__DOXYGEN__) +PWMDriver PWMD1; +#endif + +/** + * @brief PWMD2 driver identifier. + * @note The driver PWMD2 allocates the timer TIMER1 when enabled. + */ +#if NRF51_PWM_USE_TIMER1 || defined(__DOXYGEN__) +PWMDriver PWMD2; +#endif + +/** + * @brief PWMD3 driver identifier. + * @note The driver PWMD3 allocates the timer TIMER2 when enabled. + */ +#if NRF51_PWM_USE_TIMER2 || defined(__DOXYGEN__) +PWMDriver PWMD3; +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static void pwm_lld_serve_interrupt(PWMDriver *pwmp) { + // Deal with PWM period + if (pwmp->timer->EVENTS_COMPARE[pwmp->channels]) { + pwmp->timer->EVENTS_COMPARE[pwmp->channels] = 0; + + if (pwmp->config->callback != NULL) { + pwmp->config->callback(pwmp); + } + } + + // Deal with PWM channels + for (uint8_t n = 0 ; n < pwmp->channels ; n++) { + if (pwmp->timer->EVENTS_COMPARE[n]) { + pwmp->timer->EVENTS_COMPARE[n] = 0; + if (pwmp->config->channels[n].callback != NULL) { + pwmp->config->channels[n].callback(pwmp); + } + } + } +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +#if NRF51_PWM_USE_TIMER0 +/** + * @brief TIMER0 interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector60) { + OSAL_IRQ_PROLOGUE(); + pwm_lld_serve_interrupt(&PWMD1); + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF51_PWM_USE_TIMER0 */ + +#if NRF51_PWM_USE_TIMER1 +/** + * @brief TIMER1 interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector64) { + OSAL_IRQ_PROLOGUE(); + pwm_lld_serve_interrupt(&PWMD2); + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF51_PWM_USE_TIMER1 */ + +#if NRF51_PWM_USE_TIMER2 +/** + * @brief TIMER2 interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector68) { + OSAL_IRQ_PROLOGUE(); + pwm_lld_serve_interrupt(&PWMD3); + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF51_PWM_USE_TIMER2 */ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level PWM driver initialization. + * + * @notapi + */ +void pwm_lld_init(void) { + +#if NRF51_PWM_USE_TIMER0 + pwmObjectInit(&PWMD1); + PWMD1.channels = PWM_CHANNELS; + PWMD1.timer = NRF_TIMER0; +#endif + +#if NRF51_PWM_USE_TIMER1 + pwmObjectInit(&PWMD2); + PWMD2.channels = PWM_CHANNELS; + PWMD2.timer = NRF_TIMER1; +#endif + +#if NRF51_PWM_USE_TIMER2 + pwmObjectInit(&PWMD3); + PWMD3.channels = PWM_CHANNELS; + PWMD3.timer = NRF_TIMER2; +#endif +} + +/** + * @brief Configures and activates the PWM peripheral. + * @note Starting a driver that is already in the @p PWM_READY state + * disables all the active channels. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_start(PWMDriver *pwmp) { + // Prescaler value calculation: ftimer = 16MHz / 2^PRESCALER + uint16_t psc_ratio = NRF51_HFCLK_FREQUENCY / pwmp->config->frequency; + // Prescaler ratio must be between 1 and 512, and a power of two. + osalDbgAssert(psc_ratio <= 512 && !(psc_ratio & (psc_ratio - 1)), + "invalid frequency"); + // Prescaler value as a power of 2, must be 0..9 + uint32_t psc_value; + for (psc_value = 0; psc_value < 10; psc_value++) + if (psc_ratio == (unsigned)(1 << psc_value)) + break; + + + // Configure as 16bits timer (only TIMER0 support 32bits) + pwmp->timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit; + pwmp->timer->MODE = TIMER_MODE_MODE_Timer; + + // With clear shortcuts for period + pwmp->timer->SHORTS = + 0x1UL << (TIMER_SHORTS_COMPARE0_CLEAR_Pos + pwmp->channels); + + // Disable and reset interrupts for compare events + pwmp->timer->INTENCLR = (TIMER_INTENCLR_COMPARE0_Msk | + TIMER_INTENCLR_COMPARE1_Msk | + TIMER_INTENCLR_COMPARE2_Msk | + TIMER_INTENCLR_COMPARE3_Msk ); + pwmp->timer->EVENTS_COMPARE[0] = 0; + pwmp->timer->EVENTS_COMPARE[1] = 0; + pwmp->timer->EVENTS_COMPARE[2] = 0; + pwmp->timer->EVENTS_COMPARE[3] = 0; + + // Set prescaler + pwmp->timer->PRESCALER = psc_value; + + // Set period + pwmp->timer->CC[pwmp->channels] = pwmp->period; + + // Clear everything + pwmp->timer->TASKS_CLEAR = 1; + + + // Enable interrupt +#if NRF51_PWM_USE_TIMER0 + if (&PWMD1 == pwmp) { + nvicEnableVector(TIMER0_IRQn, NRF51_PWM_TIMER0_PRIORITY); + } +#endif + +#if NRF51_PWM_USE_TIMER1 + if (&PWMD2 == pwmp) { + nvicEnableVector(TIMER1_IRQn, NRF51_PWM_TIMER1_PRIORITY); + } +#endif + +#if NRF51_PWM_USE_TIMER2 + if (&PWMD3 == pwmp) { + nvicEnableVector(TIMER2_IRQn, NRF51_PWM_TIMER2_PRIORITY); + } +#endif + + // Start timer + pwmp->timer->TASKS_START = 1; +} + +/** + * @brief Deactivates the PWM peripheral. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_stop(PWMDriver *pwmp) { + pwmp->timer->TASKS_STOP = 1; + +#if NRF51_PWM_USE_TIMER0 + if (&PWMD1 == pwmp) { + nvicDisableVector(TIMER0_IRQn); + } +#endif + +#if NRF51_PWM_USE_TIMER1 + if (&PWMD2 == pwmp) { + nvicDisableVector(TIMER1_IRQn); + } +#endif + +#if NRF51_PWM_USE_TIMER2 + if (&PWMD3 == pwmp) { + nvicDisableVector(TIMER2_IRQn); + } +#endif +} + +/** + * @brief Enables a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is active using the specified configuration. + * @note The function has effect at the next cycle start. + * @note Channel notification is not enabled. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * @param[in] width PWM pulse width as clock pulses number + * + * @notapi + */ +void pwm_lld_enable_channel(PWMDriver *pwmp, + pwmchannel_t channel, + pwmcnt_t width) { + pwmp->timer->CC[channel] = width; +} + +/** + * @brief Disables a PWM channel and its notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is disabled and its output line returned to the + * idle state. + * @note The function has effect at the next cycle start. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * + * @notapi + */ +void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel) { + pwmp->timer->CC[channel] = 0; +} + +/** + * @brief Enables the periodic activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @note If the notification is already enabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_enable_periodic_notification(PWMDriver *pwmp) { + pwmp->timer->INTENSET = + 0x1UL << (TIMER_INTENSET_COMPARE0_Pos + pwmp->channels); +} + +/** + * @brief Disables the periodic activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @note If the notification is already disabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_disable_periodic_notification(PWMDriver *pwmp) { + pwmp->timer->INTENCLR = + 0x1UL << (TIMER_INTENCLR_COMPARE0_Pos + pwmp->channels); +} + +/** + * @brief Enables a channel de-activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @pre The channel must have been activated using @p pwmEnableChannel(). + * @note If the notification is already enabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * + * @notapi + */ +void pwm_lld_enable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel) { + pwmp->timer->INTENSET = + 0x1UL << (TIMER_INTENSET_COMPARE0_Pos + channel); +} + +/** + * @brief Disables a channel de-activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @pre The channel must have been activated using @p pwmEnableChannel(). + * @note If the notification is already disabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * + * @notapi + */ +void pwm_lld_disable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel) { + pwmp->timer->INTENCLR = + 0x1UL << (TIMER_INTENCLR_COMPARE0_Pos + channel); +} + +#endif /* HAL_USE_PWM */ + +/** @} */ diff --git a/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.h b/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.h new file mode 100644 index 0000000..0ddd65c --- /dev/null +++ b/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.h @@ -0,0 +1,276 @@ +/* + ChibiOS/HAL - Copyright (C) 2016 Stéphane D'Alu + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file hal_pwm_lld.h + * @brief NRF51 PWM subsystem low level driver header. + * + * @addtogroup PWM + * @{ + */ + +#ifndef HAL_PWM_LLD_H_ +#define HAL_PWM_LLD_H_ + +#if HAL_USE_PWM || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @brief Number of PWM channels per PWM driver. + */ +#define PWM_CHANNELS 3 + + +#define PWM_FREQUENCY_16MHZ 16000000 /** @brief 16MHz */ +#define PWM_FREQUENCY_8MHZ 8000000 /** @brief 8MHz */ +#define PWM_FREQUENCY_4MHZ 4000000 /** @brief 4MHz */ +#define PWM_FREQUENCY_2MHZ 2000000 /** @brief 2MHz */ +#define PWM_FREQUENCY_1MHZ 1000000 /** @brief 1MHz */ +#define PWM_FREQUENCY_500KHZ 500000 /** @brief 500kHz */ +#define PWM_FREQUENCY_250KHZ 250000 /** @brief 250kHz */ +#define PWM_FREQUENCY_125KHZ 125000 /** @brief 125kHz */ +#define PWM_FREQUENCY_62500HZ 62500 /** @brief 62500Hz */ +#define PWM_FREQUENCY_31250HZ 31250 /** @brief 31250Hz */ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +#if !defined(NRF51_PWM_USE_TIMER0) +#define NRF51_PWM_USE_TIMER0 FALSE +#endif + +#if !defined(NRF51_PWM_USE_TIMER1) +#define NRF51_PWM_USE_TIMER1 FALSE +#endif + +#if !defined(NRF51_PWM_USE_TIMER2) +#define NRF51_PWM_USE_TIMER2 FALSE +#endif + +/** + * @brief TIMER0 interrupt priority level setting. + */ +#if !defined(NRF51_PWM_TIMER0_PRIORITY) || defined(__DOXYGEN__) +#define NRF51_PWM_TIMER0_PRIORITY 12 +#endif + +/** + * @brief TIMER1 interrupt priority level setting. + */ +#if !defined(NRF51_PWM_TIMER1_PRIORITY) || defined(__DOXYGEN__) +#define NRF51_PWM_TIMER1_PRIORITY 12 +#endif + +/** + * @brief TIMER2 interrupt priority level setting. + */ +#if !defined(NRF51_PWM_TIMER2_PRIORITY) || defined(__DOXYGEN__) +#define NRF51_PWM_TIMER2_PRIORITY 12 +#endif + +/** @} */ + +/** + * @name Configuration options + * @{ + */ +/** @} */ + +/*===========================================================================*/ +/* Configuration checks. */ +/*===========================================================================*/ + +#if !NRF51_PWM_USE_TIMER0 && !NRF51_PWM_USE_TIMER1 && !NRF51_PWM_USE_TIMER2 +#error "PWM driver activated but no TIMER peripheral assigned" +#endif + +#if (NRF51_ST_USE_TIMER0 == TRUE) && (NRF51_PWM_USE_TIMER0 == TRUE) +#error "TIMER0 used for ST and PWM" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type of a PWM mode. + */ +typedef uint32_t pwmmode_t; + +/** + * @brief Type of a PWM channel. + */ +typedef uint8_t pwmchannel_t; + +/** + * @brief Type of a channels mask. + */ +typedef uint32_t pwmchnmsk_t; + +/** + * @brief Type of a PWM counter. + */ +typedef uint16_t pwmcnt_t; + +/** + * @brief Type of a PWM driver channel configuration structure. + */ +typedef struct { + /** + * @brief Channel active logic level. + */ + pwmmode_t mode; + + /** + * @brief Channel callback pointer. + * @note This callback is invoked on the channel compare event. If set to + * @p NULL then the callback is disabled. + */ + pwmcallback_t callback; + /* End of the mandatory fields.*/ +} PWMChannelConfig; + +/** + * @brief Type of a PWM driver configuration structure. + */ +typedef struct { + /** + * @brief Timer clock in Hz. + * @note The low level can use assertions in order to catch invalid + * frequency specifications. + */ + uint32_t frequency; + /** + * @brief PWM period in ticks. + * @note The low level can use assertions in order to catch invalid + * period specifications. + */ + pwmcnt_t period; + /** + * @brief Periodic callback pointer. + * @note This callback is invoked on PWM counter reset. If set to + * @p NULL then the callback is disabled. + */ + pwmcallback_t callback; + /** + * @brief Channels configurations. + */ + PWMChannelConfig channels[PWM_CHANNELS]; + /* End of the mandatory fields.*/ +} PWMConfig; + +/** + * @brief Structure representing a PWM driver. + */ +struct PWMDriver { + /** + * @brief Driver state. + */ + pwmstate_t state; + /** + * @brief Current driver configuration data. + */ + const PWMConfig *config; + /** + * @brief Current PWM period in ticks. + */ + pwmcnt_t period; + /** + * @brief Mask of the enabled channels. + */ + pwmchnmsk_t enabled; + /** + * @brief Number of channels in this instance. + */ + pwmchannel_t channels; +#if defined(PWM_DRIVER_EXT_FIELDS) + PWM_DRIVER_EXT_FIELDS +#endif + /* End of the mandatory fields.*/ + /** + * @brief Pointer to the TIMER registers block. + */ + NRF_TIMER_Type *timer; +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Changes the period the PWM peripheral. + * @details This function changes the period of a PWM unit that has already + * been activated using @p pwmStart(). + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The PWM unit period is changed to the new value. + * @note The function has effect at the next cycle start. + * @note If a period is specified that is shorter than the pulse width + * programmed in one of the channels then the behavior is not + * guaranteed. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] period new cycle time in ticks + * + * @notapi + */ +#define pwm_lld_change_period(pwmp, period) \ + do { \ + (pwmp)->timer->CC[(pwmp)->channels] = ((period) - 1); \ + } while(0) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if NRF51_PWM_USE_TIMER0 || defined(__DOXYGEN__) +extern PWMDriver PWMD1; +#endif +#if NRF51_PWM_USE_TIMER1 || defined(__DOXYGEN__) +extern PWMDriver PWMD2; +#endif +#if NRF51_PWM_USE_TIMER2 || defined(__DOXYGEN__) +extern PWMDriver PWMD3; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void pwm_lld_init(void); + void pwm_lld_start(PWMDriver *pwmp); + void pwm_lld_stop(PWMDriver *pwmp); + void pwm_lld_enable_channel(PWMDriver *pwmp, + pwmchannel_t channel, + pwmcnt_t width); + void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel); + void pwm_lld_enable_periodic_notification(PWMDriver *pwmp); + void pwm_lld_disable_periodic_notification(PWMDriver *pwmp); + void pwm_lld_enable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel); + void pwm_lld_disable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_PWM */ + +#endif /* HAL_PWM_LLD_H_ */ + +/** @} */ diff --git a/os/hal/ports/NRF51/NRF51822/platform.mk b/os/hal/ports/NRF51/NRF51822/platform.mk index fbe977e..b937e39 100644 --- a/os/hal/ports/NRF51/NRF51822/platform.mk +++ b/os/hal/ports/NRF51/NRF51822/platform.mk @@ -34,6 +34,9 @@ endif ifneq ($(findstring HAL_USE_RNG TRUE,$(HALCONF)),) PLATFORMSRC += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_rng_lld.c endif +ifneq ($(findstring HAL_USE_PWM TRUE,$(HALCONF)),) +PLATFORMSRC += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c +endif else PLATFORMSRC = ${CHIBIOS}/os/hal/ports/common/ARMCMx/nvic.c \ ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_lld.c \ @@ -47,7 +50,8 @@ PLATFORMSRC = ${CHIBIOS}/os/hal/ports/common/ARMCMx/nvic.c \ ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_adc_lld.c \ ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_gpt_lld.c \ ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_wdg_lld.c \ - ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_rng_lld.c + ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_rng_lld.c \ + ${CHIBIOS_CONTRIB}/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c endif # Required include directories -- cgit v1.2.3 From aad6da5b99e6af86aafd0247e7123cd71b421056 Mon Sep 17 00:00:00 2001 From: Stephane D'Alu Date: Thu, 5 May 2016 21:38:44 +0200 Subject: old C style --- os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'os/hal/ports') diff --git a/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c b/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c index 6952fcb..76bc572 100644 --- a/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c +++ b/os/hal/ports/NRF51/NRF51822/hal_pwm_lld.c @@ -78,7 +78,8 @@ static void pwm_lld_serve_interrupt(PWMDriver *pwmp) { } // Deal with PWM channels - for (uint8_t n = 0 ; n < pwmp->channels ; n++) { + uint8_t n; + for (n = 0 ; n < pwmp->channels ; n++) { if (pwmp->timer->EVENTS_COMPARE[n]) { pwmp->timer->EVENTS_COMPARE[n] = 0; if (pwmp->config->channels[n].callback != NULL) { -- cgit v1.2.3