ESP32 / RMT loosing ticks /

dirkxus
Posts: 4
Joined: Fri Sep 21, 2018 12:13 pm

ESP32 / RMT loosing ticks /

Postby dirkxus » Fri Sep 21, 2018 12:24 pm

I am having an odd issue with the latest version of IDF (which may well be a regression introduced with 1.0.0).

Every 5 to 100 seconds the RMT output seems to miss a tick/beat or show some other glitch:

Image
Image

Below is a simplified example which still exhibits the issue. The code outputs simply 13 1's and then 13 0's within 400 RMT items; these are managed in typical 'double buffer' style.

Does this ring a bell with anyone ? I was assuming the RTM to be totally hardware based and essentially un-fased by anything happening elsewhere (and in the example below - loop() is empty; there is no WiFi -- and I still see the glitches).

Note that running it contineously without any refresh works fine; and that the digitalWrite() in the IRQ shows it getting called regularly at a steady clip.

Suggestions how to get to the bottom of this appreciated.

Dw.

Code: Select all

/* Simple 'RMT' example which uses a 'double buffering' method
 *  to populate the other buffer once it starts on the second one.
 *  
 *  (c) Copyright 2018 Dirk-Willem van Gulik - All rights reserved.
 *      Under the Apache Software License version 2.0.
 */

#include "driver/rmt.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define RMT_TX_CHANNEL (RMT_CHANNEL_0)
#define RMT_TX_GPIO (GPIO_NUM_5)

// Lenght of 1's or 0's to emit until toggling to the other.
//
#define RUNLENGTH (13)

// Number of bits in our buffer - 
#define ITEM_CNT_HALF (200)

#if ITEM_CNT_HALF*2 > 512
#error Only 512 rmt_item32 items for both our buffers.
#endif

// Our run spans multiple bottom/top halfs; so we keep a global counter./
static unsigned roll = 0;

// Keep track of wether we do the top of bottom half.
//
static unsigned int at = 0;
 
void IRAM_ATTR rmt_isr_handler(void *arg) {
  RMT.int_clr.ch0_tx_thr_event = 1;

  fillTopOrBottomHalf();
}


void setup() {
  rmt_config_t config;
  pinMode(RMT_TX_GPIO, OUTPUT);

  config.rmt_mode = RMT_MODE_TX;
  config.channel = RMT_TX_CHANNEL;
  config.gpio_num = RMT_TX_GPIO;

  config.mem_block_num = ((ITEM_CNT_HALF*2) / 64 + 1);
  config.clk_div = 1;

  config.tx_config.loop_en = 1;

  config.tx_config.idle_output_en = true;
  config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;

  config.tx_config.carrier_en = false;
  config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;


  ESP_ERROR_CHECK(rmt_config(&config));
  ESP_ERROR_CHECK(rmt_set_source_clk(RMT_TX_CHANNEL, RMT_BASECLK_APB)); // 80 Mhz.
  ESP_ERROR_CHECK(rmt_isr_register(rmt_isr_handler, NULL, ESP_INTR_FLAG_LEVEL1, 0));

  // Fill the entire block with 'end of block' markers.
  for (int i = 0; i < config.mem_block_num; i++)
    for (int j = 0 ; j < 64; j++)
      RMTMEM.chan[RMT_TX_CHANNEL + i ].data32[j].val = 0;

  fillTopOrBottomHalf();
  fillTopOrBottomHalf();

  /* Trigger the interupt every ITEM_CNT_HALF entries; i.e. at the start of the second block
   * or the start of the first block - while the other is getting sent out.
   */
  ESP_ERROR_CHECK(rmt_set_tx_thr_intr_en(RMT_TX_CHANNEL, true, ITEM_CNT_HALF));
  ESP_ERROR_CHECK(rmt_tx_start(RMT_TX_CHANNEL, true));
}

void fillTopOrBottomHalf()
 {  
  // specify which half we do; top or bottom.
  //
  if (at == ITEM_CNT_HALF) at = 0; else at = ITEM_CNT_HALF;

  for (int i = 0; i < ITEM_CNT_HALF; i++) {
    unsigned char level1 = 0 , level2 = 0;

    if (roll < RUNLENGTH) level1  = 1;
  
    roll++;
    if (roll >= RUNLENGTH*2) roll = 0;

    if (roll < RUNLENGTH) level2  = 1;

    roll++;    
    if (roll >= RUNLENGTH*2) roll = 0;

    rmt_item32_t w =   {{{ 400, level1, 400, level2 }}};
    RMTMEM.chan[RMT_TX_CHANNEL].data32[at + i].val = w.val;
  }
}

void loop() {
}

dirkxus
Posts: 4
Joined: Fri Sep 21, 2018 12:13 pm

Re: ESP32 / RMT loosing ticks /

Postby dirkxus » Fri Sep 21, 2018 6:27 pm

Updated version - which should make it easier to understand. Still shows glitches though.

Code: Select all

/* Simple 'RMT' example which uses a 'double buffering' method
    to populate the other buffer once it starts on the second one.

    (c) Copyright 2018 Dirk-Willem van Gulik - All rights reserved.
        Under the Apache Software License version 2.0.
*/

#include "driver/rmt.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define RMT_TX_CHANNEL (RMT_CHANNEL_0)
#define RMT_TX_GPIO (GPIO_NUM_5)

// Lenght of 1's or 0's to emit until toggling to the other.
//
#define RUNLENGTH (7)

// Number of bits in our buffer -
#define ITEM_CNT_HALF (200)

#if ITEM_CNT_HALF*2 > 512
#error Only 512 rmt_item32 items for both our buffers.
#endif

// Our run spans multiple bottom/top halfs; so we keep a global counter./
static unsigned roll = 0;

// Keep track of wether we do the top of bottom half.
//
static unsigned int at = 0;

void IRAM_ATTR rmt_isr_handler(void *arg) {
  RMT.int_clr.ch0_tx_thr_event = 1;
  RMT.apb_conf.fifo_mask = RMT_DATA_MODE_MEM;
  digitalWrite(GPIO_NUM_13, !digitalRead(GPIO_NUM_13));
  fillTopOrBottomHalf();
  digitalWrite(GPIO_NUM_15, at != 0);
}


void setup() {
  rmt_config_t config;
  pinMode(RMT_TX_GPIO, OUTPUT);

  pinMode(GPIO_NUM_13, OUTPUT);
  pinMode(GPIO_NUM_15, OUTPUT);

  config.rmt_mode = RMT_MODE_TX;
  config.channel = RMT_TX_CHANNEL;
  config.gpio_num = RMT_TX_GPIO;

  config.mem_block_num = ((ITEM_CNT_HALF * 2) / 64 + 1);
  config.clk_div = 1;

  config.tx_config.loop_en = 1;

  config.tx_config.idle_output_en = true;
  config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;

  config.tx_config.carrier_en = false;
  config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;


  ESP_ERROR_CHECK(rmt_config(&config));
  ESP_ERROR_CHECK(rmt_set_source_clk(RMT_TX_CHANNEL, RMT_BASECLK_APB)); // 80 Mhz.
  ESP_ERROR_CHECK(rmt_isr_register(rmt_isr_handler, NULL, ESP_INTR_FLAG_LEVEL1, 0));

  // Fill the entire block with 'end of block' markers.
  rmt_item32_t endSentinel =   {{{ 0, 0, 0, 0 }}};
  for (int i = 0; i < config.mem_block_num; i++)
    for (int j = 0 ; j < 64; j++)
      RMTMEM.chan[RMT_TX_CHANNEL + i ].data32[j].val = endSentinel.val;

  fillTopOrBottomHalf(); // fill bottom half
  fillTopOrBottomHalf(); // fill top half
  assert(at != 0);

  ESP_ERROR_CHECK(rmt_set_tx_thr_intr_en(RMT_TX_CHANNEL, true, ITEM_CNT_HALF));
  ESP_ERROR_CHECK(rmt_tx_start(RMT_TX_CHANNEL, true));

}


void fillTopOrBottomHalf()
{
  for (int i = 0; i < ITEM_CNT_HALF; i++) {
    unsigned char level1 = 0 , level2 = 0;

    if (roll < RUNLENGTH) level1  = 1;

    roll++;
    if (roll >= RUNLENGTH * 2) roll = 0;

    if (roll < RUNLENGTH) level2  = 1;

    roll++;
    if (roll >= RUNLENGTH * 2) roll = 0;

    unsigned int d = 400, e = 400;

    rmt_item32_t w =   {{{ d, level1, e, level2 }}};
    RMTMEM.chan[RMT_TX_CHANNEL].data32[at + i].val = w.val;
  }
  
  // specify which half we do next time; top or bottom.
  //
  if (at) at = 0; else at = ITEM_CNT_HALF;
}

void loop() {
}

dirkxus
Posts: 4
Joined: Fri Sep 21, 2018 12:13 pm

Re: ESP32 / RMT loosing ticks /

Postby dirkxus » Sun Sep 23, 2018 5:05 pm

Am strongly suspecting that, when using the RMT system in loop rather than in one-shot mode; it either ignores, or gets confused by the sentinels; and mis-counts during the final ticks in the final block.

If one ensures that all blocks are fully filled with no '0' length blocks (i.e. not use the 0,1,0,0 or 0,0,0,0 sentinels) -- the glitches go away.

This does mean that any pulse train needs to massaged into 64x2 element sequences.

https://github.com/dirkx/SMPTE-EBU-Time ... ator-ESP32 - and specifically the file uses this approach: https://github.com/dirkx/SMPTE-EBU-Time ... or/RMT.ino -- and this is stable against the March verson of IDF.

remcob
Posts: 2
Joined: Sun Dec 06, 2020 8:41 pm

Re: ESP32 / RMT loosing ticks /

Postby remcob » Sun Dec 06, 2020 8:52 pm

> Am strongly suspecting that, when using the RMT system in loop rather than in one-shot mode; it either ignores, or gets confused by the sentinels; and mis-counts during the final ticks in the final block.

IMHO what happens is that the sentinel itself counts as a single event. So for the double-buffer approach you would need to alternate between 200 (lower) and 201 (upper) events for the treshold.

I was running the RMT in loops with one or two samples and noticed the treshold counts where off by 2x and 1.5x respectively. Accounting for the sentinel fixes that.

Another useful observation: if you set the threshold hardware register directly (`RMT.tx_lim_ch[CHANNEL].limit = ..`) it seems that you can always use values up to 511 even if you only have one memory block assigned to the channel. This helps me lower the intereupt frequency.

szguyer
Posts: 19
Joined: Thu Jan 11, 2018 5:41 pm

Re: ESP32 / RMT loosing ticks /

Postby szguyer » Mon Dec 07, 2020 3:40 am

We've used this technique successfully in the ESP32 support for the FastLED library. BUT I've never set it up where rmt_set_tx_thr_intr_en is being given a value that isn't a multiple of 32 or 64. We don't use sentinels at all.(Or rather, we only use the special 0-value sentinel that indicates we are done sending (which does generate an interrupt!)

You can check out our code here:

https://github.com/FastLED/FastLED/blob ... _esp32.cpp

As a side note: I found that using explicit array/struct accesses to fill the RMT memory was very slow (for some reason it didn't seem to get optimized well). So I replaced with a pointer that moves through the memory. See the fillNext() method.

Who is online

Users browsing this forum: No registered users and 176 guests