espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.58k stars 7.4k forks source link

Not able to make accurate Mains Frequency Counter with ESP32 #4325

Closed MG-Tawfeek closed 4 years ago

MG-Tawfeek commented 4 years ago

OptoCoupler_Circuit

This is the circuit I'm using to measure Frequency of mains , it's as simple as it can be but with ESP32 , for some reason .. I'm getting inconsistent data .

This same circuit works perfectly with great accuracy with Arduino Uno , You just have to connect the OptoCoupler to arduino instead of ESP32

The Code I'm using to test this is a simple while loops to detect high/low cycles , and again I'm getting great accuracy from Arduino . When using ESP32 (devKit) , : 1-If I connect the Power and GND of the PC817/EL817 from ESP32 -> lots of wrong values 2-If I power the PC817 from an External Power source and share a common ground with esp32, it gets better , but occasionally I get Zero or half the value This is a sample of the value I get Screenshot from 2020-09-05 10-41-34

and those are the Zeros and Half Values I get

Screenshot from 2020-09-04 07-07-08

I use 2 sketches to debug this -first one is to see the stability of reading per minute , just a state machine sketch

`` int count = 0; unsigned long lastEntry; int stat = 0;

define INPUTPIN 34

void setup() {

Serial.begin(115200); pinMode(INPUTPIN, INPUT);

//Serial.printf("ESP TIMER : %jd \n", esp_timer_get_time()); }

void loop() {

noInterrupts(); count = 0; lastEntry = millis(); while (millis() < lastEntry + 1000) {

bool inputSignal = (digitalRead(INPUTPIN));
switch (stat) 
{
  case 0:
    if (inputSignal) 
    {
      count++;
      stat = 1;
    }
    break;

  case 1:
    if (!inputSignal) 
      stat = 0;
    break;

  default: 
    break;
}

} Serial.print("Frequency : "); Serial.println(count);

interrupts(); delay(500); }

The 2nd Sketch is to measure the Cycle length

``int FC_PIN = 34 ; void setup() { Serial.begin(115200); pinMode(FC_PIN,INPUT); noInterrupts() ; delay(1000); }

void loop() { char buf[8] = ""; volatile int64_t start_time, end_time, cycle_duration ; // int64_t end_time ; // int64_t cycle_duration ;

// if(digitalRead(FC_PIN)== HIGH)
//   continue ;

//noInterrupts() ;
while(digitalRead(FC_PIN) != LOW)
// while(analogRead(FC_PIN) > 0)
  {
    // If we get here while the phase is high .. wait till the phase is LOW
  }

while(digitalRead(FC_PIN) == LOW)
// while(analogRead(FC_PIN) == 0)
  {
    // Wait till the LOW Phase ends so we start the timer
  }

start_time = esp_timer_get_time(); // timer in microSeconds

while(digitalRead(FC_PIN) == HIGH)
// while(analogRead(FC_PIN) > 0)
  {
  // another wait till the  HIGH phase ends 
  }

while(digitalRead(FC_PIN) == LOW)
// while(analogRead(FC_PIN) == 0)
{
  // another wait till we reach the high phase again
}

end_time = esp_timer_get_time() ;

cycle_duration  =  end_time - start_time ;

/////// Inaccurate .. multiple wrong readings !
// int64_t high = pulseInLong(FC_PIN, HIGH);
// int64_t low = pulseInLong(FC_PIN, LOW);
// // int64_t 
// cycle_duration = high + low ;

//sprintf(buf, "%.3f", (cycle_duration/1000.0)) ;
// dtostrf((cycle_duration/1000.0), 6, 3, buf);
//client.publish("test/dk-2", buf);

//interrupts();
Serial.printf("Start_Time : %jd \t End_Time : %jd \t", start_time, end_time);
Serial.printf("Duration for 1 Cycle is : %.3f mSec\n", cycle_duration/1000.0);

//start_time = end_time = cycle_duration = 0 ;

delay(500);

}

hopefully I'm missing something here I've seen lots of people telling me to go for Ledc , PCNT , RMT .. and so on All of those are fine If I'm getting a constant data but more accuracy is needed , but now it's just inconsistent data and why I'm getting either the zero (very low value) or nearly half the reading ?

Thanks

lbernstone commented 4 years ago

This forum is for issues with the repository codebase. Please ask general questions at https://esp32.com

MG-Tawfeek commented 4 years ago

I actually think this is a hardware problem in the chip .

lbernstone commented 4 years ago

So post it somewhere the engineers might see it. This repository is for the arduino-esp32 codebase, which is an API shim. What I will tell you is the same as others- the esp32 is not good for bitbanging. It is a multi-tasking OS. Use the pcnt peripheral, like in https://esp32.com/viewtopic.php?f=19&t=17018

atanisoft commented 4 years ago

I actually think this is a hardware problem in the chip .

Highly unlikely. You are trying to do timing sensitive operations in a way that is guaranteed to fail due to the RTOS nature of the ESP32. There WILL be other tasks running on the system that WILL interrupt your task. You have a few options to work with the RTOS nature of the ESP32 and you have already mentioned them. As @lbernstone has mentioned, this should have been posted on the esp32 forums.

MG-Tawfeek commented 4 years ago

@Ibernstone

OK , I was under the impression that any thing related to Arduino should be posted here

Highly unlikely.

I think it's very likely this behaviour is hardware related . -Measuring a cycle of nearly 20 mSec by a 240Mhz MCU , I wouldn't call this an intensive operation -Original Code was a FreeRtos Task and we got the same behaviour The sketch and circuit above is the bare minimum to illustrate the issue

but , Thanks anyway

atanisoft commented 4 years ago

@MG-Tawfeek even in a FreeRTOS task it will be preempted by other tasks with higher priority. The only way to avoid that would be using an isr and track the timing inside the isr with the Arduino side (or FreeRTOS task) poll the frequency as reported inside the isr. Keep in mind that the setup() and loop() methods run in a FreeRTOS task with priority 1 (just above idle) and all other tasks in the system are at much higher priorities.

MG-Tawfeek commented 4 years ago

@atanisoft

I closed the issue but Just to clarify why I keep saying this is a hardware issue :

One of the tests I made was to connect 2 ESPs , One to Generate a square wave and the other to receive it Only 1 Wire is connected between the 2 ESPs in that case .

The result ? .. the Kind of accuracy that you would expect from a 240Mhz MCU reading 50Hz , not a single faulty reading I also started WIFI in that test , and the biggest difference in reading was few MicroSeconds .

When you connect an ESP32 to an External Source and have to shared Power or GND , this issue appears
Appeared with the original circuit we had and also appeared on this test circuit . Really we tried all kinds of Code including the interrupts methods , but it's always like that If you look at the data , you'll find that this can't be the result of other Tasks running , it doesn't make sense to get like Fixed 2 wrong values .. always !

Thanks for your time

atanisoft commented 4 years ago

The result ? .. the Kind of accuracy that you would expect from a 240Mhz MCU reading 50Hz , not a single faulty reading I also started WIFI in that test , and the biggest difference in reading was few MicroSeconds .

That would mostly rule out a hardware issue as the hardware (esp32) is able to more or less accurately read the signal you are feeding it.

When you connect an ESP32 to an External Source and have to shared Power or GND , this issue appears Appeared with the original circuit we had and also appeared on this test circuit .

Your test circuit is nearly identical to the circuit used in almost every model railroad DCC interface which converts the AC (8kHz roughly) like signal to an MCU friendly high/low pulse pattern. In the case of these it uses attachInterrupt(pin, function, CHANGE); though with the function being very basic to read the state of the pin and record the system time (micros() or similar). A sketch that one user sent to me that implements this on the esp8266 (and would work on esp32) is: https://gist.github.com/Beherith/9d090f64437d6ca721a76e70a097a664. This code is similar to your second sketch.

DougArmstrong commented 4 years ago

This could be an interface issue. I built a system that measures the speed of a 3phase wind generator and it works great up to over 1KHZ so 50hz is nothing for ESP32.

Verify you are using Pins that support pullups or add external pull ups to be sure. I have used an opto isolator in the past to go from HV to logic voltage, this solves any ground reference issues. image

Finally note if you are using an ISR (my system does) that on some pins the RISING/FALLING mode does not work properly. There is a good write up on that here: https://github.com/espressif/arduino-esp32/issues/1111#issuecomment-659694716

In my implementation, I use a FRTOS queue to write timer values from inside the ISR to the Queue and then unpack those in the foreground. Here is some code that might work for you. Create the RPM Task in Free RTOS or just run it in the main loop. NOTE that Pin 34 is not a good ISR Pin but pin 17 works great, the discussion above goes into all the detail you will need.

Hope this is helpful.

// =============================================== // WindGenMPPT Rev 1 (reworked) Interface Pins // =============================================== //#define RPM_PIN 34 // RPM Input Pin (No Internal Pull Up)

define RPM_PIN 17 // RPM Input Pin (Note PCB Rev 2.2 Jumpered to Pin 34)

// =============================================== // RPM Setup Code - Pass in pin# for interrupt // source. We config the pin and install the ISR // // void RPM_Setup(int Pin) // // interrupt: the number of the interrupt(int) // pin : the pin number(Arduino Due, Zero, MKR1000 only) // ISR : the ISR to call when the interrupt occurs; // this function must take no parameters and return nothing. // This function is sometimes referred to as an interrupt service routine. // mode: defines when the interrupt should be triggered.Four constants are predefined as valid values : // // LOW to trigger the interrupt whenever the pin is low, // CHANGE to trigger the interrupt whenever the pin changes value // RISING to trigger when the pin goes from low to high, // FALLING for when the pin goes from high to low. // // The Due, Zero and MKR1000 boards allows also : // HIGH to trigger the interrupt whenever the pin is high. // // =============================================== void RPM_Setup(int Pin) {

/* Create a queue capable of containing 10 unsigned long values. */
RPM_Queue = xQueueCreate(RPM_QUEUE_LENGTH, RPM_ITEM_SIZE);

RPM_Timer = timerBegin(0, 80, true);               // this returns a pointer to the hw_timer_t global variable
                                    // 0 = first timer
                                    // 80 is prescaler so 80MHZ divided by 80 = 1MHZ signal ie 0.000001 of a second                                     // true - counts up
timerStart(RPM_Timer);

pinMode(Pin, INPUT_PULLUP);                                         // Pull Up the pin and make it an input

//pinMode(Light, OUTPUT);

// Be sure the ISR is fully initialized by this point.
attachInterrupt(digitalPinToInterrupt(Pin), RPM_ISR, CHANGE);       // if changed must change ISR filter

}

// =============================================== // RPM Interuupt Service Routine // We measure the time between pulses and do stuff // =============================================== void IRAM_ATTR RPM_ISR() {

static  uint64_t StartValue = 0;                // First interrupt value
BaseType_t xHigherPriorityTaskWoken;
uint32_t PeriodCount;
uint64_t TempVal;

xHigherPriorityTaskWoken = pdFALSE;

TempVal = timerRead(RPM_Timer);         // value of timer at interrupt
PeriodCount = (TempVal - StartValue);                   // period count between rising edges in 0.000001 of a second

if (PeriodCount > 500)
{
    xQueueSendToBackFromISR(RPM_Queue, &PeriodCount, &xHigherPriorityTaskWoken);
                                    // puts latest reading as start for next calculation
    StartValue = TempVal;

}

} // RPM_ISR // =============================================== // RPM Task - Does the math on numbers supplied // by the RPM ISR Queue // =============================================== void RPM_Task(void* Params) {

 int SampleCount = 0;                   // array index for samples
 uint64_t Interval = 0;                 // working time interval  
 uint32_t Qval;
 TickType_t xLastWakeTime;
 const TickType_t xFrequency = 250;     // every half second.

 // Config the RPM ISR Pin and ISR
 RPM_Setup(RPM_PIN);
 Log.notice(_Tag, "RPM ISR Setup Complete." CR);

 while (true)
 {
     //Serial.print("Samples =");
     Interval = 0;
     SampleCount = 0;
     xLastWakeTime = xTaskGetTickCount();                   // Get wake up ticks used to correct task delay next pass.
     while (xQueueReceive(RPM_Queue, &(Qval), (TickType_t)10) == pdPASS)
     {                                                      // Add up samples here so we do a single assign
        // Serial.print((long)Qval);
        // Serial.print(" ");
         Interval += Qval;
         SampleCount++;
     }
     if (SampleCount > 0)
     {
         RPM_AVG_Interval = Interval / SampleCount;             // RPM Average Interval in Microseconds
     }
     else
     {
         RPM_AVG_Interval = 0;
     }
     //Serial.print("SC =");
     //Serial.print(SampleCount);
     //Serial.print(" Avg=");
     //Serial.println(RPM_AVG_Interval);

     if (RPM_AVG_Interval > 0)                              // Protects for Div by 0
     {
         WindGen.RPM = (1000000 / RPM_AVG_Interval) * 4;
     } 
     else
     {
         WindGen.RPM = 0;
     }
     vTaskDelayUntil(&xLastWakeTime, xFrequency);           // Corrects for processing delay
 }

}

MG-Tawfeek commented 4 years ago

Thank you all for your help Problem was solved by inverting the signal ! have no idea why is that ..

Thread at esp32.com https://esp32.com/viewtopic.php?f=19&t=17188