RMT - simplest receive example working, but idle_threshold problem
Re: RMT - simplest receive example working, but idle_threshold problem
Thanks, I'll see if this works for my project. Cheers!
Re: RMT - simplest receive example working, but idle_threshold problem
Hello everyone,
That last solution works like a charm for me! It's a bit hacky, but whatever!
What I changed is: I just readout the first value in memory and immediately after that use:
Thank you @jcsbanks for putting your time into investigating that!
Additionally, it solves my problem in my post viewtopic.php?t=12873.
I just haven't thought about using the RMT module.
Best regard,
jw--rt
That last solution works like a charm for me! It's a bit hacky, but whatever!
What I changed is: I just readout the first value in memory and immediately after that use:
Code: Select all
rmt_item32_t* item = nullptr; // NULL
rmt_rx_start(RMT_CHANNEL_N, true);
for(;;)
{
item = (rmt_item32_t*) (RMT_CHANNEL_MEM(RMT_CHANNEL_N));
// Do math with item->duration0 and item->duration1
rmt_memory_rw_rst(RMT_CHANNEL_N);
vTaskDelay(DELAY_TIME_MS/portTICK_PERIOD_MS);
}
Additionally, it solves my problem in my post viewtopic.php?t=12873.
I just haven't thought about using the RMT module.
Best regard,
jw--rt
Last edited by jw--rt on Wed Nov 13, 2019 9:50 pm, edited 3 times in total.
Re: RMT - simplest receive example working, but idle_threshold problem
Is that loop ever going to be blocked/yield or just burn up CPU cycles?
Re: RMT - simplest receive example working, but idle_threshold problem
Hello,
good point. For completeness, I edited my answer.
Best regards,
jw--rt
good point. For completeness, I edited my answer.
Best regards,
jw--rt
Re: RMT - simplest receive example working, but idle_threshold problem
Hi, i am desperately trying to clear the memory in order to make a new measurement. But it seems that all I try has no effect, just when I remove the connection and reconnect, meaning a signal break > idle time, I can manage to get a reset. Can anyone point me in the right direction? Right now I am just starting the RMT module, no interrupt nor ringbuffer, and use a function to read it's content.
-
- Posts: 8
- Joined: Fri Apr 01, 2022 3:06 pm
Re: RMT - simplest receive example working, but idle_threshold problem
Hi all, I just wanted to add onto this thread with my solution. I was running into problems with the RMT falling into wait if the buffer overflowed or going to idle and not recovering if the input frequency dropped too low as well as poor resolution in measuring duty cycle across the range. Ultimately this example is able to measure from 5-6000 Hz with a 50% duty cycle. Duty cycle plays a part in the minimum frequency as a long high or low pulse can exceed the limits of the 16 bit counters.
This code handles those two error conditions as well as dynamically adjusts the clock prescaler to improve duty cycle resolution. If you don't need to measure as high of a frequency you can reduce the size of mem_block_num and/or decrease the loop rate of the task. Alternatively increase the number of memory blocks or the loop rate of the task to read higher frequencies (not tested). Ultimately max frequency is limited by the number of cycles you can count (64 cycles per memory block) between resets of the rmt rx memory.
In the header:
And the source code:
This code handles those two error conditions as well as dynamically adjusts the clock prescaler to improve duty cycle resolution. If you don't need to measure as high of a frequency you can reduce the size of mem_block_num and/or decrease the loop rate of the task. Alternatively increase the number of memory blocks or the loop rate of the task to read higher frequencies (not tested). Ultimately max frequency is limited by the number of cycles you can count (64 cycles per memory block) between resets of the rmt rx memory.
In the header:
Code: Select all
#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "freertos/task.h"
#include <driver/gpio.h>
#define IO_FI_1 GPIO_NUM_39
#define IO_FI_2 GPIO_NUM_36
#define IO_FI_3 GPIO_NUM_35
enum frequencyInput_t {FI_1, FI_2, FI_3 };
void frequencyInputBegin();
uint32_t getFrequency(frequencyInput_t index);
uint32_t getDutyCycle(frequencyInput_t index);
Code: Select all
#include <frequencyInput.hpp>
#define MIN(A,B) ((A)<=(B)?(A):(B))
#define MAX(A,B) ((A)>=(B)?(A):(B))
#define MEDIAN(A,B,C) ((B)<=(A)?(A):((B)>=(C)?(C):(B)))
#define FI_COUNT 3
typedef struct rmt_channel
{
rmt_channel_t channel;
gpio_num_t pin;
uint32_t frequency; //Hz x10
uint32_t dutyCycle; //% x10
} rmt_channel;
rmt_channel rmt_channels[FI_COUNT] = {
{
RMT_CHANNEL_0,
IO_FI_1,
0,
0
},
{
RMT_CHANNEL_1,
IO_FI_2,
0,
0
},
{
RMT_CHANNEL_2,
IO_FI_3,
0,
0
}
};
static void frequencyReadTask(void * pData)
{
int idleCount = 0;
while(1)
{ //infinite loop
for (int i = 0; i < FI_COUNT; i++)
{
uint32_t status = 0;
uint8_t state = 0;
rmt_get_status(rmt_channels[i].channel, &status);
state = (status & RMT_STATE_CH0) >> RMT_STATE_CH0_S;
//check for error states
if(status & RMT_MEM_OWNER_ERR_CH0)
{
/*
ownership of the memory block is violated
typically happens when frequency drops too low
*/
rmt_set_memory_owner(rmt_channels[i].channel, RMT_MEM_OWNER_RX);
rmt_channels[i].dutyCycle = 0;
rmt_channels[i].frequency = 0;
}
if(status & RMT_MEM_FULL_CH0)
{
/*
received more data that memory allows
typically happens if we capture too many pulses for our given sample time
*/
rmt_rx_memory_reset(rmt_channels[i].channel);
}
switch(state)
{
case 0x0: //idle
{
/*
Sometimes we get here if the frequency suddenly chops and the divider setting
hasn't kept up. Check if it's not default and reset it.
*/
uint8_t divider;
rmt_get_clk_div(rmt_channels[i].channel, ÷r);
if(divider < UINT8_MAX)
{
rmt_set_clk_div(rmt_channels[i].channel, UINT8_MAX);
rmt_rx_memory_reset(rmt_channels[i].channel);
}
if(idleCount++)
{
//came into the idle loop more than once in a row, assume 0 Hz
rmt_channels[i].dutyCycle = 0;
rmt_channels[i].frequency = 0;
}
break;
}
case 0x3: //receive
{
uint8_t cycleCount = status & 0x3F;
//check that we have more than one pulse captured to reduce jitter at low frequency
if(cycleCount > 1)
{
uint32_t highCount = 0;
uint32_t totalCount = 0;
uint32_t maxTick = 0;
uint8_t divider;
rmt_item32_t * item = (rmt_item32_t*) (RMT_CHANNEL_MEM(rmt_channels[i].channel));
//the RMT device may have captured multiple pulses, perform an average
for(int j = 1; j <= cycleCount; j++)
{
highCount += item->level0 ? item->duration0 : item->duration1;
totalCount += item->duration0 + item->duration1;
maxTick = MAX(maxTick, MAX(item->duration0, item->duration1));
item++;
}
highCount /= cycleCount;
totalCount /= cycleCount;
//capture the clock divider to calculate frequency and DC
rmt_get_clk_div(rmt_channels[i].channel, ÷r);
//offer some protection from garbage data with some basic checks
if(highCount && highCount != totalCount && totalCount > 2)
{
rmt_channels[i].dutyCycle =
static_cast<uint32_t>((1000u*highCount) / totalCount);
rmt_channels[i].frequency =
static_cast<uint32_t>((10.0f * APB_CLK_FREQ / divider) / totalCount);
/*
Recalculate the clock divider to stay near the target, this broadens the
frequency range we can accurately measure. Otherwise a low divider allows
us to accurately measure high frequencies, but limits our minimum. A high
divider lowers our minimum frequency, but reduces our accuracy at measuring
high frequencies.
*/
divider = MIN(maxTick * divider / 12000, UINT8_MAX);
divider = MAX(1, divider);
rmt_set_clk_div(rmt_channels[i].channel, divider);
}
//reset the memory to capture the next series of pulses
rmt_rx_memory_reset(rmt_channels[i].channel);
}
idleCount = 0;
break;
}
case 0x4: //wait
{
//rmt is stuck in wait set, change the idle threshold to 0 to reset it
int16_t temp = RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres;
RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = 0;
RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = temp;
idleCount = 0;
break;
}
case 0x1: //send
case 0x2: //read memory
default:
break;
}
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void frequencyInputBegin()
{
/*
set up the RMT module to monitor the three frequency inputs
make use of two memory blocks per input so we can capture up to 128 cycles instead of just 64,
this halves how often we need to grab and empty the memory before risk of overflow
*/
for (int i = 0; i < FI_COUNT; i++)
{
rmt_config_t rmt_rx_config = {
.rmt_mode = RMT_MODE_RX,
.channel = rmt_channels[i].channel,
.gpio_num = rmt_channels[i].pin,
.clk_div = UINT8_MAX,
.mem_block_num = 2,
.flags = 0,
.rx_config = {
.idle_threshold = INT16_MAX,
.filter_ticks_thresh = UINT8_MAX,
.filter_en = false
}
};
assert(ESP_OK == rmt_config(&rmt_rx_config));
assert(ESP_OK == rmt_rx_start(rmt_channels[i].channel, true));
}
xTaskCreate (frequencyReadTask, "FIHandler", 2048, NULL, 4, NULL) ;
}
uint32_t getFrequency(frequencyInput_t index)
{
return rmt_channels[index].frequency;
}
uint32_t getDutyCycle(frequencyInput_t index)
{
return rmt_channels[index].dutyCycle;
}
Last edited by Maxzillian on Tue Jul 23, 2024 6:46 pm, edited 1 time in total.
-
- Posts: 2
- Joined: Sat Apr 01, 2023 12:53 am
Re: RMT - simplest receive example working, but idle_threshold problem
Hello all, would like to read in several PWM channels. The PWM frequency of the channels is about 50Hz.
The H-pulse width is 1-2ms
I need the H-pulse width as well as the exact frequency.
I use the code from @Maxzillian. However, only the first channel is always read correctly and I can't find the error.
It is always the first channel that is correct.
Changes in frequency or duty cycle have no effect on channel 2.
I really don't know what to do.
The example code is compiled in vscode with the Arduino framework.
Here are the exemplary outputs:
The H-pulse width is 1-2ms
I need the H-pulse width as well as the exact frequency.
I use the code from @Maxzillian. However, only the first channel is always read correctly and I can't find the error.
It is always the first channel that is correct.
Changes in frequency or duty cycle have no effect on channel 2.
I really don't know what to do.
The example code is compiled in vscode with the Arduino framework.
Code: Select all
// main.cpp
#include <Arduino.h>
#include <Ticker.h>
#include "frequencyInput.hpp"
void setup()
{
Serial.begin(115200);
Serial.println("V0.1 310323");
// set pins to input
pinMode(GPIO_NUM_13, INPUT);
pinMode(GPIO_NUM_14, INPUT);
pinMode(GPIO_NUM_27, INPUT);
frequencyInputBegin();
}
void loop()
{
Serial.printf("Pin: 13 f: %lu d: %lu%% rawHighCount: %lu rawTotalCount: %lu divider; %hhu\n",
getFrequency(FI_1) / 10,
getDutyCycle(FI_1) / 10,
getRawHighCount(FI_1),
getRawTotalCount(FI_1),
getClkDivider(FI_1));
Serial.printf("Pin: 14 f: %lu d: %lu%% rawHighCount: %lu rawTotalCount: %lu divider; %hhu\n",
getFrequency(FI_2) / 10,
getDutyCycle(FI_2) / 10,
getRawHighCount(FI_2),
getRawTotalCount(FI_2),
getClkDivider(FI_2));
Serial.println("");
vTaskDelay(1000);
}
Code: Select all
// frequencyInput.hpp
#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "freertos/task.h"
#include <driver/gpio.h>
#define IO_FI_1 GPIO_NUM_13
#define IO_FI_2 GPIO_NUM_14
#define IO_FI_3 GPIO_NUM_27
enum frequencyInput_t {FI_1, FI_2, FI_3 };
void frequencyInputBegin();
uint32_t getFrequency(frequencyInput_t index);
uint32_t getDutyCycle(frequencyInput_t index);
uint32_t getRawTotalCount(frequencyInput_t index);
uint32_t getRawHighCount(frequencyInput_t index);
uint8_t getClkDivider(frequencyInput_t index);
Code: Select all
// frequencyInput.cpp
#include <frequencyInput.hpp>
#define MIN(A, B) ((A) <= (B) ? (A) : (B))
#define MAX(A, B) ((A) >= (B) ? (A) : (B))
#define MEDIAN(A, B, C) ((B) <= (A) ? (A) : ((B) >= (C) ? (C) : (B)))
#define FI_COUNT 3
#define RMT_CLK_DIV 80 // 80 MHz
typedef struct rmt_channel
{
rmt_channel_t channel;
gpio_num_t pin;
uint32_t frequency; // Hz x10
uint32_t dutyCycle; //% x10
uint32_t rawHighCount; // raw High ticks
uint32_t rawTotalCount; // raw Low ticks
uint8_t divider; // actual channel devider
} rmt_channel;
rmt_channel rmt_channels[FI_COUNT] = {
{RMT_CHANNEL_0,
IO_FI_1,
0,
0,
0,
0,
0},
{RMT_CHANNEL_1,
IO_FI_2,
0,
0,
0,
0,
0},
{RMT_CHANNEL_2,
IO_FI_3,
0,
0,
0,
0,
0}};
static void frequencyReadTask(void *pData)
{
int idleCount = 0;
while (1)
{ // infinite loop
for (int i = 0; i < FI_COUNT; i++)
{
uint32_t status = 0;
uint8_t state = 0;
rmt_get_status(rmt_channels[i].channel, &status);
state = (status & RMT_STATE_CH0) >> RMT_STATE_CH0_S;
// check for error states
if (status & RMT_MEM_OWNER_ERR_CH0)
{
/*
ownership of the memory block is violated
typically happens when frequency drops too low
*/
rmt_set_memory_owner(rmt_channels[i].channel, RMT_MEM_OWNER_RX);
rmt_channels[i].dutyCycle = 0;
rmt_channels[i].frequency = 0;
}
if (status & RMT_MEM_FULL_CH0)
{
/*
received more data that memory allows
typically happens if we capture too many pulses for our given sample time
*/
rmt_rx_memory_reset(rmt_channels[i].channel);
}
switch (state)
{
case 0x0: // idle
{
/*
Sometimes we get here if the frequency suddenly chops and the divider setting
hasn't kept up. Check if it's not default and reset it.
*/
uint8_t divider;
rmt_get_clk_div(rmt_channels[i].channel, ÷r);
if (divider < UINT8_MAX)
{
rmt_set_clk_div(rmt_channels[i].channel, UINT8_MAX);
rmt_rx_memory_reset(rmt_channels[i].channel);
}
if (idleCount++)
{
// came into the idle loop more than once in a row, assume 0 Hz
rmt_channels[i].dutyCycle = 0;
rmt_channels[i].frequency = 0;
}
break;
}
case 0x3: // receive
{
uint8_t cycleCount = status & 0xFF;
// check that we have more than one pulse captured to reduce jitter at low frequency
if (cycleCount > 1)
{
uint32_t highCount = 0;
uint32_t totalCount = 0;
uint32_t maxTick = 0;
uint8_t divider;
rmt_item32_t *item = (rmt_item32_t *)(RMT_CHANNEL_MEM(rmt_channels[i].channel));
// the RMT device may have captured multiple pulses, perform an average
for (int j = 1; j <= cycleCount; j++)
{
highCount += item->level0 ? item->duration0 : item->duration1;
totalCount += item->duration0 + item->duration1;
maxTick = MAX(maxTick, MAX(item->duration0, item->duration1));
item++;
}
highCount /= cycleCount;
totalCount /= cycleCount;
// capture the clock divider to calculate frequency and DC
rmt_get_clk_div(rmt_channels[i].channel, ÷r);
// offer some protection from garbage data with some basic checks
if (highCount && highCount != totalCount && totalCount > 2)
{
rmt_channels[i].dutyCycle =
static_cast<uint32_t>((1000u * highCount) / totalCount);
rmt_channels[i].frequency =
static_cast<uint32_t>((10.0f * APB_CLK_FREQ / divider) / totalCount);
rmt_channels[i].rawHighCount =
static_cast<uint32_t>( highCount * divider / RMT_CLK_DIV); // highCount / (divider / 80)
rmt_channels[i].rawTotalCount =
static_cast<uint32_t>(totalCount * divider / RMT_CLK_DIV);
rmt_channels[i].divider =
static_cast<uint8_t>(divider);
/*
Recalculate the clock divider to stay near the target, this broadens the
frequency range we can accurately measure. Otherwise a low divider allows
us to accurately measure high frequencies, but limits our minimum. A high
divider lowers our minimum frequency, but reduces our accuracy at measuring
high frequencies.
*/
divider = MIN(maxTick * divider / 12000, UINT8_MAX);
divider = MAX(1, divider);
rmt_set_clk_div(rmt_channels[i].channel, divider);
}
// reset the memory to capture the next series of pulses
rmt_rx_memory_reset(rmt_channels[i].channel);
}
idleCount = 0;
break;
}
case 0x4: // wait
{
// rmt is stuck in wait set, change the idle threshold to 0 to reset it
int16_t temp = RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres;
RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = 0;
RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = temp;
idleCount = 0;
break;
}
case 0x1: // send
case 0x2: // read memory
default:
break;
}
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void frequencyInputBegin()
{
/*
set up the RMT module to monitor the three frequency inputs
make use of two memory blocks per input so we can capture up to 128 cycles instead of just 64,
this halves how often we need to grab and empty the memory before risk of overflow
*/
for (int i = 0; i < FI_COUNT; i++)
{
rmt_config_t rmt_rx_config = {
.rmt_mode = RMT_MODE_RX,
.channel = rmt_channels[i].channel,
.gpio_num = rmt_channels[i].pin,
.clk_div = UINT8_MAX,
.mem_block_num = 2,
.flags = 0,
.rx_config = {
.idle_threshold = INT16_MAX,
.filter_ticks_thresh = UINT8_MAX,
.filter_en = false}};
assert(ESP_OK == rmt_config(&rmt_rx_config));
assert(ESP_OK == rmt_rx_start(rmt_channels[i].channel, true));
}
xTaskCreate(frequencyReadTask, "FIHandler", 2048, NULL, 4, NULL);
}
uint32_t getFrequency(frequencyInput_t index)
{
return rmt_channels[index].frequency;
}
uint32_t getDutyCycle(frequencyInput_t index)
{
return rmt_channels[index].dutyCycle;
}
uint32_t getRawHighCount(frequencyInput_t index)
{
return rmt_channels[index].rawHighCount;
}
uint8_t getClkDivider(frequencyInput_t index)
{
return rmt_channels[index].divider;
}
uint32_t getRawTotalCount(frequencyInput_t index)
{
return rmt_channels[index].rawTotalCount;
}
Code: Select all
_
pin13 | |________ p:1,4ms T:20ms
__
pin14 ___| |_______ P:1,6ms T:20ms
Pin: 13 f: 50 d: 6% rawHighCount: 1396 rawTotalCount: 19999 divider; 124
Pin: 14 f: 178 d: 86% rawHighCount: 4832 rawTotalCount: 5603 divider; 255
Pin: 13 f: 50 d: 6% rawHighCount: 1395 rawTotalCount: 19998 divider; 124
Pin: 14 f: 178 d: 86% rawHighCount: 4832 rawTotalCount: 5603 divider; 255
Pin: 13 f: 50 d: 6% rawHighCount: 1396 rawTotalCount: 19999 divider; 124
Pin: 14 f: 178 d: 86% rawHighCount: 4832 rawTotalCount: 5603 divider; 255
-
- Posts: 8
- Joined: Fri Apr 01, 2022 3:06 pm
Re: RMT - simplest receive example working, but idle_threshold problem
Sorry for the delay; you may need to insert some print statements into the frequencyReadTask loop to see what the individual counters are doing. Namely pay attention to what the state is of each channel and see if the second and third are never hitting the receive state.
One thing I'm noticing is that despite your pin 14 input being at a higher frequency, the divider is stuck at 255. Now that shouldn't stop it from counting at all, but it is suspicious. That said, that only affects the accuracy and shouldn't stop it from counting at all. You should consider removing the divider and RMT_CLK_DIV factors from your rawHighCount and rawTotalCount statistics as you're just making it harder to diagnose as you're not really reporting the raw values in that case.
One thing I'm noticing is that despite your pin 14 input being at a higher frequency, the divider is stuck at 255. Now that shouldn't stop it from counting at all, but it is suspicious. That said, that only affects the accuracy and shouldn't stop it from counting at all. You should consider removing the divider and RMT_CLK_DIV factors from your rawHighCount and rawTotalCount statistics as you're just making it harder to diagnose as you're not really reporting the raw values in that case.
-
- Posts: 2
- Joined: Sat Apr 01, 2023 12:53 am
Re: RMT - simplest receive example working, but idle_threshold problem
Thank you for your reply.
I have solved it this way in the meantime:
https://github.com/rewegit/esp32-rmt-pwm-reader
https://github.com/rewegit/esp32-rmt-pw ... ItWorks.md
I am doing very well with this in my application.
Thanks for pushing me in that direction.
I have solved it this way in the meantime:
https://github.com/rewegit/esp32-rmt-pwm-reader
https://github.com/rewegit/esp32-rmt-pw ... ItWorks.md
I am doing very well with this in my application.
Thanks for pushing me in that direction.
-
- Posts: 8
- Joined: Fri Apr 01, 2022 3:06 pm
Re: RMT - simplest receive example working, but idle_threshold problem
I did manage to figure out why my code snippet only works for the first RMT channel. I had an error in the cycle counter that was masked by 0xFF, but it should have been limited to 0x3F as RMT can only capture up to 64 pulses. This was leading to the other channels capturing part of the memory range indicated in the status register as the cycle count and leading to bad calculations. I've edited my earlier post.
Who is online
Users browsing this forum: No registered users and 66 guests