mbed-classic: added new PWM implementation

The previous pwm implementation was subject to innaccurracies
due to BLE interrupts. This new implementation, courtesy of
@finneyj is resiliant to BLE interrupts, other than a small
time window when changing the pwm period.
master
James Devine 2016-10-22 22:18:52 +01:00
parent 3d3ff2e7eb
commit be51bd33c1
1 changed files with 246 additions and 235 deletions

View File

@ -19,272 +19,245 @@
#include "pinmap.h"
#include "mbed_error.h"
#define NO_PWMS 3
#define TIMER_PRECISION 4 //4us ticks
#define TIMER_PRESCALER 6 //4us ticks = 16Mhz/(2**6)
static const PinMap PinMap_PWM[] = {
{p0, PWM_1, 1},
{p1, PWM_1, 1},
{p2, PWM_1, 1},
{p3, PWM_1, 1},
{p4, PWM_1, 1},
{p5, PWM_1, 1},
{p6, PWM_1, 1},
{p7, PWM_1, 1},
{p8, PWM_1, 1},
{p9, PWM_1, 1},
{p10, PWM_1, 1},
{p11, PWM_1, 1},
{p12, PWM_1, 1},
{p13, PWM_1, 1},
{p14, PWM_1, 1},
{p15, PWM_1, 1},
{p16, PWM_1, 1},
{p17, PWM_1, 1},
{p18, PWM_1, 1},
{p19, PWM_1, 1},
{p20, PWM_1, 1},
{p21, PWM_1, 1},
{p22, PWM_1, 1},
{p23, PWM_1, 1},
{p24, PWM_1, 1},
{p25, PWM_1, 1},
{p28, PWM_1, 1},
{p29, PWM_1, 1},
{p30, PWM_1, 1},
{NC, NC, 0}
};
#define PWM_CHANNELS 3
#define PWM_VALUE_INVALID 0xFFFF
static NRF_TIMER_Type *Timers[1] = {
NRF_TIMER2
};
#define PWM_PERIOD_DEFAULT 20000
uint16_t PERIOD = 20000 / TIMER_PRECISION; //20ms
uint8_t PWM_taken[NO_PWMS] = {0, 0, 0};
uint16_t PULSE_WIDTH[NO_PWMS] = {1, 1, 1}; //set to 1 instead of 0
uint16_t ACTUAL_PULSE[NO_PWMS] = {0, 0, 0};
// use the last timer Capture/Compare channel to define the period.
#define TIMER_PERIOD_CC 3
// 1uS precision, which with a 16bit timer gives us a maximum period of around 65ms.
#define TIMER_PRESCALER_MIN 4
#define TIMER_PRESCALER_MAX 9
static NRF_TIMER_Type *timer = NRF_TIMER2;
// PWM levels, both in absolute microseconds, and TIMER ticks (we cache both for simplicity and performance).
static uint32_t pwm_us[PWM_CHANNELS] = {0};
static uint16_t pwm_ticks[PWM_CHANNELS] = {PWM_VALUE_INVALID};
// PWM period, both in absolute microseconds, and TIMER ticks (we cache both for simplicity and performance).
static uint32_t pwm_period_us = PWM_PERIOD_DEFAULT;
static uint16_t pwm_period_ticks = PWM_PERIOD_DEFAULT;
// List of which pins the GPIOTE channels are allocated to.
static PinName pwm_pins[PWM_CHANNELS] = {NC};
static uint8_t pwm_last_allocated = 0;
// TIMER prescaler currently in use.
static uint8_t pwm_prescaler = TIMER_PRESCALER_MIN;
static uint8_t timer_enabled = 0;
/** @brief Function for handling timer 2 peripheral interrupts.
*/
#ifdef __cplusplus
extern "C" {
#endif
void TIMER2_IRQHandler(void)
{
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->CC[3] = PERIOD;
timer->TASKS_STOP = 1;
if (PWM_taken[0]) {
NRF_TIMER2->CC[0] = PULSE_WIDTH[0];
}
if (PWM_taken[1]) {
NRF_TIMER2->CC[1] = PULSE_WIDTH[1];
}
if (PWM_taken[2]) {
NRF_TIMER2->CC[2] = PULSE_WIDTH[2];
}
// update the CC values of all channels.
for (int i=0; i<PWM_CHANNELS; i++)
timer->CC[i] = pwm_ticks[i];
NRF_TIMER2->TASKS_START = 1;
// Disable this interrupt - it is needed only when PWM values change.
timer->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk;
timer->INTENCLR = TIMER_INTENCLR_COMPARE3_Msk;
timer->PRESCALER = pwm_prescaler;
timer->CC[TIMER_PERIOD_CC] = pwm_period_ticks;
timer->TASKS_START = 1;
timer->EVENTS_COMPARE[TIMER_PERIOD_CC] = 0;
}
#ifdef __cplusplus
}
#endif
/** @brief Function for initializing the Timer peripherals.
*/
void timer_init(uint8_t pwmChoice)
static void timer_init()
{
NRF_TIMER_Type *timer = Timers[0];
timer->TASKS_STOP = 0;
if(timer_enabled)
return;
if (pwmChoice == 0) {
timer->POWER = 0;
timer->POWER = 1;
timer->MODE = TIMER_MODE_MODE_Timer;
timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
timer->PRESCALER = TIMER_PRESCALER;
timer->CC[3] = PERIOD;
}
timer->TASKS_STOP = 1;
timer->POWER = 0;
timer->POWER = 1;
timer->MODE = TIMER_MODE_MODE_Timer;
timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
timer->PRESCALER = pwm_prescaler;
timer->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk;
timer->CC[TIMER_PERIOD_CC] = pwm_period_ticks;
timer->CC[pwmChoice] = PULSE_WIDTH[pwmChoice];
//high priority application interrupt
NVIC_SetPriority(TIMER2_IRQn, 1);
NVIC_EnableIRQ(TIMER2_IRQn);
timer->TASKS_START = 0x01;
timer->TASKS_START = 1;
timer_enabled = 1;
}
static uint16_t PWM_US_TO_TICKS(uint32_t value)
{
return ((uint16_t) (value / ((uint32_t)(1 << (pwm_prescaler - TIMER_PRESCALER_MIN)))));
}
static int8_t pwm_get_channel(PinName pin)
{
// If a channel is already assigned to this pin, return that.
for (int i=0; i<PWM_CHANNELS; i++)
{
if (pwm_pins[i] == pin)
{
pwm_last_allocated = i;
return i;
}
}
return NC;
}
static void pwm_disconnect(PinName pin)
{
// We need two PPI channels for each PWM. We precompute this here for simplicity.
uint8_t channel = 0xFF;
for (int i=0; i < PWM_CHANNELS; i++)
{
if (pwm_pins[i] == pin)
{
channel = i;
break;
}
}
// if PWM is not connected, nothing to do.
if (channel == 0xFF)
return;
NRF_GPIOTE->CONFIG[channel] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
(31UL << GPIOTE_CONFIG_PSEL_Pos) |
(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos);
NRF_PPI->CHENCLR = (1 << channel*2) | (1 << (channel*2 + 1));
pwm_pins[channel] = NC;
}
static int8_t pwm_allocate_channel(PinName pin)
{
// Allocate the first unused one.
for (int i=0; i<PWM_CHANNELS; i++)
if (pwm_pins[i] == NC)
{
pwm_pins[i] = pin;
pwm_last_allocated = i;
return i;
}
// We're out of available channels. Perform a round robin to recycle
// the least recently used one.
pwm_last_allocated = (pwm_last_allocated + 1) % PWM_CHANNELS;
pwm_disconnect(pwm_pins[pwm_last_allocated]);
pwm_pins[pwm_last_allocated] = pin;
return pwm_last_allocated;
}
/** @brief Function for initializing the GPIO Tasks/Events peripheral.
*/
void gpiote_init(PinName pin, uint8_t channel_number)
{
// Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output.
NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
| (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos)
| (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos)
| (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos)
| (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
NRF_GPIO->OUTCLR = (1UL << pin);
// Configure GPIOTE channel 0 to toggle the PWM pin state
// @note Only one GPIOTE task can be connected to an output pin.
/* Configure channel to Pin31, not connected to the pin, and configure as a tasks that will set it to proper level */
NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
(31UL << GPIOTE_CONFIG_PSEL_Pos) |
(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos);
/* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
__NOP();
__NOP();
__NOP();
/* Launch the task to take the GPIOTE channel output to the desired level */
NRF_GPIOTE->TASKS_OUT[channel_number] = 1;
static void pwm_connect(PinName pin, uint8_t gpiote_channel)
{
// We need two PPI channels for each PWM. We precompute this here for simplicity.
uint8_t ppi_channel = gpiote_channel*2;
/* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly.
If it does not, the channel output inheritance sets the proper level. */
NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)pin << GPIOTE_CONFIG_PSEL_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |
((uint32_t)GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos); // ((uint32_t)GPIOTE_CONFIG_OUTINIT_High <<
// GPIOTE_CONFIG_OUTINIT_Pos);//
// Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output.
NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
| (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos)
| (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos)
| (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos)
| (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
/* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
__NOP();
__NOP();
__NOP();
}
NRF_GPIO->OUTCLR = (1UL << pin);
/** @brief Function for initializing the Programmable Peripheral Interconnect peripheral.
*/
static void ppi_init(uint8_t pwm)
{
//using ppi channels 0-7 (only 0-7 are available)
uint8_t channel_number = 2 * pwm;
NRF_TIMER_Type *timer = Timers[0];
/* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly.
If it does not, the channel output inheritance sets the proper level. */
NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)pin << GPIOTE_CONFIG_PSEL_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |
((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos); // ((uint32_t)GPIOTE_CONFIG_OUTINIT_High <<
// GPIOTE_CONFIG_OUTINIT_Pos);//
// Configure PPI channel 0 to toggle ADVERTISING_LED_PIN_NO on every TIMER1 COMPARE[0] match
NRF_PPI->CH[channel_number].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[pwm];
NRF_PPI->CH[channel_number + 1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[pwm];
NRF_PPI->CH[channel_number].EEP = (uint32_t)&timer->EVENTS_COMPARE[pwm];
NRF_PPI->CH[channel_number + 1].EEP = (uint32_t)&timer->EVENTS_COMPARE[3];
/* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
__NOP();
__NOP();
__NOP();
// Enable PPI channels.
NRF_PPI->CHEN |= (1 << channel_number) |
(1 << (channel_number + 1));
}
/* Launch the task to take the GPIOTE channel output to the desired level */
NRF_GPIOTE->TASKS_OUT[gpiote_channel] = 1;
void setModulation(pwmout_t *obj, uint8_t toggle, uint8_t high)
{
if (high) {
NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);
if (toggle) {
NRF_GPIOTE->CONFIG[obj->pwm] |= (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
} else {
NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos);
}
} else {
NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);
// Bring up a PWM
NRF_PPI->CH[ppi_channel].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[gpiote_channel];
NRF_PPI->CH[ppi_channel].EEP = (uint32_t)&timer->EVENTS_COMPARE[gpiote_channel];
//on a compare event on RTC 1, flip the pin again, i.e. pwm down
NRF_PPI->CH[ppi_channel+1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[gpiote_channel];
NRF_PPI->CH[ppi_channel+1].EEP = (uint32_t)&timer->EVENTS_COMPARE[TIMER_PERIOD_CC];
NRF_PPI->CHENSET = (1 << ppi_channel) | (1 << (ppi_channel + 1));
}
if (toggle) {
NRF_GPIOTE->CONFIG[obj->pwm] |= (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
} else {
NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos);
}
}
}
void pwmout_init(pwmout_t *obj, PinName pin)
{
// determine the channel
uint8_t pwmOutSuccess = 0;
PWMName pwm = (PWMName)pinmap_peripheral(pin, PinMap_PWM);
MBED_ASSERT(pin != (PinName)NC);
MBED_ASSERT(pwm != (PWMName)NC);
timer_init();
if (PWM_taken[(uint8_t)pwm]) {
for (uint8_t i = 1; !pwmOutSuccess && (i<NO_PWMS); i++) {
if (!PWM_taken[i]) {
pwm = (PWMName)i;
PWM_taken[i] = 1;
pwmOutSuccess = 1;
}
}
} else {
pwmOutSuccess = 1;
PWM_taken[(uint8_t)pwm] = 1;
int8_t channel = pwm_get_channel(pin);
if (channel == NC)
{
// Bring up a new channel (this can't fail).
channel = pwm_allocate_channel(pin);
timer->CC[channel] = PWM_VALUE_INVALID;
pwm_connect(pin, channel);
}
if (!pwmOutSuccess) {
error("PwmOut pin mapping failed. All available PWM channels are in use.");
}
obj->pwm = pwm;
obj->pwm = channel;
obj->pin = pin;
gpiote_init(pin, (uint8_t)pwm);
ppi_init((uint8_t)pwm);
if (pwm == 0) {
NRF_POWER->TASKS_CONSTLAT = 1;
}
timer_init((uint8_t)pwm);
//default to 20ms: standard for servos, and fine for e.g. brightness control
pwmout_period_ms(obj, 20);
pwmout_write (obj, 0);
pwmout_pulsewidth_us(obj, 0);
}
void pwmout_free(pwmout_t *obj)
{
MBED_ASSERT(obj->pwm != (PWMName)NC);
PWM_taken[obj->pwm] = 0;
pwmout_write(obj, 0);
pwm_disconnect(obj->pin);
}
void pwmout_write(pwmout_t *obj, float value)
{
uint16_t oldPulseWidth;
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->TASKS_STOP = 1;
if (value < 0.0f) {
value = 0.0;
} else if (value > 1.0f) {
value = 1.0;
}
oldPulseWidth = ACTUAL_PULSE[obj->pwm];
ACTUAL_PULSE[obj->pwm] = PULSE_WIDTH[obj->pwm] = value * PERIOD;
if (PULSE_WIDTH[obj->pwm] == 0) {
PULSE_WIDTH[obj->pwm] = 1;
setModulation(obj, 0, 0);
} else if (PULSE_WIDTH[obj->pwm] == PERIOD) {
PULSE_WIDTH[obj->pwm] = PERIOD - 1;
setModulation(obj, 0, 1);
} else if ((oldPulseWidth == 0) || (oldPulseWidth == PERIOD)) {
setModulation(obj, 1, oldPulseWidth == PERIOD);
}
NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
NRF_TIMER2->TASKS_START = 1;
pwmout_pulsewidth_us(obj, (int)((float)pwm_period_us * value));
}
float pwmout_read(pwmout_t *obj)
{
return ((float)PULSE_WIDTH[obj->pwm] / (float)PERIOD);
int8_t channel = pwm_get_channel(obj->pin);
if(channel == NC)
return 0;
return (float)pwm_us[channel] / (float)pwm_period_us;
}
void pwmout_period(pwmout_t *obj, float seconds)
{
pwmout_period_us(obj, seconds * 1000000.0f);
pwmout_period_us(obj,(int)(seconds * (float)1000000));
}
void pwmout_period_ms(pwmout_t *obj, int ms)
@ -295,26 +268,48 @@ void pwmout_period_ms(pwmout_t *obj, int ms)
// Set the PWM period, keeping the duty cycle the same.
void pwmout_period_us(pwmout_t *obj, int us)
{
uint32_t periodInTicks = us / TIMER_PRECISION;
uint32_t p = 0x10000;
int prescaler = TIMER_PRESCALER_MIN;
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->TASKS_STOP = 1;
// Quick validation - do nothing if the frequency is identical to it's current value.
if (us == pwm_period_us)
return;
if (periodInTicks>((1 << 16) - 1)) {
PERIOD = (1 << 16) - 1; //131ms
} else if (periodInTicks<5) {
PERIOD = 5;
} else {
PERIOD = periodInTicks;
// Calculate the ideal prescaler for this value. If it's higher than our current prescaler.
while (p < us && p != (uint32_t) (1 << 31))
{
p = p << 1;
prescaler++;
}
NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
NRF_TIMER2->TASKS_START = 1;
// Silently ignore requests to go out of scope (all we can do with the mbed API at present).
if (prescaler > TIMER_PRESCALER_MAX)
return;
// Update global frequency timestamps.
pwm_prescaler = prescaler;
pwm_period_ticks = PWM_US_TO_TICKS(us);
pwmout_t pwm;
pwm.pin = NC;
pwm.pwm = NC;
// Update all PWM channel values to maintin the same duty cycle (mbed standard behaviour).
for (int i=0; i<PWM_CHANNELS; i++)
if (pwm_ticks[i] != PWM_VALUE_INVALID)
{
pwm.pin = pwm_pins[i];
pwmout_pulsewidth_us(&pwm, (uint32_t) ((float)pwm_us[i] * ((float)us / (float)pwm_period_us)));
pwm.pin = NC;
}
// cache our new period in microseconds for later use
pwm_period_us = us;
}
void pwmout_pulsewidth(pwmout_t *obj, float seconds)
{
pwmout_pulsewidth_us(obj, seconds * 1000000.0f);
pwmout_pulsewidth_us(obj,(int)(seconds * (float)1000000));
}
void pwmout_pulsewidth_ms(pwmout_t *obj, int ms)
@ -324,24 +319,40 @@ void pwmout_pulsewidth_ms(pwmout_t *obj, int ms)
void pwmout_pulsewidth_us(pwmout_t *obj, int us)
{
uint32_t pulseInTicks = us / TIMER_PRECISION;
uint16_t oldPulseWidth = ACTUAL_PULSE[obj->pwm];
PinName pin = (PinName)obj->pin;
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->TASKS_STOP = 1;
int8_t channel = pwm_get_channel(pin);
ACTUAL_PULSE[obj->pwm] = PULSE_WIDTH[obj->pwm] = pulseInTicks;
if (PULSE_WIDTH[obj->pwm] == 0) {
PULSE_WIDTH[obj->pwm] = 1;
setModulation(obj, 0, 0);
} else if (PULSE_WIDTH[obj->pwm] == PERIOD) {
PULSE_WIDTH[obj->pwm] = PERIOD - 1;
setModulation(obj, 0, 1);
} else if ((oldPulseWidth == 0) || (oldPulseWidth == PERIOD)) {
setModulation(obj, 1, oldPulseWidth == PERIOD);
if (channel == NC)
{
// Bring up a new channel (this can't fail).
channel = pwm_allocate_channel(pin);
timer->CC[channel] = PWM_VALUE_INVALID;
pwm_connect(pin, channel);
}
NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
NRF_TIMER2->TASKS_START = 1;
// Record (internally for now) the new pulse width of this channel.
pwm_us[channel] = us;
pwm_ticks[channel] = PWM_US_TO_TICKS(us);
// If
if (us == 0 || us >= pwm_period_us)
{
pwm_disconnect(pin);
if (us)
NRF_GPIO->OUTSET = (1UL << pin);
else
NRF_GPIO->OUTCLR = (1UL << pin);
return;
}
// Configure to receive a one-shot interrupt when the next PWM period completes (also stop the timer at this point to avoid potential race conditions)
__disable_irq();
timer->EVENTS_COMPARE[TIMER_PERIOD_CC] = 0;
timer->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
timer->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
__enable_irq();
}