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
  • Based on your feedback Torbjörn and Jörg, I have messed about for a while trying to create a working PWM example where I use the nrfx_pwm drivers instead. 

    I was quite amazed to get this to work. I based it on a simple-pwm example I found for the nrf_pwm driver but needed to tweak it a bit. Mostly by replacing nrf references with corresponding nrfx equivalents.

    I needed to use Zephyr to get a reference to the LED0 from the devicetree but then it compiled and worked.

    Thanks for your help gentlemen!

    /Jonas

    /**
     * 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 
    nrf_pwm_values_individual_t seq_values[] = {0, 0, 0, 0};
    nrf_pwm_sequence_t const seq =
    {
        .values.p_individual = seq_values,
        .length          = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats         = 0,
        .end_delay       = 0
    };
    
    
    // Set duty cycle between 0 and 100%
    void pwm_update_duty_cycle(uint8_t duty_cycle)
    {
        
        // Check if value is outside of range. If so, set to 100%
        if(duty_cycle >= 100)
        {
            seq_values->channel_0 = 100;
        }
        else
        {
            seq_values->channel_0 = duty_cycle;
        }
        
        nrfx_pwm_simple_playback(&m_pwm0, &seq, 1, NRFX_PWM_FLAG_LOOP);
    }
    
    static void pwm_init(void)
    {
        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,
            .count_mode   = NRF_PWM_MODE_UP,
            .top_value    = 100,
            .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
        // Init PWM without error handler
        nrfx_pwm_init(&m_pwm0, &config0, NULL, NULL);
        
    }
    
    
    int main(void)
    {
    
        // Start clock for accurate frequencies
        NRF_CLOCK->TASKS_HFCLKSTART = 1; 
        // Wait for clock to start
        while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) 
            ;
        
        pwm_init();
    
        for (;;)
        {
            for(int i = 0; i <= 100; i++)
            {
                k_msleep(10);
                pwm_update_duty_cycle(i);
            }
        }
    }
    
    
    /** @} */

  • Hi Jonas

    Good to hear you got it working Slight smile

    Older examples written for the nRF5 SDK will require some porting, since the older nrf_drv drivers are all replaced by nrfx drivers in Zephyr. 

    Best regards
    Torbjørn

Reply Children
No Data
Related