Multiple flow meters on 1 mcu

Yes you would but you don't need to count them. If a state change does not happen for a while then there is no flow

Make it like a watch dog timer.

Have one ESP32 task counting up and checking if the count reaches X value but have every pulse coming in from the flow meter reset the count to zero.

4 spots to monitor 4 watchdog timer tasks

void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 86300; // 1st time update in 100 counts
  int maxNonMQTTresponse = 60;
  for (;;)
  {
    vTaskDelay( 1000 );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
}

From the code I posted earlier. A watchdog task.

Heck, if you look a bit closer you'll see the watchdog is reset by a interrupt service routine. In essence, the code posted has all the elements you are looking for, you just have to rearrange and you don't have to use the PCNT API. them.

How does the PCNT affect the main program. does it still use interrupts? from API docs it looks like this can support 8 pulse input. is this simultaneously or 1 at a time.
how would i use the _ctrl.pin.

I found a example of the pulse counter that is less involved but still not confident enough to use it yet.

How can i convert this standard interrupt,

#define LED_BUILTIN 2
#define SENSOR 27
 
long currentMillis = 0;
long previousMillis = 0;
int interval = 1000;
boolean ledState = LOW;
float calibrationFactor = 4.5;
volatile byte pulseCount;
byte pulse1Sec = 0;
float flowRate;
unsigned int flowMilliLitres;
unsigned long totalMilliLitres;
 
void IRAM_ATTR pulseCounter()
{
 pulseCount++;
}
 
void setup()
{
 Serial.begin(115200);
 
 pinMode(LED_BUILTIN, OUTPUT);
 pinMode(SENSOR, INPUT_PULLUP);
 
 pulseCount = 0;
 flowRate = 0.0;
 flowMilliLitres = 0;
 totalMilliLitres = 0;
 previousMillis = 0;
 
 attachInterrupt(digitalPinToInterrupt(SENSOR), pulseCounter, FALLING);
}
 
void loop()
{
 currentMillis = millis();
 if (currentMillis – previousMillis > interval) {
 
 pulse1Sec = pulseCount;
 pulseCount = 0;
 
 // Because this loop may not complete in exactly 1 second intervals we calculate
 // the number of milliseconds that have passed since the last execution and use
 // that to scale the output. We also apply the calibrationFactor to scale the output
 // based on the number of pulses per second per units of measure (litres/minute in
 // this case) coming from the sensor.
 flowRate = ((1000.0 / (millis() – previousMillis)) * pulse1Sec) / calibrationFactor;
 previousMillis = millis();
 
 // Divide the flow rate in litres/minute by 60 to determine how many litres have
 // passed through the sensor in this 1 second interval, then multiply by 1000 to
 // convert to millilitres.
 flowMilliLitres = (flowRate / 60) * 1000;
 
 // Add the millilitres passed in this second to the cumulative total
 totalMilliLitres += flowMilliLitres;
 
 // Print the flow rate for this second in litres / minute
 Serial.print("Flow rate: ");
 Serial.print(int(flowRate)); // Print the integer part of the variable
 Serial.print("L/min");
 Serial.print("\t"); // Print tab space
 
 // Print the cumulative total of litres flowed since starting
 Serial.print("Output Liquid Quantity: ");
 Serial.print(totalMilliLitres);
 Serial.print("mL / ");
 Serial.print(totalMilliLitres / 1000);
 Serial.println("L");
 }
}

into the the esp PCNT,

#include "driver/pcnt.h"


// the number of the LED pin
const int pwmFanPin1 = 21;  // 21 corresponds to GPIO21?

// setting PWM properties
const int freq = 1;
const int ledChannel = 0;
const int resolution = 8;

xQueueHandle pcnt_evt_queue;   // A queue to handle pulse counter events
pcnt_isr_handle_t user_isr_handle = NULL; //user's ISR service handle

/* A sample structure to pass events from the PCNT
   interrupt handler to the main program.
*/
typedef struct {
  int unit;  // the PCNT unit that originated an interrupt
  uint32_t status; // information on the event type that caused the interrupt
  unsigned long timeStamp; // The time the event occured
} pcnt_evt_t;

/* Decode what PCNT's unit originated an interrupt
   and pass this information together with the event type
   and timestamp to the main program using a queue.
*/
static void IRAM_ATTR pcnt_intr_handler(void *arg)
{
  unsigned long currentMillis = millis(); //Time at instant ISR was called
  uint32_t intr_status = PCNT.int_st.val;
  int i = 0;
  pcnt_evt_t evt;
  portBASE_TYPE HPTaskAwoken = pdFALSE;


  for (i = 0; i < PCNT_UNIT_MAX; i++) {
    if (intr_status & (BIT(i))) {
      evt.unit = i;
      /* Save the PCNT event type that caused an interrupt
         to pass it to the main program */
      evt.status = PCNT.status_unit[i].val;
      evt.timeStamp = currentMillis;
      PCNT.int_clr.val = BIT(i);
      xQueueSendFromISR(pcnt_evt_queue, &evt, &HPTaskAwoken);
      if (HPTaskAwoken == pdTRUE) {
        portYIELD_FROM_ISR();
      }
    }
  }
}


/* Initialize PCNT functions for one channel:
    - configure and initialize PCNT with pos-edge counting
    - set up the input filter
    - set up the counter events to watch
   Variables:
   UNIT - Pulse Counter #, INPUT_SIG - Signal Input Pin, INPUT_CTRL - Control Input Pin,
   Channel - Unit input channel, H_LIM - High Limit, L_LIM - Low Limit,
   THRESH1 - configurable limit 1, THRESH0 - configurable limit 2,
*/
void pcnt_init_channel(pcnt_unit_t PCNT_UNIT, int PCNT_INPUT_SIG_IO , int PCNT_INPUT_CTRL_IO = PCNT_PIN_NOT_USED, pcnt_channel_t PCNT_CHANNEL = PCNT_CHANNEL_0, int PCNT_H_LIM_VAL = 19, int PCNT_L_LIM_VAL = -20, int PCNT_THRESH1_VAL = 50, int PCNT_THRESH0_VAL = -50 ) {
  /* Prepare configuration for the PCNT unit */
  pcnt_config_t pcnt_config;
  // Set PCNT input signal and control GPIOs
  pcnt_config.pulse_gpio_num = PCNT_INPUT_SIG_IO;
  pcnt_config.ctrl_gpio_num = PCNT_INPUT_CTRL_IO;
  pcnt_config.channel = PCNT_CHANNEL;
  pcnt_config.unit = PCNT_UNIT;
  // What to do on the positive / negative edge of pulse input?
  pcnt_config.pos_mode = PCNT_COUNT_INC;   // Count up on the positive edge
  pcnt_config.neg_mode = PCNT_COUNT_DIS;   // Keep the counter value on the negative edge
  // What to do when control input is low or high?
  pcnt_config.lctrl_mode = PCNT_MODE_REVERSE; // Reverse counting direction if low
  pcnt_config.hctrl_mode = PCNT_MODE_KEEP;    // Keep the primary counter mode if high
  // Set the maximum and minimum limit values to watch
  pcnt_config.counter_h_lim = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim = PCNT_L_LIM_VAL;

  /* Initialize PCNT unit */
  pcnt_unit_config(&pcnt_config);
  /* Configure and enable the input filter */
  pcnt_set_filter_value(PCNT_UNIT, 100);
  pcnt_filter_enable(PCNT_UNIT);
  pcnt_event_enable(PCNT_UNIT, PCNT_EVT_ZERO);
  pcnt_event_enable(PCNT_UNIT, PCNT_EVT_H_LIM);
  // pcnt_event_enable(PCNT_UNIT, PCNT_EVT_L_LIM);

  /* Initialize PCNT's counter */
  pcnt_counter_pause(PCNT_UNIT);
  pcnt_counter_clear(PCNT_UNIT);
  /* Register ISR handler and enable interrupts for PCNT unit */
  pcnt_isr_register(pcnt_intr_handler, NULL, 0, &user_isr_handle);
  pcnt_intr_enable(PCNT_UNIT);

  /* Everything is set up, now go to counting */
  pcnt_counter_resume(PCNT_UNIT);
  pcnt_counter_resume(PCNT_UNIT_1);
}

/* Count RPM Function - takes first timestamp and last timestamp,
  number of pulses, and pulses per revolution */
int countRPM(int firstTime, int lastTime, int pulseTotal, int pulsePerRev) {
  int timeDelta = (lastTime - firstTime); //lastTime - firstTime
  if (timeDelta <= 0) { // This means we've gotten something wrong
    return -1;
  }
  return ((60000 * (pulseTotal / pulsePerRev)) / timeDelta);
}
void setup() {
  ledcSetup(ledChannel, freq, resolution);
  // attach the channel to the GPIO to be control
  ledcAttachPin(21, ledChannel);
  ledcWrite(ledChannel, 10); // 1Hz PWM with duty cycle of 10/255
}

void loop()
{
  /* Initialize PCNT event queue and PCNT functions */
  pcnt_evt_queue = xQueueCreate(10, sizeof(pcnt_evt_t));
  
  pcnt_init_channel(PCNT_UNIT_0, 4); // Initialize Unit 0 to pin 4

  int RPM0 = -1; // Fan 0 RPM
  int lastStamp0 = 0; //for previous time stamp for fan 0


  pcnt_evt_t evt;
  portBASE_TYPE res;
  for (;;) {
    /* Wait for the event information passed from PCNT's interrupt handler.
      Once received, decode the event type and print it on the serial monitor.
    */
    res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS);
    if (res == pdTRUE) {
      printf("Event PCNT unit[%d]; Status: %u\n", evt.unit, evt.status);
      if (evt.unit == 0) { // Fan 0 - TURN THIS BLOCK INTO A FUNCTION THAT TAKES THE FAN OBJECT
        if (lastStamp0 == 0) {
          lastStamp0 = evt.timeStamp;
        }
        RPM0 = countRPM(lastStamp0, evt.timeStamp, 20, 2);
        if (RPM0 == -1) {
          printf("RPM Calc error detected!\n");
          continue;
        }
        lastStamp0 = evt.timeStamp;
      }
      printf("Fan 0 RPM: %d", RPM0);
    }
  }
  if (user_isr_handle) {
    //Free the ISR service handle.
    esp_intr_free(user_isr_handle);
    user_isr_handle = NULL;
  }
}

You don't need the PCNT it is hard to learn and understand. It would be easier route for you to go the interrupt route, see my post # 22.

I'm sorry i still just don't understand.

Also what is this

 for (;;)

research will show that's an infinite for loop.

so what is a "tick" to vTaskDelay. is it a ms or an interrupt? what do you mean by have a task counting up. what is incrementing the count? im trying to understand how to use this logic for the flow meter. also, does vtask block the rest of the code?

i read this but it did not mention,

Reading about vTaskDelay will answer a lot of basic questions about vTaskDelay.

Yes, but im still trying to understand your logic. You know we are working with 4 flow meters, why counting up/ what is counting up. i dont get it

what happens after pulse resets counter to 0?

OK, so you don't get what's going on there.

This is a task that runs in its own thread that count up and if the count is reached the task will reset the CPU.

Do you understand what the task is doing and why, now?

The task can be checking for no flow, if their is no flow the count will keep going up and then generate an alarm or a no flow condition and then you do the no flow things.

void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
    int i = 0; // extract payload
    for ( i; i < length; i++)
    {
      x_message.payload[i] = ((char)payload[i]);
    }
    x_message.payload[i] = '\0';
    xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} //

That's an interrupt routine, ISR, when an interrupt occurs a parsing task is triggered. The interrupt would equal to your hall sensors sending pulses to a interrupt routine.

void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK = 0;
    xSemaphoreGive( sema_mqttOK );
  }
} // void fparseMQTT( void *pvParameters )
//////

The parsing task which is triggered by the interrupt clears the mqttOK count. Now remember back to the mqttwatchdog task? That task keeps counting up. That would represent no flow, if the MQTTok count is not cleared after a time then the no flow things can be done. But if the interrupt keeps triggering the parser and the parser keeps on resetting the count, the flow continues.

Okay im going to try see if that sinks in. btw there should only be flow periodically. would it make more sense if to count up when there is flow?

Yea, you could do that oo.

I just have a few more questions,

How many tasks can i create?

should i have a different task for each flow meter?

do these tasks use a different cores on the processor?

If i use this task method, would this mean i would get useable readings from the flow meters?

You can create lots of tasks.

I would.

Task can be assigned to the same core or to different cores, except when using WiFi/BLE.

That would depend on how you write your code.

Because im at a crossroad right now. I am just using them to detect a no flow situation, However if i could get readings from them that were useful that would be a bonus.

As much as i like and want to understand the ESP functions. i don't want to go through all the trouble of writing a sophisticated program to handle reading/counting pulses if they are useless anyways.

forgive my knowledge.

I think the whole point of using interrupts is for accuracy? how accurate would the PCNT be compared to interrupt.

The PCNT is an interrupt driven module that can count pulses. Anyways the PCNT seems to be above your skill level try a different idea. , Sorry to had wasted your time. Good luck.

No i said i don't want to waste my time if the pulses are not useable because of some accuracy problem because i didn't use interrupt.

I found an example that uses PCNT for a flow meter but it wont compile in arduino ide.

will you write a program to start with that could support the flow meter. there is a flow meter example in post 23.

The code in the post I made earlier has how to setup the PCNT in the Arduino IDE setup() function that handles the issue of set up and using the ESP32 initializer structures. issue.

So i tried to compile the code you posted earlier i was going to try to delete everything from the code i could so that i can get a base to start with and understand it better...

It wont compile it says the for loop has no effect,

void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
    int i = 0; // extract payload
    for ( i; i < length; i++)
    {
      x_message.payload[i] = ((char)payload[i]);
    }
    x_message.payload[i] = '\0';
    xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////

what i need is a simple version that just counts pulses from a flow meter. it dont need wifi or unit conversions. though ml conversion would be nice.

That code complies and runs for me. I do not know why it does not work for you.

About writing code just for you, I don't do that.