Jitter on 'one shot' RMT output

janlksrckts
Posts: 6
Joined: Thu Feb 02, 2023 8:53 am

Jitter on 'one shot' RMT output

Postby janlksrckts » Sun Sep 03, 2023 10:04 am

I implemented a programmable 'one-shot circuit' using RMT; a rising edge on an input GPIO pin starts a programmable delay (range from 5 us to at least 30 ms), after which an output GPIO is set high for a programmable duration (similar range).
The only problem is a 1 us – wide 'jitter' on the produced output pulse.
While applying a square wave signal from a signal generator to PIN_IN_EDGE_DETECT, measuring on a scope showed that RMT starts between 2.6 us and 3.6 us after the call to 'rmt_write_items(..)'. More precisely the start-time drifts from 2.6 us to 3.6 us in about 2 seconds, then it rolls over back to 2.6 us and repeats.

'rmt_write_items' in rmt.c has a lot of code to execute which probably explains it.
The constant latency of 2.6 us can be compensated for because it is a stable value, but how to get rid of the jitter?
Is there a way of initializing first (which may take variable time), and then sending a low - and fixed latency 'start'?

Code below runs on an AZ-Delivery ESP32-S2 @ 240 MHz using its 'dedicated GPIO bundles' for faster GPIO.

Code: Select all

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/rmt_tx.h>
#include <inttypes.h>
#include "soc/gpio_struct.h"
#include <driver/rmt.h>
#include "sdkconfig.h"

// ESP32-S2 supports 'dedicated GPIO' for max speed
#include "driver/dedic_gpio.h"
#include "hal/dedic_gpio_cpu_ll.h"

//____ User Values ____________________________________________________________

#define PIN_IN_EDGE_DETECT		5
#define PIN_OUT_EDGE_DETECTED	15
#define PIN_OUT_RMT_ONE_SHOT	21

//____ Globals ________________________________________________________________

#define ESP_INTR_FLAG_DEFAULT 0

#define PIN_OUT_SEL  (1ULL << PIN_OUT_EDGE_DETECTED)
#define PIN_IN_SEL   (1ULL << PIN_IN_EDGE_DETECT)

#define BIT_OUT_EDGE_DETECTED 1
static const int _gpios_out_bundle[] = {PIN_OUT_EDGE_DETECTED};
static const int _gpios_in_bundle[] = {PIN_IN_EDGE_DETECT};

dedic_gpio_bundle_handle_t _bundle_in = NULL;

rmt_config_t _rmt_cfg;
rmt_item32_t _pulse_item;

//____ Code ___________________________________________________________________

void init_rmt_deprec_api() {
	_rmt_cfg.rmt_mode = RMT_MODE_TX;
	_rmt_cfg.channel = RMT_CHANNEL_0;
	_rmt_cfg.gpio_num = PIN_OUT_RMT_ONE_SHOT;
	_rmt_cfg.mem_block_num = 1;
	_rmt_cfg.tx_config.loop_en = 0;
	_rmt_cfg.tx_config.carrier_en = 0;
	_rmt_cfg.tx_config.idle_output_en = 1;
	_rmt_cfg.tx_config.idle_level = 0;
	_rmt_cfg.tx_config.carrier_duty_percent = 50;
	_rmt_cfg.tx_config.carrier_freq_hz = 1000;
	_rmt_cfg.tx_config.carrier_level = 1;
	_rmt_cfg.clk_div = 80;		// -> 1 us resolution
	ESP_ERROR_CHECK(rmt_config(&_rmt_cfg));
	ESP_ERROR_CHECK(rmt_driver_install(_rmt_cfg.channel, 0, 0));

	// values in us:
	_pulse_item.duration0 = 100;
	_pulse_item.level0 = 1;
	_pulse_item.duration1 = 20;
	_pulse_item.level1 = 0;
}

static void IRAM_ATTR _gpio_isr_handler(void * arg) {
    uint32_t in_value = dedic_gpio_bundle_read_in(_bundle_in);
	
    if (in_value != 0) {	// rising edge detected
    	asm volatile("set_bit_gpio_out %0" : : "i"(BIT_OUT_EDGE_DETECTED) : );	// 1.6 us delay since rising edge
    	rmt_write_items(_rmt_cfg.channel, &_pulse_item, 1, 0);
		// starts between 2.6 us and 3.6 us after PIN_OUT_EDGE_DETECTED goes high and drifts in this range :-(
    }
    else {		// falling edge detected
    	asm volatile("clr_bit_gpio_out %0" : : "i"(BIT_OUT_EDGE_DETECTED) : );	// 1.6 us delay since falling edge
    }
}

void init_gpio(void) {
    gpio_config_t io_conf = {};
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = PIN_OUT_SEL;	// bit mask of output pins
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    ESP_ERROR_CHECK(gpio_config(&io_conf));

    io_conf.intr_type = GPIO_INTR_POSEDGE;
    io_conf.pin_bit_mask = PIN_IN_SEL;	// bit mask of input pins
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pull_up_en = 1;
    ESP_ERROR_CHECK(gpio_config(&io_conf));

    // https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/dedic_gpio.html
    dedic_gpio_bundle_handle_t bundle_out = NULL;
    dedic_gpio_bundle_config_t bundle_out_config = {
    		.gpio_array = _gpios_out_bundle,
			.array_size = 1,
			.flags = { .out_en = 1, },
    };
    ESP_ERROR_CHECK(dedic_gpio_new_bundle(&bundle_out_config, &bundle_out));

    dedic_gpio_bundle_config_t bundle_in_config = {
    		.gpio_array = _gpios_in_bundle,
			.array_size = 1,
			.flags = { .in_en = 1, },
    };
    ESP_ERROR_CHECK(dedic_gpio_new_bundle(&bundle_in_config, &_bundle_in));

    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    gpio_isr_handler_add(PIN_IN_EDGE_DETECT, _gpio_isr_handler, (void *) PIN_IN_EDGE_DETECT);
}

void app_main(void) {
	init_rmt_deprec_api();
	init_gpio();
}
Last edited by janlksrckts on Tue Sep 05, 2023 1:28 pm, edited 1 time in total.

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: Jitter on RMT output

Postby ESP_Sprite » Mon Sep 04, 2023 1:13 am

You may want to use the dedicated IO interrupt capability rather than the 'normal' one. Also try raising the interrupt level and flagging it as IRAM capable.

janlksrckts
Posts: 6
Joined: Thu Feb 02, 2023 8:53 am

Re: Jitter on RMT output

Postby janlksrckts » Tue Sep 05, 2023 12:22 pm

Thanks for your reply. I found that using dedicated interrupt IO indeed makes the fixed part of the delay somewhat shorter, but the 1 us-wide 'jitter' remained the same.

I also found that the 'jitter range' was equal to the resolution:
rmt_config_t.clk_div = 80; // -> 1 us resolution -> 1 us 'jitter-range'
rmt_config_t.clk_div = 20; // -> 250 ns resolution -> 250 ns 'jitter-range'

So improving the resolution reduces the 'jitter-range', but this also reduces the maximum duration: 32767 * 250 ns = 8.192 ms
But by 'concatenating' multiple rmt_item32_t's any duration can be accomplished, like for instance:
_pulse_items[0].duration0 = 32767; // 32767 * 250 ns = 8.192 ms
_pulse_items[0].level0 = 0;
_pulse_items[0].duration1 = 32767;
_pulse_items[0].level1 = 0;
_pulse_items[1].duration0 = 32767;
_pulse_items[1].level0 = 0;
_pulse_items[1].duration1 = 32767;
_pulse_items[1].level1 = 0;
_pulse_items[2].duration0 = 32767;
_pulse_items[2].level0 = 0;
_pulse_items[2].duration1 = 32767;
_pulse_items[2].level1 = 0;
_pulse_items[3].duration0 = 32767; // Here total duration with level 0 = 32767 * 7 * 250 ns = 57.342 ms
_pulse_items[3].level0 = 0;
_pulse_items[3].duration1 = 400; // 400 * 250 ns = 100 us
_pulse_items[3].level1 = 1;

Very pleased this problem is finally solved!

Who is online

Users browsing this forum: No registered users and 43 guests