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

  • Try this code for inverted PWM after you configured two PWM in the device tree

    const struct pwm_dt_spec pwm_1 = PWM_DT_SPEC_GET(DT_ALIAS(pwm0));
    const struct pwm_dt_spec pwm_2 = PWM_DT_SPEC_GET(DT_ALIAS(pwm1));
    
    void configure_pwm_opposite_polarity(uint32_t period_usec, uint32_t duty_cycle_usec)
    {
        if (!device_is_ready(pwm_1.dev) || !device_is_ready(pwm_2.dev)) {
            printk("Error: PWM device is not ready.\n");
            return;
        }
    
        // Set PWM for the first channel (normal polarity)
        pwm_set_dt(&pwm_1, period_usec, duty_cycle_usec);
    
        // Set PWM for the second channel (inverted polarity)
        pwm_set_dt(&pwm_2, period_usec, period_usec - duty_cycle_usec);
    }

  • 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.

    Maybe there's something I need to do in the overlay file where I define the PWMs?

  • Here's what I've defined in my overlay file:

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

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

    Here's what's at the top my source file:

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

    #define AUDIO_CHANNEL   0
    #define BOOST_CHANNEL   1

    It simply will not compile; it is unable to find pwm0 (I think; it is actually impossible to tell what the build system is unhappy about).  Here is the compiler output:

    C:/ncs/v2.7.0/zephyr/include/zephyr/device.h:91:41: error: '__device_dts_ord_DT_N_S_soc_S_pwm_4001c000_P_pwms_IDX_0_PH_ORD' undeclared here (not in a function)
       91 | #define DEVICE_NAME_GET(dev_id) _CONCAT(__device_, dev_id)
          |                                         ^~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/common.h:137:26: note: in definition of macro '_DO_CONCAT'
      137 | #define _DO_CONCAT(x, y) x ## y
          |                          ^
    C:/ncs/v2.7.0/zephyr/include/zephyr/device.h:91:33: note: in expansion of macro '_CONCAT'
       91 | #define DEVICE_NAME_GET(dev_id) _CONCAT(__device_, dev_id)
          |                                 ^~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/device.h:228:37: note: in expansion of macro 'DEVICE_NAME_GET'
      228 | #define DEVICE_DT_NAME_GET(node_id) DEVICE_NAME_GET(Z_DEVICE_DT_DEV_ID(node_id))
          |                                     ^~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/device.h:245:34: note: in expansion of macro 'DEVICE_DT_NAME_GET'
      245 | #define DEVICE_DT_GET(node_id) (&DEVICE_DT_NAME_GET(node_id))
          |                                  ^~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/drivers/pwm.h:260:24: note: in expansion of macro 'DEVICE_DT_GET'
      260 |                 .dev = DEVICE_DT_GET(DT_PWMS_CTLR_BY_IDX(node_id, idx)),       \
          |                        ^~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/drivers/pwm.h:328:34: note: in expansion of macro 'PWM_DT_SPEC_GET_BY_IDX'
      328 | #define PWM_DT_SPEC_GET(node_id) PWM_DT_SPEC_GET_BY_IDX(node_id, 0)
          |                                  ^~~~~~~~~~~~~~~~~~~~~~
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/src/speaker.c:16:32: note: in expansion of macro 'PWM_DT_SPEC_GET'
       16 | const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));
          |                                ^~~~~~~~~~~~~~~
    In file included from C:/ncs/v2.7.0/zephyr/include/zephyr/arch/arm/arch.h:20,
                     from C:/ncs/v2.7.0/zephyr/include/zephyr/arch/cpu.h:19,
                     from C:/ncs/v2.7.0/zephyr/include/zephyr/kernel_includes.h:36:
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/build/zephyr/include/generated/devicetree_generated.h:5965:36: error: 'DT_N_S_soc_S_pwm_4001c000_P_pwms_IDX_0_VAL_channel' undeclared here (not in a function); did you mean 'DT_N_S_soc_S_pwm_4001c000_REG_IDX_0_VAL_SIZE'?
     5965 | #define DT_N_NODELABEL_pwm0        DT_N_S_soc_S_pwm_4001c000
          |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree.h:4543:9: note: in definition of macro 'DT_CAT7'
     4543 |         a1 ## a2 ## a3 ## a4 ## a5 ## a6 ## a7
          |         ^~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree/pwms.h:136:9: note: in expansion of macro 'DT_PHA_BY_IDX'
      136 |         DT_PHA_BY_IDX(node_id, pwms, idx, cell)
          |         ^~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree/pwms.h:208:9: note: in expansion of macro 'DT_PWMS_CELL_BY_IDX'
      208 |         DT_PWMS_CELL_BY_IDX(node_id, idx, channel)
          |         ^~~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/drivers/pwm.h:261:28: note: in expansion of macro 'DT_PWMS_CHANNEL_BY_IDX'
      261 |                 .channel = DT_PWMS_CHANNEL_BY_IDX(node_id, idx),               \
          |                            ^~~~~~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/drivers/pwm.h:328:34: note: in expansion of macro 'PWM_DT_SPEC_GET_BY_IDX'
      328 | #define PWM_DT_SPEC_GET(node_id) PWM_DT_SPEC_GET_BY_IDX(node_id, 0)
          |                                  ^~~~~~~~~~~~~~~~~~~~~~
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/src/speaker.c:16:32: note: in expansion of macro 'PWM_DT_SPEC_GET'
       16 | const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));
          |                                ^~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree.h:4532:24: note: in expansion of macro 'DT_N_NODELABEL_pwm0'
     4532 | #define DT_CAT(a1, a2) a1 ## a2
          |                        ^~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree.h:200:29: note: in expansion of macro 'DT_CAT'
      200 | #define DT_NODELABEL(label) DT_CAT(DT_N_NODELABEL_, label)
          |                             ^~~~~~
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/src/speaker.c:16:48: note: in expansion of macro 'DT_NODELABEL'
       16 | const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));
          |                                                ^~~~~~~~~~~~
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/build/zephyr/include/generated/devicetree_generated.h:5965:36: error: 'DT_N_S_soc_S_pwm_4001c000_P_pwms_IDX_0_VAL_period' undeclared here (not in a function); did you mean 'DT_N_S_soc_S_pwm_4001c000_IRQ_IDX_0_VAL_irq'?
     5965 | #define DT_N_NODELABEL_pwm0        DT_N_S_soc_S_pwm_4001c000
          |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree.h:4543:9: note: in definition of macro 'DT_CAT7'
     4543 |         a1 ## a2 ## a3 ## a4 ## a5 ## a6 ## a7
          |         ^~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree/pwms.h:136:9: note: in expansion of macro 'DT_PHA_BY_IDX'
      136 |         DT_PHA_BY_IDX(node_id, pwms, idx, cell)
          |         ^~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree/pwms.h:249:9: note: in expansion of macro 'DT_PWMS_CELL_BY_IDX'
      249 |         DT_PWMS_CELL_BY_IDX(node_id, idx, period)
          |         ^~~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/drivers/pwm.h:262:27: note: in expansion of macro 'DT_PWMS_PERIOD_BY_IDX'
      262 |                 .period = DT_PWMS_PERIOD_BY_IDX(node_id, idx),                 \
          |                           ^~~~~~~~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/drivers/pwm.h:328:34: note: in expansion of macro 'PWM_DT_SPEC_GET_BY_IDX'
      328 | #define PWM_DT_SPEC_GET(node_id) PWM_DT_SPEC_GET_BY_IDX(node_id, 0)
          |                                  ^~~~~~~~~~~~~~~~~~~~~~
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/src/speaker.c:16:32: note: in expansion of macro 'PWM_DT_SPEC_GET'
       16 | const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));
          |                                ^~~~~~~~~~~~~~~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree.h:4532:24: note: in expansion of macro 'DT_N_NODELABEL_pwm0'
     4532 | #define DT_CAT(a1, a2) a1 ## a2
          |                        ^~
    C:/ncs/v2.7.0/zephyr/include/zephyr/devicetree.h:200:29: note: in expansion of macro 'DT_CAT'
      200 | #define DT_NODELABEL(label) DT_CAT(DT_N_NODELABEL_, label)
          |                             ^~~~~~
    C:/Clients/Venture/Phone/src/Keyboard/peripheral_hids_keyboard/src/speaker.c:16:48: note: in expansion of macro 'DT_NODELABEL'
       16 | const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_NODELABEL(pwm0));
          |                                                ^~~~~~~~~~~~

  • 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.

Related