diff --git a/cores/nRF5/wiring_analog_nRF51.c b/cores/nRF5/wiring_analog_nRF51.c index f22a8b5d..9fb5cb74 100644 --- a/cores/nRF5/wiring_analog_nRF51.c +++ b/cores/nRF5/wiring_analog_nRF51.c @@ -28,30 +28,29 @@ 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; + +NRF_TIMER_Type* pwms[PWM_MODULE_COUNT] = { + NRF_TIMER1, + NRF_TIMER2 }; -static 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 } +struct PWMContext pwmContext[PWM_COUNT] = { + { 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 } }; -static int timerEnabled = 0; - -static uint32_t adcReference = ADC_CONFIG_REFSEL_SupplyOneThirdPrescaling; -static uint32_t adcPrescaling = ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling; +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 ) { @@ -61,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 ) @@ -227,70 +247,145 @@ void analogWrite( uint32_t ulPin, uint32_t ulValue ) return; } - ulPin = g_ADigitalPinMap[ulPin]; - - if (!timerEnabled) { - 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; - - NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Msk; + uint32_t ulPin_ = g_ADigitalPinMap[ulPin]; - NRF_TIMER1->TASKS_START = 0x1UL; + // 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); + } + } - timerEnabled = true; + 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); - ulValue = mapResolution(ulValue, writeResolution, 8); + // 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; + } - pwmContext[i].value = ulValue; + pwm->CC[pwmContext[i].channel] = ulValue; + pwm->INTENSET |= pwmContext[i].mask; - NRF_TIMER1->CC[pwmContext[i].channel] = ulValue; + pwmStatus[pwmContext[i].module].numActive++; + return; + } + } - NRF_TIMER1->INTENSET = pwmContext[i].mask; + // fallback to digitalWrite if no available PWM channel + if (ulValue < halfAnalogWriteMax) + { + digitalWrite(ulPin, LOW); + } + else + { + digitalWrite(ulPin, HIGH); + } +} - break; +void TIMER1_IRQHandler(void) +{ + 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 TIMER1_IRQHandler(void) +void TIMER2_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_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 b6999468..ec9e0970 100644 --- a/cores/nRF5/wiring_analog_nRF52.c +++ b/cores/nRF5/wiring_analog_nRF52.c @@ -31,23 +31,34 @@ 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_MODULE_COUNT] = { NRF_PWM0, NRF_PWM1, NRF_PWM2 }; -static uint32_t pwmChannelPins[PWM_COUNT] = { - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF +struct PWMContext pwmContext[PWM_COUNT] = { + { 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 } }; -static uint16_t pwmChannelSequence[PWM_COUNT]; + +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 ) { @@ -56,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 ) @@ -216,35 +236,80 @@ 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 (pwmChannelPins[i] == 0xFFFFFFFF || pwmChannelPins[i] == ulPin) { - pwmChannelPins[i] = ulPin; - pwmChannelSequence[i] = 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)&pwmChannelSequence[i]; - 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; - - break; + pwm->TASKS_SEQSTART[0] = 1; + + pwmStatus[pwmContext[i].module].numActive++; + return; } } + + // fallback to digitalWrite if no available PWM channel + if (ulValue < halfAnalogWriteMax) + { + digitalWrite(ulPin, LOW); + } + else + { + digitalWrite(ulPin, HIGH); + } } #ifdef __cplusplus diff --git a/cores/nRF5/wiring_digital.c b/cores/nRF5/wiring_digital.c index 1d9c27fd..05491d13 100644 --- a/cores/nRF5/wiring_digital.c +++ b/cores/nRF5/wiring_digital.c @@ -84,20 +84,19 @@ void digitalWrite( uint32_t ulPin, uint32_t ulVal ) return; } + // disable PWM on ulPin + analogWrite(ulPin, 0); + ulPin = g_ADigitalPinMap[ulPin]; switch ( ulVal ) { case LOW: NRF_GPIO->OUTCLR = (1UL << ulPin); - break ; - + break; default: NRF_GPIO->OUTSET = (1UL << ulPin); - break ; } - - return ; } int digitalRead( uint32_t ulPin ) diff --git a/cores/nRF5/wiring_private.h b/cores/nRF5/wiring_private.h index 1907c234..1828e0bc 100644 --- a/cores/nRF5/wiring_private.h +++ b/cores/nRF5/wiring_private.h @@ -26,9 +26,36 @@ extern "C" { #endif - #include "wiring_constants.h" +#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; + #ifdef NRF51 + uint32_t value; + uint32_t mask; + uint32_t event; + #endif + uint32_t channel; + uint32_t module; +}; + +struct PWMStatus { + int8_t numActive; + int8_t irqNumber; +}; #ifdef __cplusplus } // extern "C"