From 29843dacb0c34c9ee151f1f05046d591544452a0 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Mon, 3 Aug 2020 01:40:29 -0700 Subject: [PATCH] Redefine tone() without ISR Other minor changes include: * Enable RTT option for output (requires platform.local.txt or boards.local.txt) * Allow setting compiler optimization (requires platform.local.txt or boards.local.txt) --- cores/nRF5/HardwarePWM.h | 2 + cores/nRF5/Tone.cpp | 526 +++++++++++++++++++++++++++++---------- cores/nRF5/Tone.h | 39 ++- cores/nRF5/main.cpp | 9 +- platform.txt | 7 +- 5 files changed, 452 insertions(+), 131 deletions(-) diff --git a/cores/nRF5/HardwarePWM.h b/cores/nRF5/HardwarePWM.h index 46bcd3349..1afa0c37d 100644 --- a/cores/nRF5/HardwarePWM.h +++ b/cores/nRF5/HardwarePWM.h @@ -117,6 +117,8 @@ class HardwarePWM #if CFG_DEBUG static void DebugOutput(Stream& logger); +#else + static void inline DebugOutput(Stream& logger) { (void)logger; }; #endif // CFG_DEBUG }; diff --git a/cores/nRF5/Tone.cpp b/cores/nRF5/Tone.cpp index 9cdf312b4..4aff67413 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 ------- ----------- -------- -------- @@ -37,136 +37,408 @@ Version Modified By Date Comments #include "Arduino.h" #include "Tone.h" #include "WVariant.h" - -volatile unsigned long int count_duration=0; -volatile bool no_stop = false; -uint8_t pin_sound=0; -static uintptr_t _toneToken = 0x656e6f54; // 'T' 'o' 'n' 'e' +#include "limits.h" // NOTE: Currently hard-coded to only use PWM2 ... // These are the relvant hard-coded variables -// (plus the ISR PWM2_IRQHandler) -static IRQn_Type const _IntNo = PWM2_IRQn; +// 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 TONE_PWM_CONFIG { + 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 + boolean is_initialized; //< defaults to uninitialized + + public: + bool EnsurePwmPeripheralOwnership(void) noexcept; + bool InitializeFromPulseCountAndTimePeriod(uint64_t pulse_count, uint16_t time_period) noexcept; + bool ApplyConfiguration(uint32_t pin) noexcept; + bool StartPlayback(void) noexcept; + bool StopPlayback(bool releaseOwnership = false) noexcept; + bool ApplyConfigurationAndStartPlayback(uint32_t pin) noexcept; + uint64_t CalculateExpectedPulseCount(void) noexcept; +}; +TONE_PWM_CONFIG _pwm_config; + +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) throw() { + // 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) throw() { + // range for frequency == [20..25000], + // range for duration == [ 1..0xFFFF_FFFF] + // so range of result == [ 1..0x18_FFFF_FFE7] (requires 37 bits) + static_assert(sizeof(unsigned long long) >= sizeof(uint64_t)); + 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) noexcept { + 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) noexcept { + 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) { - bool new_no_stop; - unsigned long int new_count_duration = (unsigned long int)-1L; - unsigned int time_per=0; - - // limit frequency to reasonable audible range - if((frequency < 20) | (frequency > 25000)) { - LOG_LV1("TON", "frequency outside range [20..25000] -- ignoring"); - return; - } - - // set nostop to true to avoid race condition. - // Specifically, race between a tone finishing - // after checking for ownership (which releases ownership) - // No effect if a tone is not playing.... - no_stop = true; - - // Use fixed PWM2 (due to need to connect interrupt) - if (!_HwPWM->isOwner(_toneToken) && - !_HwPWM->takeOwnership(_toneToken)) { - LOG_LV1("TON", "unable to allocate PWM2 to Tone"); - return; - } - - float per=float(1)/frequency; - time_per=per/0.000008; - unsigned int duty=time_per/2; - if(duration > 0) { - new_no_stop = false; - float mil=float(duration)/1000; - if(per>mil) { - new_count_duration = 1; - } else { - new_count_duration = mil/per; - } - } else { - new_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 + static_assert(sizeof(unsigned long int) <= sizeof(uint32_t)); + static_assert(sizeof(unsigned int) <= sizeof(uint32_t)); + + // 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 = [] () throw() { //< use a lambda to both initialize AND give the mutex + SemaphoreHandle_t handle = xSemaphoreCreateBinaryStatic(&_tone_semaphore_allocation); + auto mustSucceed = xSemaphoreGive(handle); + (void)mustSucceed; + NRFX_ASSERT(mustSucceed == pdTRUE); + return handle; }; + static SemaphoreHandle_t _tone_semaphore = init_semaphore(); - 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]; - - // enable interrupt - count_duration = 0x6FFF; // large enough to avoid hitting zero in next few lines - no_stop = new_no_stop; - - 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); // shortcut for when SEQ0 ends, PWM output will automatically stop - 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); - count_duration = new_count_duration; - nrf_pwm_task_trigger(_PWMInstance, NRF_PWM_TASK_SEQSTART0); -} + // 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) { - bool notInIsr = !isInISR(); - - if (!_HwPWM->isOwner(_toneToken)) { - if (notInIsr) { - LOG_LV1("TON", "Attempt to set noTone when not the owner of the PWM peripheral. Ignoring call...."); - } - return; - } - nrf_pwm_task_trigger(_PWMInstance, NRF_PWM_TASK_STOP); - nrf_pwm_disable(_PWMInstance); - _PWMInstance->PSEL.OUT[0] = NRF_PWM_PIN_NOT_CONNECTED; - NVIC_DisableIRQ(_IntNo); - _HwPWM->releaseOwnership(_toneToken); - if (_HwPWM->isOwner(_toneToken)) { - if (notInIsr) { - LOG_LV1("TON", "stopped tone, but failed to release ownership of PWM peripheral?"); - } - return; - } + ( void )pin; // avoid unreferenced parameter compiler warning + _pwm_config.StopPlayback(true); // release ownership of PWM peripheral } -#ifdef __cplusplus -extern "C"{ -#endif - -void PWM2_IRQHandler(void){ - nrf_pwm_event_clear(NRF_PWM2, NRF_PWM_EVENT_PWMPERIODEND); - if(!no_stop){ - count_duration--; - if(count_duration == 0) { - uint8_t pin = _PWMInstance->PSEL.OUT[0]; - noTone(pin); - } else { - nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART0); - } - } else { - nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART0); - } +bool TONE_PWM_CONFIG::EnsurePwmPeripheralOwnership(void) { + if (!_HwPWM->isOwner(TONE_PWM_CONFIG::toneToken) && !_HwPWM->takeOwnership(TONE_PWM_CONFIG::toneToken)) { + LOG_LV1("TON", "unable to allocate PWM2 to Tone"); + return false; + } + return true; } -#ifdef __cplusplus +// +// 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 TONE_PWM_CONFIG::InitializeFromPulseCountAndTimePeriod(uint64_t pulse_count_x, uint16_t time_period) noexcept { + + 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) { + LOG_LV3("TON", "Infinite duration requested\n"); + + 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) { + LOG_LV3("TON", "Edge case: exactly one pulse\n"); + // yes, this is an edge case + 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 { + LOG_LV3("TON", "Non-infinite duration of at least two pulses requested\n"); + + // 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. + LOG_LV3("TON", "Using %d bits for refresh, and %d bits for loop_count\n", bits_for_refresh, bits_for_loop_count); + + // 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 + + LOG_LV3("TON", "total_refresh_count is 0x%" PRIx32 " (%" PRIu32 ")\n", total_refresh_count, total_refresh_count); + LOG_LV3("TON", "full_loops is 0x%" PRIx32 " (%" PRIu32 ")\n", full_loops, full_loops); + + // 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) { + LOG_LV3("TON", "Pulse count (minus one) is exact multiple of total_refresh_count -- starting at SEQ1\n"); + seq0_count = total_refresh_count / 2; // range is [1 .. 2^23] + this->task_to_start = NRF_PWM_TASK_SEQSTART1; + } + else { + LOG_LV3("TON", "Pulse count (minus one) requires extra %" PRIu32 " pulses ... setting SEQ0 to that value\n", extraPulsesNeededIfStartingAtSequence1); + 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; + + LOG_LV3("TON", "seq0_count is %" PRIu32 ", so refresh will be set to %" PRIu32 "\n", seq0_count, this->seq0_refresh); + LOG_LV3("TON", "seq1_count is %" PRIu32 ", so refresh will be set to %" PRIu32 "\n", (total_refresh_count - seq0_count), this->seq1_refresh); + + this->shorts = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK; + } + this->is_initialized = true; + return true; +} +bool TONE_PWM_CONFIG::ApplyConfiguration(uint32_t pin) noexcept { + if (!this->is_initialized) { + return false; + } + if (pin >= PINS_COUNT) { + return false; + } + if (!this->EnsurePwmPeripheralOwnership()) { + return false; + } + this->StopPlayback(false); + + 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, TONE_PWM_CONFIG::clock_frequency, TONE_PWM_CONFIG::pwm_mode, this->time_period); + nrf_pwm_decoder_set(_PWMInstance, TONE_PWM_CONFIG::load_mode, TONE_PWM_CONFIG::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[1] = { this->duty_with_polarity }; + + nrf_pwm_seq_ptr_set(_PWMInstance, 0, &seq_values[0]); + nrf_pwm_seq_ptr_set(_PWMInstance, 1, &seq_values[0]); + 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; +} +bool TONE_PWM_CONFIG::StartPlayback(void) noexcept { + if (!this->is_initialized) { + LOG_LV1("TON", "Cannot start playback without first initializing"); + return false; + } + 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 TONE_PWM_CONFIG::StopPlayback(bool releaseOwnership) { + + bool notInIsr = !isInISR(); + + if (!_HwPWM->isOwner(TONE_PWM_CONFIG::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; + } + + if (releaseOwnership) { + _HwPWM->releaseOwnership(TONE_PWM_CONFIG::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/main.cpp b/cores/nRF5/main.cpp index 98e3a2918..18e7432cd 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 @@ -106,11 +109,15 @@ extern "C" int _write (int fd, const void *buf, size_t count) { (void) fd; - +#if (CFG_LOGGER == 2) + unsigned numBytes = count; + SEGGER_RTT_Write(0, buf, numBytes); +#else if ( Serial ) { return Serial.write( (const uint8_t *) buf, count); } +#endif return 0; } 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}'