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

  • Your PWM period time of 100000 is in the ms range, and you cannot set a new value during that period. See PS for details.

    What you could do - IMHO not in zephyr, but by programming the hardware directly - is using the same buffer for the SAADC result and the PWM input. This way the PWM can pick up a newly sampled ADC value for each period.

  • Thanks for quick feedback Turbo J!!

    I've tried experimenting with different pulse values down to ns and although it idoes affect the PWM signal, it does not change the update timing unfortunately.

    Your tip regarding the PWM and SAADC seems really interesting. Any tips on where I can read further on this? Perhaps examples?

    Thanks

    /Jonas

  • I've been studying a bit more to try to comprehend the limitations. Here are my conclusions. They might be wrong:

    1. I have not yet found any info on how Zephyr actually implements PWM but the frequency update limitation mensioned above is still valid.


    2. nrf53 development are based around Zephyr and the possibility of using the nrf5 SDK is not available.


    3. However, nrfx API can be activated (still have to learn how) and seems to support nrf53. In this case, I suspect some Zephyr bits should be deactivated to avoid conflict. 

    4. To get high performance PWM (>200 kHz), I need to activate and use the PWM peripheral in the nrf53 (using nrfx commands)

    Examples and documentation I have found so far:
    https://docs.zephyrproject.org/latest/samples/boards/nrf/nrfx/README.html

    https://devzone.nordicsemi.com/f/nordic-q-a/69264/outputting-a-pwm-signal-and-it-s-inverse-thru-gpio-pins

    So far, I have not found an example where the nrfx PWM is implemented and Zephyr is deactivated.

    I would greatly appreciate any help and guidance on this topic.

    /Jonas

  • What I meant by "programming the hardware directly" was reading the PS document where all those SAADC and PWM registers are documented, and then translating that information directly into C code.

    And yes, that ignores Zephyr and nrfx (almost) completely - both won't allow you enough control over the hardware IMHO.

    Otherwise I don't see how to implement you application.

    This is actually quite a common theme in µC programming whenever you code something interesting that the original developers (of the APIs and Zephyr) did not anticipate before.

  • Hi Jonas

    I wouldn't recommend programming the hardware directly, but it is possible to bypass the Zephyr PWM driver and use the nrfx_pwm driver instead. Then you are much closer to the hardware, but you don't need to spend a lot of time understanding how the hardware works. 

    As Jörg is hinting at it is possible to write directly to the RAM buffer assigned to the PWM, rather than starting the PWM explicitly each time you want to change it. 

    When using the nrfx_pwm driver you have to provide the buffer to the driver, which means you have access to it in the application and can update it without involving the driver. 

    The idea of connecting the ADC to the same buffer is interesting, but this is not something we have tested. Also, the drawback of this method is that you are not able to do any scaling, inversion or anything else that you might want to do to the data before you output it on the PWM. 

    Best regards
    Torbjørn

Related