PWM testing, learning

Eventually I would like to drive the typical IR_LED at 38Khz (26 microseconds per cycle). I cloned Binky_PWM to start. 

I thought it prudent to flash a visible LED (led0) at 1hz to confirm I understand... It does flash, but I doubt the rate is 1hz and I don't understand at least three things:

  1. I can't set PWM_HZ(4) to anything lower than 4 or I get no flashing. I expected to enter a 1 here.
  2. I don't fully understand: ret = pwm_set_dt(&pwm_led0, period, period / 2U);  especially "period / 2U". I understand this to be the pulse width, but I expected a value similar to "50" (50%) or "13" (13µs, 50% of the duty cycle at 38Khz)
  3. I don't understand how k_msleep(xxx); works here. Any value for k_msleep doesn't seem to affect the flashing rate, however I am used to bare metal programming and only beginning to use threads, so it's unsurprising that k_msleep behavior is unclear to me.

Ultimately I wish to flash:  38Khz, 50% duty cycle

Potentially, I will also need to modulate the carrier on/off every 600us (I'm using 1ms, 10ms, etc just for my current testing). However unlike TSOP modules, the TSSP module (TSSP58038) I am looking at doesn't appear to require modulation. I thought k_msleep is how I would modulate the 38Khz every 10ms, but maybe that's not how it works.

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

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

static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));

#define ir_rate PWM_HZ(4) //value must be >=4 ???

int main(void)
{
	uint32_t period;
	int ret;

	printk("PWM-based LED Control\n");

	if (!pwm_is_ready_dt(&pwm_led0)) {
		printk("Error: PWM device %s is not ready\n",
		       pwm_led0.dev->name);
		return 0;
	}

	period = ir_rate;
	while (1) {
		ret = pwm_set_dt(&pwm_led0, period, period / 2U); //don't understand duty cycle setting???

		if (ret) {
			printk("Error %d: failed to set pulse width\n", ret);
			return 0;
		}

		k_msleep(10000); //don't understand how k_msleep works here. Doesn't seem to affect anything???
	}
	return 0;
}

Parents
  • Hello,

    I suggest to try and fail a bit, and observe what you measure on the output to understand how this works. In essence the pulse period is the total duration of an ON and OFF state for the PWM signal. The pulse width is the duration of the ON state for the PWM signal. So that way you can control both the frequency and the duty cycle. The k_msleep() just put the CPU in sleep until the CPU will continue to run the code, e.g. if you want to periodically update the PWM signal you can set the sleep to 600us if you want.

    pwm_set_dt(const struct pwm_dt_spec *spec, uint32_t period, uint32_t pulse)

    Kenneth

  • Hi Kenneth, Thank you for your reply. After waiting for 3 days I was hoping for more than "try and fail a bit". This isn't a hobby for me. I am trying to standardize all our engineering solutions on Nordic processors, but the ramp up time has prevented making any meaningful progress for the last couple of years so we keep reverting to what we know. I am trying to change that in 2024. I count on DevZone Support to help get us where we want to be in a reasonable amount of time. I understand the theory of PWM. I've been implementing it effortlessly on PIC, Arduino, ESP32 for years... My problem is with implementing it with Nordic/Zephyr.

    Here are my specific questions:

    #define ir_rate PWM_HZ(1) why does the value have to be greater than 4 in the example?

    #define ir_rate PWM_KHZ(38); please confirm this is the correct setting for 38KHz?

    pwm_set_dt(&pwm_led0, period, period / 2U); what value would I use here for 50% duty cycle? Does "period/2U" set a 50% duty cycle? I'm guessing that it does...?

    I probably need to do more research into exactly what k_msleep does, but what I have done, even a value as long as 10000ms, doesn't pause the LED flashing at all. I expected it to interrupt the flashing for some period of time, but it doesn't seem to.

  • The period and pulse parameters are in nanoseconds, so the PWM_HZ() macro bascially convert Hz to nanoseconds. Since the register is 32bit, the max value is 4294967296ns, which is 4.3seconds. So that may explain the limit of 4. Also see the documentation on the api:

    /**
     * @name PWM period set helpers
     * The period cell in the PWM specifier needs to be provided in nanoseconds.
     * However, in some applications it is more convenient to use another scale.
     * @{
     */
    
    /** Specify PWM period in nanoseconds */
    #define PWM_NSEC(x)	(x)
    /** Specify PWM period in microseconds */
    #define PWM_USEC(x)	(PWM_NSEC(x) * 1000UL)
    /** Specify PWM period in milliseconds */
    #define PWM_MSEC(x)	(PWM_USEC(x) * 1000UL)
    /** Specify PWM period in seconds */
    #define PWM_SEC(x)	(PWM_MSEC(x) * 1000UL)
    /** Specify PWM frequency in hertz */
    #define PWM_HZ(x)	(PWM_SEC(1UL) / (x))
    /** Specify PWM frequency in kilohertz */
    #define PWM_KHZ(x)	(PWM_HZ((x) * 1000UL))

    /**
     * @brief Set the period and pulse width in nanoseconds from a struct
     *        pwm_dt_spec (with custom period).
     *
     * This is equivalent to:
     *
     *     pwm_set(spec->dev, spec->channel, period, pulse, spec->flags)
     *
     * The period specified in @p spec is ignored. This API call can be used when
     * the period specified in Devicetree needs to be changed at runtime.
     *
     * @param[in] spec PWM specification from devicetree.
     * @param period Period (in nanoseconds) set to the PWM.
     * @param pulse Pulse width (in nanoseconds) set to the PWM.
     *
     * @return A value from pwm_set().
     *
     * @see pwm_set_pulse_dt()
     */
    static inline int pwm_set_dt(const struct pwm_dt_spec *spec, uint32_t period,
    			     uint32_t pulse)
    {
    	return pwm_set(spec->dev, spec->channel, period, pulse, spec->flags);
    }

    Setting pulse half of period should give 50% duty cycle yes.

    The PWM peripheral will run independent of the CPU, so whether the CPU run some arbitrary code or go to sleep, this will not affect the PWM in any way. It's really only pwm_set_dt() that will affect the PWM output.

    Kenneth

Reply
  • The period and pulse parameters are in nanoseconds, so the PWM_HZ() macro bascially convert Hz to nanoseconds. Since the register is 32bit, the max value is 4294967296ns, which is 4.3seconds. So that may explain the limit of 4. Also see the documentation on the api:

    /**
     * @name PWM period set helpers
     * The period cell in the PWM specifier needs to be provided in nanoseconds.
     * However, in some applications it is more convenient to use another scale.
     * @{
     */
    
    /** Specify PWM period in nanoseconds */
    #define PWM_NSEC(x)	(x)
    /** Specify PWM period in microseconds */
    #define PWM_USEC(x)	(PWM_NSEC(x) * 1000UL)
    /** Specify PWM period in milliseconds */
    #define PWM_MSEC(x)	(PWM_USEC(x) * 1000UL)
    /** Specify PWM period in seconds */
    #define PWM_SEC(x)	(PWM_MSEC(x) * 1000UL)
    /** Specify PWM frequency in hertz */
    #define PWM_HZ(x)	(PWM_SEC(1UL) / (x))
    /** Specify PWM frequency in kilohertz */
    #define PWM_KHZ(x)	(PWM_HZ((x) * 1000UL))

    /**
     * @brief Set the period and pulse width in nanoseconds from a struct
     *        pwm_dt_spec (with custom period).
     *
     * This is equivalent to:
     *
     *     pwm_set(spec->dev, spec->channel, period, pulse, spec->flags)
     *
     * The period specified in @p spec is ignored. This API call can be used when
     * the period specified in Devicetree needs to be changed at runtime.
     *
     * @param[in] spec PWM specification from devicetree.
     * @param period Period (in nanoseconds) set to the PWM.
     * @param pulse Pulse width (in nanoseconds) set to the PWM.
     *
     * @return A value from pwm_set().
     *
     * @see pwm_set_pulse_dt()
     */
    static inline int pwm_set_dt(const struct pwm_dt_spec *spec, uint32_t period,
    			     uint32_t pulse)
    {
    	return pwm_set(spec->dev, spec->channel, period, pulse, spec->flags);
    }

    Setting pulse half of period should give 50% duty cycle yes.

    The PWM peripheral will run independent of the CPU, so whether the CPU run some arbitrary code or go to sleep, this will not affect the PWM in any way. It's really only pwm_set_dt() that will affect the PWM output.

    Kenneth

Children
Related