Created
April 4, 2023 10:23
-
-
Save hyhkjiy/715d5bb4faf13f293bab92209e1041fd to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/** | |
* Copyright (c) 2015 - 2021, 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_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 != NRFX_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 != NRFX_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 != NRFX_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 = NRFX_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 != NRFX_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 = NRFX_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 != NRFX_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 = NRFX_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 == NRFX_DRV_STATE_POWERED_ON) | |
{ | |
app_pwm_disable(p_instance); | |
} | |
else if (p_cb->state == NRFX_DRV_STATE_UNINITIALIZED) | |
{ | |
return NRF_ERROR_INVALID_STATE; | |
} | |
pwm_dealloc(p_instance); | |
p_cb->state = NRFX_DRV_STATE_UNINITIALIZED; | |
return NRF_SUCCESS; | |
} | |
//lint -restore | |
#endif //NRF_MODULE_ENABLED(APP_PWM) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment