From 4ddb539e8de5d53856060831275e43c79878e426 Mon Sep 17 00:00:00 2001 From: micooke Date: Fri, 29 Sep 2017 14:01:39 +0930 Subject: [PATCH 1/2] Fix bug #169 wiring_analog_* : fallback to digitalWrite if no available PWM channel (copied from AVR core) wiring_analog_nRF52.c : convert pwmChannelPins & pwmChannelSequence -> pwmContext to semi-standardise pwm pin allocation and pwm status between nRF51 and nRF52 wiring_private.h : Move pwm structures defines out of wiring_analog_* into wiring_private and make the instantiation of these structure externs instead of statics wiring_digital.c : disable the appropriate pwm timer when a digitalWrite is sent to an allocated (from analogWrite) pwm pin, and free up the pwm channel for re-allocation --- cores/nRF5/wiring_analog_nRF51.c | 39 ++++++++++++++------------------ cores/nRF5/wiring_analog_nRF52.c | 37 ++++++++++++++++++------------ cores/nRF5/wiring_digital.c | 39 ++++++++++++++++++++++++++++---- cores/nRF5/wiring_private.h | 19 +++++++++++++++- 4 files changed, 93 insertions(+), 41 deletions(-) diff --git a/cores/nRF5/wiring_analog_nRF51.c b/cores/nRF5/wiring_analog_nRF51.c index f22a8b5d..0feda151 100644 --- a/cores/nRF5/wiring_analog_nRF51.c +++ b/cores/nRF5/wiring_analog_nRF51.c @@ -28,27 +28,16 @@ extern "C" { #endif -#define PWM_COUNT 3 -#define PIN_FREE 0xffffffff - -struct PWMContext { - uint32_t pin; - uint32_t value; - uint32_t channel; - uint32_t mask; - uint32_t event; -}; +static uint32_t adcReference = ADC_CONFIG_REFSEL_SupplyOneThirdPrescaling; +static uint32_t adcPrescaling = ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling; -static struct PWMContext pwmContext[PWM_COUNT] = { +struct PWMContext pwmContext[PWM_COUNT] = { { PIN_FREE, 0, 1, TIMER_INTENSET_COMPARE1_Msk, 1 }, { PIN_FREE, 0, 2, TIMER_INTENSET_COMPARE2_Msk, 2 }, { PIN_FREE, 0, 3, TIMER_INTENSET_COMPARE3_Msk, 3 } }; -static int timerEnabled = 0; - -static uint32_t adcReference = ADC_CONFIG_REFSEL_SupplyOneThirdPrescaling; -static uint32_t adcPrescaling = ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling; +struct PWMStatus pwmStatus[PWM_TIMER_COUNT] = {0, TIMER1_IRQn}; static uint32_t readResolution = 10; static uint32_t writeResolution = 8; @@ -229,7 +218,7 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) ulPin = g_ADigitalPinMap[ulPin]; - if (!timerEnabled) { + if (pwmStatus[0].numActive == 0) { NVIC_SetPriority(TIMER1_IRQn, 3); NVIC_ClearPendingIRQ(TIMER1_IRQn); NVIC_EnableIRQ(TIMER1_IRQn); @@ -245,8 +234,6 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Msk; NRF_TIMER1->TASKS_START = 0x1UL; - - timerEnabled = true; } for (int i = 0; i < PWM_COUNT; i++) { @@ -259,17 +246,25 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) | ((uint32_t)GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); - ulValue = mapResolution(ulValue, writeResolution, 8); - pwmContext[i].value = ulValue; NRF_TIMER1->CC[pwmContext[i].channel] = ulValue; NRF_TIMER1->INTENSET = pwmContext[i].mask; - - break; + pwmStatus[0].numActive++; + return; } } + + // fallback to digitalWrite if no available PWM channel + if (ulValue < 128) + { + digitalWrite(ulPin, LOW); + } + else + { + digitalWrite(ulPin, LOW); + } } void TIMER1_IRQHandler(void) diff --git a/cores/nRF5/wiring_analog_nRF52.c b/cores/nRF5/wiring_analog_nRF52.c index b6999468..4e0ebc33 100644 --- a/cores/nRF5/wiring_analog_nRF52.c +++ b/cores/nRF5/wiring_analog_nRF52.c @@ -31,20 +31,19 @@ extern "C" { static uint32_t saadcReference = SAADC_CH_CONFIG_REFSEL_Internal; static uint32_t saadcGain = SAADC_CH_CONFIG_GAIN_Gain1_5; -#define PWM_COUNT 3 - -static NRF_PWM_Type* pwms[PWM_COUNT] = { +NRF_PWM_Type* pwms[PWM_COUNT] = { NRF_PWM0, NRF_PWM1, NRF_PWM2 }; -static uint32_t pwmChannelPins[PWM_COUNT] = { - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF +struct PWMContext pwmContext[PWM_COUNT] = { + { PIN_FREE, 0 }, + { PIN_FREE, 0 }, + { PIN_FREE, 0 } }; -static uint16_t pwmChannelSequence[PWM_COUNT]; + +struct PWMStatus pwmStatus[PWM_TIMER_COUNT] = {0, 0}; static int readResolution = 10; static int writeResolution = 8; @@ -220,9 +219,9 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) ulPin = g_ADigitalPinMap[ulPin]; for (int i = 0; i < PWM_COUNT; i++) { - if (pwmChannelPins[i] == 0xFFFFFFFF || pwmChannelPins[i] == ulPin) { - pwmChannelPins[i] = ulPin; - pwmChannelSequence[i] = ulValue | bit(15); + if (pwmContext[i].pin == PIN_FREE || pwmContext[i].pin == ulPin) { + pwmContext[i].pin = ulPin; + pwmContext[i].value = ulValue | bit(15); NRF_PWM_Type* pwm = pwms[i]; @@ -236,15 +235,25 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) pwm->COUNTERTOP = (1 << writeResolution) - 1; pwm->LOOP = 0; pwm->DECODER = ((uint32_t)PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | ((uint32_t)PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos); - pwm->SEQ[0].PTR = (uint32_t)&pwmChannelSequence[i]; + pwm->SEQ[0].PTR = (uint32_t)&(pwmContext[i].value); pwm->SEQ[0].CNT = 1; pwm->SEQ[0].REFRESH = 1; pwm->SEQ[0].ENDDELAY = 0; pwm->TASKS_SEQSTART[0] = 0x1UL; - - break; + pwmStatus[0].numActive++; + return; } } + + // fallback to digitalWrite if no available PWM channel + if (ulValue < 128) + { + digitalWrite(ulPin, LOW); + } + else + { + digitalWrite(ulPin, LOW); + } } #ifdef __cplusplus diff --git a/cores/nRF5/wiring_digital.c b/cores/nRF5/wiring_digital.c index 1d9c27fd..8b9df904 100644 --- a/cores/nRF5/wiring_digital.c +++ b/cores/nRF5/wiring_digital.c @@ -20,6 +20,13 @@ #include "nrf.h" #include "Arduino.h" +#include "wiring_private.h" + +extern struct PWMContext pwmContext[PWM_COUNT]; +extern struct PWMStatus pwmStatus[PWM_TIMER_COUNT]; +#ifdef NRF52 +extern NRF_PWM_Type* pwms[PWM_COUNT]; +#endif #ifdef __cplusplus extern "C" { @@ -86,18 +93,42 @@ void digitalWrite( uint32_t ulPin, uint32_t ulVal ) ulPin = g_ADigitalPinMap[ulPin]; + for (uint8_t i = 0; i < PWM_COUNT; i++) + { + if (pwmContext[i].pin == ulPin) + { + pwmContext[i].pin = PIN_FREE; + #ifdef NRF52 + // Disable the PWM + NRF_PWM_Type* pwm = pwms[i]; + pwm->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); + #endif + pwmStatus[0].numActive--; + } + } + + #ifdef NRF51 + // Turn off the Timer if no pwm signals are allocated + for (uint8_t i = 0; i < PWM_TIMER_COUNT; i++) + { + if (pwmStatus[i].numActive == 0) + { + NVIC_ClearPendingIRQ(pwmStatus[i].irqNumber); + NVIC_DisableIRQ(pwmStatus[i].irqNumber); + } + } + #endif + switch ( ulVal ) { case LOW: NRF_GPIO->OUTCLR = (1UL << ulPin); - break ; + break; default: NRF_GPIO->OUTSET = (1UL << ulPin); - break ; + break; } - - return ; } int digitalRead( uint32_t ulPin ) diff --git a/cores/nRF5/wiring_private.h b/cores/nRF5/wiring_private.h index 1907c234..ff85af11 100644 --- a/cores/nRF5/wiring_private.h +++ b/cores/nRF5/wiring_private.h @@ -26,9 +26,26 @@ extern "C" { #endif - #include "wiring_constants.h" +#define PWM_COUNT 3 +#define PWM_TIMER_COUNT 1 // 3 channels of TIMER1 are used. TIMER2 also could be used for PWM +#define PIN_FREE 0xffffffff + +struct PWMContext { + uint32_t pin; + uint32_t value; + #ifdef NRF51 + uint32_t channel; + uint32_t mask; + uint32_t event; + #endif +}; + +struct PWMStatus { + int8_t numActive; + int8_t irqNumber; +}; #ifdef __cplusplus } // extern "C" From 532d7d66dadc896c9b088f9a57c367ccc4eef5bc Mon Sep 17 00:00:00 2001 From: micooke Date: Mon, 9 Oct 2017 14:14:46 +1030 Subject: [PATCH 2/2] * split PWM_COUNT to PWM_MODULE_COUNT and PWM_CHANNEL_COUNT (N modules with M channels each) * replace static 8b resolution for nRF51 and nRF52 with user-defined up to 16b resolution * enable 6x PWM signals for nRF51 (up from 3) * enable 12x PWM signals for nRF52 (up from 3) * disable / free-up PWM signals when ZERO is written as the analog value analogWrite(PIN, 0) --- cores/nRF5/wiring_analog_nRF51.c | 176 ++++++++++++++++++++++++------- cores/nRF5/wiring_analog_nRF52.c | 116 ++++++++++++++------ cores/nRF5/wiring_digital.c | 38 +------ cores/nRF5/wiring_private.h | 18 +++- 4 files changed, 241 insertions(+), 107 deletions(-) diff --git a/cores/nRF5/wiring_analog_nRF51.c b/cores/nRF5/wiring_analog_nRF51.c index 0feda151..9fb5cb74 100644 --- a/cores/nRF5/wiring_analog_nRF51.c +++ b/cores/nRF5/wiring_analog_nRF51.c @@ -31,16 +31,26 @@ extern "C" { static uint32_t adcReference = ADC_CONFIG_REFSEL_SupplyOneThirdPrescaling; static uint32_t adcPrescaling = ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling; +NRF_TIMER_Type* pwms[PWM_MODULE_COUNT] = { + NRF_TIMER1, + NRF_TIMER2 +}; + struct PWMContext pwmContext[PWM_COUNT] = { - { PIN_FREE, 0, 1, TIMER_INTENSET_COMPARE1_Msk, 1 }, - { PIN_FREE, 0, 2, TIMER_INTENSET_COMPARE2_Msk, 2 }, - { PIN_FREE, 0, 3, TIMER_INTENSET_COMPARE3_Msk, 3 } + { PIN_FREE, 0, TIMER_INTENSET_COMPARE1_Msk, 1, 1, 0 }, + { PIN_FREE, 0, TIMER_INTENSET_COMPARE2_Msk, 2, 2, 0 }, + { PIN_FREE, 0, TIMER_INTENSET_COMPARE3_Msk, 3, 3, 0 }, + { PIN_FREE, 0, TIMER_INTENSET_COMPARE1_Msk, 1, 1, 1 }, + { PIN_FREE, 0, TIMER_INTENSET_COMPARE2_Msk, 2, 2, 1 }, + { PIN_FREE, 0, TIMER_INTENSET_COMPARE3_Msk, 3, 3, 1 } }; -struct PWMStatus pwmStatus[PWM_TIMER_COUNT] = {0, TIMER1_IRQn}; +struct PWMStatus pwmStatus[PWM_MODULE_COUNT] = {{0, TIMER1_IRQn},{0, TIMER2_IRQn}}; static uint32_t readResolution = 10; static uint32_t writeResolution = 8; +static uint32_t halfAnalogWriteMax = 128; // default for 8b +static uint32_t NRF_TIMER_BITMODE = TIMER_BITMODE_BITMODE_08Bit; void analogReadResolution( int res ) { @@ -50,6 +60,27 @@ void analogReadResolution( int res ) void analogWriteResolution( int res ) { writeResolution = res; + + // TIMER1 has either 16b or 8b PWM resolution + if ((res > 1) && (res < 17)) + { + if (res < 9) + { + halfAnalogWriteMax = 128; + NRF_TIMER_BITMODE = TIMER_BITMODE_BITMODE_08Bit; + } + else + { + halfAnalogWriteMax = 32768; // (2^16) >> 1 + NRF_TIMER_BITMODE = TIMER_BITMODE_BITMODE_16Bit; + } + } + else //default to 8b for any invalid res values + { + writeResolution = 8; + halfAnalogWriteMax = 128; + NRF_TIMER_BITMODE = TIMER_BITMODE_BITMODE_08Bit; + } } static inline uint32_t mapResolution( uint32_t value, uint32_t from, uint32_t to ) @@ -216,76 +247,145 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) return; } - ulPin = g_ADigitalPinMap[ulPin]; - - if (pwmStatus[0].numActive == 0) { - NVIC_SetPriority(TIMER1_IRQn, 3); - NVIC_ClearPendingIRQ(TIMER1_IRQn); - NVIC_EnableIRQ(TIMER1_IRQn); - - NRF_TIMER1->MODE = (NRF_TIMER1->MODE & ~TIMER_MODE_MODE_Msk) | ((TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos) & TIMER_MODE_MODE_Msk); - - NRF_TIMER1->BITMODE = (NRF_TIMER1->BITMODE & ~TIMER_BITMODE_BITMODE_Msk) | ((TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos) & TIMER_BITMODE_BITMODE_Msk); - - NRF_TIMER1->PRESCALER = (NRF_TIMER1->PRESCALER & ~TIMER_PRESCALER_PRESCALER_Msk) | ((7 << TIMER_PRESCALER_PRESCALER_Pos) & TIMER_PRESCALER_PRESCALER_Msk); - - NRF_TIMER1->CC[0] = 0; + uint32_t ulPin_ = g_ADigitalPinMap[ulPin]; - NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Msk; + // Turn off PWM if duty cycle == 0 + if (ulValue == 0) + { + for (uint8_t i = 0; i < PWM_COUNT; i++) + { + if (pwmContext[i].pin == ulPin_) + { + pwmContext[i].pin = PIN_FREE; + pwmStatus[pwmContext[i].module].numActive--; + + // allocate the pwm channel + NRF_TIMER_Type* pwm = pwms[pwmContext[i].module]; + + // Turn off the PWM module if no pwm channels are allocated + if (pwmStatus[pwmContext[i].module].numActive == 0) + { + NVIC_ClearPendingIRQ(pwmStatus[pwmContext[i].module].irqNumber); + NVIC_DisableIRQ(pwmStatus[pwmContext[i].module].irqNumber); + + pwm->TASKS_STOP = 1; + pwm->TASKS_CLEAR = 1; + } + + digitalWrite(ulPin, 0); + } + } - NRF_TIMER1->TASKS_START = 0x1UL; + return; } - for (int i = 0; i < PWM_COUNT; i++) { - if (pwmContext[i].pin == PIN_FREE || pwmContext[i].pin == ulPin) { - pwmContext[i].pin = ulPin; + for (uint8_t i = 0; i < PWM_COUNT; i++) + { + if (pwmContext[i].pin == PIN_FREE || pwmContext[i].pin == ulPin_) + { + pwmContext[i].pin = ulPin_; - NRF_GPIO->PIN_CNF[ulPin] = ((uint32_t)GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) + NRF_GPIO->PIN_CNF[ulPin_] = ((uint32_t)GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) | ((uint32_t)GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) | ((uint32_t)GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | ((uint32_t)GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); - pwmContext[i].value = ulValue; + // rescale from an arbitrary resolution to either 8b or 16b (transparent to the user) + // This assumes that the user wont specify an 8b res then pass a 16b res value.. + pwmContext[i].value = mapResolution( ulValue, writeResolution, (writeResolution < 9)?8:16); + + // allocate the pwm channel + NRF_TIMER_Type* pwm = pwms[pwmContext[i].module]; + + // if this is the first channel allocated to the module, turn on pwm + if (pwmStatus[pwmContext[i].module].numActive == 0) + { + NVIC_SetPriority(pwmStatus[pwmContext[i].module].irqNumber, 3); + NVIC_ClearPendingIRQ(pwmStatus[pwmContext[i].module].irqNumber); + NVIC_EnableIRQ(pwmStatus[pwmContext[i].module].irqNumber); + + pwm->MODE = (pwm->MODE & ~TIMER_MODE_MODE_Msk) | ((TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos) & TIMER_MODE_MODE_Msk); + pwm->BITMODE = (pwm->BITMODE & ~TIMER_BITMODE_BITMODE_Msk) | ((NRF_TIMER_BITMODE << TIMER_BITMODE_BITMODE_Pos) & TIMER_BITMODE_BITMODE_Msk); + pwm->PRESCALER = (pwm->PRESCALER & ~TIMER_PRESCALER_PRESCALER_Msk) | ((7 << TIMER_PRESCALER_PRESCALER_Pos) & TIMER_PRESCALER_PRESCALER_Msk); + pwm->INTENSET = TIMER_INTENSET_COMPARE0_Msk; + pwm->CC[0] = 0; + pwm->TASKS_START = 0x1UL; + } - NRF_TIMER1->CC[pwmContext[i].channel] = ulValue; + pwm->CC[pwmContext[i].channel] = ulValue; + pwm->INTENSET |= pwmContext[i].mask; - NRF_TIMER1->INTENSET = pwmContext[i].mask; - pwmStatus[0].numActive++; + pwmStatus[pwmContext[i].module].numActive++; return; } } // fallback to digitalWrite if no available PWM channel - if (ulValue < 128) + if (ulValue < halfAnalogWriteMax) { digitalWrite(ulPin, LOW); } else { - digitalWrite(ulPin, LOW); + digitalWrite(ulPin, HIGH); } } void TIMER1_IRQHandler(void) { - if (NRF_TIMER1->EVENTS_COMPARE[0]) { - for (int i = 0; i < PWM_COUNT; i++) { - if (pwmContext[i].pin != PIN_FREE && pwmContext[i].value != 0) { + if (NRF_TIMER1->EVENTS_COMPARE[0]) // channel 0 sets all PWM signals HIGH + { + for (uint8_t i = 0; i < PWM_CHANNEL_COUNT; i++) + { + if (pwmContext[i].pin != PIN_FREE && pwmContext[i].value != 0) + { + NRF_GPIO->OUTSET = (1UL << pwmContext[i].pin); + } + } + + NRF_TIMER1->EVENTS_COMPARE[0] = 0; + } + + for (uint8_t i = 0; i < PWM_CHANNEL_COUNT; i++) // compare to CC sets the individual PWM signal LOW + { + if (NRF_TIMER1->EVENTS_COMPARE[pwmContext[i].event]) + { + if (pwmContext[i].pin != PIN_FREE && pwmContext[i].value != 2*halfAnalogWriteMax - 1) + { + NRF_GPIO->OUTCLR = (1UL << pwmContext[i].pin); + } + + NRF_TIMER1->EVENTS_COMPARE[pwmContext[i].event] = 0; + } + } +} + +void TIMER2_IRQHandler(void) +{ + if (NRF_TIMER2->EVENTS_COMPARE[0]) // channel 0 sets all PWM signals HIGH + { + for (uint8_t i = PWM_CHANNEL_COUNT; i < 2*PWM_CHANNEL_COUNT; i++) + { + if (pwmContext[i].pin != PIN_FREE && pwmContext[i].value != 0) + { NRF_GPIO->OUTSET = (1UL << pwmContext[i].pin); } } - NRF_TIMER1->EVENTS_COMPARE[0] = 0x0UL; + NRF_TIMER2->EVENTS_COMPARE[0] = 0; } - for (int i = 0; i < PWM_COUNT; i++) { - if (NRF_TIMER1->EVENTS_COMPARE[pwmContext[i].event]) { - if (pwmContext[i].pin != PIN_FREE && pwmContext[i].value != 255) { + for (uint8_t i = PWM_CHANNEL_COUNT; i < 2*PWM_CHANNEL_COUNT; i++) // compare to CC sets the individual PWM signal LOW + { + if (NRF_TIMER2->EVENTS_COMPARE[pwmContext[i].event]) + { + if (pwmContext[i].pin != PIN_FREE && pwmContext[i].value != 2*halfAnalogWriteMax - 1) + { NRF_GPIO->OUTCLR = (1UL << pwmContext[i].pin); } - NRF_TIMER1->EVENTS_COMPARE[pwmContext[i].event] = 0x0UL; + NRF_TIMER2->EVENTS_COMPARE[pwmContext[i].event] = 0; } } } diff --git a/cores/nRF5/wiring_analog_nRF52.c b/cores/nRF5/wiring_analog_nRF52.c index 4e0ebc33..ec9e0970 100644 --- a/cores/nRF5/wiring_analog_nRF52.c +++ b/cores/nRF5/wiring_analog_nRF52.c @@ -31,22 +31,34 @@ extern "C" { static uint32_t saadcReference = SAADC_CH_CONFIG_REFSEL_Internal; static uint32_t saadcGain = SAADC_CH_CONFIG_GAIN_Gain1_5; -NRF_PWM_Type* pwms[PWM_COUNT] = { +NRF_PWM_Type* pwms[PWM_MODULE_COUNT] = { NRF_PWM0, NRF_PWM1, NRF_PWM2 }; struct PWMContext pwmContext[PWM_COUNT] = { - { PIN_FREE, 0 }, - { PIN_FREE, 0 }, - { PIN_FREE, 0 } + { PIN_FREE, 0, 0 }, + { PIN_FREE, 1, 0 }, + { PIN_FREE, 2, 0 }, + { PIN_FREE, 3, 0 }, + { PIN_FREE, 0, 1 }, + { PIN_FREE, 1, 1 }, + { PIN_FREE, 2, 1 }, + { PIN_FREE, 3, 1 }, + { PIN_FREE, 0, 2 }, + { PIN_FREE, 1, 2 }, + { PIN_FREE, 2, 2 }, + { PIN_FREE, 3, 2 } }; -struct PWMStatus pwmStatus[PWM_TIMER_COUNT] = {0, 0}; +uint16_t pwmValue[PWM_MODULE_COUNT][PWM_CHANNEL_COUNT] = {0}; + +struct PWMStatus pwmStatus[PWM_MODULE_COUNT] = {0}; static int readResolution = 10; static int writeResolution = 8; +static uint32_t halfAnalogWriteMax = 128; // default for 8b void analogReadResolution( int res ) { @@ -55,7 +67,16 @@ void analogReadResolution( int res ) void analogWriteResolution( int res ) { - writeResolution = res; + if ((res > 1) && (res < 17)) // up to 16b PWM resolution + { + writeResolution = res; + halfAnalogWriteMax = (2^res) >> 1; + } + else //default to 8b for any invalid res values + { + writeResolution = 8; + halfAnalogWriteMax = 128; + } } static inline uint32_t mapResolution( uint32_t value, uint32_t from, uint32_t to ) @@ -215,44 +236,79 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) if (ulPin >= PINS_COUNT) { return; } + + uint32_t ulPin_ = g_ADigitalPinMap[ulPin]; - ulPin = g_ADigitalPinMap[ulPin]; + // Turn off PWM if duty cycle == 0 + if (ulValue == 0) + { + for (uint8_t i = 0; i < PWM_COUNT; i++) + { + if (pwmContext[i].pin == ulPin_) + { + pwmContext[i].pin = PIN_FREE; + + // Disable the PWM + NRF_PWM_Type* pwm = pwms[pwmContext[i].module]; + // disconnect from the PWM channel + pwm->PSEL.OUT[pwmContext[i].channel] = PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos; + + pwmStatus[pwmContext[i].module].numActive--; + + // Turn off the PWM module if no pwm channels are allocated + if (pwmStatus[pwmContext[i].module].numActive == 0) + { + pwm->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); + } + + digitalWrite(ulPin, 0); + } + } + + return; + } - for (int i = 0; i < PWM_COUNT; i++) { - if (pwmContext[i].pin == PIN_FREE || pwmContext[i].pin == ulPin) { - pwmContext[i].pin = ulPin; - pwmContext[i].value = ulValue | bit(15); - - NRF_PWM_Type* pwm = pwms[i]; - - pwm->PSEL.OUT[0] = ulPin; - pwm->PSEL.OUT[1] = ulPin; - pwm->PSEL.OUT[2] = ulPin; - pwm->PSEL.OUT[3] = ulPin; - pwm->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos); - pwm->PRESCALER = PWM_PRESCALER_PRESCALER_DIV_1; - pwm->MODE = PWM_MODE_UPDOWN_Up; - pwm->COUNTERTOP = (1 << writeResolution) - 1; - pwm->LOOP = 0; - pwm->DECODER = ((uint32_t)PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | ((uint32_t)PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos); - pwm->SEQ[0].PTR = (uint32_t)&(pwmContext[i].value); - pwm->SEQ[0].CNT = 1; + for (uint8_t i = 0; i < PWM_COUNT; i++) + { + if (pwmContext[i].pin == PIN_FREE || pwmContext[i].pin == ulPin_) + { + pwmContext[i].pin = ulPin_; + pwmValue[pwmContext[i].module][pwmContext[i].channel] = ulValue | bit(15); + + // allocate the pwm channel + NRF_PWM_Type* pwm = pwms[pwmContext[i].module]; + // if this is the first channel allocated to the module, turn on pwm + if (pwmStatus[pwmContext[i].module].numActive == 0) + { + pwm->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos); + pwm->PRESCALER = PWM_PRESCALER_PRESCALER_DIV_1; + pwm->MODE = PWM_MODE_UPDOWN_Up; + pwm->COUNTERTOP = (1 << writeResolution) - 1; + pwm->LOOP = 0; + pwm->DECODER = ((uint32_t)PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | ((uint32_t)PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos); + } + + pwm->PSEL.OUT[pwmContext[i].channel] = pwmContext[i].pin; + + pwm->SEQ[0].PTR = (uint32_t)&pwmValue[pwmContext[i].module]; + pwm->SEQ[0].CNT = 4; pwm->SEQ[0].REFRESH = 1; pwm->SEQ[0].ENDDELAY = 0; - pwm->TASKS_SEQSTART[0] = 0x1UL; - pwmStatus[0].numActive++; + pwm->TASKS_SEQSTART[0] = 1; + + pwmStatus[pwmContext[i].module].numActive++; return; } } // fallback to digitalWrite if no available PWM channel - if (ulValue < 128) + if (ulValue < halfAnalogWriteMax) { digitalWrite(ulPin, LOW); } else { - digitalWrite(ulPin, LOW); + digitalWrite(ulPin, HIGH); } } diff --git a/cores/nRF5/wiring_digital.c b/cores/nRF5/wiring_digital.c index 8b9df904..05491d13 100644 --- a/cores/nRF5/wiring_digital.c +++ b/cores/nRF5/wiring_digital.c @@ -20,13 +20,6 @@ #include "nrf.h" #include "Arduino.h" -#include "wiring_private.h" - -extern struct PWMContext pwmContext[PWM_COUNT]; -extern struct PWMStatus pwmStatus[PWM_TIMER_COUNT]; -#ifdef NRF52 -extern NRF_PWM_Type* pwms[PWM_COUNT]; -#endif #ifdef __cplusplus extern "C" { @@ -91,43 +84,18 @@ void digitalWrite( uint32_t ulPin, uint32_t ulVal ) return; } - ulPin = g_ADigitalPinMap[ulPin]; - - for (uint8_t i = 0; i < PWM_COUNT; i++) - { - if (pwmContext[i].pin == ulPin) - { - pwmContext[i].pin = PIN_FREE; - #ifdef NRF52 - // Disable the PWM - NRF_PWM_Type* pwm = pwms[i]; - pwm->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); - #endif - pwmStatus[0].numActive--; - } - } + // disable PWM on ulPin + analogWrite(ulPin, 0); - #ifdef NRF51 - // Turn off the Timer if no pwm signals are allocated - for (uint8_t i = 0; i < PWM_TIMER_COUNT; i++) - { - if (pwmStatus[i].numActive == 0) - { - NVIC_ClearPendingIRQ(pwmStatus[i].irqNumber); - NVIC_DisableIRQ(pwmStatus[i].irqNumber); - } - } - #endif + ulPin = g_ADigitalPinMap[ulPin]; switch ( ulVal ) { case LOW: NRF_GPIO->OUTCLR = (1UL << ulPin); break; - default: NRF_GPIO->OUTSET = (1UL << ulPin); - break; } } diff --git a/cores/nRF5/wiring_private.h b/cores/nRF5/wiring_private.h index ff85af11..1828e0bc 100644 --- a/cores/nRF5/wiring_private.h +++ b/cores/nRF5/wiring_private.h @@ -28,18 +28,28 @@ extern "C" { #include "wiring_constants.h" -#define PWM_COUNT 3 -#define PWM_TIMER_COUNT 1 // 3 channels of TIMER1 are used. TIMER2 also could be used for PWM +#ifdef NRF52 +#define PWM_COUNT 12 +#define PWM_MODULE_COUNT 3 // 3 PWM modules +#define PWM_CHANNEL_COUNT 4 // 4 channels per PWM module +#else +#define PWM_COUNT 6 +#define PWM_MODULE_COUNT 2 // 2 TIMER modules (1,2) are used. TIMER0 is used by the softdevice +#define PWM_CHANNEL_COUNT 3 // 4 channels per TIMER module. Channel 0 is used for setting the PWM signal HIGH, so only 3 are available +#endif + + #define PIN_FREE 0xffffffff struct PWMContext { uint32_t pin; - uint32_t value; #ifdef NRF51 - uint32_t channel; + uint32_t value; uint32_t mask; uint32_t event; #endif + uint32_t channel; + uint32_t module; }; struct PWMStatus {