PWM base clock frequency periodically changing

I have an Arduino Nano 33 BLE using the nRF52840, and I'm trying to use PWM to output audio.  As a test I have the PWM configured to output a 440 Hz sine wave using no prescaler (so 16 MHz clock) and a CLOCKTOP of 400 resulting in a PWM frequency of 40kHz for audio.  I also have two LEDs running on the same PWM peripheral at 1Hz and 2Hz.  The issue I'm having is that for about 4 seconds everything is (mostly) fine and I get a 440 Hz tone out of the speaker, then suddenly the frequency drops to 436 Hz (15 cents flat of A4) then after about 30 seconds it changes to 470 Hz (15 cents sharp of A#4) then it will drop to 403 Hz then jump to 537 Hz where it will stay (until it stops completely because of floating point precision error in my code).

these frequency changes are always to the same frequency and always at the same time.  The two LEDs can also be visually seen to be blinking at different rates as the audio frequency changes.


I'm also having a secondary issue of the tone having an added buzzing sound to it that shouldn't be there, I have not looked further into this yet

float cos_table[256];

constexpr float _2_PI = 2.0f * 3.14159265358979f;

static uint32_t out_pins[4] = {43, 6, 13, NRF_PWM_PIN_NOT_CONNECTED};
static uint16_t pwm_duties[4*256];

float t = 0;
float dt = 1.0f / 40000.0f;

float cos_(float x) {
  x = x - (int)x;
  x *= 256.0f;
  int x_ = (int)x;
  return cos_table[x_];
}

void get_data() {
  Serial.println("Data Get Begin");
  for (int i = 0; i < 256; i++) {
    float value1 = (cos_(t * 440.0f) * 0.2f + 0.5f);
    float value2 = (cos_(t * 1.0f) * 0.5f + 0.5f);
    float value3 = (cos_(t * 2.0f) * 0.5f + 0.5f);
    t += dt;
    pwm_duties[i * 4 + 0] = (uint16_t) (value1 * 400.0f);
    pwm_duties[i * 4 + 1] = (uint16_t) (value2 * 400.0f);
    pwm_duties[i * 4 + 2] = (uint16_t) (value3 * 400.0f);
  }
  Serial.println("Data Get End");
}

void setup() {

  for (int i = 0; i < 256; i++) {
    cos_table[i] = cos(i / 256.0f * _2_PI);
  }
  memset(pwm_duties, 0, sizeof(pwm_duties));
  
}

void loop() {
  
  nrf_pwm_pins_set(NRF_PWM0, out_pins);
  nrf_pwm_enable(NRF_PWM0);
  nrf_pwm_configure(NRF_PWM0, NRF_PWM_CLK_16MHz, NRF_PWM_MODE_UP, 400);
  nrf_pwm_loop_set(NRF_PWM0, 1);
  nrf_pwm_decoder_set(NRF_PWM0, NRF_PWM_LOAD_INDIVIDUAL, NRF_PWM_STEP_AUTO);
  nrf_pwm_seq_ptr_set(NRF_PWM0, 0, pwm_duties);
  nrf_pwm_seq_cnt_set(NRF_PWM0, 0, ((sizeof(pwm_duties) / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos));
  nrf_pwm_seq_refresh_set(NRF_PWM0, 0, 0);
  nrf_pwm_seq_end_delay_set(NRF_PWM0, 0, 0);
  nrf_pwm_seq_ptr_set(NRF_PWM0, 1, pwm_duties);
  nrf_pwm_seq_cnt_set(NRF_PWM0, 1, ((sizeof(pwm_duties) / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos));
  nrf_pwm_seq_refresh_set(NRF_PWM0, 1, 0);
  nrf_pwm_seq_end_delay_set(NRF_PWM0, 1, 0);
  nrf_pwm_shorts_enable(NRF_PWM0, NRF_PWM_SHORT_LOOPSDONE_SEQSTART0_MASK);
  nrf_pwm_task_trigger(NRF_PWM0, NRF_PWM_TASK_SEQSTART0);

  while(1) {
    if (nrf_pwm_event_check(NRF_PWM0, NRF_PWM_EVENT_SEQEND0)) {
      nrf_pwm_event_clear(NRF_PWM0, NRF_PWM_EVENT_SEQEND0);
      get_data();
    }
    if (nrf_pwm_event_check(NRF_PWM0, NRF_PWM_EVENT_SEQEND1)) {
      nrf_pwm_event_clear(NRF_PWM0, NRF_PWM_EVENT_SEQEND1);
      get_data();
    }
  }
}

Parents Reply Children
Related