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

  • 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;
    }
  • I also got it working in a slightly different way, see here

    6165.pwm.zip

    The trick i used was to use this below in the overlay where I set one pin to inverted

    &pwm0 {
        status = "okay";
        pinctrl-0 = <&pwm0_default>;
        pinctrl-1 = <&pwm0_sleep>;
        pinctrl-names = "default", "sleep";
    };
    
    &pinctrl {
        pwm0_default: pwm0_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 13)>,
                        <NRF_PSEL(PWM_OUT1, 0, 14)>;
                nordic,drive-mode = <NRF_DRIVE_H0H1>;
            };
        };
    
        pwm0_sleep: pwm0_sleep {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 13)>,
                        <NRF_PSEL(PWM_OUT1, 0, 14)>;
                nordic,drive-mode = <NRF_DRIVE_H0H1>;
            };
        };
    };

    And in the code I drove both pins same but one should be inverted since it is forced from overlay

       // Configure PWM_OUT0 (normal polarity)
        if (pwm_set(pwm_dev, PWM_CHANNEL0, PWM_PERIOD_USEC, pulse_width, PWM_POLARITY_NORMAL)) {
            printk("Failed to configure PWM channel 0\n");
        } else {
            printk("PWM channel 0 configured: 50%% duty cycle, normal polarity\n");
        }
    
        // Configure PWM_OUT1 (inverted polarity)
        if (pwm_set(pwm_dev, PWM_CHANNEL1, PWM_PERIOD_USEC, pulse_width, PWM_POLARITY_INVERTED)) {
            printk("Failed to configure PWM channel 1\n");
        } else {
            printk("PWM channel 1 configured: 50%% duty cycle, inverted polarity\n");
        }

    I guess there are many ways to do it.

Related