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