How to configure 2 PWM pins with same frequency and duty cycle but opposite polarity

I'm developing a product with the nRF52840, using Zephyr (Toolchain & SDK version 2.7.0).  Is there a way to set up two PWM outputs having the same frequency and duty cycle, but with opposite polarity (such that when one pin goes high, the other goes low, and vice-versa)?

I'll also need to start and stop the outputs together (or at least close to it).

Thanks in advance for any help,

--mkj

Parents
  • mkjones said:
    Since my duty cycle is 50%, the pulse length for pwm_2 is exactly the same as pmw_1; I don't understand how this gets the opposite polarity.

    the second channel of the PWM configured with period_usec - duty_cycle_usec, meaning it starts low and becomes high when Channel 0 transitions to low.

    The error:'__device_dts_ord_DT_N_S_soc_S_pwm_4001c000_P_pwms_IDX_0_PH_ORD' undeclared

    and the code looks something like, not compiled but to show you the logic

    #include <zephyr/drivers/pwm.h>
    
    const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));
    
    void configure_pwm_single_instance(uint32_t period_usec, uint32_t duty_cycle_usec)
    {
        if (!device_is_ready(pwm.dev)) {
            printk("Error: PWM device is not ready.\n");
            return;
        }
    
        int ret = pwm_set(pwm.dev, 0, PWM_USEC(period_usec), PWM_USEC(duty_cycle_usec));
        if (ret) {
            printk("Failed to configure channel 0 (normal polarity): %d\n", ret);
        }
    
        ret = pwm_set(pwm.dev, 1, PWM_USEC(period_usec), PWM_USEC(period_usec - duty_cycle_usec));
        if (ret) {
            printk("Failed to configure channel 1 (inverted polarity): %d\n", ret);
        }
    
        printk("PWM configured with complementary signals on a single instance.\n");
    }

  • The line that will not compile for me is:

    const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));

    It clearly does not like the way I've defined my pwm device.  I have tried following the two (different) examples:

    https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-4-pulse-width-modulation-pwm/topic/zephyr-pwm-api/

    https://blog.golioth.io/adding-pwm-sound-in-zephyr/

    Nothing I do makes the compiler happy.  Can you also give me the contents of the .overlay file that will make the code sample you've provided compile?

    This is *incredibly* frustrating; the whole DTS thing is certainly powerful, but unless you are already an expert (and I am not), it is virtually impossible to produce code that will compile (never mind work correctly) unless you can find a sample that does exactly what you need.

Reply Children
  • OK, I've got it to compile (I ended up using C:\ncs\v2.7.0\nrf\samples\bluetooth\fast_pair\locator_tag\ as a model):

    / {
        aliases {
            pwm-spk = &buzzer;
        };

        pwmbuzzer {
            compatible = "pwm-leds";
            status = "okay";

            buzzer: buzzer_pwm {
                pwms =  <&pwm0 0 PWM_HZ(4100) PWM_POLARITY_NORMAL>,
                        <&pwm0 1 PWM_HZ(4100) PWM_POLARITY_INVERTED>;
                label = "PWM_1";
            };
        };
    };

    &pwm0 {
        status = "okay";
        pinctrl-0 = <&pwm0_default>;
        pinctrl-names = "default";
    };

    &pinctrl {
        pwm0_default: pwm0_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 22)>,
                        <NRF_PSEL(PWM_OUT0, 0, 23)>;
                nordic,drive-mode = <NRF_DRIVE_H0H1>;
            };
        };
    };

    However, when I execute this function:

    int speaker_on()
    {
        int err;

        if (!device_is_ready(pwm.dev))
        {
            LOG_ERR("Speaker PWM not ready");
            err = -ENODEV;
        }
        else if ((err = pwm_set(pwm.dev, AUDIO_CHANNEL, SPEAKER_PERIOD, SPEAKER_PULSE, PWM_POLARITY_NORMAL)))
        {
            LOG_ERR("pwm_set audio failed, err=%d", err);
        }
        else if ((err = pwm_set(pwm.dev, BOOST_CHANNEL, SPEAKER_PERIOD, SPEAKER_PULSE, PWM_POLARITY_INVERTED)))
        {
            LOG_ERR("pwm_set boost failed, err=%d", err);
        }
        return err;
    }

    I only get output from the second channel (GPIO 0.23); GPIO 0.22 just stays low.

  • This should have been a very simple thing, sorry that it is taking this long, i will test this tomorrow on DK, it shouldn't be this difficult to generate an inverted signal on two pins using a PWM.

  • Through much trial-and-error, I finally got it working.  First, I gave up on trying to use PWM_DT_SPEC_GET; in the end, I just used the PMW device directly.  Also, the other bit of magic which was not obvious was that each channel needs to use a different function (so, PMW_OUT0 and PMW_OUT1 below)  So, here's the overlay file:

    &pwm0 {
        status = "okay";
        pinctrl-0 = <&pwm0_default>;
        pinctrl-names = "default";
    };

    &pinctrl {
        pwm0_default: pwm0_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 22)>,
                        <NRF_PSEL(PWM_OUT1, 0, 23)>;
                nordic,drive-mode = <NRF_DRIVE_H0H1>;
            };
        };
    };

    And here's the source file; the final bit of magic was to use PWM_POLARITY_NORMAL for one channel and PWM_POLARITY_INVERTED for the other.  Now I get identical (but inverted) waveforms on the two channels:

    /** @file speaker.c
     *
     * Interface to the speaker.
     */
    #include "speaker.h"
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/pwm.h>

    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(SPKR, LOG_LEVEL_DBG);

    // The PWM device (must contain two channels, 0=audio, 1=boost)
    const struct device *pwm = DEVICE_DT_GET(DT_NODELABEL(pwm0));

    // Channel numbers for the two PWMs
    #define AUDIO_CHANNEL   0
    #define BOOST_CHANNEL   1

    // Frequency to run the speaker at (4.1KHz, 50% duty cycle)
    #define SPEAKER_FREQUENCY_HZ    4100
    #define SPEAKER_PERIOD          PWM_HZ(SPEAKER_FREQUENCY_HZ)
    #define SPEAKER_PULSE           (SPEAKER_PERIOD / 2)

    /**
     * @brief Initialize the speaker
     */
    int speaker_init()
    {
        int err;

        if (!device_is_ready(pwm))
        {
            LOG_ERR("PWM device is not ready");
            err = -ENODEV;
        }
        else
        {
            LOG_INF("PWM initalized");
            err = 0;
        }
        return err;
    }

    /**
     * @brief Turn the speaker on
     */
    int speaker_on()
    {
        int err;

        if (!device_is_ready(pwm))
        {
            LOG_ERR("PWM device not ready");
            err = -ENODEV;
        }
        else if ((err = pwm_set(pwm, AUDIO_CHANNEL, SPEAKER_PERIOD, SPEAKER_PULSE, PWM_POLARITY_NORMAL)))
        {
            LOG_ERR("pwm_set audio failed, err=%d", err);
        }
        else if ((err = pwm_set(pwm, BOOST_CHANNEL, SPEAKER_PERIOD, SPEAKER_PULSE, PWM_POLARITY_INVERTED)))
        {
            LOG_ERR("pwm_set boost failed, err=%d", err);
        }
        return err;
    }

    /**
     * @brief Turn the speaker off
     */
    int speaker_off()
    {
        int err;

        if (!device_is_ready(pwm))
        {
            LOG_ERR("PWM device not ready");
            err = -ENODEV;
        }
        else if ((err = pwm_set(pwm, AUDIO_CHANNEL, SPEAKER_PERIOD, 0, PWM_POLARITY_NORMAL)))
        {
            LOG_ERR("pwm_set audio failed, err=%d", err);
        }
        else if ((err = pwm_set(pwm, BOOST_CHANNEL, SPEAKER_PERIOD, 0, PWM_POLARITY_NORMAL)))
        {
            LOG_ERR("pwm_set boost failed, err=%d", err);
        }
        return err;
    }
Related