Dali bitbang timer deviates

Hi,

I am trying to build a Dali driver for the nRF52 with nRF Connect SDK. In the current approach, the Dali signal is send on the bus with a bit-banging approach using Zephyr's GPIO library and an nRFx timer. To summarize Dali, the data is sent with a baud rate of 1200 bps, and the data is manchester encoded (the data 1's and 0's are encoded in rising and falling edges). Dali specifies that a half-bit should be between 400µs and 433.3 µs (typically 416.7µs), and a double half-bit between 800µs  and 866.7µs (typically 833.3µs).

However, I have trouble getting the timing right. Most edges are correct. However, sometimes an edge deviates and is sent outside the window (see image below), and with quite a big amount (up to 30µs). But which edge deviates (if any), is random. To ensure nothing is interrupted 

The sample from the image is measured with a logic analyzer sampling at 1MHz, connected directly to the tx of the nRF52833DK. I verified with an oscilloscope that slope of the signal is correct.

In the background, Bluetooth mesh is running, but in this test it is not actively used (I also tried disabling Bluetooth, but no difference).

Timer library:

static struct dali_timer_data dali_timer_data =
{
    .p_instance = NRFX_TIMER_INSTANCE(CONFIG_DALI_TIMER_INSTANCE),
    .cfg = {
        .frequency = NRF_TIMER_FREQ_16MHz,
        .mode = NRF_TIMER_MODE_TIMER,
        .bit_width = NRF_TIMER_BIT_WIDTH_32,
        .interrupt_priority = 0
    }
};

static void timer_handler(nrf_timer_event_t event_type, void *p_context)
{
    struct dali_timer *timer = (struct dali_timer *)p_context;
    struct dali_timer_data *data = (struct dali_timer_data *)timer->timer_data;
    if(event_type == NRF_TIMER_EVENT_COMPARE0)
    {
        timer->callback(timer, timer->user_data);
    }
}

int dali_timer_init(struct dali_timer *timer, dali_timer_cb_t cb)
{
    timer->callback = cb;
    timer->timer_data = (void *) &dali_timer_data;
    dali_timer_data.cfg.p_context = (void *)timer;

    nrfx_timer_init(&dali_timer_data.p_instance, &dali_timer_data.cfg, timer_handler);

    IRQ_CONNECT(TIMER2_IRQn, IRQ_ZERO_LATENCY, nrfx_timer_2_irq_handler, NULL, 0);

    return 0;
}

int dali_timer_start(struct dali_timer *timer, dali_timeout_t period)
{
    struct dali_timer_data * timer_data = timer->timer_data;
    nrfx_timer_extended_compare(&timer_data->p_instance,
                                NRF_TIMER_CC_CHANNEL0,
                                period.ticks,
                                NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                true);

    nrfx_timer_enable(&timer_data->p_instance);
    return 0;
}

int dali_timer_stop(struct dali_timer *timer)
{
    struct dali_timer_data * timer_data = timer->timer_data;
    nrfx_timer_disable(&timer_data->p_instance);
    nrfx_timer_clear(&timer_data->p_instance);
    return 0;
}


Dali timer handler:

static void dali_timer_handler(struct dali_timer *timer, void * user_data)
{
    struct dali_gpio_data *data = (struct dali_gpio_data *) user_data;
    struct dali_gpio_cfg const * cfg = data->dev->config;

    gpio_pin_set_dt(&cfg->tx, BIT_READ64(data->tx_buff.edge_data, data->edge_count));

    data->edge_count++;

    if(data->tx_buff.n_edges == data->edge_count)
    {
        dali_timer_stop(timer);
        tx_settle_time_schedule(data);
        k_event_set(&data->dali_drv_event, 0);
        user_callback(data->dev, &(struct dali_drv_evt_data){
            .type = DALI_EVT_TX_SEND,
            .tx_send = {
                .data = data->tx_buff.data
            }
        });
    }
}


To ensure nothing interrupts the timer, I used `IRQ_ZERO_LATENCY` for this test. But it surprises me that despite this, some edges deviate from the timer while most are spot-on.
Does anyone know if I did something wrong here?

Would it help to use nrfx GPIO(TE) instead? 
Would it be possible (and efficient) using nrfx GPIOTE with PPI and nrfx timer for this?

Or would it be a good option to use SPI at 2400bps and leave all pins except for MOSI unconnected? For this the concern is that as part of the multi-master specification, the device must read back the value it set on the bus to detect if there is a collision.

Thanks in advance. 

Parents
  • I suspect some house keeping by the zephyr rtos that cause delay to the timer execution. Using nrfx directly I suspect will give the same result, though maybe a bit less both in terms of delay caused and the occurance rate. 

    But if you were to prepare next pin toggling by configuring a gpiote task and a timer CC[] event that would trigger the gpiote task, then I don't really see any way that should fail, since you can then prepare (from your description) up to 400us before it needs to be triggered. The CC[] event that triggered the GPIOTE toggle can also be for the next TIMER interrupt.

    Or would it be a good option to use SPI at 2400bps and leave all pins except for MOSI unconnected? For this the concern is that as part of the multi-master specification, the device must read back the value it set on the bus to detect if there is a collision.

    You can connect a small resistor (1khom) on the MOSI pin to the MISO pin, and the MISO pin is then connected directly to the peer. Then if you setup a SPI transfer with empty rx_buf, then you should get the same data in rx_buf as tx_buf if there are no collisions (though may consider running SPI on higher datarate to check for potentially collissions), ref setup:

    CLK must be defined to a pin even if not connected physically to anything.

    Hope that helps,
    Kenneth

Reply
  • I suspect some house keeping by the zephyr rtos that cause delay to the timer execution. Using nrfx directly I suspect will give the same result, though maybe a bit less both in terms of delay caused and the occurance rate. 

    But if you were to prepare next pin toggling by configuring a gpiote task and a timer CC[] event that would trigger the gpiote task, then I don't really see any way that should fail, since you can then prepare (from your description) up to 400us before it needs to be triggered. The CC[] event that triggered the GPIOTE toggle can also be for the next TIMER interrupt.

    Or would it be a good option to use SPI at 2400bps and leave all pins except for MOSI unconnected? For this the concern is that as part of the multi-master specification, the device must read back the value it set on the bus to detect if there is a collision.

    You can connect a small resistor (1khom) on the MOSI pin to the MISO pin, and the MISO pin is then connected directly to the peer. Then if you setup a SPI transfer with empty rx_buf, then you should get the same data in rx_buf as tx_buf if there are no collisions (though may consider running SPI on higher datarate to check for potentially collissions), ref setup:

    CLK must be defined to a pin even if not connected physically to anything.

    Hope that helps,
    Kenneth

Children
Related