/** * Copyright (c) 2015 - 2017, Nordic Semiconductor ASA * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "sdk_common.h" #if NRF_MODULE_ENABLED(APP_PWM) #include "app_pwm.h" #include "nrf_drv_timer.h" #include "nrf_drv_ppi.h" #include "nrf_drv_common.h" #include "nrf_drv_gpiote.h" #include "nrf_gpiote.h" #include "nrf_gpio.h" #include "app_util_platform.h" #include "nrf_assert.h" #define APP_PWM_CHANNEL_INITIALIZED 1 #define APP_PWM_CHANNEL_UNINITIALIZED 0 #define APP_PWM_CHANNEL_ENABLED 1 #define APP_PWM_CHANNEL_DISABLED 0 #define TIMER_PRESCALER_MAX 9 #define TIMER_MAX_PULSEWIDTH_US_ON_16M 4095 #ifndef GPIOTE_SET_CLEAR_TASKS #define APP_PWM_REQUIRED_PPI_CHANNELS_PER_INSTANCE 2 #endif #define APP_PWM_REQUIRED_PPI_CHANNELS_PER_CHANNEL 2 #define UNALLOCATED 0xFFFFFFFFUL #define BUSY_STATE_CHANGING 0xFE #define BUSY_STATE_IDLE 0xFF #define PWM_MAIN_CC_CHANNEL 2 #define PWM_SECONDARY_CC_CHANNEL 3 #ifdef GPIOTE_SET_CLEAR_TASKS static bool m_use_ppi_delay_workaround; #endif /** * @brief PWM busy status * * Stores the number of a channel being currently updated. * */ static volatile uint8_t m_pwm_busy[TIMER_COUNT]; /** * @brief New duty cycle value * * When the channel duty cycle reaches this value, the update process is complete. */ static volatile uint32_t m_pwm_target_value[TIMER_COUNT]; /** * @brief PWM ready counter * * The value in this counter is decremented in every PWM cycle after initiating the update. * If an event handler function was specified by the user, it is being called * after two cycle events (at least one full PWM cycle). */ volatile uint8_t m_pwm_ready_counter[TIMER_COUNT][APP_PWM_CHANNELS_PER_INSTANCE]; /** * @brief Pointers to instances * * This array connects any active timer instance number with the pointer to the PWM instance. * It is used by the interrupt runtime. */ static const app_pwm_t * m_instances[TIMER_COUNT]; // Macros for getting the polarity of given instance/channel. #define POLARITY_ACTIVE(INST,CH) (( ((INST)->p_cb)->channels_cb[(CH)].polarity == \ APP_PWM_POLARITY_ACTIVE_LOW)?(0):(1)) #define POLARITY_INACTIVE(INST,CH) (( ((INST)->p_cb)->channels_cb[(CH)].polarity == \ APP_PWM_POLARITY_ACTIVE_LOW)?(1):(0)) //lint -save -e534 /** * @brief Workaround for PAN-73. * * @param[in] timer Timer. * @param[in] enable Enable or disable. */ static void pan73_workaround(NRF_TIMER_Type * p_timer, bool enable) { #ifndef GPIOTE_SET_CLEAR_TASKS if (p_timer == NRF_TIMER0) { *(uint32_t *)0x40008C0C = (enable ? 1 : 0); } else if (p_timer == NRF_TIMER1) { *(uint32_t *)0x40009C0C = (enable ? 1 : 0); } else if (p_timer == NRF_TIMER2) { *(uint32_t *)0x4000AC0C = (enable ? 1 : 0); } #else UNUSED_PARAMETER(p_timer); UNUSED_PARAMETER(enable); #endif } bool app_pwm_busy_check(app_pwm_t const * const p_instance) { uint8_t busy_state = (m_pwm_busy[p_instance->p_timer->instance_id]); bool busy = true; if (busy_state != BUSY_STATE_IDLE) { if (busy_state != BUSY_STATE_CHANGING) { if (nrf_drv_timer_capture_get(p_instance->p_timer, (nrf_timer_cc_channel_t) busy_state) == m_pwm_target_value[p_instance->p_timer->instance_id]) { m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; busy = false; } } } else { busy = false; } return busy; } /** * @brief Function for enabling the IRQ for a given PWM instance. * * @param[in] p_instance PWM instance. */ __STATIC_INLINE void pwm_irq_enable(app_pwm_t const * const p_instance) { nrf_drv_timer_compare_int_enable(p_instance->p_timer, PWM_MAIN_CC_CHANNEL); } /** * @brief Function for disabling the IRQ for a given PWM instance. * * @param[in] p_instance PWM instance. */ __STATIC_INLINE void pwm_irq_disable(app_pwm_t const * const p_instance) { nrf_drv_timer_compare_int_disable(p_instance->p_timer, PWM_MAIN_CC_CHANNEL); } #ifndef GPIOTE_SET_CLEAR_TASKS /** * @brief Function for disabling PWM channel PPI. * * @param[in] p_instance PWM instance. */ __STATIC_INLINE void pwm_channel_ppi_disable(app_pwm_t const * const p_instance, uint8_t channel) { app_pwm_cb_t * p_cb = p_instance->p_cb; nrf_drv_ppi_channel_disable(p_cb->channels_cb[channel].ppi_channels[0]); nrf_drv_ppi_channel_disable(p_cb->channels_cb[channel].ppi_channels[1]); } /** * @brief Function for disabling PWM PPI. * * @param[in] p_instance PWM instance. */ __STATIC_INLINE void pwm_ppi_disable(app_pwm_t const * const p_instance) { app_pwm_cb_t * p_cb = p_instance->p_cb; nrf_drv_ppi_channel_disable(p_cb->ppi_channels[0]); nrf_drv_ppi_channel_disable(p_cb->ppi_channels[1]); } #endif /** * @brief This function is called on interrupt after duty set. * * @param[in] timer Timer used by PWM. * @param[in] timer_instance_id Timer index. */ void pwm_ready_tick(nrf_timer_event_t event_type, void * p_context) { uint32_t timer_instance_id = (uint32_t)p_context; uint8_t disable = 1; for (uint8_t channel = 0; channel < APP_PWM_CHANNELS_PER_INSTANCE; ++channel) { if (m_pwm_ready_counter[timer_instance_id][channel]) { --m_pwm_ready_counter[timer_instance_id][channel]; if (!m_pwm_ready_counter[timer_instance_id][channel]) { app_pwm_cb_t * p_cb = m_instances[timer_instance_id]->p_cb; p_cb->p_ready_callback(timer_instance_id); } else { disable = 0; } } } if (disable) { pwm_irq_disable(m_instances[timer_instance_id]); } } /** * @brief Function for resource de-allocation. * * @param[in] p_instance PWM instance. */ //lint -e{650} static void pwm_dealloc(app_pwm_t const * const p_instance) { app_pwm_cb_t * p_cb = p_instance->p_cb; #ifdef GPIOTE_SET_CLEAR_TASKS nrf_drv_ppi_channel_free(p_cb->ppi_channel); #else for (uint8_t i = 0; i < APP_PWM_REQUIRED_PPI_CHANNELS_PER_INSTANCE; ++i) { if (p_cb->ppi_channels[i] != (nrf_ppi_channel_t)(uint8_t)(UNALLOCATED)) { nrf_drv_ppi_channel_free(p_cb->ppi_channels[i]); } } if (p_cb->ppi_group != (nrf_ppi_channel_group_t)UNALLOCATED) { nrf_drv_ppi_group_free(p_cb->ppi_group); } #endif //GPIOTE_SET_CLEAR_TASKS for (uint8_t ch = 0; ch < APP_PWM_CHANNELS_PER_INSTANCE; ++ch) { for (uint8_t i = 0; i < APP_PWM_REQUIRED_PPI_CHANNELS_PER_CHANNEL; ++i) { if (p_cb->channels_cb[ch].ppi_channels[i] != (nrf_ppi_channel_t)UNALLOCATED) { nrf_drv_ppi_channel_free(p_cb->channels_cb[ch].ppi_channels[i]); p_cb->channels_cb[ch].ppi_channels[i] = (nrf_ppi_channel_t)UNALLOCATED; } } if (p_cb->channels_cb[ch].gpio_pin != UNALLOCATED) { nrf_drv_gpiote_out_uninit(p_cb->channels_cb[ch].gpio_pin); p_cb->channels_cb[ch].gpio_pin = UNALLOCATED; } p_cb->channels_cb[ch].initialized = APP_PWM_CHANNEL_UNINITIALIZED; } nrf_drv_timer_uninit(p_instance->p_timer); return; } #ifndef GPIOTE_SET_CLEAR_TASKS /** * @brief PWM state transition from (0%, 100%) to 0% or 100%. * * @param[in] p_instance PWM instance. * @param[in] channel PWM channel number. * @param[in] ticks Number of clock ticks. */ static void pwm_transition_n_to_0or100(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; nrf_ppi_channel_group_t p_ppigrp = p_cb->ppi_group; pwm_ppi_disable(p_instance); nrf_drv_ppi_group_clear(p_ppigrp); nrf_drv_ppi_channels_include_in_group( nrf_drv_ppi_channel_to_mask(p_ch_cb->ppi_channels[0]) | nrf_drv_ppi_channel_to_mask(p_ch_cb->ppi_channels[1]), p_ppigrp); if (!ticks) { nrf_drv_ppi_channel_assign(p_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, channel), nrf_drv_ppi_task_addr_group_disable_get(p_ppigrp)); nrf_drv_timer_compare(p_instance->p_timer, (nrf_timer_cc_channel_t) PWM_SECONDARY_CC_CHANNEL, 0, false); m_pwm_target_value[p_instance->p_timer->instance_id] = nrf_drv_timer_capture_get(p_instance->p_timer, (nrf_timer_cc_channel_t) channel); nrf_drv_ppi_channel_assign(p_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, channel), nrf_drv_timer_capture_task_address_get(p_instance->p_timer, PWM_SECONDARY_CC_CHANNEL)); } else { ticks = p_cb->period; nrf_drv_ppi_channel_assign(p_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_MAIN_CC_CHANNEL), nrf_drv_ppi_task_addr_group_disable_get(p_ppigrp)); // Set secondary CC channel to non-zero value: nrf_drv_timer_compare(p_instance->p_timer, (nrf_timer_cc_channel_t) PWM_SECONDARY_CC_CHANNEL, 1, false); m_pwm_target_value[p_instance->p_timer->instance_id] = 0; // The captured value will be equal to 0, because timer clear on main PWM CC channel compare is enabled. nrf_drv_ppi_channel_assign(p_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_MAIN_CC_CHANNEL), nrf_drv_timer_capture_task_address_get(p_instance->p_timer, PWM_SECONDARY_CC_CHANNEL)); } nrf_drv_ppi_channel_enable(p_cb->ppi_channels[0]); nrf_drv_ppi_channel_enable(p_cb->ppi_channels[1]); p_ch_cb->pulsewidth = ticks; m_pwm_busy[p_instance->p_timer->instance_id] = PWM_SECONDARY_CC_CHANNEL; } /** * @brief PWM state transition from (0%, 100%) to (0%, 100%). * * @param[in] p_instance PWM instance. * @param[in] channel PWM channel number. * @param[in] ticks Number of clock ticks. */ static void pwm_transition_n_to_m(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; nrf_ppi_channel_group_t p_ppigrp = p_cb->ppi_group; pwm_ppi_disable(p_instance); nrf_drv_ppi_group_clear(p_ppigrp); nrf_drv_ppi_channels_include_in_group( nrf_drv_ppi_channel_to_mask(p_cb->ppi_channels[0]) | nrf_drv_ppi_channel_to_mask(p_cb->ppi_channels[1]), p_ppigrp); nrf_drv_ppi_channel_assign(p_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_SECONDARY_CC_CHANNEL), nrf_drv_timer_capture_task_address_get(p_instance->p_timer, channel)); if (ticks + ((nrf_timer_frequency_get(p_instance->p_timer->p_reg) == NRF_TIMER_FREQ_16MHz) ? 1 : 0) < p_ch_cb->pulsewidth) { // For lower value, we need one more transition. Timer task delay is included. // If prescaler is disabled, one tick must be added because of 1 PCLK16M clock cycle delay. nrf_drv_ppi_channel_assign(p_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_SECONDARY_CC_CHANNEL), nrf_drv_gpiote_out_task_addr_get(p_ch_cb->gpio_pin)); } else { nrf_drv_ppi_channel_remove_from_group(p_cb->ppi_channels[1], p_ppigrp); } p_ch_cb->pulsewidth = ticks; nrf_drv_timer_compare(p_instance->p_timer, (nrf_timer_cc_channel_t) PWM_SECONDARY_CC_CHANNEL, ticks, false); nrf_drv_ppi_group_enable(p_ppigrp); m_pwm_target_value[p_instance->p_timer->instance_id] = ticks; m_pwm_busy[p_instance->p_timer->instance_id] = channel; } /** * @brief PWM state transition from 0% or 100% to (0%, 100%). * * @param[in] p_instance PWM instance. * @param[in] channel PWM channel number. * @param[in] ticks Number of clock ticks. */ static void pwm_transition_0or100_to_n(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; nrf_ppi_channel_group_t p_ppigrp = p_cb->ppi_group; nrf_timer_cc_channel_t pwm_ch_cc = (nrf_timer_cc_channel_t)(channel); pwm_ppi_disable(p_instance); pwm_channel_ppi_disable(p_instance, channel); nrf_drv_timer_compare(p_instance->p_timer, pwm_ch_cc, ticks, false); nrf_drv_ppi_group_clear(p_ppigrp); nrf_drv_ppi_channels_include_in_group( nrf_drv_ppi_channel_to_mask(p_ch_cb->ppi_channels[0])| nrf_drv_ppi_channel_to_mask(p_ch_cb->ppi_channels[1]), p_ppigrp); if (!p_ch_cb->pulsewidth) { // Channel is at 0%. nrf_drv_ppi_channel_assign(p_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, channel), nrf_drv_ppi_task_addr_group_enable_get(p_ppigrp)); nrf_drv_timer_compare(p_instance->p_timer, (nrf_timer_cc_channel_t) PWM_SECONDARY_CC_CHANNEL, 0, false); m_pwm_target_value[p_instance->p_timer->instance_id] = nrf_drv_timer_capture_get(p_instance->p_timer, (nrf_timer_cc_channel_t) channel); nrf_drv_ppi_channel_assign(p_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, channel), nrf_drv_timer_capture_task_address_get(p_instance->p_timer, PWM_SECONDARY_CC_CHANNEL)); } else { // Channel is at 100%. nrf_drv_ppi_channel_assign(p_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_MAIN_CC_CHANNEL), nrf_drv_ppi_task_addr_group_enable_get(p_ppigrp)); // Set secondary CC channel to non-zero value: nrf_drv_timer_compare(p_instance->p_timer, (nrf_timer_cc_channel_t) PWM_SECONDARY_CC_CHANNEL, 1, false); m_pwm_target_value[p_instance->p_timer->instance_id] = 0; // The captured value will be equal to 0, because timer clear on main PWM CC channel compare is enabled. nrf_drv_ppi_channel_assign(p_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_MAIN_CC_CHANNEL), nrf_drv_timer_capture_task_address_get(p_instance->p_timer, PWM_SECONDARY_CC_CHANNEL)); } nrf_drv_ppi_channel_enable(p_cb->ppi_channels[0]); nrf_drv_ppi_channel_enable(p_cb->ppi_channels[1]); p_ch_cb->pulsewidth = ticks; m_pwm_busy[p_instance->p_timer->instance_id] = PWM_SECONDARY_CC_CHANNEL; } /** * @brief PWM state transition from 0% or 100% to 0% or 100%. * * @param[in] p_instance PWM instance. * @param[in] channel PWM channel number. * @param[in] ticks Number of clock ticks. */ static void pwm_transition_0or100_to_0or100(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; nrf_timer_cc_channel_t pwm_ch_cc = (nrf_timer_cc_channel_t)(channel); pwm_ppi_disable(p_instance); pwm_channel_ppi_disable(p_instance, channel); if (!ticks) { // Set to 0%. nrf_drv_gpiote_out_task_force(p_ch_cb->gpio_pin, POLARITY_INACTIVE(p_instance, channel)); } else if (ticks >= p_cb->period) { // Set to 100%. ticks = p_cb->period; nrf_drv_gpiote_out_task_force(p_ch_cb->gpio_pin, POLARITY_ACTIVE(p_instance, channel)); } nrf_drv_timer_compare(p_instance->p_timer, pwm_ch_cc, ticks, false); p_ch_cb->pulsewidth = ticks; m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; return; } static void pwm_transition(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_instance->p_cb->channels_cb[channel]; // Pulse width change sequence: if (!p_ch_cb->pulsewidth || p_ch_cb->pulsewidth >= p_cb->period) { // Channel is disabled (0%) or at 100%. if (!ticks || ticks >= p_cb->period) { // Set to 0 or 100%. pwm_transition_0or100_to_0or100(p_instance, channel, ticks); } else { // Other value. pwm_transition_0or100_to_n(p_instance, channel, ticks); } } else { // Channel is at other value. if (!ticks || ticks >= p_cb->period) { // Disable channel (set to 0%) or set to 100%. pwm_transition_n_to_0or100(p_instance, channel, ticks); } else { // Set to any other value. pwm_transition_n_to_m(p_instance, channel, ticks); } } } #else //GPIOTE_SET_CLEAR_TASKS /** * @brief PWM state transition. * * @param[in] p_instance PWM instance. * @param[in] channel PWM channel number. * @param[in] ticks Number of clock ticks. */ static void pwm_transition(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; nrf_timer_cc_channel_t pwm_ch_cc = (nrf_timer_cc_channel_t)(channel); nrf_drv_ppi_channel_disable(p_cb->ppi_channel); if (!ticks) { nrf_drv_ppi_channel_disable(p_ch_cb->ppi_channels[1]); nrf_drv_ppi_channel_enable(p_ch_cb->ppi_channels[0]); m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; } else if (ticks >= p_cb->period) { ticks = p_cb->period; nrf_drv_ppi_channel_disable(p_ch_cb->ppi_channels[0]); nrf_drv_ppi_channel_enable(p_ch_cb->ppi_channels[1]); m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; } else { // Set to any other value. if ((p_ch_cb->pulsewidth != p_cb->period) && (p_ch_cb->pulsewidth != 0) && (ticks < p_ch_cb->pulsewidth)) { nrf_drv_timer_compare(p_instance->p_timer, (nrf_timer_cc_channel_t)PWM_SECONDARY_CC_CHANNEL, p_ch_cb->pulsewidth, false); nrf_drv_ppi_channel_assign(p_cb->ppi_channel, nrf_drv_timer_compare_event_address_get(p_instance->p_timer, (nrf_timer_cc_channel_t)PWM_SECONDARY_CC_CHANNEL), p_ch_cb->polarity ? nrf_drv_gpiote_clr_task_addr_get(p_ch_cb->gpio_pin) : nrf_drv_gpiote_set_task_addr_get(p_ch_cb->gpio_pin)); nrf_drv_ppi_channel_enable(p_cb->ppi_channel); m_pwm_busy[p_instance->p_timer->instance_id] = channel; m_pwm_target_value[p_instance->p_timer->instance_id] = ticks; } else { m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; } nrf_drv_timer_compare(p_instance->p_timer, pwm_ch_cc, ticks, false); nrf_drv_ppi_channel_enable(p_ch_cb->ppi_channels[0]); nrf_drv_ppi_channel_enable(p_ch_cb->ppi_channels[1]); } p_ch_cb->pulsewidth = ticks; return; } #endif //GPIOTE_SET_CLEAR_TASKS ret_code_t app_pwm_channel_duty_ticks_set(app_pwm_t const * const p_instance, uint8_t channel, uint16_t ticks) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; ASSERT(channel < APP_PWM_CHANNELS_PER_INSTANCE); ASSERT(p_ch_cb->initialized == APP_PWM_CHANNEL_INITIALIZED); if (p_cb->state != NRF_DRV_STATE_POWERED_ON) { return NRF_ERROR_INVALID_STATE; } if (ticks == p_ch_cb->pulsewidth) { if (p_cb->p_ready_callback) { p_cb->p_ready_callback(p_instance->p_timer->instance_id); } return NRF_SUCCESS; // No action required. } if (app_pwm_busy_check(p_instance)) { return NRF_ERROR_BUSY; // PPI channels for synchronization are still in use. } m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_CHANGING; // Set new value. pwm_transition(p_instance, channel, ticks); if (p_instance->p_cb->p_ready_callback) { //PWM ready interrupt handler will be called after one full period. m_pwm_ready_counter[p_instance->p_timer->instance_id][channel] = 2; pwm_irq_enable(p_instance); } return NRF_SUCCESS; } uint16_t app_pwm_channel_duty_ticks_get(app_pwm_t const * const p_instance, uint8_t channel) { app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; return p_ch_cb->pulsewidth; } uint16_t app_pwm_cycle_ticks_get(app_pwm_t const * const p_instance) { app_pwm_cb_t * p_cb = p_instance->p_cb; return (uint16_t)p_cb->period; } ret_code_t app_pwm_channel_duty_set(app_pwm_t const * const p_instance, uint8_t channel, app_pwm_duty_t duty) { uint32_t ticks = ((uint32_t)app_pwm_cycle_ticks_get(p_instance) * (uint32_t)duty) / 100UL; return app_pwm_channel_duty_ticks_set(p_instance, channel, ticks); } app_pwm_duty_t app_pwm_channel_duty_get(app_pwm_t const * const p_instance, uint8_t channel) { uint32_t value = ((uint32_t)app_pwm_channel_duty_ticks_get(p_instance, channel) * 100UL) \ / (uint32_t)app_pwm_cycle_ticks_get(p_instance); return (app_pwm_duty_t)value; } /** * @brief Function for initializing the PWM channel. * * @param[in] p_instance PWM instance. * @param[in] channel Channel number. * @param[in] pin GPIO pin number. * * @retval NRF_SUCCESS If initialization was successful. * @retval NRF_ERROR_NO_MEM If there were not enough free resources. * @retval NRF_ERROR_INVALID_STATE If the timer is already in use or initialization failed. */ static ret_code_t app_pwm_channel_init(app_pwm_t const * const p_instance, uint8_t channel, uint32_t pin, app_pwm_polarity_t polarity) { ASSERT(channel < APP_PWM_CHANNELS_PER_INSTANCE); app_pwm_cb_t * p_cb = p_instance->p_cb; app_pwm_channel_cb_t * p_channel_cb = &p_cb->channels_cb[channel]; if (p_cb->state != NRF_DRV_STATE_UNINITIALIZED) { return NRF_ERROR_INVALID_STATE; } p_channel_cb->pulsewidth = 0; p_channel_cb->polarity = polarity; ret_code_t err_code; /* GPIOTE setup: */ nrf_drv_gpiote_out_config_t out_cfg = GPIOTE_CONFIG_OUT_TASK_TOGGLE( POLARITY_INACTIVE(p_instance, channel) ); err_code = nrf_drv_gpiote_out_init((nrf_drv_gpiote_pin_t)pin,&out_cfg); if (err_code != NRF_SUCCESS) { return NRF_ERROR_NO_MEM; } p_cb->channels_cb[channel].gpio_pin = pin; // Set output to inactive state. if (polarity) { nrf_gpio_pin_clear(pin); } else { nrf_gpio_pin_set(pin); } /* PPI setup: */ for (uint8_t i = 0; i < APP_PWM_REQUIRED_PPI_CHANNELS_PER_CHANNEL; ++i) { if (nrf_drv_ppi_channel_alloc(&p_channel_cb->ppi_channels[i]) != NRF_SUCCESS) { return NRF_ERROR_NO_MEM; // Resource de-allocation is done by callee. } } nrf_drv_ppi_channel_disable(p_channel_cb->ppi_channels[0]); nrf_drv_ppi_channel_disable(p_channel_cb->ppi_channels[1]); #ifdef GPIOTE_SET_CLEAR_TASKS uint32_t deactivate_task_addr = polarity ? nrf_drv_gpiote_clr_task_addr_get(p_channel_cb->gpio_pin) : nrf_drv_gpiote_set_task_addr_get(p_channel_cb->gpio_pin); uint32_t activate_task_addr = polarity ? nrf_drv_gpiote_set_task_addr_get(p_channel_cb->gpio_pin) : nrf_drv_gpiote_clr_task_addr_get(p_channel_cb->gpio_pin); nrf_drv_ppi_channel_assign(p_channel_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, channel), deactivate_task_addr); nrf_drv_ppi_channel_assign(p_channel_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_MAIN_CC_CHANNEL), activate_task_addr); #else //GPIOTE_SET_CLEAR_TASKS nrf_drv_ppi_channel_assign(p_channel_cb->ppi_channels[0], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, channel), nrf_drv_gpiote_out_task_addr_get(p_channel_cb->gpio_pin)); nrf_drv_ppi_channel_assign(p_channel_cb->ppi_channels[1], nrf_drv_timer_compare_event_address_get(p_instance->p_timer, PWM_MAIN_CC_CHANNEL), nrf_drv_gpiote_out_task_addr_get(p_channel_cb->gpio_pin)); #endif //GPIOTE_SET_CLEAR_TASKS p_channel_cb->initialized = APP_PWM_CHANNEL_INITIALIZED; m_pwm_ready_counter[p_instance->p_timer->instance_id][channel] = 0; return NRF_SUCCESS; } /** * @brief Function for calculating target timer frequency, which will allow to set given period length. * * @param[in] period_us Desired period in microseconds. * * @retval Timer frequency. */ __STATIC_INLINE nrf_timer_frequency_t pwm_calculate_timer_frequency(uint32_t period_us) { uint32_t f = (uint32_t) NRF_TIMER_FREQ_16MHz; uint32_t min = (uint32_t) NRF_TIMER_FREQ_31250Hz; while ((period_us > TIMER_MAX_PULSEWIDTH_US_ON_16M) && (f < min)) { period_us >>= 1; ++f; } #ifdef GPIOTE_SET_CLEAR_TASKS if ((m_use_ppi_delay_workaround) && (f == (uint32_t) NRF_TIMER_FREQ_16MHz)) { f = (uint32_t) NRF_TIMER_FREQ_8MHz; } #endif // GPIOTE_SET_CLEAR_TASKS return (nrf_timer_frequency_t) f; } ret_code_t app_pwm_init(app_pwm_t const * const p_instance, app_pwm_config_t const * const p_config, app_pwm_callback_t p_ready_callback) { ASSERT(p_instance); if (!p_config) { return NRF_ERROR_INVALID_DATA; } app_pwm_cb_t * p_cb = p_instance->p_cb; if (p_cb->state != NRF_DRV_STATE_UNINITIALIZED) { return NRF_ERROR_INVALID_STATE; } uint32_t err_code = nrf_drv_ppi_init(); if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_MODULE_ALREADY_INITIALIZED)) { return NRF_ERROR_NO_MEM; } if (!nrf_drv_gpiote_is_init()) { err_code = nrf_drv_gpiote_init(); if (err_code != NRF_SUCCESS) { return NRF_ERROR_INTERNAL; } } #ifdef GPIOTE_SET_CLEAR_TASKS if (((*(uint32_t *)0xF0000FE8) & 0x000000F0) == 0x30) { m_use_ppi_delay_workaround = false; } else { m_use_ppi_delay_workaround = true; } #endif // Innitialize resource status: #ifdef GPIOTE_SET_CLEAR_TASKS p_cb->ppi_channel = (nrf_ppi_channel_t)UNALLOCATED; #else p_cb->ppi_channels[0] = (nrf_ppi_channel_t)UNALLOCATED; p_cb->ppi_channels[1] = (nrf_ppi_channel_t)UNALLOCATED; p_cb->ppi_group = (nrf_ppi_channel_group_t)UNALLOCATED; #endif //GPIOTE_SET_CLEAR_TASKS for (uint8_t i = 0; i < APP_PWM_CHANNELS_PER_INSTANCE; ++i) { p_cb->channels_cb[i].initialized = APP_PWM_CHANNEL_UNINITIALIZED; p_cb->channels_cb[i].ppi_channels[0] = (nrf_ppi_channel_t)UNALLOCATED; p_cb->channels_cb[i].ppi_channels[1] = (nrf_ppi_channel_t)UNALLOCATED; p_cb->channels_cb[i].gpio_pin = UNALLOCATED; } // Allocate PPI channels and groups: #ifdef GPIOTE_SET_CLEAR_TASKS if (nrf_drv_ppi_channel_alloc(&p_cb->ppi_channel) != NRF_SUCCESS) { pwm_dealloc(p_instance); return NRF_ERROR_NO_MEM; } #else //GPIOTE_SET_CLEAR_TASKS if (nrf_drv_ppi_group_alloc(&p_cb->ppi_group) != NRF_SUCCESS) { pwm_dealloc(p_instance); return NRF_ERROR_NO_MEM; } for (uint8_t i = 0; i < APP_PWM_REQUIRED_PPI_CHANNELS_PER_INSTANCE; ++i) { if (nrf_drv_ppi_channel_alloc(&p_cb->ppi_channels[i]) != NRF_SUCCESS) { pwm_dealloc(p_instance); return NRF_ERROR_NO_MEM; } } #endif //GPIOTE_SET_CLEAR_TASKS // Initialize channels: for (uint8_t i = 0; i < APP_PWM_CHANNELS_PER_INSTANCE; ++i) { if (p_config->pins[i] != APP_PWM_NOPIN) { err_code = app_pwm_channel_init(p_instance, i, p_config->pins[i], p_config->pin_polarity[i]); if (err_code != NRF_SUCCESS) { pwm_dealloc(p_instance); return err_code; } app_pwm_channel_duty_ticks_set(p_instance, i, 0); } } // Initialize timer: nrf_timer_frequency_t timer_freq = pwm_calculate_timer_frequency(p_config->period_us); nrf_drv_timer_config_t timer_cfg = { .frequency = timer_freq, .mode = NRF_TIMER_MODE_TIMER, .bit_width = NRF_TIMER_BIT_WIDTH_16, .interrupt_priority = APP_IRQ_PRIORITY_LOWEST, .p_context = (void *) (uint32_t) p_instance->p_timer->instance_id }; err_code = nrf_drv_timer_init(p_instance->p_timer, &timer_cfg, pwm_ready_tick); if (err_code != NRF_SUCCESS) { pwm_dealloc(p_instance); return err_code; } uint32_t ticks = nrf_drv_timer_us_to_ticks(p_instance->p_timer, p_config->period_us); p_cb->period = ticks; nrf_drv_timer_clear(p_instance->p_timer); nrf_drv_timer_extended_compare(p_instance->p_timer, (nrf_timer_cc_channel_t) PWM_MAIN_CC_CHANNEL, ticks, NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK, true); nrf_drv_timer_compare_int_disable(p_instance->p_timer, PWM_MAIN_CC_CHANNEL); p_cb->p_ready_callback = p_ready_callback; m_instances[p_instance->p_timer->instance_id] = p_instance; m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; p_cb->state = NRF_DRV_STATE_INITIALIZED; return NRF_SUCCESS; } void app_pwm_enable(app_pwm_t const * const p_instance) { app_pwm_cb_t * p_cb = p_instance->p_cb; ASSERT(p_cb->state != NRF_DRV_STATE_UNINITIALIZED); for (uint32_t channel = 0; channel < APP_PWM_CHANNELS_PER_INSTANCE; ++channel) { app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; m_pwm_ready_counter[p_instance->p_timer->instance_id][channel] = 0; if (p_ch_cb->initialized) { nrf_drv_gpiote_out_task_force(p_ch_cb->gpio_pin, POLARITY_INACTIVE(p_instance, channel)); nrf_drv_gpiote_out_task_enable(p_ch_cb->gpio_pin); p_ch_cb->pulsewidth = 0; } } m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_IDLE; pan73_workaround(p_instance->p_timer->p_reg, true); nrf_drv_timer_clear(p_instance->p_timer); nrf_drv_timer_enable(p_instance->p_timer); p_cb->state = NRF_DRV_STATE_POWERED_ON; return; } void app_pwm_disable(app_pwm_t const * const p_instance) { app_pwm_cb_t * p_cb = p_instance->p_cb; ASSERT(p_cb->state != NRF_DRV_STATE_UNINITIALIZED); nrf_drv_timer_disable(p_instance->p_timer); pwm_irq_disable(p_instance); #ifdef GPIOTE_SET_CLEAR_TASKS nrf_drv_ppi_channel_disable(p_cb->ppi_channel); #else for (uint8_t ppi_channel = 0; ppi_channel < APP_PWM_REQUIRED_PPI_CHANNELS_PER_INSTANCE; ++ppi_channel) { nrf_drv_ppi_channel_disable(p_cb->ppi_channels[ppi_channel]); } #endif //GPIOTE_SET_CLEAR_TASKS for (uint8_t channel = 0; channel < APP_PWM_CHANNELS_PER_INSTANCE; ++channel) { app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel]; if (p_ch_cb->initialized) { uint8_t polarity = POLARITY_INACTIVE(p_instance, channel); if (polarity) { nrf_gpio_pin_set(p_ch_cb->gpio_pin); } else { nrf_gpio_pin_clear(p_ch_cb->gpio_pin); } nrf_drv_gpiote_out_task_disable(p_ch_cb->gpio_pin); nrf_drv_ppi_channel_disable(p_ch_cb->ppi_channels[0]); nrf_drv_ppi_channel_disable(p_ch_cb->ppi_channels[1]); } } pan73_workaround(p_instance->p_timer->p_reg, false); p_cb->state = NRF_DRV_STATE_INITIALIZED; return; } ret_code_t app_pwm_uninit(app_pwm_t const * const p_instance) { app_pwm_cb_t * p_cb = p_instance->p_cb; if (p_cb->state == NRF_DRV_STATE_POWERED_ON) { app_pwm_disable(p_instance); } else if (p_cb->state == NRF_DRV_STATE_UNINITIALIZED) { return NRF_ERROR_INVALID_STATE; } pwm_dealloc(p_instance); p_cb->state = NRF_DRV_STATE_UNINITIALIZED; return NRF_SUCCESS; } //lint -restore #endif //NRF_MODULE_ENABLED(APP_PWM)