Nrfx timer capturing/comparing

Hi, 

I'm aiming to develop a custom two-wire serial interface (TX, RX). To achieve this, I'm planning to implement output capture for TX and use GPIO interrupts for RX. For the RX side, I'll need to read a microsecond timer to measure the time duration of each signal level.

Here’s what I've implemented so far:

 

/* ---------------------------------------------------------------------------
 *  micro-node/application
 * ---------------------------------------------------------------------------
 *  Name: main.c
 * --------------------------------------------------------------------------*/

#include <debug/cpu_load.h>
#include <helpers/nrfx_gppi.h>
#include <ncs_version.h>
#include <nrfx_gpiote.h>
#include <nrfx_power.h>
#include <nrfx_ppi.h>
#include <nrfx_timer.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>

#define GPIOTE_INST_IDX 0
#define TIMER_INST_IDX  0

static bool hal_dalix_init(void);

const struct gpio_dt_spec DaliRxPin =
    GPIO_DT_SPEC_GET(DT_PATH(zephyr_user), dali_rx_gpios);
const struct gpio_dt_spec DaliTxPin =
    GPIO_DT_SPEC_GET(DT_PATH(zephyr_user), dali_tx_gpios);

nrfx_timer_t TimerInst = NRFX_TIMER_INSTANCE(TIMER_INST_IDX);

static uint32_t TxWave[] = {666, 666, 500, 500, 250, 250, 100, 100, 50, 50, 50};
static uint32_t TxWaveIndex = 0;

static uint32_t RxWave[16];
static uint32_t RxWaveIndex = 0;

// Define the callback structure
static struct gpio_callback DaliRxCbData;

static void timer_handler(nrf_timer_event_t event_type, void *context) {
    uint32_t cc = 0, next_delay = 0;

    switch (event_type) {
        case NRF_TIMER_EVENT_COMPARE0: {
            break;
        }
        case NRF_TIMER_EVENT_COMPARE1: {
            break;
        }
        case NRF_TIMER_EVENT_COMPARE2: {
            // cc = nrfx_timer_capture(&TimerInst, NRF_TIMER_CC_CHANNEL2);

            // If ppi is responsible for capturing
            cc = nrfx_timer_capture_get(TimerInst.p_reg, NRF_TIMER_CC_CHANNEL2);
            next_delay = UINT32_MAX;
            if (sizeof(TxWave) > TxWaveIndex) {
                next_delay = TxWave[TxWaveIndex++];
            }
            if (UINT32_MAX == next_delay) {
                nrfx_timer_extended_compare(
                    &TimerInst, NRF_TIMER_CC_CHANNEL2, 0,
                    NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK, false);
            } else {
                nrfx_timer_extended_compare(
                    &TimerInst, NRF_TIMER_CC_CHANNEL2, next_delay,
                    NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK, true);
            }
            break;
        }
        case NRF_TIMER_EVENT_COMPARE3: {
            break;
        }
        default: {
            break;
        }
    }
}

static void gpio_handler(const struct device *dev, struct gpio_callback *cb,
                         uint32_t pins) {
    if (pins & BIT(DaliRxPin.pin)) {
        // int rx_pin_state = gpio_pin_get_dt(&DaliRxPin);
        // How can I get edge type?
        // Pin state could be not up-to-date, can be changed since interrupt occured

        // uint32_t us_counter = k_ticks_to_us_ceil32(k_uptime_ticks());
        uint32_t us_counter =
            nrfx_timer_capture(&TimerInst, NRF_TIMER_CC_CHANNEL1);
        RxWave[RxWaveIndex++] = us_counter;
    }
}

int main(void) {
    printk("/*****************************************************/\n");
    printk("/* build time: " __DATE__ " " __TIME__ "\n");

    bool rc = hal_dalix_init();
    printk("hal_dalix_init rc %d\n", rc);

    while (1) {
        printk("TX\n");
        TxWaveIndex = 0;
        RxWaveIndex = 0;
        uint32_t cc = nrfx_timer_capture(&TimerInst, NRF_TIMER_CC_CHANNEL2);
        uint32_t us = nrfx_timer_us_to_ticks(&TimerInst, TxWave[TxWaveIndex]);
        nrfx_timer_extended_compare(&TimerInst, NRF_TIMER_CC_CHANNEL2, cc + us,
                                    NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK, true);

        k_sleep(K_MSEC(1000));
        for (int i = 0; i < RxWaveIndex; i++) {
            printk("[%d] %u\n", i, RxWave[i]);
        }

        uint32_t us_start =
            nrfx_timer_capture(&TimerInst, NRF_TIMER_CC_CHANNEL1);
        k_sleep(K_MSEC(2));
        uint32_t us_end = nrfx_timer_capture(&TimerInst, NRF_TIMER_CC_CHANNEL1);
        printk("Two millis %u\n", us_end - us_start);
        k_sleep(K_MSEC(4000));
    }
}

static bool hal_dalix_init(void) {
    nrfx_err_t err;
    uint8_t out_channel;
    uint8_t gppi_channel;
    bool res = false;

    // PPI will triger event as fast as it is possible, not so well solution in
    // strictly power saving mode
    nrf_power_task_trigger(NRF_POWER, NRF_POWER_TASK_CONSTLAT);

    ///////////////////////////////////////////////////////////////////////////
    int ret =
        gpio_pin_configure_dt(&DaliRxPin, GPIO_INPUT | DaliRxPin.dt_flags);
    if (ret != 0) {
        printk("Failed to configure DaliRx pin\n");
    }

    ret = gpio_pin_interrupt_configure_dt(&DaliRxPin, GPIO_INT_EDGE_BOTH);
    if (ret != 0) {
        printk("Failed to configure interrupt for DaliRx pin\n");
        goto DONE;
    }

    // Initialize the GPIO callback
    gpio_init_callback(&DaliRxCbData, gpio_handler, BIT(DaliRxPin.pin));

    // Add the callback to the GPIO device
    gpio_add_callback(DaliRxPin.port, &DaliRxCbData);

    ////////////////////////////////////////////////////////////////////////////
    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);

    // Is it working beacuse gpiote is enable in device tree?
    nrfx_gpiote_t const gpiote_inst = NRFX_GPIOTE_INSTANCE(GPIOTE_INST_IDX);
    // err =
    //     nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
    // if (NRFX_SUCCESS == err) {
    //     PRI_INF("nrfx_gpiote_init succ");
    // } else {
    //     PRI_ERR("nrfx_gpiote_init rc %d", err);
    // }
    printk("GPIOTE status: %s\n", nrfx_gpiote_init_check(&gpiote_inst)
                                      ? "initialized"
                                      : "not initialized");

    err = nrfx_gpiote_channel_alloc(&gpiote_inst, &out_channel);
    if (NRFX_SUCCESS == err) {
        printk("nrfx_gpiote_channel_alloc succ\n");
    } else {
        printk("nrfx_gpiote_channel_alloc rc %d\n", err);
        goto DONE;
    }

    // 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_NOPULL,
    };

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

    err = nrfx_gpiote_output_configure(&gpiote_inst, DaliTxPin.pin,
                                       &output_config, &task_config);
    if (NRFX_SUCCESS == err) {
        printk("nrfx_gpiote_output_configure succ\n");
    } else {
        printk("nrfx_gpiote_output_configure rc %d\n", err);
        goto DONE;
    }

    nrfx_gpiote_out_task_enable(&gpiote_inst, DaliTxPin.pin);
    nrfx_gpiote_out_task_force(&gpiote_inst, DaliTxPin.pin, 1);

    nrfx_timer_config_t timer_config =
        NRFX_TIMER_DEFAULT_CONFIG(UINT32_C(1000000));
    timer_config.bit_width = NRF_TIMER_BIT_WIDTH_32;

    err = nrfx_timer_init(&TimerInst, &timer_config, timer_handler);
    if (NRFX_SUCCESS == err) {
        printk("nrfx_gpiote_output_configure succ\n");
    } else {
        printk("nrfx_gpiote_output_configure rc %d\n", err);
        goto DONE;
    }

    nrfx_timer_clear(&TimerInst);

    err = nrfx_gppi_channel_alloc(&gppi_channel);
    if (NRFX_SUCCESS == err) {
        printk("nrfx_gpiote_output_configure succ\n");
    } else {
        printk("nrfx_gpiote_output_configure rc %d\n", err);
        goto DONE;
    }

    // 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,
        nrfx_timer_compare_event_address_get(&TimerInst, NRF_TIMER_CC_CHANNEL2),
        nrfx_gpiote_out_task_address_get(&gpiote_inst, DaliTxPin.pin));

    nrfx_gppi_channels_enable(BIT(gppi_channel));

    nrfx_timer_enable(&TimerInst);
    nrfx_timer_compare(&TimerInst, NRF_TIMER_CC_CHANNEL0, 0, false);
    nrfx_timer_compare(&TimerInst, NRF_TIMER_CC_CHANNEL1, 0, false);
    nrfx_timer_compare(&TimerInst, NRF_TIMER_CC_CHANNEL2, 0, false);
    nrfx_timer_compare(&TimerInst, NRF_TIMER_CC_CHANNEL3, 0, false);

    printk("Timer status: %s\n",
           nrfx_timer_is_enabled(&TimerInst) ? "enabled" : "disabled");

    res = true;
DONE:
    return (res);
}

/* ---------------------------------------------------------------------------
 * end of file
 * --------------------------------------------------------------------------*/

These few lines of code should generate a wave on the TX pin and read the same wave on the RX pin. The wave generation works correctly (verified with an oscilloscope), but the microsecond values that are read in the GPIO interrupt are incorrect. The difference between consecutive samples in the microsecond readings should equal the time of the signal level, but they don't.

Here’s what the logs show:

TX
[0] 22
[1] 21
[2] 21
[3] 21
[4] 21
[5] 21
[6] 21
[7] 21
[8] 21
[9] 21
[10] 21
[11] 21
[12] 21
Two millis 2088

Parents Reply Children
  • trafficode said:
    Major question is: Why I read so bad values from channel 1 if it is not used to nothing more, and it looks like is reseted when channel 2 is captured...

    Looking at the code, you have e.g. this line:

    nrfx_timer_extended_compare(&TimerInst, NRF_TIMER_CC_CHANNEL2, cc + us,
    NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK, true);

    So you have a shortcut between the compare event on the channel and the timer CLEAR task

Related