This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nRF52 MIDI/Piano - PWM Feature Request

The PWM (Pulse Width Modulation) peripheral coupled with EasyDMA makes an excellent waveform generator. However it has one drawback which I would like to request be added as a feature on next silicon, since it is a simple change. The request is to add an option to allow the LOOP register to be automatically reloaded to an arbitrary value.

So why would we want this? Let's assume the goal is to replay a complex sequence such as a MIDI stream or nRF52 Piano design with absolutely no cpu time after the initial start; no interrupts, no wakeups, ziltch, nada, nothing. That scenario eliminates any potential timing issues with SoftDevice and guarantees absolute minimum power consumption from the coin cell battery.

Were interrupts available, the Single Mode could be used, with timers and PPI making changes on the fly but with no guarantee of unwanted timing distortions:
 

// Quarter Note Value in Beats per Minute  Duration in Milliseconds
//               Dotted         Dotted
//      Quarter  Quarter Whole  Half    Half  Eighth  Sixteenth
// ===  =======  ======= =====  ======  ====  ======  =========
//  60   1000    1500    4000   3000    2000    500     250
// 120    500     750    2000   1500    1000    250     125

// One-Line 4  PWM                PWM     PWM    Repeat            
// ==========  Count      Freq    Count   Count  Count  Act  Err% 
//             =====     ======  =======  =====  ====== ===  ====
Ps4D      =    3405, //  293.66  3405.30  3405   147    11   -3
Ps4A      =    2273, //  440.00  2272.73  2273   220    17    0

//  DECODER.LOAD = Single
//  =====================
//   +----------------------------------> COMP0 OUT[0] Compare 0
//   |         +------------------------> COMP1 OUT[1] Compare 1
//   |         |         +--------------> COMP2 OUT[2] Compare 2
//   |         |         |        +-----> COMP3 OUT[3] Compare 3
//   |         |         |        |
// +---------+---------+---------+---------+
// | Compare | Compare | Compare | Compare | Cycle N
// +---------+---------+---------+---------+
// | Compare | Compare | Compare | Compare | Cycle N+1
// +---------+---------+---------+---------+
//
//                                        COUNTERTOP Cycle Period, 1MHz clocks
//                                        LOOP       Repeat Count, number of periods

The Waveform Mode helps with this considerably, as the top Compare register can hold the COUNTERTOP reload value
 
//  DECODER.LOAD = Waveform
//  =======================
//   +----------------------------------> COMP0 OUT[0] Compare 0
//   |         +------------------------> COMP1 OUT[1] Compare 1
//   |         |         +--------------> COMP2 OUT[2] Compare 2
//   |         |         |                COMP3 (used as TOP reload value)
//   |         |         |
// +---------+---------+---------+---------+
// | Compare | Compare | Compare | Compare | Cycle N
// +---------+---------+---------+---------+
// | Compare | Compare | Compare | Compare | Cycle N+1
// +---------+---------+---------+---------+
//                                 |
//                                 +----> COUNTERTOP Cycle Period, 1MHz clocks
//                                        LOOP       Repeat Count, number of periods

Consider two notes, A at 440Hz and D at 293.66Hz. With a PWM clock of 1MHz, that requires a COUNTERTOP of 2273 and 3405 respectively at 60 beats per minute (BPM). Using a common divisor as a LOOP setting reduces the number of required lines, but with many different notes there is no good common divisor. In the table above I chose 13 as it gave reasonable results and some code reduction. 147 -> 11 and 220 -> 17 lines respectively, but this introduces errors in note durations (frequency remains accurate).

// The 15-bit wave counter is responsible for generating the pulses at a duty cycle that depends on the compare
// values, and at a frequency that depends on COUNTERTOP. MS bit is the sign, '1' active high starting pulse

#define SOUND_P (0x8000| 500)  // High pulse at start of PWM cycle
#define SOUND_N (        500)  // Low  pulse at start of PWM cycle
#define QUIET   (0x8000|   0)  // No pulse in PWM cycle

#define Qt                   \
  {QUIET,   0, 0, Ps4A},     \
  {QUIET,   0, 0, Ps4A},     \
  {QUIET,   0, 0, Ps4A},     \
  {QUIET,   0, 0, Ps4A},

#define s4A                     \
  {SOUND_P, SOUND_N, 0, Ps4A},  \
  {SOUND_P, SOUND_N, 0, Ps4A},  \
  ... 220 identical lines .. or /13 with LOOP=13 (say)
  {SOUND_P, SOUND_N, 0, Ps4A},  \
  {SOUND_P, SOUND_N, 0, Ps4A},

#define s4D                     \
  {SOUND_P, SOUND_N, 0, Ps4D},  \
  {SOUND_P, SOUND_N, 0, Ps4D},  \
  ... 147 identical lines .. or /13 with LOOP=13
  {SOUND_P, SOUND_N, 0, Ps4D},  \
  {SOUND_P, SOUND_N, 0, Ps4D},  

nrf_pwm_values_wave_form_t MyTune[] = {s4A Qt s4D Qt Qt s4A Qt s4D s4D Qt};

// Note and length of time to hold for quarter note (tbd)
typedef struct {
  nrf_pwm_values_wave_form_t* pWaveForm;
  uint16_t                    WaveLength;
  uint16_t                    WaveFormRepeats;  // 15-bit Loops register
}PwmNote_t;

PwmNote_t PlayTune= {MyTune, sizeof(MyTune)/sizeof(MyTune[0].channel_0),  13};

So with the new PDM Mode the replay sequence would be reduced in size by a large factor:

//  DECODER.LOAD = PDM Mode
//  =======================
//   +----------------------------------> COMP0 OUT[0] Compare 0
//   |         +------------------------> COMP1 OUT[1] Compare 1
//   |         |                          COMP2 (used as LOOP reload value)
//   |         |                          COMP3 (used as TOP reload value)
//   |         |
// +---------+---------+---------+---------+
// | Compare | Compare | Compare | Compare | Cycle N
// +---------+---------+---------+---------+
// | Compare | Compare | Compare | Compare | Cycle N+1
// +---------+---------+---------+---------+
//                       |         |
//                       |         +----> COUNTERTOP Cycle Period, 1MHz clocks
//                       +--------------> LOOP       Repeat Count, number of periods

// Simplified - and very much smnaller - musical tune database using new PDM option
#define Qt {QUIET,   0, 40, Ps4A},
#define s4A {SOUND_P, SOUND_N, 220, Ps4A},
#define s4D {SOUND_P, SOUND_N, 147, Ps4D},

Can you kindly add that to the next hardware revision of nRF52832 and nRF52840? :-)

Related