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
parent
3d3ff2e7eb
commit
be51bd33c1
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue