Asymmetric pulse generator with timers.

NCS 2.9.1 - NRF5340 DK

I've developed the gppi one_to_one example to try and create an asymmetric pulse that I can control with the Timer compare registers. It boggles my mind why this code doesn't produce a 25% duty cycle pwm as I have configured. 

#include <nrfx_example.h>
#include <helpers/nrfx_gppi.h>
#include <nrfx_timer.h>
#include <nrfx_gpiote.h>

#define NRFX_LOG_MODULE                 EXAMPLE
#define NRFX_EXAMPLE_CONFIG_LOG_ENABLED 1
#define NRFX_EXAMPLE_CONFIG_LOG_LEVEL   3
#include <nrfx_log.h>

/**
 * @defgroup nrfx_gppi_one_to_one_example One-to-one GPPI example
 * @{
 * @ingroup nrfx_gppi_examples
 *
 * @brief Example showing basic functionality of a nrfx_gppi helper.
 *
 * @details Application initializes nrfx_gpiote, nrfx_timer drivers and nrfx_gppi helper in a way that
 *          TIMER compare event is set up to be forwarded via PPI/DPPI to GPIOTE and toggle a pin.
 */

/** @brief Symbol specifying timer instance to be used. */
#define TIMER_INST_IDX 0

/** @brief Symbol specifying time in milliseconds to wait for handler execution. */
#define TIME_TO_WAIT_MS 20UL

/** @brief Symbol specifying GPIOTE instance to be used. */
#define GPIOTE_INST_IDX 0

/** @brief Symbol specifying ouput pin associated with the task. */
#define OUTPUT_PIN 7

static uint32_t pulse;
static uint32_t period_len;

/**
 * @brief Function for handling TIMER driver events.
 *
 * @param[in] event_type Timer event.
 * @param[in] p_context  General purpose parameter set during initialization of the timer.
 *                       This parameter can be used to pass additional information to the handler
 *                       function for example the timer ID.
 */
static void timer_handler(nrf_timer_event_t event_type, void * p_context)
{
    switch(event_type)
    {
        case NRF_TIMER_EVENT_COMPARE0:
        NRFX_LOG_INFO("Compare 0");
        break;

        case NRF_TIMER_EVENT_COMPARE1:
        NRFX_LOG_INFO("Compare 1");
        break;

        default:
        break;
    }
    
}

/**
 * @brief Function for application main entry.
 *
 * @return Nothing.
 */
int main(void)
{
    nrfx_err_t status;
    (void)status;

    uint8_t out_channel;
    uint8_t gppi_channel_hi;
    uint8_t gppi_channel_lo;

#if defined(__ZEPHYR__)
    IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_TIMER_INST_GET(TIMER_INST_IDX)), IRQ_PRIO_LOWEST,
                NRFX_TIMER_INST_HANDLER_GET(TIMER_INST_IDX), 0, 0);
    IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE_INST_GET(GPIOTE_INST_IDX)), IRQ_PRIO_LOWEST,
                NRFX_GPIOTE_INST_HANDLER_GET(GPIOTE_INST_IDX), 0, 0);
#endif

    NRFX_EXAMPLE_LOG_INIT();

    NRFX_LOG_INFO("Starting nrfx_gppi basic one-to-one example.");
    NRFX_EXAMPLE_LOG_PROCESS();

    nrfx_gpiote_t const gpiote_inst = NRFX_GPIOTE_INSTANCE(GPIOTE_INST_IDX);
    status = nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
    NRFX_ASSERT(status == NRFX_SUCCESS);
    NRFX_LOG_INFO("GPIOTE status: %s",
                  nrfx_gpiote_init_check(&gpiote_inst) ? "initialized" : "not initialized");

    status = nrfx_gpiote_channel_alloc(&gpiote_inst, &out_channel);
    NRFX_ASSERT(status == NRFX_SUCCESS);


    /*
     * Initialize output pin. The SET task will turn the LED on,
     * CLR will turn it off and OUT will toggle it.
     */
    static const nrfx_gpiote_output_config_t output_config =
    {
        .drive = NRF_GPIO_PIN_S0S1,
        .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
        .pull = NRF_GPIO_PIN_PULLDOWN,
    };

    const nrfx_gpiote_task_config_t task_config_hi =
    {
        .task_ch = out_channel,
        .polarity = NRF_GPIOTE_POLARITY_TOGGLE,
        .init_val = NRF_GPIOTE_INITIAL_VALUE_HIGH,
    };

    status = nrfx_gpiote_output_configure(&gpiote_inst, OUTPUT_PIN, &output_config, &task_config_hi);
    NRFX_ASSERT(status == NRFX_SUCCESS);

    nrfx_gpiote_out_task_enable(&gpiote_inst, OUTPUT_PIN);

    nrfx_timer_t timer_inst = NRFX_TIMER_INSTANCE(TIMER_INST_IDX);
    uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer_inst.p_reg);
    nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
    timer_config.bit_width = NRF_TIMER_BIT_WIDTH_32;
    timer_config.p_context = "Some context";

    status = nrfx_timer_init(&timer_inst, &timer_config, timer_handler);
    NRFX_ASSERT(status == NRFX_SUCCESS);

    nrfx_timer_clear(&timer_inst);

    /* Creating variable desired_ticks to store the output of nrfx_timer_ms_to_ticks function. */
    pulse = nrfx_timer_ms_to_ticks(&timer_inst, 10);
    period_len = nrfx_timer_ms_to_ticks(&timer_inst, 40);
    NRFX_LOG_INFO("Period Length: %lu ms", TIME_TO_WAIT_MS*2);

    /*
     * Setting the timer channel NRF_TIMER_CC_CHANNEL0 in the extended compare mode to clear
     * the timer and to trigger an interrupt if the internal counter register is equal to
     * desired_ticks.
     */
    nrfx_timer_compare(&timer_inst, NRF_TIMER_CC_CHANNEL0, pulse, true);
    nrfx_timer_extended_compare(&timer_inst, NRF_TIMER_CC_CHANNEL1, period_len,
                                NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);


    status = nrfx_gppi_channel_alloc(&gppi_channel_hi);
    NRFX_ASSERT(status == NRFX_SUCCESS);

    status = nrfx_gppi_channel_alloc(&gppi_channel_lo);
    NRFX_ASSERT(status == NRFX_SUCCESS);

    /*
     * Configure endpoints of the channel so that the input timer event is connected with the output
     * pin OUT task. This means that each time the timer interrupt occurs, the LED pin will be toggled.
     */
    nrfx_gppi_channel_endpoints_setup(gppi_channel_lo,
        nrfx_timer_compare_event_address_get(&timer_inst, NRF_TIMER_CC_CHANNEL0),
        nrfx_gpiote_out_task_address_get(&gpiote_inst, OUTPUT_PIN));

    nrfx_gppi_channel_endpoints_setup(gppi_channel_hi,
        nrfx_timer_compare_event_address_get(&timer_inst, NRF_TIMER_CC_CHANNEL1),
        nrfx_gpiote_out_task_address_get(&gpiote_inst, OUTPUT_PIN));

    nrfx_gppi_channels_enable(BIT(gppi_channel_hi));
    nrfx_gppi_channels_enable(BIT(gppi_channel_lo));

    nrfx_timer_enable(&timer_inst);
    NRFX_LOG_INFO("Timer status: %s", nrfx_timer_is_enabled(&timer_inst) ? "enabled" : "disabled");


    while (1)
    {
        NRFX_EXAMPLE_LOG_PROCESS();
    }
}

/** @} */

Instead it only produces a square wave as if it's ignoring the additional toggle gppi channel I've configured - even though I can see the timer interrupt gets handled for both CC events. Do I have this configured correctly?

Parents
  • Hi SRall,

    Going over your code, I don't think anything is wrong either.

    I don't really have a starting point with this, so perhaps we can try the probe and see approach. Can you try to comment out one of the PPI channels enabling, once for each channel, and see if the waveform is any different? Perhaps we can also try having the two TIMER CC events connected to the task of two different GPIO pins instead.

    Hieu

Reply
  • Hi SRall,

    Going over your code, I don't think anything is wrong either.

    I don't really have a starting point with this, so perhaps we can try the probe and see approach. Can you try to comment out one of the PPI channels enabling, once for each channel, and see if the waveform is any different? Perhaps we can also try having the two TIMER CC events connected to the task of two different GPIO pins instead.

    Hieu

Children
  • Commenting out the enable lines individually rendered the pin at a steady state with no waveform at all. I also tried configuring with two separate output pins as shown below. 

    #include <nrfx_example.h>
    #include <helpers/nrfx_gppi.h>
    #include <nrfx_timer.h>
    #include <nrfx_gpiote.h>
    
    #define NRFX_LOG_MODULE                 EXAMPLE
    #define NRFX_EXAMPLE_CONFIG_LOG_ENABLED 1
    #define NRFX_EXAMPLE_CONFIG_LOG_LEVEL   3
    #include <nrfx_log.h>
    
    /**
     * @defgroup nrfx_gppi_one_to_one_example One-to-one GPPI example
     * @{
     * @ingroup nrfx_gppi_examples
     *
     * @brief Example showing basic functionality of a nrfx_gppi helper.
     *
     * @details Application initializes nrfx_gpiote, nrfx_timer drivers and nrfx_gppi helper in a way that
     *          TIMER compare event is set up to be forwarded via PPI/DPPI to GPIOTE and toggle a pin.
     */
    
    /** @brief Symbol specifying timer instance to be used. */
    #define TIMER_INST_IDX 0
    
    /** @brief Symbol specifying time in milliseconds to wait for handler execution. */
    #define TIME_TO_WAIT_MS 20UL
    
    /** @brief Symbol specifying GPIOTE instance to be used. */
    #define GPIOTE_INST_IDX 0
    
    /** @brief Symbol specifying ouput pin associated with the task. */
    #define OUTPUT_PIN  7
    #define OUTPUT_PIN2 25
    
    static uint32_t pulse;
    static uint32_t period_len;
    
    /**
     * @brief Function for handling TIMER driver events.
     *
     * @param[in] event_type Timer event.
     * @param[in] p_context  General purpose parameter set during initialization of the timer.
     *                       This parameter can be used to pass additional information to the handler
     *                       function for example the timer ID.
     */
    static void timer_handler(nrf_timer_event_t event_type, void * p_context)
    {
        switch(event_type)
        {
            case NRF_TIMER_EVENT_COMPARE0:
            NRFX_LOG_INFO("Compare 0");
            break;
    
            case NRF_TIMER_EVENT_COMPARE1:
            NRFX_LOG_INFO("Compare 1");
            break;
    
            default:
            break;
        }
        
    }
    
    /**
     * @brief Function for application main entry.
     *
     * @return Nothing.
     */
    int main(void)
    {
        nrfx_err_t status;
        (void)status;
    
        uint8_t out_channel_hi;
        uint8_t out_channel_lo;
        uint8_t gppi_channel_hi;
        uint8_t gppi_channel_lo;
    
    #if defined(__ZEPHYR__)
        IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_TIMER_INST_GET(TIMER_INST_IDX)), IRQ_PRIO_LOWEST,
                    NRFX_TIMER_INST_HANDLER_GET(TIMER_INST_IDX), 0, 0);
        IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE_INST_GET(GPIOTE_INST_IDX)), IRQ_PRIO_LOWEST,
                    NRFX_GPIOTE_INST_HANDLER_GET(GPIOTE_INST_IDX), 0, 0);
    #endif
    
        NRFX_EXAMPLE_LOG_INIT();
    
        NRFX_LOG_INFO("Starting nrfx_gppi basic one-to-one example.");
        NRFX_EXAMPLE_LOG_PROCESS();
    
        nrfx_gpiote_t const gpiote_inst = NRFX_GPIOTE_INSTANCE(GPIOTE_INST_IDX);
        status = nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
        NRFX_ASSERT(status == NRFX_SUCCESS);
        NRFX_LOG_INFO("GPIOTE status: %s",
                      nrfx_gpiote_init_check(&gpiote_inst) ? "initialized" : "not initialized");
    
        status = nrfx_gpiote_channel_alloc(&gpiote_inst, &out_channel_hi);
        NRFX_ASSERT(status == NRFX_SUCCESS);
        status = nrfx_gpiote_channel_alloc(&gpiote_inst, &out_channel_lo);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
    
        /*
         * Initialize output pin. The SET task will turn the LED on,
         * CLR will turn it off and OUT will toggle it.
         */
        static const nrfx_gpiote_output_config_t output_config =
        {
            .drive = NRF_GPIO_PIN_S0S1,
            .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
            .pull = NRF_GPIO_PIN_PULLDOWN,
        };
    
        const nrfx_gpiote_task_config_t task_config_hi =
        {
            .task_ch = out_channel_hi,
            .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_HIGH,
        };
    
        const nrfx_gpiote_task_config_t task_config_lo =
        {
            .task_ch = out_channel_lo,
            .polarity = NRF_GPIOTE_POLARITY_HITOLO,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_HIGH,
        };
    
        status = nrfx_gpiote_output_configure(&gpiote_inst, OUTPUT_PIN, &output_config, &task_config_hi);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        status = nrfx_gpiote_output_configure(&gpiote_inst, OUTPUT_PIN2, &output_config, &task_config_lo);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        nrfx_gpiote_out_task_enable(&gpiote_inst, OUTPUT_PIN);
        nrfx_gpiote_out_task_enable(&gpiote_inst, OUTPUT_PIN2);
    
        nrfx_timer_t timer_inst = NRFX_TIMER_INSTANCE(TIMER_INST_IDX);
        uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer_inst.p_reg);
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
        timer_config.bit_width = NRF_TIMER_BIT_WIDTH_32;
        timer_config.p_context = "Some context";
    
        status = nrfx_timer_init(&timer_inst, &timer_config, timer_handler);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        nrfx_timer_clear(&timer_inst);
    
        /* Creating variable desired_ticks to store the output of nrfx_timer_ms_to_ticks function. */
        pulse = nrfx_timer_ms_to_ticks(&timer_inst, 15);
        period_len = nrfx_timer_ms_to_ticks(&timer_inst, 40);
        NRFX_LOG_INFO("Period Length: %lu ms", period_len);
    
        /*
         * Setting the timer channel NRF_TIMER_CC_CHANNEL0 in the extended compare mode to clear
         * the timer and to trigger an interrupt if the internal counter register is equal to
         * desired_ticks.
         */
        nrfx_timer_compare(&timer_inst, NRF_TIMER_CC_CHANNEL0, pulse, true);
        nrfx_timer_extended_compare(&timer_inst, NRF_TIMER_CC_CHANNEL1, period_len,
                                    NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);
    
    
        status = nrfx_gppi_channel_alloc(&gppi_channel_hi);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        status = nrfx_gppi_channel_alloc(&gppi_channel_lo);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        /*
         * Configure endpoints of the channel so that the input timer event is connected with the output
         * pin OUT task. This means that each time the timer interrupt occurs, the LED pin will be toggled.
         */
        nrfx_gppi_channel_endpoints_setup(gppi_channel_lo,
            nrfx_timer_compare_event_address_get(&timer_inst, NRF_TIMER_CC_CHANNEL0),
            nrfx_gpiote_out_task_address_get(&gpiote_inst, OUTPUT_PIN2));
    
        nrfx_gppi_channel_endpoints_setup(gppi_channel_hi,
            nrfx_timer_compare_event_address_get(&timer_inst, NRF_TIMER_CC_CHANNEL1),
            nrfx_gpiote_out_task_address_get(&gpiote_inst, OUTPUT_PIN));
    
        nrfx_gppi_channels_enable(BIT(gppi_channel_hi));
        nrfx_gppi_channels_enable(BIT(gppi_channel_lo));
    
        nrfx_timer_enable(&timer_inst);
        NRFX_LOG_INFO("Timer status: %s", nrfx_timer_is_enabled(&timer_inst) ? "enabled" : "disabled");
    
    
        while (1)
        {
            NRFX_EXAMPLE_LOG_PROCESS();
        }
    }

    The same thing happens and there is no waveform. Can someone please try to replicate this? All I'm trying to achieve is an asymmetric pulse wave (non-50% duty cycle) that I can control with CC registers. I don't want to use pwm here because I only want to send a very small number of pulses total and the pwm driver doesn't have one-shot capabilities and I don't want to time the start and stop. 

    Tangentially, I'm nearly certain this is possible because this was how I understood the pwm module even functioned in the first place? Can anyone achieve varying asymmetric pulses with GPPI? 

Related