strange - if you are using a signal generator the displayed value should be stable
as well as connecting the generated signal to pin 22 have you connected the signal generator GND to the CYD GND?
Yes, the CYD has a small 4-pin port and comes with matching connector with 4 wires, one wire is ground. Strange thing - if I disconnect the ground wire from the CYD, the display shows a constant / stable 125.5 hz.
This is how I'm converting 5v to 3.3 v:
I'm using a 200 pf cap (not 22 pf). The scope ground, signal generator ground, this converter circuit ground, and CYD ground are all connected together. But when I remove the CYD ground connection the display is stable 125.5 hz.
The CYD is powered from USB cable connected to a nearby computer. So there must be some interaction with the USB ground and the signal ground. I will try to power the CYD from a battery next.
That sounds like a double counting problem. Since you are using a series capacitor much larger than recommended, try using just the resistors without the 200pf.
The scope signal above shows the output of the circuit. The top trace. It looks just fine.
Edit: Ok, I see what the problem is. With no capacitor, the rise-time of the output signal is somewhat slow - about 150 ns (0 to 3 volts) but no ringing. With 200 pf the rise time is much faster, but there is ringing (one cycle) probably enough to reach a logic low and that's what is causing the false triggering, but it is altered by removing the ground connection. A lower capacitor (100 pf) the ringing is worse.
I'm doing this while running the CYD from a single 18650 battery.
I put two 200 pf in parallel (so 400 pf) and the ringing is less but the front end of the signal is higher (peak is about 4 volts) so the ring does not trigger logic low. The rise time is 12 ns. It's debatible if the cap is needed, but if it's used it must be in the 400 pf range. If you want the signal not to exceed 3.3v then maybe use 300 pf but any lower will give ringing glitch.
And with the CYD powered from the computer through USB, it works, the freq reading is stable. And ground must be connected.
And I've tried connecting the ESP32 input to the 5v signal side, and it works fine, and I've read where the ESP32 GPIO can probably handle 5v logic levels just fine.
I have also seen such statements
however, I would only input 5V signals to microcomputer GPIO pins which are stated in manufacturers datasheets as being 5V tolerant, e.g. some PIC24s, STM32s, etc
I have not seen any Espressif datasheets stating that ESP32 GPIOs are 5V tolerant
3.3V input devices may work with 5V signals for a couple milliseconds, 10 seconds, an hour, a day or for ever or may die immediately
Is this sketch counting the pulses that happen during 0.1 seconds? Or is it measuring the time between consecutive rising edges of the input signal and creating an average value of the period between edges during 0.1 seconds and then calculating frequency from that?
I'd rather have this code perform a count of the rising edges that happen over a precise 1.000 second time interval.
the code of post 38 is performing a continuous running average of the time between rising edges and calculating frequency from that
and printing every 0.1 seconds
change the code to do what you require?
How can the code be modified to calculate frequency based on measuring the period for a fixed number of pulses? Say 50 or 100 pulses.
this does a running average length set by RUNNING_SIZE
// ESP32 - running average RUNNING_SIZE - measure pulse frequency using rising edge interrupts
// updated for Arduino ESP32 core 3.0 see
// https://docs.espressif.com/projects/arduino-esp32/en/latest/api/timer.html
// CYD TFT setup
#include <Arduino.h>
#include <TFT_eSPI.h>
#define RUNNING_SIZE 1000 // size of running average buffer
TFT_eSPI tft;
#define risePin 22
// note using hw_timer_t or micros() give similar values
hw_timer_t *Timer1_Cfg = NULL; // timer object
// pulse timing data
volatile uint64_t riseTime, period;
volatile uint64_t averageFreq = 0, frequency;
// rising edge interrupt calculate period uSec
void IRAM_ATTR rising() {
static uint64_t runningData[RUNNING_SIZE] = { 0 };
uint64_t rise = timerReadMicros(Timer1_Cfg);
period = rise - riseTime;
riseTime = rise;
frequency = 100000000ul / period;
if (averageFreq == 0) {
averageFreq = frequency; // initialize
for (int i = 0; i < RUNNING_SIZE; i++) runningData[i] = frequency;
} else {
averageFreq = (frequency + averageFreq) / 2; // running average
for (int i = 0; i < RUNNING_SIZE - 1; i++)
averageFreq += runningData[i] = runningData[i + 1];
runningData[RUNNING_SIZE - 1] = frequency;
averageFreq /= RUNNING_SIZE;
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.printf("\n\nESP32 frequency running average - measure pin %d pulse information \n", risePin);
//Timer1_Cfg = timerBegin(0, 80, true); // API 2.x setup timer for 1uSec
if ((Timer1_Cfg = timerBegin(10000000)) == NULL) // API 3.0 setup timer for 1uSec
Serial.println("timerBegin Failed!!");
Serial.print("timerBegin() OK frequenmcy ");
Serial.println(timerGetFrequency(Timer1_Cfg));
// setup interrput routines - connect signal to following pins
attachInterrupt(digitalPinToGPIONumber(risePin), rising, RISING); // detect rising edge on pin 16
Serial.println("displaying results");
// setup CYD TFT display
Serial.println("\ninitialise TFT");
// Initializing tft_espi
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextSize(3);
}
void loop() {
static char text[40] = { 0 };
static unsigned long int timer = millis();
// if .1 seconds have elapsed since last reading print results
if (millis() - timer > 100) {
timer += 100;
if (text[0] != 0) { // erase old text ?
tft.setCursor(0, 0, 2);
tft.setTextColor(TFT_BLACK, TFT_BLACK);
tft.println(text);
}
// display frequency to serial
static int i;
Serial.printf("%d %d %.1f %.1f\n", i++, (int)period, frequency / 100.0, averageFreq / 100.0);
// display frequency to TFT
sprintf(text, "\n\nfreq %.1fHz ", averageFreq / 100.0);
tft.setCursor(0, 0, 2);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.println(text);
}
}
for a RUNNING_SIZE=1000 results for a frequency change from 100Hz to 1.1kHz results plotted every 0.1seconds
1 10000 100.0 100.0
2 10000 100.0 100.0
3 10000 100.0 100.0
4 10000 100.0 100.0
5 10000 100.0 100.0
6 909 1100.1 195.3
7 909 1100.1 305.3
8 909 1100.1 415.4
9 909 1100.1 525.4
10 909 1100.1 635.5
11 911 1097.7 745.5
12 909 1100.1 855.6
13 909 1100.1 965.7
14 909 1100.1 1075.7
15 909 1100.1 1100.0
16 909 1100.1 1100.0
17 909 1100.1 1100.0
18 909 1100.1 1100.0
plot to results
with a buffer of 1000 results change from 100Hz to 1.1kHz is shown to take a second
you could use a more sophisticated smoothing algorithm, e.g. weighted running average
Your solutions seem to feature updating the display at 0.1 seconds (10 hz). That is not desirable for this application.
My first idea was to just measure the counts over a fixed time frame (1 second) and multipy the result by 15 to get RPM. But that method doesnât count whole pulses (ie there might be 55.5 pulses in 1.000 seconds) so there is built-in variability with this method.
My next idea is to measure the period (T) for N pulses. Upon the rising or falling edge of the first pulse, start a high-resolution timer. On the Nâth pulse, stop the timer, read the timer. If N = 10, then 600 / T = RPM. Print RPM, then reset the timer and start again. In my case, N = 40 represents 10 rotations (4 pulses per rotation). So i am automatically creating an average using 10 rotations. At 500 RPM, T = 1.2 seconds. So the display is updated every 1.2 seconds (not 0.1 seconds). At 3200 RPM, the display is updated every 0.19 seconds. The update interval is variable, and is a consequence of the method, and it is not pre-determined.
I am taking these code examples and trying to impliment this method, but it is not easy to understand this programming language (which is C or C++ I guess). In the past I have written large programs with thousands of lines of code using power basic, but I struggle to understand the formatting used by C.
Is millis() the real-time value of the timer?
Can I treat it like a variable? Can I do this (or how would I do this):
======================
// this assumes pulsecounter automatically increments on rising edge of GPIO-22
targetc = 40 // define targetc to be the target count value
loop-here // a location in the code
pulsecounter = 0 // set pulsecounter to 0, the ESP32 automatically increments pulsecounter
while pulsecounter = 0
wend
// above while-wend loop is waiting for pulsecounter to equal 1
millis() = 0 // set hi-res counter to zero
while pulsecounter < targetc
period = millis()
wend
// above while-wend loop is waiting for pulsecounter to equal targetc + 1, period contains last timer value
rpm = 600000 / period // this assumes period has units of milli-seconds
// rpm can be (probably should be) integer variable, not real
print rpm
goto loop-here
=====================
The above is pseudo-code. How to code it for real?
if ((Timer1_Cfg = timerBegin(10000000)) == NULL) // API 3.0 setup timer for 1uSec
The format for timerBegin is to spec the frequency the timer will operate. It seems to me that the above statement will operate the timer at 10 mhz. But for 1 uSec operation (1 micro-second) the frequency should be set to 1 mhz, not 10. Can someone explains how this works?
If I want the timer to report in milli-seconds (ie - 1 / 1000 of a second) wouldnât this be the correct statement:
if ((Timer1_Cfg = timerBegin(1000)) == NULL) // API 3.0 setup timer for 1mSec
?
I have compiled and uploaded the code for post #48 into the CYD.
I have changed the update from 100 ms to 1000 ms just so there would be fewer lines printed to the serial monitor.
In regard to the serial monitor, I note that the code shows that there should be some lines printed initially in the setup part of the code:
Serial.printf("\n\nESP32 frequency running average - measure pin %d pulse information \n", risePin);
Serial.println("timerBegin Failed!!");
Serial.print("timerBegin() OK frequenmcy ");
Serial.println(timerGetFrequency(Timer1_Cfg));
Serial.println("displaying results");
I am not seeing those lines printed to the serial monitor.
I am seeing the line being printed in the loop part of the code.
edit: apparently it has been seen before that serial print inside setup is not working or seen. The following code has been suggested and has been seen to work:
Serial.begin(115200);
while (!Serial && (millis() < 5000)) ;
delay(1000);
but Iâve added it and still not seeing the lines being printed.
But changing the delay(1000) to delay(5000) does work - I see the serial.print lines. The while(serial) line apparently does nothing.
Yes, it starts as soon as code starts to run.
Yes but it needs to be an unsigned long variable type, and it is very not recommended to try and change its value. In fact I am not sure that would work anyway.
No it will not work, because you should never use absolute numbers of millis() ticks as they only happen once from the start of running the code. You need to use:-
target = millis() + 40;
The targetc is target-count. I am incrementing a counter each time the rising-edge interrupt happens. counter is incremented in the interrupt handling routing and compared to the target-count inside the IHR. I am assuming the counter is only incremented once per rising edge, and that rising edges are reliably detected by the ESP32 hardware. I expect these rising edges to happen at a rate of 30 to 350 times per second. See my next post below for a related question on the IHR.
This is my proposed interrupt handling routine.
// was - rising edge interrupt calculate period uSec
// now - rising edge interrupt will increment pulsecount if pulsecount < 41
// the first time this interrupt happens, pulsecount will be 0, so reset hw_timer
//
void IRAM_ATTR rising() {
//static uint64_t runningData[RUNNING_SIZE] = { 0 };
// Array to store frequency data
//uint64_t rise = timerReadMillis(Timer1_Cfg); // Read current time in microseconds
//period = rise - riseTime; // Calculate the period between rising edges
//riseTime = rise; // Update the last rising time
//frequency = 100000 / period; // Calculate frequency in Hz
if (pulsecount = 0) {
void timerRestart(hw_timer_t * Timer1_Cfg);
}
if (pulsecount < 41) {
pulsecount += 1;
}
if (pulsecount = 41) {
period = timerReadMillis(Timer1_Cfg);
pulsecount = 0;
}
}
I expect pulsecount and period variables to be accessible in other parts of the sketch and not local to the IHR, not sure how to do that.
I expect that this IHR is reliably executed when the hardware detects a rising edge on the appropriate GPIO, and only executed once per rising edge. I expect that the first time the IHR is called (and periodically after that) the condition (pulsecount = 0) will be satisfied and the timer Timer1_Cfg will be set to 0.
I expect pulsecount to increment unless itâs current value is 41. I expect that if pulsecount is 40, that it will be incremented to 41, and then this will satisfy the next condition (pulsecount = 41) at which point the variable âperiodâ will be read from the timer (Timer1_Cfg) AND THEN pulsecount will be set to 0.
Will this work as written?
Ok, this works. The output formatting might need a little work.
// ESP32 - engine tachometer - measure pulse frequency and calculate RPM
// using rising edge interrupts
// update display only when RPM changes
// updated for Arduino ESP32 core 3.0 see
// https://docs.espressif.com/projects/arduino-esp32/en/latest/api/timer.html
// CYD TFT setup
#include <Arduino.h>
#include <TFT_eSPI.h>
TFT_eSPI tft;
#define risePin 22
// note using hw_timer_t or micros() give similar valuesTimer1_Cfg
hw_timer_t *Timer1_Cfg = NULL; // timer object
// pulse timing data
volatile uint16_t period; // time measurement period for rotations in milli-seconds
volatile uint16_t oldperiod;
volatile uint16_t pulsecount = 0;
volatile uint16_t rpm;
volatile uint16_t rotations = 10; // number of rotations for rpm calculation
volatile uint16_t pprotation = 4; // number of pulses per rotation
volatile uint32_t tval = rotations * 60 * 1000; // temp value for RPM calculation
volatile uint16_t ppcperiod = 1 + rotations * pprotation; // pulses per calculation period
void IRAM_ATTR rising() {
if (pulsecount == 0) { // first pulse detected so reset period counter
timerRestart(Timer1_Cfg);
}
if (pulsecount < ppcperiod) { // increment pulsecount until pulses-per-count-period is reached
pulsecount ++;
}
if (pulsecount == ppcperiod) { // pulses-per-count-period has been reached, read the count period from timer
period = timerReadMillis(Timer1_Cfg);
pulsecount = 0; // next count period will begin, so reset pulsecount to zero
}
}
void setup() {
Serial.begin(115200);
// while (!Serial && (millis() < 5000)) ; // I couldn't get this to work, so fixed delay was increased to 5 seconds
delay(5000);
Serial.printf("\n\nESP32 Pulse Counter / Tachometer (measures %d rotations @ %d pulses per rev) - GPIO pin %d pulse information \n", rotations, pprotation, risePin);
//Timer1_Cfg = timerBegin(0, 80, true); // API 2.x setup timer for 1uSec
if ((Timer1_Cfg = timerBegin(100000)) == NULL) // API 3.0 setup timer for 1uSec
Serial.println("timerBegin Failed!!");
Serial.print("timerBegin() OK frequency ");
Serial.println(timerGetFrequency(Timer1_Cfg));
// setup interrput routines - connect signal to following pins
attachInterrupt(digitalPinToGPIONumber(risePin), rising, RISING); // detect rising edge on pin risePin
Serial.println("displaying results: period(ms) RPM");
// setup CYD TFT display
Serial.println("\ninitialise TFT");
// Initializing tft_espi
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextSize(3);
}
void loop() {
static char text[40] = { 0 };
static char text1[40] = { 0 };
static char text2[40] = { 0 };
if (oldperiod != period) {
oldperiod = period;
rpm = tval / period;
if (text[0] != 0) { // erase old text ?
tft.setCursor(0, 0, 2);
tft.setTextColor(TFT_BLACK, TFT_BLACK);
tft.println(text);
}
// display frequency to serial
static int i;
Serial.print(i++);
Serial.print(" ");
Serial.print(period);
Serial.print(" ");
Serial.println(rpm);
// Serial.println(pulsecount);
// display frequency to TFT
sprintf(text1, "Period ms: %d \n", period);
sprintf(text2, "RPM: %d \n", rpm);
tft.setCursor(0, 0, 2);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.println(text1);
tft.println(text2);
tft.println(text);
}
}
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.

