I'm struggling when using PWM and DMA to generate a sine wave. I'm sure I'm not interpreting the documentation right, and I hope someone can point me in the right direction
I tried the following code
#include <stdio.h> #include <string.h> #include "nrf_drv_pwm.h" #include "app_util_platform.h" #include "app_error.h" #include "boards.h" #include "bsp.h" #include "app_timer.h" #include "nrf_drv_clock.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" static nrf_drv_pwm_t m_pwm = NRF_DRV_PWM_INSTANCE(0); static void init_bsp() { APP_ERROR_CHECK(nrf_drv_clock_init()); nrf_drv_clock_lfclk_request(NULL); APP_ERROR_CHECK(app_timer_init()); } void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info) { bsp_board_leds_on(); app_error_save_and_stop(id, pc, info); } static void demo(void) { NRF_LOG_INFO("Demo 1"); /* * */ // This array cannot be allocated on stack (hence "static") and it must // be in RAM (hence no "const", though its content is not changed). static nrf_pwm_values_wave_form_t /*const*/ m_demo_seq_values[] = { 500,500,500,1000, 578,578,578,1000, 655,655,655,1000, 727,727,727,1000, 794,794,794,1000, 854,854,854,1000, 905,905,905,1000, 946,946,946,1000, 976,976,976,1000, 994,994,994,1000, 1000,1000,1000,1000, 994,994,994,1000, 976,976,976,1000, 946,946,946,1000, 905,905,905,1000, 854,854,854,1000, 794,794,794,1000, 727,727,727,1000, 655,655,655,1000, 578,578,578,1000, 500,500,500,1000, 422,422,422,1000, 345,345,345,1000, 273,273,273,1000, 206,206,206,1000, 146,146,146,1000, 95,95,95,1000, 54,54,54,1000, 24,24,24,1000, 6,6,6,1000, 0,0,0,1000, 6,6,6,1000, 24,24,24,1000, 54,54,54,1000, 95,95,95,1000, 146,146,146,1000, 206,206,206,1000, 273,273,273,1000, 345,345,345,1000, 422,422,422,1000 }; static nrf_pwm_sequence_t const m_demo_seq = { .values.p_wave_form = &m_demo_seq_values, .length = NRF_PWM_VALUES_LENGTH(m_demo_seq_values), .repeats = 0, .end_delay = 0 }; nrf_drv_pwm_config_t const config = { .output_pins = { ARDUINO_A0_PIN, NRFX_PWM_PIN_NOT_USED, NRFX_PWM_PIN_NOT_USED, NRFX_PWM_PIN_NOT_USED }, .irq_priority = APP_IRQ_PRIORITY_LOWEST, .base_clock = NRF_PWM_CLK_16MHz, .count_mode = NRF_PWM_MODE_UP, .top_value = 40, .load_mode = NRF_PWM_LOAD_WAVE_FORM, .step_mode = NRF_PWM_STEP_AUTO }; APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm, &config, NULL)); (void)nrf_drv_pwm_simple_playback(&m_pwm, &m_demo_seq, 1, NRF_DRV_PWM_FLAG_LOOP); } int main(void) { APP_ERROR_CHECK(NRF_LOG_INIT(NULL)); NRF_LOG_DEFAULT_BACKENDS_INIT(); init_bsp(); NRF_LOG_INFO("PWM example started."); demo(); for (;;) { // Wait for an event. __WFE(); // Clear the event register. __SEV(); __WFE(); NRF_LOG_FLUSH(); } }
If I understand correctly how PWM works, with a 16MHz clock and a 1000 counter top, should give me a "pwm step" of 16kHz. Since I have 40 elements in the sine lookup table, the resulting sine frequency should be 400Hz. And I get something very close (minus the analog filtering part, that I'm still trying to figure out how to do properly)
In order to get to 10kHz, I need to reduce counter top to 40, and use 40 steps for the sine lookup table (or any similar combination, 40 and 40 just happens to work nicely with a 16Mz clock). Which seems rather low to get a good sine wave. I tried using the following, and sure enough I get a 10kHz wave
static nrf_pwm_values_wave_form_t /*const*/ m_demo_seq_values[] = { 20,0,0,40, 23,0,0,40, 26,0,0,40, 29,0,0,40, 32,0,0,40, 34,0,0,40, 36,0,0,40, 38,0,0,40, 39,0,0,40, 40,0,0,40, 40,0,0,40, 40,0,0,40, 39,0,0,40, 38,0,0,40, 36,0,0,40, 34,0,0,40, 32,0,0,40, 29,0,0,40, 26,0,0,40, 23,0,0,40, 20,0,0,40, 17,0,0,40, 14,0,0,40, 11,0,0,40, 8,0,0,40, 6,0,0,40, 4,0,0,40, 2,0,0,40, 1,0,0,40, 0,0,0,40, 0,0,0,40, 0,0,0,40, 1,0,0,40, 2,0,0,40, 4,0,0,40, 6,0,0,40, 8,0,0,40, 11,0,0,40, 14,0,0,40, 17,0,0,40 };
Do I understand correctly the relationship between counter top and the values in the sequence array? Is there a way to do better than what I'm doing in generating a 10kHz sine wave? An old trick for memory limited processors was to store only the first 90 degrees of the sine table, and "mirror" it 4 times, as appropriate, but I could not think of a way to make it work here
Since I need only one channel, do I need to have the nrf_pwm_values_wave_form_t array to have all those 0 values, or is there a way to save memory?
Lastly, any idea why I get a "warning: initialization of 'const nrf_pwm_values_wave_form_t *' from incompatible pointer type 'nrf_pwm_values_wave_form_t (*)[20]' [-Wincompatible-pointer-types]" in line 88? I thought I used the pointers as defined by nrf_pwm.h