diff --git a/cores/nRF5/HardwarePWM.cpp b/cores/nRF5/HardwarePWM.cpp index 881c054df..f9927727d 100644 --- a/cores/nRF5/HardwarePWM.cpp +++ b/cores/nRF5/HardwarePWM.cpp @@ -53,9 +53,123 @@ HardwarePWM* HwPWMx[] = #endif }; -HardwarePWM::HardwarePWM(NRF_PWM_Type* pwm) +#if CFG_DEBUG +bool can_stringify_token(uintptr_t token) { - _pwm = pwm; + uint8_t * t = (uint8_t *)&token; + for (size_t i = 0; i < sizeof(uintptr_t); ++i, ++t) + { + uint8_t x = *t; + if ((x < 0x20) || (x > 0x7E)) return false; + } + return true; +} + +void HardwarePWM::DebugOutput(Stream& logger) +{ + const size_t count = arrcount(HwPWMx); + logger.printf("HwPWM Debug:"); + for (size_t i = 0; i < count; i++) { + HardwarePWM const * pwm = HwPWMx[i]; + uintptr_t token = pwm->_owner_token; + logger.printf(" || %d:", i); + if (can_stringify_token(token)) { + uint8_t * t = (uint8_t*)(&token); + static_assert(sizeof(uintptr_t) == 4); + logger.printf(" \"%c%c%c%c\"", t[0], t[1], t[2], t[3] ); + } else { + static_assert(sizeof(uintptr_t) == 4); + logger.printf(" %08x", token); + } + for (size_t j = 0; j < MAX_CHANNELS; j++) { + uint32_t r = pwm->_pwm->PSEL.OUT[j]; // only read it once + if ( (r & PWM_PSEL_OUT_CONNECT_Msk) != (PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos) ) { + logger.printf(" %02x", r & 0x1F); + } else { + logger.printf(" xx"); + } + } + } + logger.printf("\n"); +} +#else +void HardwarePWM::DebugOutput(Stream& logger) {} +#endif // CFG_DEBUG + +// returns true ONLY when (1) no PWM channel has a pin, and (2) the owner token is nullptr +bool HardwarePWM::takeOwnership(uintptr_t token) +{ + bool notInIsr = !isInISR(); + if (token == 0) { + if (notInIsr) { + LOG_LV1("HwPWM", "zero / nullptr is not a valid ownership token (attempted use in takeOwnership)"); + } + return false; // cannot take ownership with nullptr + } + if (token == this->_owner_token) { + if (notInIsr) { + LOG_LV1("HwPWM", "failing to acquire ownership because already owned by requesting token (cannot take ownership twice)"); + } + } + if (this->_owner_token != 0) { + return false; + } + if (this->usedChannelCount() != 0) { + return false; + } + if (this->enabled()) { + return false; + } + // TODO: warn, but do not fail, if taking ownership with IRQs already enabled + // NVIC_GetActive + + // Use C++11 atomic CAS operation + uintptr_t newValue = 0U; + return this->_owner_token.compare_exchange_strong(newValue, token); +} +// returns true ONLY when (1) no PWM channel has a pin attached, and (2) the owner token matches +bool HardwarePWM::releaseOwnership(uintptr_t token) +{ + bool notInIsr = !isInISR(); + if (token == 0) { + if (notInIsr) { + LOG_LV1("HwPWM", "zero / nullptr is not a valid ownership token (attempted use in releaseOwnership)"); + } + return false; + } + if (!this->isOwner(token)) { + if (notInIsr) { + LOG_LV1("HwPWM", "attempt to release ownership when not the current owner"); + } + return false; + } + if (this->usedChannelCount() != 0) { + if (notInIsr) { + LOG_LV1("HwPWM", "attempt to release ownership when at least on channel is still connected"); + } + return false; + } + if (this->enabled()) { + if (notInIsr) { + LOG_LV1("HwPWM", "attempt to release ownership when PWM peripheral is still enabled"); + } + return false; // if it's enabled, do not allow ownership to be released, even with no pins in use + } + // TODO: warn, but do not fail, if releasing ownership with IRQs enabled + // NVIC_GetActive + + // Use C++11 atomic CAS operation + bool result = this->_owner_token.compare_exchange_strong(token, 0U); + if (!result) { + LOG_LV1("HwPWM", "race condition resulted in failure to acquire ownership"); + } + return result; +} + +HardwarePWM::HardwarePWM(NRF_PWM_Type* pwm) : + _pwm(pwm) +{ + _owner_token = 0U; arrclr(_seq0); _max_value = 255; @@ -204,7 +318,7 @@ bool HardwarePWM::writePin(uint8_t pin, uint16_t value, bool inverted) return writeChannel(ch, value, inverted); } -uint16_t HardwarePWM::readPin(uint8_t pin) +uint16_t HardwarePWM::readPin(uint8_t pin) const { int ch = pin2channel(pin); VERIFY( ch >= 0, 0); @@ -212,9 +326,27 @@ uint16_t HardwarePWM::readPin(uint8_t pin) return readChannel(ch); } -uint16_t HardwarePWM::readChannel(uint8_t ch) +uint16_t HardwarePWM::readChannel(uint8_t ch) const { // remove inverted bit return (_seq0[ch] & 0x7FFF); } +uint8_t HardwarePWM::usedChannelCount(void) const +{ + uint8_t usedChannels = 0; + for(int i=0; iPSEL.OUT[i] & PWM_PSEL_OUT_CONNECT_Msk) != (PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos) ) + { + usedChannels++; + } + } + return usedChannels; +} + +uint8_t HardwarePWM::freeChannelCount(void) const +{ + return MAX_CHANNELS - usedChannelCount(); +} + diff --git a/cores/nRF5/HardwarePWM.h b/cores/nRF5/HardwarePWM.h index bf2691e3f..024a8c848 100644 --- a/cores/nRF5/HardwarePWM.h +++ b/cores/nRF5/HardwarePWM.h @@ -38,6 +38,7 @@ #include "common_inc.h" #include "nrf.h" +#include #ifdef NRF52840_XXAA #define HWPWM_MODULE_NUM 4 @@ -49,7 +50,8 @@ class HardwarePWM { private: enum { MAX_CHANNELS = 4 }; // Max channel per group - NRF_PWM_Type* _pwm; + NRF_PWM_Type * const _pwm; + std::atomic_uintptr_t _owner_token; uint16_t _seq0[MAX_CHANNELS]; @@ -67,10 +69,23 @@ class HardwarePWM void setClockDiv(uint8_t div); // value is PWM_PRESCALER_PRESCALER_DIV_x, DIV1 is 16Mhz + // Cooperative ownership sharing + + // returns true ONLY when (1) no PWM channel has a pin, and (2) the owner token is nullptr + bool takeOwnership (uintptr_t token); + // returns true ONLY when (1) no PWM channel has a pin attached, and (2) the owner token matches + bool releaseOwnership(uintptr_t token); + + // allows caller to verify that they own the peripheral + __INLINE bool isOwner(uintptr_t token) const + { + return this->_owner_token == token; + } + bool addPin (uint8_t pin); bool removePin (uint8_t pin); - int pin2channel(uint8_t pin) + int pin2channel(uint8_t pin) const { pin = g_ADigitalPinMap[pin]; for(int i=0; i= 0; } @@ -94,8 +109,14 @@ class HardwarePWM bool writeChannel(uint8_t ch , uint16_t value, bool inverted = false); // Read current set value - uint16_t readPin (uint8_t pin); - uint16_t readChannel (uint8_t ch); + uint16_t readPin (uint8_t pin) const; + uint16_t readChannel (uint8_t ch) const; + + // Get count of used / free channels + uint8_t usedChannelCount(void) const; + uint8_t freeChannelCount(void) const; + + static void DebugOutput(Stream& logger); }; extern HardwarePWM HwPWM0; diff --git a/cores/nRF5/Tone.cpp b/cores/nRF5/Tone.cpp index e244d3915..c2321d8d9 100644 --- a/cores/nRF5/Tone.cpp +++ b/cores/nRF5/Tone.cpp @@ -1,22 +1,22 @@ /* Tone.cpp - A Tone Generator Library +A Tone Generator Library - Written by Brett Hagman +Written by Brett Hagman - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Version Modified By Date Comments ------- ----------- -------- -------- @@ -34,161 +34,382 @@ Version Modified By Date Comments 0010 Arduino.org 16/07/27 Added Arduino Primo support *************************************************/ - +#include "Arduino.h" #include "Tone.h" #include "WVariant.h" +#include "limits.h" + +// NOTE: Currently hard-coded to only use PWM2 ... +// These are the relvant hard-coded variables +// TODO: Consider allowing dynamic use of any available PWM peripheral (but start with #2 for compatibility) +static NRF_PWM_Type * const _PWMInstance = NRF_PWM2; +static HardwarePWM * const _HwPWM = HwPWMx[2]; + +// Defined a struct, to simplify validation testing ... also provides context when debugging +class TonePwmConfig { + private: + const nrf_pwm_clk_t clock_frequency = NRF_PWM_CLK_125kHz; + const nrf_pwm_mode_t pwm_mode = NRF_PWM_MODE_UP; + const nrf_pwm_dec_step_t step_mode = NRF_PWM_STEP_AUTO; + const nrf_pwm_dec_load_t load_mode = NRF_PWM_LOAD_COMMON; + const uintptr_t toneToken = 0x656e6f54; //< 'T' 'o' 'n' 'e' + private: + uint64_t pulse_count; //< total number of PWM pulses + uint32_t seq0_refresh; //< count of pulses for each SEQ0 iteration + uint32_t seq1_refresh; //< count of pulses for each SEQ1 iteration + uint16_t loop_count; //< how many times to restart SEQ0/SEQ1? + uint16_t time_period; //< how many clock cycles allocated to each PWM pulse? + uint16_t duty_with_polarity; //< SEQ[N].PTR will point here, length == 1 + uint8_t arduino_pin; //< the arduino pin for playback + uint8_t nrf_pin; //< the nrf pin for playback + nrf_pwm_task_t task_to_start; //< Whether to start playback at SEQ0 or SEQ1 + nrf_pwm_short_mask_t shorts; //< shortcuts to enable + + public: + bool ensurePwmPeripheralOwnership(void); + bool initializeFromPulseCountAndTimePeriod(uint64_t pulse_count, uint16_t time_period); + bool applyConfiguration(uint32_t pin); + bool startPlayback(void); + bool stopPlayback(bool releaseOwnership = false); +}; +TonePwmConfig _pwm_config; -unsigned long int count_duration=0; -volatile bool no_stop = false; -uint8_t pin_sound=0; +inline static bool _is_pwm_enabled(NRF_PWM_Type const * pwm_instance) { + bool isEnabled = + (pwm_instance->ENABLE & PWM_ENABLE_ENABLE_Msk) == + (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos); + return isEnabled; +} + +/* These two functions were verified as equivalent to + the prior calculations (except where the old floating-point + based code resulted in rounding errors ... and thus there + are some one-off differences ... but the integer-based + functions are more accurate). + + These functions entirely avoid floating point math, and all + the nasty edge cases they can create... (NaN, INF, etc.) + See https://gist.github.com/henrygab/6b570ebd51354bf247633c72b8dc383b + for code that compares the new lambdas to the old calculations. +*/ +constexpr inline static uint16_t _calculate_time_period(uint32_t frequency) { + // range for frequency == [20..25000], + // so range of result == [ 5..62500] + // which fits in 16 bits. + return 125000 / frequency; +}; +constexpr inline static uint64_t _calculate_pulse_count(uint32_t frequency, uint32_t duration) { + // range for frequency == [20..25000], + // range for duration == [ 1..0xFFFF_FFFF] + // so range of result == [ 1..0x18_FFFF_FFE7] (requires 37 bits) + return + (duration == 0) ? + 0 : + ((duration < 1000ULL) && (duration * frequency < 1000ULL)) ? + 1ULL : + (UINT64_MAX / frequency < duration) ? + (duration / 1000ULL) * frequency : + (((uint64_t)duration) * frequency / 1000ULL); +}; +inline static int _bits_used(unsigned long x) { + if (0 == x) return 0; + unsigned int result = 0; + do { + result++; + } while (x >>= 1); + return result; +} +inline static int _bits_used(unsigned long long x) { + if (0 == x) return 0; + unsigned int result = 0; + do { + result++; + } while (x >>= 1); + return result; +} +/* +* In older versions of the BSP, tone() would cause an interrupt for every cycle, tracking whether +* the playback should be infinite or duration-based via two global, volatile variables, "nostop" +* and "count_duration". The interrupt would then trigger the STARTSEQ0 task, or stop playback +* by calling noTone(). +* +* What is available to configure looping with minimal memory usage: +* 1. SEQ[n].REFRESH === how many PWM period before loading next sample from sequence +* Thus, setting to 99 will cause 100 pulses per item +* Treat as a 23-bit value. +* 2. LOOP === how many times to loop back to SEQ[0] +* SEQ[0] will play the same count if started PWM at SEQ[0] +* SEQ[0] will play one fewer times if started PWM at SEQ[1] +* Treat as a 15-bit value. +* +* Therefore, between REFRESH and LOOP, can support up to 40-bit repeat WITHOUT INTERRUPTS. +* +* The value of duration is given in milliseconds. +* Frequency is limited to range [20 ... 25000]. +* +* Therefore, maximum pulse count is limited as follows: +* (32-bit duration) * (20 ... 25000) / 1000UL +* (0xFFFF_FFFF) * 25 +* 0x18_FFFF_FFE7 ... which is 37 bits +* +* Therefore, all possible values for tone() can be supported +* via a single one-time configuration of the PWM peripheral. +* +* PWM peripheral can be de-allocated by sketch call to noTone(). +* +* Design: +* 0. At each call to tone(), unconditionally stop any existing playback. +* 1. For infinite duration, configure large REFRESH and LOOP (to minimize reading of RAM / AHB traffic), +* and setup shortcut to repeat infinitely. +* 2. For specified duration, configure both SEQ0 and SEQ1: +* 1a. SEQ[1].REFRESH <-- total count % _iterations_per_loop +* 1b. SEQ[0].REFRESH <-- _iterations_per_loop - SEQ[1].CNT +* 1c. LOOP <-- duration_count / _iterations_per_loop +* +* Result: Zero CPU usage, minimal AHB traffic +*/ void tone(uint8_t pin, unsigned int frequency, unsigned long duration) { - unsigned int time_per=0; - - if((frequency < 20) | (frequency > 25000)) return; - - - float per=float(1)/frequency; - time_per=per/0.000008; - unsigned int duty=time_per/2; - if(duration > 0){ - no_stop = false; - float mil=float(duration)/1000; - if(per>mil) - count_duration=1; - else - count_duration= mil/per; - } - else - no_stop = true; - - // Configure PWM - static uint16_t seq_values[]={0}; - //In each value, the most significant bit (15) determines the polarity of the output - //0x8000 is MSB = 1 - seq_values[0]= duty | 0x8000; - nrf_pwm_sequence_t const seq={ - seq_values, - NRF_PWM_VALUES_LENGTH(seq_values), - 0, - 0 + // Used only to protect calls against simultaneous multiple calls to tone(). + // Using a function-local static to avoid accidental reference from ISR or elsewhere, + // and to simplify ensuring the semaphore gets initialized. + static StaticSemaphore_t _tone_semaphore_allocation; + static auto init_semaphore = [] () { //< use a lambda to both initialize AND give the mutex + SemaphoreHandle_t handle = xSemaphoreCreateMutexStatic(&_tone_semaphore_allocation); + auto mustSucceed = xSemaphoreGive(handle); + (void)mustSucceed; + NRFX_ASSERT(mustSucceed == pdTRUE); + return handle; }; - -#if 0 - //assign pin to pwm channel - look at WVariant.h for details about ulPWMChannel attribute - uint8_t pwm_type=g_APinDescription[pin].ulPWMChannel; - if(pwm_type == NOT_ON_PWM) - return; - - uint32_t pins[NRF_PWM_CHANNEL_COUNT]={NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED}; - pins[pwm_type & 0x0F]=g_APinDescription[pin].ulPin; - IRQn_Type IntNo = PWM0_IRQn; - NRF_PWM_Type * PWMInstance = NRF_PWM0; - switch(pwm_type &0xF0){ - case 16://0x10 - PWMInstance = NRF_PWM1; - IntNo = PWM1_IRQn; - break; - case 32://0x20 - PWMInstance = NRF_PWM2; - IntNo = PWM2_IRQn; - break; - } -#else - // Use fixed PWM2, TODO could conflict with other usage - uint32_t pins[NRF_PWM_CHANNEL_COUNT]={NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED}; - pins[0] = g_ADigitalPinMap[pin]; - - IRQn_Type IntNo = PWM2_IRQn; - NRF_PWM_Type * PWMInstance = NRF_PWM2; -#endif - - nrf_pwm_pins_set(PWMInstance, pins); - nrf_pwm_enable(PWMInstance); - nrf_pwm_configure(PWMInstance, NRF_PWM_CLK_125kHz, NRF_PWM_MODE_UP, time_per); - nrf_pwm_decoder_set(PWMInstance, NRF_PWM_LOAD_COMMON, NRF_PWM_STEP_AUTO); - nrf_pwm_sequence_set(PWMInstance, 0, &seq); - nrf_pwm_shorts_enable(PWMInstance, NRF_PWM_SHORT_SEQEND0_STOP_MASK); - - // enable interrupt - nrf_pwm_event_clear(PWMInstance, NRF_PWM_EVENT_PWMPERIODEND); - nrf_pwm_int_enable(PWMInstance, NRF_PWM_INT_PWMPERIODEND_MASK); - NVIC_SetPriority(IntNo, 6); //low priority - NVIC_ClearPendingIRQ(IntNo); - NVIC_EnableIRQ(IntNo); - - nrf_pwm_task_trigger(PWMInstance, NRF_PWM_TASK_SEQSTART0); -} + static SemaphoreHandle_t _tone_semaphore = init_semaphore(); + + // limit frequency to reasonable audible range + if((frequency < 20) | (frequency > 25000)) { + LOG_LV1("TON", "frequency outside range [20..25000] -- ignoring"); + return; + } + if(xSemaphoreTake(_tone_semaphore, portMAX_DELAY) != pdTRUE) { + LOG_LV1("TON", "error acquiring semaphore (should never occur?)"); + return; + } + uint64_t pulse_count = _calculate_pulse_count(frequency, duration); + uint16_t time_period = _calculate_time_period(frequency); + if (!_pwm_config.ensurePwmPeripheralOwnership()) { + LOG_LV1("TON", "Unable to acquire PWM peripheral"); + } else if (!_pwm_config.stopPlayback()) { + LOG_LV1("TON", "Unable to stop playback"); + } else if (!_pwm_config.initializeFromPulseCountAndTimePeriod(pulse_count, time_period)) { + LOG_LV1("TON", "Failed calculating configuration"); + } else if (!_pwm_config.applyConfiguration(pin)) { + LOG_LV1("TON", "Failed applying configuration"); + } else if (!_pwm_config.startPlayback()) { + LOG_LV1("TON", "Failed attempting to start PWM peripheral"); + } else { + //LOG_LV2("TON", "Started playback of tone at frequency %d duration %ld", frequency, duration); + } + xSemaphoreGive(_tone_semaphore); + return; +} void noTone(uint8_t pin) { -#if 0 - uint8_t pwm_type=g_APinDescription[pin].ulPWMChannel; - NRF_PWM_Type * PWMInstance = NRF_PWM0; - switch(pwm_type &0xF0){ - case 16://0x10 - PWMInstance = NRF_PWM1; - break; - case 32://0x20 - PWMInstance = NRF_PWM2; - break; - } -#else - NRF_PWM_Type * PWMInstance = NRF_PWM2; -#endif - - nrf_pwm_task_trigger(PWMInstance, NRF_PWM_TASK_STOP); - nrf_pwm_disable(PWMInstance); + ( void )pin; // avoid unreferenced parameter compiler warning + _pwm_config.stopPlayback(true); // release ownership of PWM peripheral } -#ifdef __cplusplus -extern "C"{ -#endif - -#if 0 -void PWM0_IRQHandler(void){ - nrf_pwm_event_clear(NRF_PWM0, NRF_PWM_EVENT_PWMPERIODEND); - if(!no_stop){ - count_duration--; - if(count_duration == 0) - noTone(pin_sound); - else - nrf_pwm_task_trigger(NRF_PWM0, NRF_PWM_TASK_SEQSTART0); - } - else - nrf_pwm_task_trigger(NRF_PWM0, NRF_PWM_TASK_SEQSTART0); +bool TonePwmConfig::ensurePwmPeripheralOwnership(void) { + if (!_HwPWM->isOwner(TonePwmConfig::toneToken) && !_HwPWM->takeOwnership(TonePwmConfig::toneToken)) { + LOG_LV1("TON", "unable to allocate PWM2 to Tone"); + return false; + } + return true; +} + +// +// The final loop's SEQ1 will ALWAYS output one pulse ... +// In other words, SEQ1's refresh is *IGNORED* for the final loop. +// +// Visually, with each sequence length set to one as is done with tone(): +// ====================================================================== +// +// Starting at SEQ0, loopCnt = 2, SEQ0 refresh == 4, SEQ1 refresh = 2: +// +// [----SEQ0-----] [SEQ1-] [----SEQ0-----] [1] +// 0 0 0 0 1 1 0 0 0 0 1 +// +// ====================================================================== +// +// Starting as SEQ1, loopCnt = 2, SEQ0 refresh == 4, SEQ1 refresh = 2: +// +// [SEQ1-] [----SEQ0-----] [1] +// 1 1 0 0 0 0 1 +// +// ====================================================================== +// +// Therefore, the total count of pulses that will be emitted by the +// PWM peripheral (per the configuration of tone() API): +// +// COUNT = (SEQ0.CNT * (SEQ0.REFRESH+1); +// COUNT += (SEQ1.CNT * (SEQ1.REFRESH+1); +// COUNT *= (loopCount-1); // the number of times SEQ0 and SEQ1 both run +// COUNT += (start at SEQ0) ? (SEQ0.CNT * (SEQ0.REFRESH+1) : 0; +// COUNT += 1; // final SEQ1 emits single pulse +// +bool TonePwmConfig::initializeFromPulseCountAndTimePeriod(uint64_t pulse_count_x, uint16_t time_period) { + + if (_bits_used(pulse_count_x) > 37) { + LOG_LV1("TON", "pulse count is limited to 37 bit long value"); + return false; + } + + this->pulse_count = pulse_count_x; + this->time_period = time_period; + this->duty_with_polarity = 0x8000U | (time_period / 2U); + + if (this->pulse_count == 0) { + this->seq0_refresh = 0xFFFFFFU; // 24-bit maximum value + this->seq1_refresh = 0xFFFFFFU; // 24-bit maximum value + this->loop_count = 0xFFFFU; // 16-bit maximum value + this->task_to_start = NRF_PWM_TASK_SEQSTART0; + this->shorts = NRF_PWM_SHORT_LOOPSDONE_SEQSTART0_MASK; + } + else if (this->pulse_count == 1) { + // yes, this is an edge case; e.g., frequency == 100, duration == 100 causes this + this->seq0_refresh = 0; + this->seq1_refresh = 0; + this->loop_count = 1; + this->task_to_start = NRF_PWM_TASK_SEQSTART1; + this->shorts = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK; + } + else { + // This is the interesting section. + // + // To ensure refresh value stays within 24 bits, the maximum number of bits + // for the pulse_count is ((24 * 3) / 2) + 1 == (72/2) + 1 == 36 + 1 == 37. + // + // Validation: + // 37 * 2 / 3 == 74 / 3 == 24 (OK) -- leaves 13 bits for loop count + // 38 * 2 / 3 == 76 / 3 == 25 (fail) -- leaves 13 bits for loop count + // + // NOTE: theoretically possible to support up to 40 bit pulse count, but + // would require more complex logic. + // + unsigned int bits_needed = _bits_used(this->pulse_count); // bits_used is now in range [2..37] + + // split the number of bits between refresh and loop_count in 2:1 ratio + // so that, no matter what inputs are given, guaranteed to have interrupt-free solution + unsigned int bits_for_refresh = bits_needed * 2 / 3; // range is [1 .. 24] + //unsigned int bits_for_loop_count = bits_needed - bits_for_refresh; // range is [1 .. 13] + + // NOTE: Due to final SEQ1 outputting exactly one pulse, may need one additional bit for loop count + // ... but that will still be within the 16 bits available, because top of range is 13 bits. + + // now determine how many PWM pulses should occur per loop (when both SEQ0 and SEQ1 are played) + uint32_t total_refresh_count = 1 << bits_for_refresh; // range is [2 .. 2^24] + uint32_t full_loops = (this->pulse_count - 1) / total_refresh_count; // == loopCount - 1 + + // if (pulses - 1) % total_refresh_count == 0, then start at SEQ1 and split refresh evenly + // else, start at SEQ0, and set SEQ0 to extra pulses needed... + uint32_t extraPulsesNeededIfStartingAtSequence1 = (this->pulse_count - 1) % total_refresh_count; + uint32_t seq0_count; + + if (extraPulsesNeededIfStartingAtSequence1 == 0) { + seq0_count = total_refresh_count / 2; // range is [1 .. 2^23] + this->task_to_start = NRF_PWM_TASK_SEQSTART1; + } + else { + seq0_count = extraPulsesNeededIfStartingAtSequence1; + this->task_to_start = NRF_PWM_TASK_SEQSTART0; + } + this->loop_count = full_loops + 1; + this->seq0_refresh = seq0_count - 1; + this->seq1_refresh = (total_refresh_count - seq0_count) - 1; + this->shorts = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK; + } + return true; } +bool TonePwmConfig::applyConfiguration(uint32_t pin) { + if (pin >= PINS_COUNT) { + return false; + } + if (!this->ensurePwmPeripheralOwnership()) { + return false; + } + this->stopPlayback(false); -void PWM1_IRQHandler(void){ - nrf_pwm_event_clear(NRF_PWM1, NRF_PWM_EVENT_PWMPERIODEND); - if(!no_stop){ - count_duration--; - if(count_duration == 0) - noTone(pin_sound); - else - nrf_pwm_task_trigger(NRF_PWM1, NRF_PWM_TASK_SEQSTART0); - } - else - nrf_pwm_task_trigger(NRF_PWM1, NRF_PWM_TASK_SEQSTART0); + this->arduino_pin = pin; + this->nrf_pin = g_ADigitalPinMap[pin]; + + uint32_t pins[NRF_PWM_CHANNEL_COUNT] = { + this->nrf_pin, + NRF_PWM_PIN_NOT_CONNECTED, + NRF_PWM_PIN_NOT_CONNECTED, + NRF_PWM_PIN_NOT_CONNECTED + }; + + nrf_pwm_pins_set(_PWMInstance, pins); // must set pins before enabling + nrf_pwm_enable(_PWMInstance); + nrf_pwm_configure(_PWMInstance, TonePwmConfig::clock_frequency, TonePwmConfig::pwm_mode, this->time_period); + nrf_pwm_decoder_set(_PWMInstance, TonePwmConfig::load_mode, TonePwmConfig::step_mode); + nrf_pwm_shorts_set(_PWMInstance, this->shorts); + nrf_pwm_int_set(_PWMInstance, 0); + + // static sequence value ... This is the value actually used + // during playback ... do NOT modify without semaphore *and* + // having ensured playback has stopped! + static uint16_t seq_values; // static value + seq_values = this->duty_with_polarity; + + nrf_pwm_seq_ptr_set(_PWMInstance, 0, &seq_values); + nrf_pwm_seq_ptr_set(_PWMInstance, 1, &seq_values); + nrf_pwm_seq_cnt_set(_PWMInstance, 0, 1); + nrf_pwm_seq_cnt_set(_PWMInstance, 1, 1); + + nrf_pwm_seq_refresh_set(_PWMInstance, 0, seq0_refresh); + nrf_pwm_seq_refresh_set(_PWMInstance, 1, seq1_refresh); + nrf_pwm_seq_end_delay_set(_PWMInstance, 0, 0); + nrf_pwm_seq_end_delay_set(_PWMInstance, 1, 0); + nrf_pwm_loop_set(_PWMInstance, loop_count); + + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_STOPPED); + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_SEQSTARTED0); + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_SEQSTARTED1); + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_SEQEND0); + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_SEQEND1); + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_PWMPERIODEND); + nrf_pwm_event_clear(_PWMInstance, NRF_PWM_EVENT_LOOPSDONE); + return true; } -#endif - -void PWM2_IRQHandler(void){ - nrf_pwm_event_clear(NRF_PWM2, NRF_PWM_EVENT_PWMPERIODEND); - if(!no_stop){ - count_duration--; - if(count_duration == 0) - noTone(pin_sound); - else - nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART0); - } - else - nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART0); +bool TonePwmConfig::startPlayback(void) { + if (!this->ensurePwmPeripheralOwnership()) { + LOG_LV1("TON", "PWM peripheral not available for playback"); + return false; + } + nrf_pwm_task_trigger(_PWMInstance, this->task_to_start); + return true; } +bool TonePwmConfig::stopPlayback(bool releaseOwnership) { + + bool notInIsr = !isInISR(); + + if (!_HwPWM->isOwner(TonePwmConfig::toneToken)) { + if (notInIsr) { + LOG_LV2("TON", "Attempt to set noTone when not the owner of the PWM peripheral. Ignoring call...."); + } + return false; + } + // ensure stopped and then disable + if (_is_pwm_enabled(_PWMInstance)) { + nrf_pwm_task_trigger(_PWMInstance, NRF_PWM_TASK_STOP); + nrf_pwm_disable(_PWMInstance); + _PWMInstance->PSEL.OUT[0] = NRF_PWM_PIN_NOT_CONNECTED; + } -#ifdef __cplusplus + if (releaseOwnership) { + _HwPWM->releaseOwnership(TonePwmConfig::toneToken); + } + return true; } -#endif diff --git a/cores/nRF5/Tone.h b/cores/nRF5/Tone.h index 8874a1ced..064cfad33 100644 --- a/cores/nRF5/Tone.h +++ b/cores/nRF5/Tone.h @@ -15,6 +15,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +/** \file Tone.h */ #ifndef _WIRING_TONE_ #define _WIRING_TONE_ @@ -25,8 +26,44 @@ #include "wiring_digital.h" #include "nrf_pwm.h" - +/** + * \brief Generate a tone (PWM) output on the specified pin. + * + * \param[in] pin The Arduino pin for output + * + * \param[in] frequency The frequency, in hertz, of the requested tone. + * + * \param[in] duration An optional duration, in milliseconds (default: 0 == infinite) + * + * Generates a square wave of the specified frequency (and 50% duty cycle) + * on a pin. A duration can be specified, otherwise the wave continues until a call + * to `noTone()`. The pin can be connected to a piezo buzzer or other speaker to + * play tones. + * + * The `tone()` API has the following contracts, which were defined by + * the original Arduino code: + * + * 1. at most one `tone()` can be generated at a time + * 2. so long as the `pin` stays the same, `tone()` can be called repeatedly + * 3. a call to `noTone()` is required prior to calling `tone()` with a different pin. + * + * The third requirement is not enforced in the nRF52 BSP. Instead, if a call + * to `tone()` is made with a different pin, a call to `noTone()` occurs automatically, + * simplifying use somewhat. + * + * For the nRF52, the allowed range for parameter `frequency` is `[20..25000]`. + * + * \see noTone + */ void tone(uint8_t pin, unsigned int frequency, unsigned long duration = 0); +/** + * \brief Stops generation of a tone (PWM) output. + * + * \param pin For the nRF52, this argument is ignored + * + * Stops the generation of a square wave triggered by `tone()`. + * This function has no effect if no tone is being generated. + */ void noTone(uint8_t pin); diff --git a/cores/nRF5/common_func.h b/cores/nRF5/common_func.h index 60c7cf0d5..ad42c6260 100644 --- a/cores/nRF5/common_func.h +++ b/cores/nRF5/common_func.h @@ -73,6 +73,8 @@ #define ADA_COUNTER __LINE__ #endif +#include + #define COMMENT_OUT(x) #define memclr(buffer, size) memset(buffer, 0, size) diff --git a/cores/nRF5/main.cpp b/cores/nRF5/main.cpp index 62266ac78..875d31ae5 100644 --- a/cores/nRF5/main.cpp +++ b/cores/nRF5/main.cpp @@ -15,6 +15,9 @@ #define ARDUINO_MAIN #include "Arduino.h" +#if (CFG_LOGGER == 2) + #include +#endif // DEBUG Level 1 #if CFG_DEBUG @@ -107,17 +110,16 @@ extern "C" int _write (int fd, const void *buf, size_t count) { (void) fd; - -#if 0 // CFG_SYSVIEW - SEGGER_RTT_Write(0, (char*) buf, (int) count); - return count; +#if (CFG_LOGGER == 2) + unsigned numBytes = count; + SEGGER_RTT_Write(0, buf, numBytes); + return (int)count; #else if ( Serial ) { return Serial.write( (const uint8_t *) buf, count); } #endif - return 0; } diff --git a/cores/nRF5/wiring_analog.cpp b/cores/nRF5/wiring_analog.cpp index 570be9eb6..7c1ac04ff 100644 --- a/cores/nRF5/wiring_analog.cpp +++ b/cores/nRF5/wiring_analog.cpp @@ -36,6 +36,7 @@ #include "Arduino.h" + /* Implement analogWrite() and analogWriteResolution() using * HardwarePWM. * @@ -45,13 +46,20 @@ extern "C" { +static uint8_t _analogResolution = 8; // default is 256 levels +static uintptr_t _analogToken = 0x676f6c41; // 'A' 'l' 'o' 'g' + /** - * This will apply to all PWM Hardware + * This will apply to all PWM Hardware currently used by analogWrite(), + * and automatically apply to future calls to analogWrite(). */ void analogWriteResolution( uint8_t res ) { + // save the resolution for when adding a new instance + _analogResolution = res; for (int i = 0; iisOwner(_analogToken)) continue; HwPWMx[i]->setResolution(res); } } @@ -65,17 +73,65 @@ void analogWriteResolution( uint8_t res ) */ void analogWrite( uint32_t pin, uint32_t value ) { + /* If the pin is already in use for analogWrite, this should be fast + If the pin is not already in use, then it's OK to take slightly more time to setup + */ + + // first, handle the case where the pin is already in use by analogWrite() for(int i=0; iaddPin(pin) ) - { - HwPWMx[i]->writePin(pin, value); - return; + if (!HwPWMx[i]->isOwner(_analogToken)) { + continue; // skip if not owner of this PWM instance + } + + int const ch = HwPWMx[i]->pin2channel(pin); + if (ch < 0) { + continue; // pin not in use by this PWM instance } + HwPWMx[i]->writeChannel(ch, value); + return; } -} + // Next, handle the case where can add the pin to a PWM instance already owned by analogWrite() + for(int i=0; iisOwner(_analogToken)) { + continue; + } + if (!HwPWMx[i]->addPin(pin)) { + continue; + } + + // successfully added the pin, so write the value also + LOG_LV2("ANA", "Added pin %" PRIu32 " to already-owned PWM %d", pin, i); + HwPWMx[i]->writePin(pin, value); + return; + } + + // Attempt to acquire a new HwPWMx instance ... but only where + // 1. it's not one already used for analog, and + // 2. it currently has no pins in use. + for(int i=0; itakeOwnership(_analogToken)) { + continue; + } + + // apply the cached analog resolution to newly owned instances + HwPWMx[i]->setResolution(_analogResolution); + HwPWMx[i]->addPin(pin); + HwPWMx[i]->writePin(pin, value); + LOG_LV2("ANA", "took ownership of, and added pin %" PRIu32 " to, PWM %d", pin, i); + return; + } + + // failed to allocate a HwPWM instance. + // output appropriate debug message. + LOG_LV1("ANA", "Unable to find a free PWM peripheral"); + // TODO: Add additional diagnostics to function at higher log levels + return; } +} // end extern "C" + diff --git a/cores/nRF5/wiring_analog_nRF52.c b/cores/nRF5/wiring_analog_nRF52.c index ee95d3183..b8a50fc5e 100644 --- a/cores/nRF5/wiring_analog_nRF52.c +++ b/cores/nRF5/wiring_analog_nRF52.c @@ -33,38 +33,14 @@ static uint32_t saadcGain = SAADC_CH_CONFIG_GAIN_Gain1_6; static bool saadcBurst = SAADC_CH_CONFIG_BURST_Disabled; -#if 0 // Note: Adafruit use seperated HardwarePWM class -#define PWM_COUNT 3 - -static NRF_PWM_Type* pwms[PWM_COUNT] = { - NRF_PWM0, - NRF_PWM1, - NRF_PWM2 -}; - -static uint32_t pwmChannelPins[PWM_COUNT] = { - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF -}; -static uint16_t pwmChannelSequence[PWM_COUNT]; -#endif +// Note: Adafruit use seperated HardwarePWM class static int readResolution = 10; -//static int writeResolution = 8; - void analogReadResolution( int res ) { readResolution = res; } -#if 0 -void analogWriteResolution( int res ) -{ - writeResolution = res; -} -#endif - static inline uint32_t mapResolution( uint32_t value, uint32_t from, uint32_t to ) { if ( from == to ) @@ -273,48 +249,6 @@ uint32_t analogRead( uint32_t ulPin ) return mapResolution(value, resolution, readResolution); } -#if 0 -// Right now, PWM output only works on the pins with -// hardware support. These are defined in the appropriate -// pins_*.c file. For the rest of the pins, we default -// to digital output. -void analogWrite( uint32_t ulPin, uint32_t ulValue ) -{ - if (ulPin >= PINS_COUNT) { - return; - } - - ulPin = g_ADigitalPinMap[ulPin]; - - for (int i = 0; i < PWM_COUNT; i++) { - if (pwmChannelPins[i] == 0xFFFFFFFF || pwmChannelPins[i] == ulPin) { - pwmChannelPins[i] = ulPin; - pwmChannelSequence[i] = bit(15) | ulValue; - - 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; - pwm->SEQ[0].REFRESH = 1; - pwm->SEQ[0].ENDDELAY = 0; - pwm->TASKS_SEQSTART[0] = 0x1UL; - - break; - } - } -} -#endif - #ifdef __cplusplus } #endif diff --git a/libraries/Servo/src/Servo.h b/libraries/Servo/src/Servo.h index 50979dfbd..28df7462e 100644 --- a/libraries/Servo/src/Servo.h +++ b/libraries/Servo/src/Servo.h @@ -101,7 +101,7 @@ class Servo { public: Servo(); - uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure + uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or INVALID_SERVO if failure (zero is a valid channel number) uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes. void detach(); void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds diff --git a/libraries/Servo/src/nrf52/Servo.cpp b/libraries/Servo/src/nrf52/Servo.cpp index b9b86c02f..029955502 100644 --- a/libraries/Servo/src/nrf52/Servo.cpp +++ b/libraries/Servo/src/nrf52/Servo.cpp @@ -21,6 +21,7 @@ #include #include +static uintptr_t _servoToken = 0x76726553; // 'S' 'e' 'r' 'v' static servo_t servos[MAX_SERVOS]; // static array of servo structures uint8_t ServoCount = 0; // the total number of attached servos @@ -32,7 +33,6 @@ Servo::Servo() } else { this->servoIndex = INVALID_SERVO; // too many servos } - this->pwm = NULL; } @@ -43,55 +43,84 @@ uint8_t Servo::attach(int pin) uint8_t Servo::attach(int pin, int min, int max) { - if (this->servoIndex < MAX_SERVOS) { - pinMode(pin, OUTPUT); // set servo pin to output - servos[this->servoIndex].Pin.nbr = pin; - - if (min < MIN_PULSE_WIDTH) min = MIN_PULSE_WIDTH; - if (max > MAX_PULSE_WIDTH) max = MAX_PULSE_WIDTH; - - //fix min if conversion to pulse cycle value is too low - if((min/DUTY_CYCLE_RESOLUTION)*DUTY_CYCLE_RESOLUTIONmin = min; - this->max = max; - - servos[this->servoIndex].Pin.isActive = true; - - // Adafruit, add pin to 1 of available Hw PWM - for(int i=0; iaddPin(pin) ) - { + if (this->servoIndex == INVALID_SERVO) { + return INVALID_SERVO; + } + bool succeeded = false; + pinMode(pin, OUTPUT); // set servo pin to output + servos[this->servoIndex].Pin.nbr = pin; + + if (min < MIN_PULSE_WIDTH) { + min = MIN_PULSE_WIDTH; + } + if (max > MAX_PULSE_WIDTH) { + max = MAX_PULSE_WIDTH; + } + + //fix min if conversion to pulse cycle value is too low + if ( (min/DUTY_CYCLE_RESOLUTION) * DUTY_CYCLE_RESOLUTION < min) { + min += DUTY_CYCLE_RESOLUTION; + } + + this->min = min; + this->max = max; + + servos[this->servoIndex].Pin.isActive = true; + + // Adafruit, add pin to 1 of available Hw PWM + // first, use existing HWPWM modules (already owned by Servo) + for(int i=0; iisOwner(_servoToken) && + HwPWMx[i]->addPin(pin)) { + this->pwm = HwPWMx[i]; + succeeded = true; + break; + } + } + // if could not add to existing owned PWM modules, try to add to a new PWM module + if (!succeeded) { + for(int i=0; itakeOwnership(_servoToken) && + HwPWMx[i]->addPin(pin)) { this->pwm = HwPWMx[i]; + succeeded = true; break; } } + } + if (succeeded) { // do not use this->pwm unless success above! this->pwm->setMaxValue(MAXVALUE); this->pwm->setClockDiv(CLOCKDIV); - } - return this->servoIndex; + return succeeded ? this->servoIndex : INVALID_SERVO; // return INVALID_SERVO on failure (zero is a valid servo index) } void Servo::detach() { - uint8_t const pin = servos[this->servoIndex].Pin.nbr; - - servos[this->servoIndex].Pin.isActive = false; + if (this->servoIndex == INVALID_SERVO) { + return; + } - // remove pin from HW PWM - this->pwm->removePin(pin); + uint8_t const pin = servos[this->servoIndex].Pin.nbr; + servos[this->servoIndex].Pin.isActive = false; + // remove pin from HW PWM + HardwarePWM * pwm = this->pwm; + this->pwm = nullptr; + pwm->removePin(pin); + if (pwm->usedChannelCount() == 0) { + pwm->stop(); // disables peripheral so can release ownership + pwm->releaseOwnership(_servoToken); + } } - void Servo::write(int value) -{ - if (value < 0) +{ + if (value < 0) { value = 0; - else if (value > 180) + } else if (value > 180) { value = 180; + } value = map(value, 0, 180, this->min, this->max); writeMicroseconds(value); @@ -100,9 +129,10 @@ void Servo::write(int value) void Servo::writeMicroseconds(int value) { - uint8_t pin = servos[this->servoIndex].Pin.nbr; - - if ( this->pwm ) this->pwm->writePin(pin, value/DUTY_CYCLE_RESOLUTION); + if (this->pwm) { + uint8_t pin = servos[this->servoIndex].Pin.nbr; + this->pwm->writePin(pin, value/DUTY_CYCLE_RESOLUTION); + } } int Servo::read() // return the value as degrees @@ -112,15 +142,18 @@ int Servo::read() // return the value as degrees int Servo::readMicroseconds() { - uint8_t pin = servos[this->servoIndex].Pin.nbr; - - if ( this->pwm ) return this->pwm->readPin(pin)*DUTY_CYCLE_RESOLUTION; - + if (this->pwm) { + uint8_t pin = servos[this->servoIndex].Pin.nbr; + return this->pwm->readPin(pin)*DUTY_CYCLE_RESOLUTION; + } return 0; } bool Servo::attached() { + if (this->servoIndex == INVALID_SERVO) { + return false; + } return servos[this->servoIndex].Pin.isActive; } diff --git a/platform.txt b/platform.txt index f8f47382c..ce3cd4b9b 100644 --- a/platform.txt +++ b/platform.txt @@ -28,11 +28,14 @@ compiler.warning_flags.default= compiler.warning_flags.more=-Wall compiler.warning_flags.all=-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wno-pointer-arith +# Allow changing optimization settings via platform.local.txt / boards.local.txt +compiler.optimization_flag=-Ofast + compiler.path={runtime.tools.arm-none-eabi-gcc.path}/bin/ compiler.c.cmd=arm-none-eabi-gcc compiler.c.flags=-mcpu={build.mcu} -mthumb -c -g {compiler.warning_flags} {build.float_flags} -std=gnu11 -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500 -MMD compiler.c.elf.cmd=arm-none-eabi-gcc -compiler.c.elf.flags=-Ofast -Wl,--gc-sections -save-temps +compiler.c.elf.flags={compiler.optimization_flag} -Wl,--gc-sections -save-temps compiler.S.cmd=arm-none-eabi-gcc compiler.S.flags=-c -g -x assembler-with-cpp compiler.cpp.cmd=arm-none-eabi-g++ @@ -60,7 +63,7 @@ nordic.path={build.core.path}/nordic # build.logger_flags and build.sysview_flags and intentionally empty, # to allow modification via a user's own boards.local.txt or platform.local.txt files. -build.flags.nrf= -DSOFTDEVICE_PRESENT -DARDUINO_NRF52_ADAFRUIT -DNRF52_SERIES -DLFS_NAME_MAX=64 -Ofast {build.debug_flags} {build.logger_flags} {build.sysview_flags} "-I{build.core.path}/cmsis/Core/Include" "-I{nordic.path}" "-I{nordic.path}/nrfx" "-I{nordic.path}/nrfx/hal" "-I{nordic.path}/nrfx/mdk" "-I{nordic.path}/nrfx/soc" "-I{nordic.path}/nrfx/drivers/include" "-I{nordic.path}/nrfx/drivers/src" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include/nrf52" "-I{rtos.path}/Source/include" "-I{rtos.path}/config" "-I{rtos.path}/portable/GCC/nrf52" "-I{rtos.path}/portable/CMSIS/nrf52" "-I{build.core.path}/sysview/SEGGER" "-I{build.core.path}/sysview/Config" "-I{build.core.path}/TinyUSB" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src" +build.flags.nrf= -DSOFTDEVICE_PRESENT -DARDUINO_NRF52_ADAFRUIT -DNRF52_SERIES -DLFS_NAME_MAX=64 {compiler.optimization_flag} {build.debug_flags} {build.logger_flags} {build.sysview_flags} "-I{build.core.path}/cmsis/Core/Include" "-I{nordic.path}" "-I{nordic.path}/nrfx" "-I{nordic.path}/nrfx/hal" "-I{nordic.path}/nrfx/mdk" "-I{nordic.path}/nrfx/soc" "-I{nordic.path}/nrfx/drivers/include" "-I{nordic.path}/nrfx/drivers/src" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include" "-I{nordic.path}/softdevice/{build.sd_name}_nrf52_{build.sd_version}_API/include/nrf52" "-I{rtos.path}/Source/include" "-I{rtos.path}/config" "-I{rtos.path}/portable/GCC/nrf52" "-I{rtos.path}/portable/CMSIS/nrf52" "-I{build.core.path}/sysview/SEGGER" "-I{build.core.path}/sysview/Config" "-I{build.core.path}/TinyUSB" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore" "-I{build.core.path}/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src" # usb flags build.flags.usb= -DUSBCON -DUSE_TINYUSB -DUSB_VID={build.vid} -DUSB_PID={build.pid} '-DUSB_MANUFACTURER={build.usb_manufacturer}' '-DUSB_PRODUCT={build.usb_product}'