Unexpected PWM behavior

I need to generate a clean 10kHz sine wave. I tried using PWM (https://devzone.nordicsemi.com/f/nordic-q-a/89209/clarification-on-how-to-generate-a-10khz-sine-wave-with-pwm-and-dma), and it works, but it's hard to filter the resulting signal. Given the 16MHz clock limit, and the need to have a 10kHz sine, I can only use 40 values and 40 points in the LUT.

One of the ideas was to to use PDM instead of PWM. the nRF52840 doesn't have PDM out, nor a way to stream a sequence of bits using DMA (as far as I can tell).

I thought that I could use a PWM with only 2 pulse (possible values 0, 1, and 2) and with that build any stream of bits. It would be expensive in terms of memory (every 2 bits is stored in a 16 bit array), but would work for a 800 point LUT.

So I tried this:

#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());
}

static nrf_pwm_values_common_t /*const*/ m_demo_seq_values[] =
{
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
    1 | 0x8000,
};

static void demo(void)
{
    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_7_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    = 2,
        .load_mode    = NRF_PWM_LOAD_COMMON,
        .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();

    demo();

    for (;;)
    {
        // Wait for an event.
        __WFE();
        // Clear the event register.
        __SEV();
        __WFE();
        NRF_LOG_FLUSH();
    }
}

but I got very weird results. On my scope, I see a single pulse at a ~490Hz frequency

When I change the clock to 8MHz (.base_clock   = NRF_PWM_CLK_16MHz), clean signal as expected

My question is: am I using the SDK functions wrongly? Is there a way to make a 16MHz, two pulses, PWM signal work? Did I hit a hardware limitation?

Related