Clarification on how to generate a 10kHz sine wave with PWM and DMA

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

Related