Skip to content

Experimenting with using nrfx pwm directly instead of Zephyr on an nRF5340.

Notifications You must be signed in to change notification settings

droidecahedron/nrfx_pwm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nrfx_pwm

Overview

Driving GPIO with PWM peripheral, using nrfx. (https://infocenter.nordicsemi.com/topic/ps_nrf5340/pwm.html?cp=4_0_0_6_22)

LEDs chosen for visual feedback. (GPIO 28-31 for PWM chans)

This shows how to use nrfx to interface with the Nordic PWM peripheral, which is quite a handy and unique peripheral since it can also generate arbitrary waveforms. This application can run without CPU intervention, but the main task only switches which sequence is played by the peripheral every SLEEP_TIME_MS. Given the low CPU intervention, it would be quite arbitrary to add whatever else you need, such as BLE or other peripherals (like ADC)

hardware / documentation



Boxed in red is header location if you want to measure the waveforms for yourself.

Scope Shots

image

Note: Readme C code for sequences is formatted slightly differently to make visualizing the columns for each sequence easier.

Sequence A

Your run of the mill set-duty-cycle and forget

 static nrf_pwm_values_individual_t sequence_A;          // sequence A is a pretty basic PWM duty cycle.
    sequence_A.channel_0 = PWM_COUNTERTOP / 10 | (1 << 15); // flips really early, inverted polarity
    sequence_A.channel_1 = PWM_COUNTERTOP / 4;
    sequence_A.channel_2 = PWM_COUNTERTOP / 2; // should flip half way through
    sequence_A.channel_3 = PWM_COUNTERTOP / 10;

image

Sequence B

Stretching resolution windows, with inversion and toggling-off

static nrf_pwm_values_individual_t sequence_B[] = {
        {0x8000 | (CYCLE_A / 2), 0x8000 | (CYCLE_B / 2), 0x8000 | (CYCLE_C / 2), CYCLE_A},
        {(CYCLE_A / 2),          (CYCLE_B / 2),          (CYCLE_C / 2),          CYCLE_B},
        {0,                      0,                      0,                      CYCLE_C},
        {CYCLE_A,                CYCLE_B,                CYCLE_C,                CYCLE_D},
    };

image

Sequence C

Time delayed pulses that are off majority of the time (turn off by setting the value to COUNTERTOP in the seq)

    const uint16_t short_pulse = PWM_COUNTERTOP / 10 | (1 << 15);
    static nrf_pwm_values_individual_t sequence_C[] = {
        {short_pulse,    PWM_COUNTERTOP, PWM_COUNTERTOP, PWM_COUNTERTOP},
        {PWM_COUNTERTOP, short_pulse,    PWM_COUNTERTOP, PWM_COUNTERTOP},
        {PWM_COUNTERTOP, PWM_COUNTERTOP, short_pulse,    PWM_COUNTERTOP},
        {PWM_COUNTERTOP, PWM_COUNTERTOP, PWM_COUNTERTOP, short_pulse},
    };

image

Sequence D

Time delayed pulses with alternating short and long pulses

    const uint16_t long_pulse = PWM_COUNTERTOP / 2 | (1 << 15);
    static nrf_pwm_values_individual_t sequence_D[] = {
        {short_pulse,    PWM_COUNTERTOP, long_pulse,     PWM_COUNTERTOP},
        {PWM_COUNTERTOP, long_pulse,     PWM_COUNTERTOP, short_pulse},
        {long_pulse,     PWM_COUNTERTOP, short_pulse,    PWM_COUNTERTOP},
        {PWM_COUNTERTOP, short_pulse,    PWM_COUNTERTOP, long_pulse},
    };

image

Extra Notes

in pwm_set_duty_cycle, there are three sequences for sample outputs that it rotates through. when using waveform mode you only get 3 output channels instead of 4. If you need an adjustable countertop, use waveform mode.

You'll need to modify config0 such that NRF_PWM_PIN_NOT_CONNECTED is 31 instead for LED4, the load_mode to be NRF_PWM_LOAD_INDIVIDUAL, and p_wave_form can be p_individual where you pass sequence.

You can also have multiple PWM instances.

//if you had a single channel, this is an interesting one.
// nrf_pwm_values_wave_form_t peculiarSequenceSingleChannel[] = {
//     //   Index     Normal pin          Inverted     (Spare)  Top Value
//     //   =====     =================== ==========   =======  =========
//     { /*   0  */   0x8000|(CYCLE_A/2), (CYCLE_A/2), 0,       CYCLE_A  },
//     { /*   1  */   0x8000|(CYCLE_B/2), (CYCLE_B/2), 0,       CYCLE_B  },
//     { /*   2  */   0x8000|(CYCLE_C/2), (CYCLE_C/2), 0,       CYCLE_C  },
//     { /*   3  */   0x8000|(CYCLE_D/2), (CYCLE_D/2), 0,       CYCLE_D  },
// };

And transposed if you are using multiple channels. i.e. each column is each channel.

branches:

multiple-instance for distinct frequencies on each i/o. this branch is kind of unfinished because to generate the above screenshots only needed one instance. (thanks @inductivekickback)

snapshot_version old version that has you manually change the structs you feed the playback fxn.

About

Experimenting with using nrfx pwm directly instead of Zephyr on an nRF5340.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published