This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Faster nrf53 PWM frequency update through Zephyr?

Hello!

I am a beginner at nrf programming and have just started using the NCS and Zephyr on an nrf5340 evaluation board. I understand that Zephyr seems to be more or less a prerequisite right now when programming the nrf53 but I may have a problem.

I want to design a controller where I can to change the frequency of a PWM signal (50% duty cycle) based on the input value from an analog pin.
The change must be very fast. Ideally below 1uS

I tried a very simple example by modifying the LED_PWM code into just switching between a 0% and a 100% pulse as quickly as possible to see the delay:

/**
 * @file Sample app to demonstrate PWM.
 */

#include <zephyr.h>
#include <sys/printk.h>
#include <device.h>
#include <drivers/pwm.h>

#define PWM_LED0_NODE	DT_ALIAS(pwm_led0)

#if DT_NODE_HAS_STATUS(PWM_LED0_NODE, okay)
#define PWM_CTLR	DT_PWMS_CTLR(PWM_LED0_NODE)
#define PWM_CHANNEL	DT_PWMS_CHANNEL(PWM_LED0_NODE)
#define PWM_FLAGS	DT_PWMS_FLAGS(PWM_LED0_NODE)
#else
#error "Unsupported board: pwm-led0 devicetree alias is not defined"
#define PWM_CTLR	DT_INVALID_NODE
#define PWM_CHANNEL	0
#define PWM_FLAGS	0
#endif

#define MIN_PERIOD_USEC	(USEC_PER_SEC / 64U)
#define MAX_PERIOD_USEC	USEC_PER_SEC

void main(void)
{
	const struct device *pwm;
	uint32_t period;
	int ret;

	printk("PWM-based blinky\n");

	pwm = DEVICE_DT_GET(PWM_CTLR);
	if (!device_is_ready(pwm)) {
		printk("Error: PWM device %s is not ready\n", pwm->name);
		return;
	}


	period = 100000U;
	while (1) {
		ret = pwm_pin_set_usec(pwm, PWM_CHANNEL,
				       period, period, PWM_FLAGS);


                ret = pwm_pin_set_usec(pwm, PWM_CHANNEL,
				       period, 0, PWM_FLAGS);


	}
}

The switch time between a 100% pulse and a 0% pulse turns out to be more than 10ms when measured through oscilloscope. The delay is independent on any pulse length I set.
I assume this is due to the overhead within Zephyr or is there a way to improve this?

If not, is it possible to program the nrf53 application core directly as described in the datasheet - Setting the COUNTERTOP registry and basically bypassing Zephyr - at least for a snippet of code?

Many thanks for any assistance or pointers to examples.

/Jonas

Parents
  • Encouraged by my earlier success, I tried to make the driver run in wave_form mode in order to have the possibility of updating the top_value dynamically and thereby changing the frequency.

    Starting off with a simple single setup, I do not however get the expected output.

    The fourth value (supposed to be countertop) is ignored for some reson and the default value seems to be around 1000 which results in a 1kHz signal. With a 50 on the duty cycle position I get expected 5% duty. I have confirmed that I can change the duty cycle as expected but the countertop value is ignored.

    Any idea what I have got wrong?

    /**
     * Simple program to drive LED0 on the nrf5340 evaluation board using the nrfx driver for lower level access
     * Jonas Forssell
     */
    
    #include <stdio.h>
    #include <string.h>
    #include <nrfx_pwm.h>
    #include <zephyr.h>
    
    // Use Zephyr macro to get access to the LED0 on the board
    #define OUTPUT_PIN DT_GPIO_PIN(DT_ALIAS(led0), gpios)
    
    // Use nrfx to create a PWM instance which we will connect to the LED0 later
    static nrfx_pwm_t m_pwm0 = NRFX_PWM_INSTANCE(0);
    
    // Declare variables holding PWM sequence values. In this example only one channel is used 
    // We start off with a counter to 50 and a top value of 100 giving a 50% duty cyle.
    nrf_pwm_values_wave_form_t seq_values[] = {50, 0, 0, 100};
    
    nrf_pwm_sequence_t const seq =
    {
        .values.p_wave_form = seq_values,
        .length          = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats         = 0,
        .end_delay       = 0
    };
    
    
    nrfx_pwm_config_t const config0 =
        {
            .output_pins =
            {
                OUTPUT_PIN,                        // channel 0 now connected to LED0
                NRFX_PWM_PIN_NOT_USED,             // channel 1
                NRFX_PWM_PIN_NOT_USED,             // channel 2
                NRFX_PWM_PIN_NOT_USED,             // channel 3
            },
            .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,
            .base_clock   = NRF_PWM_CLK_1MHz,       // Here we select which prescaler to use
            .count_mode   = NRF_PWM_MODE_UP,
            .top_value    = 100,                    // This is ignored when in wave form mode
            .load_mode    = NRF_PWM_LOAD_WAVE_FORM,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
    
    int main(void) {
    
    // Init PWM without error handler
    nrfx_pwm_init(&m_pwm0, &config0, NULL, NULL);
    
    // Set and start
    nrfx_pwm_simple_playback(&m_pwm0, &seq, 1, NRFX_PWM_FLAG_LOOP);
    
    while(1) 
        k_msleep(3000);
    
    }

Reply
  • Encouraged by my earlier success, I tried to make the driver run in wave_form mode in order to have the possibility of updating the top_value dynamically and thereby changing the frequency.

    Starting off with a simple single setup, I do not however get the expected output.

    The fourth value (supposed to be countertop) is ignored for some reson and the default value seems to be around 1000 which results in a 1kHz signal. With a 50 on the duty cycle position I get expected 5% duty. I have confirmed that I can change the duty cycle as expected but the countertop value is ignored.

    Any idea what I have got wrong?

    /**
     * Simple program to drive LED0 on the nrf5340 evaluation board using the nrfx driver for lower level access
     * Jonas Forssell
     */
    
    #include <stdio.h>
    #include <string.h>
    #include <nrfx_pwm.h>
    #include <zephyr.h>
    
    // Use Zephyr macro to get access to the LED0 on the board
    #define OUTPUT_PIN DT_GPIO_PIN(DT_ALIAS(led0), gpios)
    
    // Use nrfx to create a PWM instance which we will connect to the LED0 later
    static nrfx_pwm_t m_pwm0 = NRFX_PWM_INSTANCE(0);
    
    // Declare variables holding PWM sequence values. In this example only one channel is used 
    // We start off with a counter to 50 and a top value of 100 giving a 50% duty cyle.
    nrf_pwm_values_wave_form_t seq_values[] = {50, 0, 0, 100};
    
    nrf_pwm_sequence_t const seq =
    {
        .values.p_wave_form = seq_values,
        .length          = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats         = 0,
        .end_delay       = 0
    };
    
    
    nrfx_pwm_config_t const config0 =
        {
            .output_pins =
            {
                OUTPUT_PIN,                        // channel 0 now connected to LED0
                NRFX_PWM_PIN_NOT_USED,             // channel 1
                NRFX_PWM_PIN_NOT_USED,             // channel 2
                NRFX_PWM_PIN_NOT_USED,             // channel 3
            },
            .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,
            .base_clock   = NRF_PWM_CLK_1MHz,       // Here we select which prescaler to use
            .count_mode   = NRF_PWM_MODE_UP,
            .top_value    = 100,                    // This is ignored when in wave form mode
            .load_mode    = NRF_PWM_LOAD_WAVE_FORM,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
    
    int main(void) {
    
    // Init PWM without error handler
    nrfx_pwm_init(&m_pwm0, &config0, NULL, NULL);
    
    // Set and start
    nrfx_pwm_simple_playback(&m_pwm0, &seq, 1, NRFX_PWM_FLAG_LOOP);
    
    while(1) 
        k_msleep(3000);
    
    }

Children
No Data
Related