Issuse when trying to connect a pwm peripheral and a hardware timer 2 peripheral using P

Hello everyone,

I am a EE student and this problem came up during my first big project in zephyr. I'm trying to connect a pwm peripheral (pwm1) to the timer 2 hardware timer in zephyr. The idea is to have the timer in the counter mode and have it increment every rising edge of the pwm signal generated by the pwm0 peripheral (on the peripheral itself, I'm using all 4 channels and I'm generating identical signals on all 4 - 102.4 kHz pwm that serves as a clock input to a different sensor component). After 15360 cycles of pwm signal, the timer is supposed to fire and execute a callback function.

In this code (it's just some testing code) I also try to toggle a led every time the timer time passes.  I tried using nrf_ppi.h but it didn't work since in the lines:

nrf_ppi_channel_endpoint_setup();
nrf_ppi_channel_enable();

I would have to use to actually connect the peripherals via PPI I would have to use the following arguments:

and in some online example (there is almost none) I found that for the *p_reg argument one is supposed to use NRF_PPI that is supposed to be defined in the library but for me it wasn't. Hence, I switched to dppi and it at least built. These are my includes:

these are my defines:

#define PIN_PWM0_CH0 29
#define PIN_PWM0_CH1 10
#define PIN_PWM0_CH2 8
#define PIN_PWM0_CH3 30

#define PIN_PWM1_CH0 11
#define PIN_PWM1_CH1 39
#define COUNTERTOPi 156
#define DUTY_CYCLE (COUNTERTOPi / 2)

#define LED_PIN 28  // P0.28 (confirm this for your board)
#define TIMER_INST_IDX 2

// --- PPI/Timer specific definitions ---
#define PWM_COUNT_THRESHOLD 15360 // Your desired count of PWM cycles
#define DPPI_CH 0
// Define the PPI channel to use (choose an unused one, 0-19 typically on nRF52)
#define PPI_CHANNEL_PWM_TO_TIMER 0

here's the callback function of the timer:

// Forward declaration for the interrupt handler
void timer2_irq_handler(void);

// Our custom callback function
void my_pwm_cycle_callback(void) {
    static bool led_state = false;
    led_state = !led_state;

    if (led_state) {
        nrf_gpio_pin_set(LED_PIN);
    } else {
        nrf_gpio_pin_clear(LED_PIN);
    }

    printf("PWM cycle count reached! LED toggled.\n");
}

// Timer 2 Interrupt Handler
void timer2_irq_handler(void) {
    printf("In timer 2 irq\n");
    if (NRF_TIMER2->EVENTS_COMPARE[0]) {
        NRF_TIMER2->EVENTS_COMPARE[0] = 0; // Clear the event
        printf("In if of timer 2 irq\n");
        // Call your custom callback
        my_pwm_cycle_callback();

        // Optionally, reset the timer to count again
        NRF_TIMER2->TASKS_CLEAR = 1;
        //NRF_TIMER2->TASKS_START = 1;
        // If you want to stop counting after one trigger, disable PPI:
        // nrf_ppi_channel_disable(PPI_CHANNEL_PWM_TO_TIMER);
    }
}

This is my main with the pwm bare metal generation first (this I probed and it is truly generated) followed by the timer2 setup and ppi connecting as well as some printing for debugging.

int main{   
    nrf_gpio_cfg_output(LED_PIN);
    nrf_gpio_pin_clear(LED_PIN); // Start OFF
    // --- PWM Initialization (Your existing code) ---
    NRF_PWM1->ENABLE = 1;
    printf("PWM1 ENABLE: %" PRIu32 "\n", NRF_PWM1->ENABLE);
    static uint16_t pwm1_seq[2] = { DUTY_CYCLE, DUTY_CYCLE };
    static uint16_t pwm0_seq[4] = { DUTY_CYCLE, DUTY_CYCLE, DUTY_CYCLE, DUTY_CYCLE };

    NRF_PWM1->PSEL.OUT[0] = (PIN_PWM1_CH0 << PWM_PSEL_OUT_PIN_Pos) |
                            (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    printf("PWM1 PSEL.OUT[0] = %" PRIu32 "\n", NRF_PWM1->PSEL.OUT[0]);
    NRF_PWM1->PSEL.OUT[1] = (PIN_PWM1_CH1 << PWM_PSEL_OUT_PIN_Pos) |
                            (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    NRF_PWM1->PSEL.OUT[2] = 0xFFFFFFFF;
    NRF_PWM1->PSEL.OUT[3] = 0xFFFFFFFF;

    NRF_PWM0->PSEL.OUT[0] = (PIN_PWM0_CH0 << PWM_PSEL_OUT_PIN_Pos) |
                            (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    printf("PWM0 PSEL.OUT[0] = %" PRIu32 "\n", NRF_PWM0->PSEL.OUT[0]);
    NRF_PWM0->PSEL.OUT[1] = (PIN_PWM0_CH1 << PWM_PSEL_OUT_PIN_Pos) |
                            (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    NRF_PWM0->PSEL.OUT[2] = (PIN_PWM0_CH2 << PWM_PSEL_OUT_PIN_Pos) |
                            (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    NRF_PWM0->PSEL.OUT[3] = (PIN_PWM0_CH3 << PWM_PSEL_OUT_PIN_Pos) |
                            (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);

    NRF_PWM1->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
    NRF_PWM1->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
    NRF_PWM1->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
    NRF_PWM1->COUNTERTOP = (COUNTERTOPi << PWM_COUNTERTOP_COUNTERTOP_Pos);
    NRF_PWM1->LOOP = (0xFFFF << PWM_LOOP_CNT_Pos); // Loop forever, or for a very long time
    NRF_PWM1->SHORTS = PWM_SHORTS_LOOPSDONE_SEQSTART0_Enabled << PWM_SHORTS_LOOPSDONE_SEQSTART0_Pos;



    // NRF_PWM1->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) |
    //                     (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
    NRF_PWM1->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) |
                    (PWM_DECODER_MODE_NextStep << PWM_DECODER_MODE_Pos);


    NRF_PWM0->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
    NRF_PWM0->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
    NRF_PWM0->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
    NRF_PWM0->COUNTERTOP = (COUNTERTOPi << PWM_COUNTERTOP_COUNTERTOP_Pos);
    NRF_PWM0->LOOP = (0xFFFF << PWM_LOOP_CNT_Pos);

    NRF_PWM0->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) |
                        (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);

    NRF_PWM1->SEQ[0].PTR = 2;//((uint32_t)(pwm1_seq) << PWM_SEQ_PTR_PTR_Pos);
    NRF_PWM1->SEQ[0].CNT = (2 << PWM_SEQ_CNT_CNT_Pos);
    NRF_PWM1->SEQ[0].REFRESH = 0;
    NRF_PWM1->SEQ[0].ENDDELAY = 0;

    NRF_PWM0->SEQ[0].PTR = ((uint32_t)(pwm0_seq) << PWM_SEQ_PTR_PTR_Pos);
    NRF_PWM0->SEQ[0].CNT = (4 << PWM_SEQ_CNT_CNT_Pos);
    NRF_PWM0->SEQ[0].REFRESH = 0;
    NRF_PWM0->SEQ[0].ENDDELAY = 0;

    // --- Timer 2 Initialization for Counting ---
    NRF_TIMER2->MODE = NRF_TIMER_MODE_COUNTER; // Configure as counter
    NRF_TIMER2->BITMODE = NRF_TIMER_BIT_WIDTH_32; // Use 32-bit counter for large counts
    NRF_TIMER2->PRESCALER = 0; // No prescaling, each PPI event counts as 1
    NRF_TIMER2->CC[0] = PWM_COUNT_THRESHOLD; // Set compare value
    NRF_TIMER2->INTENSET = NRF_TIMER_INT_COMPARE0_MASK;
    // Clear the timer before starting
    NRF_TIMER2->TASKS_CLEAR = 1;
    NRF_TIMER2->TASKS_START = 1;  
    
    
    IRQ_DIRECT_CONNECT(TIMER2_IRQn, 0, timer2_irq_handler, 0);
    irq_enable(TIMER2_IRQn);
    
    NRF_DPPI_ENDPOINT_SETUP((uint32_t)&NRF_PWM1->EVENTS_SEQEND[0], DPPI_CH);
    NRF_DPPI_ENDPOINT_SETUP((uint32_t)&NRF_TIMER2->TASKS_COUNT, DPPI_CH);

    // 2. Enable the channel
    nrf_dppi_channels_enable(NRF_DPPIC, (1 << DPPI_CH));
    


    
    printf("PWM1 and PWM0 starting...\n");
    NRF_PWM1->TASKS_SEQSTART[0] = 1;
    NRF_PWM0->TASKS_SEQSTART[0] = 1;

    printf("PWM cycle counter set up for %d cycles.\n", PWM_COUNT_THRESHOLD);

    if (NRF_PWM1->EVENTS_SEQEND[0]) {
        printf(" PWM1 SEQEND[0] fired\n");
        NRF_PWM1->EVENTS_SEQEND[0] = 0;
    }
    
    
    
    uint32_t pub_reg = *((uint32_t *) ((uint32_t)&NRF_PWM1->EVENTS_SEQEND[0] + 0x80));
    uint32_t sub_reg = *((uint32_t *) ((uint32_t)&NRF_TIMER2->TASKS_COUNT + 0x80));

    printf(" PWM1->PUBLISH.SEQEND[0] = 0x%u\n", pub_reg);
    printf("TIMER2->SUBSCRIBE.TASKS_COUNT = 0x%u\n", sub_reg);
    // Main loop (or just let Zephyr's idle thread run if this is a simple app)
    while (1) {
        
    NRF_TIMER2->TASKS_CAPTURE[1] = 1;
    uint32_t count = NRF_TIMER2->CC[1];
    printf(" TIMER2 live count: %u\n", count);
    
    k_sleep(K_MSEC(100)); // Sleep to prevent busy-waiting
    }

    return 0;
}

When checking the registers sub and pub I get this as my printing statement outputs

Since the pub and sub registers aren't zero it makes me believe that ppi is connected properly but the counter doesn't really count up. The statement TIMER2 live count: 0 keeps printing out and I cannot get the timer to start counting up, hence the callback never executes.

In case it matters, here's my proj.conf

CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3  




CONFIG_PWM=y
CONFIG_PWM_NRFX=y

CONFIG_PINCTRL=y
CONFIG_PINCTRL_NRF=y


CONFIG_COUNTER=y

CONFIG_GPIO=y

CONFIG_NRFX_TIMER2=y 
CONFIG_PWM_LOG_LEVEL_DBG=y
CONFIG_COUNTER_LOG_LEVEL_DBG=y
CONFIG_ZERO_LATENCY_IRQS=y

and my overlay

&led0 {
	gpios = <&egpio 9 GPIO_ACTIVE_HIGH>;
};
&timer2 {
    status = "okay";
};
/ {
aliases {
pwm_generator = &pwm0; 
pwm_cycle_counter = &timer2; 
};
};

Some stuff in these is probably unnecessary but I don't think it should mess with anything.

If anyone used PPI before to interconnect two peripherals I'd be eternally grateful if you could provide an example of how you did it or if anyone knows what the issue in my code is, let me know.

Thank youuuuu

Jana

Parents
  • Hi Jana,

    The nRF5340 has DPPI, and to use a driver for it you should use the GPPI driver which works with both PPI and DPPI. There are two examples here, that demonstrate use with a TIMER.

    Einar

  • Hi Einar,

    Thanks for providing examples. However, they take me on a bit of a different path. I managed to run them, but in these examples the timer is started in a timer mode and not in the counter mode. I truly believe that my issue lies somewhere there. The pwm generation I think is all right because I probed it. However, my counter (timer) is absolutely useless. It never increases.... I lowered the pwm to 5Hz and made it so that the variable PWM_COUNT_THRESHOLD is now only 10 cycles. Here's the way I start the counter timer thing, maybe there's a huge problem there that I just don't see:

    #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);
        #endif
        nrfx_err_t status;
        (void)status;
        uint8_t gppi_channel;
        
        NRF_TIMER2->MODE = TIMER_MODE_MODE_Counter;
        NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
        NRF_TIMER2->CC[0] = PWM_COUNT_THRESHOLD;
        NRF_TIMER2->TASKS_CLEAR = 1;
     
        NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        
        IRQ_DIRECT_CONNECT(TIMER2_IRQn, 0, timer2_irq_handler, 0);
        irq_enable(TIMER2_IRQn);
        
        status = nrfx_gppi_channel_alloc(&gppi_channel);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        nrfx_gppi_channel_endpoints_setup(gppi_channel,
            (uint32_t)&NRF_PWM1->EVENTS_SEQEND[0],
            (uint32_t)&NRF_TIMER2->TASKS_COUNT);
        
        nrfx_gppi_channels_enable(BIT(gppi_channel));
    

    and here's my callback:

    void timer2_irq_handler(nrf_timer_event_t event_type,void * p_context) {
        LOG_INF("In timer 2 irq\n");
        if  (event_type == NRF_TIMER_EVENT_COMPARE0){
    
            LOG_INF("In if of timer 2 irq\n");
        
            counter=counter+1;
            LOG_INF("counter %u\n", counter);
            NRF_TIMER2->EVENTS_COMPARE[0] = 0; 
            
            NRF_TIMER2->TASKS_CLEAR = 1;
            
        }
    }

    If you know anything more about getting the IRQ to fire when the timer is in counter mode and you see what I'm doing wrong in its initialization please let me know.

    Jana

Reply
  • Hi Einar,

    Thanks for providing examples. However, they take me on a bit of a different path. I managed to run them, but in these examples the timer is started in a timer mode and not in the counter mode. I truly believe that my issue lies somewhere there. The pwm generation I think is all right because I probed it. However, my counter (timer) is absolutely useless. It never increases.... I lowered the pwm to 5Hz and made it so that the variable PWM_COUNT_THRESHOLD is now only 10 cycles. Here's the way I start the counter timer thing, maybe there's a huge problem there that I just don't see:

    #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);
        #endif
        nrfx_err_t status;
        (void)status;
        uint8_t gppi_channel;
        
        NRF_TIMER2->MODE = TIMER_MODE_MODE_Counter;
        NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
        NRF_TIMER2->CC[0] = PWM_COUNT_THRESHOLD;
        NRF_TIMER2->TASKS_CLEAR = 1;
     
        NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        
        IRQ_DIRECT_CONNECT(TIMER2_IRQn, 0, timer2_irq_handler, 0);
        irq_enable(TIMER2_IRQn);
        
        status = nrfx_gppi_channel_alloc(&gppi_channel);
        NRFX_ASSERT(status == NRFX_SUCCESS);
    
        nrfx_gppi_channel_endpoints_setup(gppi_channel,
            (uint32_t)&NRF_PWM1->EVENTS_SEQEND[0],
            (uint32_t)&NRF_TIMER2->TASKS_COUNT);
        
        nrfx_gppi_channels_enable(BIT(gppi_channel));
    

    and here's my callback:

    void timer2_irq_handler(nrf_timer_event_t event_type,void * p_context) {
        LOG_INF("In timer 2 irq\n");
        if  (event_type == NRF_TIMER_EVENT_COMPARE0){
    
            LOG_INF("In if of timer 2 irq\n");
        
            counter=counter+1;
            LOG_INF("counter %u\n", counter);
            NRF_TIMER2->EVENTS_COMPARE[0] = 0; 
            
            NRF_TIMER2->TASKS_CLEAR = 1;
            
        }
    }

    If you know anything more about getting the IRQ to fire when the timer is in counter mode and you see what I'm doing wrong in its initialization please let me know.

    Jana

Children
No Data
Related