sekdiy / FlowMeter

Arduino flow meter library that provides calibrated liquid flow and volume measurement with flow sensors.
MIT License
104 stars 42 forks source link

Question about the sensor fm-hl3012 #25

Closed khseal closed 2 years ago

khseal commented 2 years ago

I use a sensor fm-hl3012 in a coffee maker. Here is the datasheet https://datasheet.lcsc.com/szlcsc/1912111437_EPT-TECH-FM-HL3012_C379915.pdf Based on the datasheet, my settings are FlowSensorProperties HL3012 = {0.50f, 76.0f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; Are the settings correct? When I check the coffee maker without filters, the measurements are approximately correct. When I insert the filter even without coffee. Measurements are approximately doubled. Why such big differences in measurements? Need a more accurate calibration?

khseal commented 2 years ago

I have only one answer. Most likely this is due to the fact that in coffee machines there is a thermoblock, and in a coffee maker there is a boiler, and perhaps when the pressure is higher, it pumps more water into the boiler ...

sekdiy commented 2 years ago

Hi,

what coffee maker is that sensor in (if you don't mind revealing)?

Based on the datasheet, my settings are FlowSensorProperties HL3012 = {0.50f, 76.0f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; Are the settings correct?

These values make sense, but I don't know that sensor.

The datasheet says it's giving 1600 pulses/min at a flow rate of 0.475 l/min (when mounted at 0°).

That's 1600/60/0.475 ≈ 56,14 pulses/s/l/min (k-Factor). The high number would be around 60 pulses/s/l/min for 270° mounting.

Did I read that right (I might have misunderstood the datasheet due to language barrier)?

How did you arrive at 0.5 l and 76 pulses/s/l/min?

According to the datasheet, it is rated for less than 760ml/min max.

Can you confirm that you are below that flow rate?

Overall, the sensor seems comparable to the coffee maker sensor I mention in the documentation (see: https://github.com/sekdiy/FlowMeter/wiki/Sensors#fhksc), but with slightly different parameters.

When I check the coffee maker without filters, the measurements are approximately correct.

Here it wold help to know what you mean by filters.

Cheers!

sekdiy commented 2 years ago

Oh, just after posting, I realized:

I really might have misread the datasheet, because it clearly says 'based on 760ml test of per minute'.

That would lead to a k-Factor of 1600/60/0.76 ≈ 35,09 pulses/s/l/min (about half of your value).

khseal commented 2 years ago

How did you arrive at 0.5 l and 76 pulses/s/l/min?

On the sites where they are indicated sold F(Hz)=(76*Q)±3% Q=L/Min 4560 pulses/l. Rated flow: 75ml-570ml/min. This information may not be correct.. I thought, K factor is F.. No? You have calibrated the FHKSC to 0.4L. Although its maximum capacity is 1.2L? The datasheet says 1600 for 0.475 for 0 degrees. This table confused me. Therefore, I specified approximately 0.5... I tried 0.760ml. Didn't notice the difference. The correct settings would be: FlowSensorProperties HL3012 = {0.475f, 35.09f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; or FlowSensorProperties HL3012 = {0570, 35.09f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; or FlowSensorProperties HL3012 = {0.760f, 35.09f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; ? %)

Here it wold help to know what you mean by filters.

A filter for pouring coffee.

image

sekdiy commented 2 years ago

On the sites where they are indicated sold F(Hz)=(76*Q)±3% Q=L/Min 4560 pulses/l. Rated flow: 75ml-570ml/min.

Okay, I see... :)

I thought, K factor is F.. No?

Yes, although the term (and the symbol) don't seem to be very well defined.

You have calibrated the FHKSC to 0.4L. Although its maximum capacity is 1.2L?

The flow sensors in this series can support up to 1.2l, depending on nozzle diameter. I've used the 1mm nozzle in the calibration example – where the graph in the datasheet ends at 0.4l.

This table confused me. Therefore, I specified approximately 0.5...

Yes, it confused me as well... ;)

I tried 0.760ml. Didn't notice the difference.

That's alright, since you're currently only calibrating the k-factor, but not the m-factors.

Since you're leaving all coefficients a 1.0 (unity), no m-factor correction is applied (which essentially means that the capacity parameter isn't actually used).

The correct settings would be […]?

I recommend that you get the k-factor right at first.

You will then be able to figure out what your maximum flow rate is.

Without understanding your application (i.e. how much water the coffee maker will actually pump), I can't recommend a capacity yet (but like I stated above, it doesn't matter unless you also want to do a correction of non-linearities using m-factors).

A filter for pouring coffee.

Alright, so it's an espresso machine? :)

khseal commented 2 years ago

Alright, so it's an espresso machine? :)

Yes.

To correctly calculate everything. Need to run water through a filter in a measuring cup in one minute? On the basis of the received volume to do calibration?

sekdiy commented 2 years ago

On the basis of the received volume to do calibration?

Okay, so in order to do the calibration in a meaningful way, the machine must provide correct measurement values first.

You initially wrote that your measured volume nearly doubles when you use a filter. Is this still the case?

Overall, is your machine in a working condition and do you assume that pressure and flow rate (with filter and coffee) are good for making coffee?

To correctly calculate everything. Need to run water through a filter in a measuring cup in one minute?

I recommend that you do a couple of coffee extractions and carefully measure time (stopwatch) and mass/volume (precision scale), respectively.

The reason is that the flow rate will vary with degree of extraction. The first couple of seconds will provide less flow than the next (and so on) – but one minute is too long for e.g. an espresso.

In a good espresso machine, I'd aim for 1ml/s on average. This will lead to 25–30ml after 25–30s. With your FM-HL3012, that might correspond to around 1–3 pulses/second.

khseal commented 2 years ago

The problem is that I have two filters for one dose and a double espresso. It turns out that they will also have different calibrations? I have many filters and depending on the grind and the quality of the coffee I choose different filters... There are filters with valves and they all give slightly different values.

I wonder how coffee machines measure. In the same place, a different amount of coffee can be specified and a different grind. Everything affects the amount of liquid in the end.

sekdiy commented 2 years ago

It turns out that they will also have different calibrations?

I don't think it's that complicated.

You won't need multiple calibrations at the same time (although you might want to calibrate after some time in order to compensate for ageing).

After all, the idea here is that you're calibrating the sensor, not the whole machine.

I have many filters and depending on the grind and the quality of the coffee I choose different filters...

Of course. As you should. ;)

I wonder how coffee machines measure.

They usually keep it simple.

They measure either time (with a simple timer) or volume (using the approach you're trying here).

Some also measure mass/weight through a precision scale underneath the cup (e.g. https://decentespresso.com/scale).

In the same place, a different amount of coffee can be specified and a different grind. Everything affects the amount of liquid in the end.

Yes, because the flow rates will be different.

This is exactly why it's important to use a flow meter with a range that matches the application.

But the flow meter behaviour should/would not differ once it is set up correctly.

The target goal here is to ideally always measure the true flow rate, no matter how large or small (within the range of > 0 ml/s and < 12 ml/s).

Once you're able to reliably measure the flow rate throughout the whole applicable range with sufficient precision, you're good.

The rest would then have to be controlled in other software functions.

In other words: in the end you need to control the total volume per shot via flow rate times duration. The FlowMeter library already provides that through its getCurrentFlowrate() and getCurrentVolume() functions. Particularly getCurrentVolume() is a measure of accumulated volume over the duration since the last call to the reset() function.

So, simply put: at the start of a shot, you reset() the internal state and then let it flow until getCurrentVolume() provides the desired volume for the shot (including pre-infusion, etc.).

By the way, the getTotalVolume() function returns the total volume since turning the machine on, so survives a call to reset().

khseal commented 2 years ago

They measure either time (with a simple timer) or volume (using the approach you're trying here). Most coffee machines use flow meters.

Tomorrow I'll try to take measurements with coffee. I use scales for calibration.

The target goal here is to ideally always measure the true flow rate, no matter how large or small (within the range of > 0 ml/s and < 12 ml/s).

What if the flow is not constant? I have seen values around 3-4ml/s I got about 250 ml per minute without coffee, with a filter.

Thanks for the clarification, I already read about the functions.

sekdiy commented 2 years ago

What if the flow is not constant?

That's a given, the flow through tamped espresso will never be constant.

It never is in an espresso extraction, it's always low in the beginning (sometimes even zero) and approaches a maximum towards the end.

Adjusting everything so that the flow has a prolonged constant phase with good wetting from the beginning and no overextraction or channeling in the end is the holy grail. ;)

I got about 250 ml per minute without coffee, with a filter.

The flow rate without resistance (without coffee, i.e. 0 bar pressure) only depends on your pump and valves/nozzles.

The classic Ulka EX5, for example, has a flow rate of about 250 ml/min at 10 bar (see: http://ulka-ceme.co.uk/Ulka_E_Models.html) or up to 650 ml/min at 0 bar.

Your flow meter probably has no nozzle to reduze flow (at least I can't decipher this from the datasheet), so it's possible you end up with too much flow even through fine, tamped espresso.

But maybe your machine has an option to adjust flow...

khseal commented 2 years ago

Your flow meter probably has no nozzle to reduze flow (at least I can't decipher this from the datasheet), so it's possible you end up with too much flow even through fine, tamped espresso.

There is a nozzle in the flow meter, but its diameter is not indicated in the datasheet.

The flow rate without resistance (without coffee, i.e. 0 bar pressure) only depends on your pump and valves/nozzles.

I have filters with a valve =) This morning I will continue the calibration =)

With coffee, calibration is more difficult. When I press the coffee hard, some of the water escapes through the check valve...

I think in coffee machines coffee is not pressed so much and there is a valve at the outlet that constantly creates the same pressure. The coffee machine has stable pressure parameters.

khseal commented 2 years ago

ESP32 support? I checked on esp32 and got some strange values. The calibration parameters are completely different. There is a feeling that the interrupt does not work.

Particularly getCurrentVolume() is a measure of accumulated volume over the duration since the last call to the reset() function.

This parameter does not accumulate measurements for me. Is this a bug?

sekdiy commented 2 years ago

ESP32 support? I checked on esp32 and got some strange values.

That reminds me of a pull request that I haven't merged yet.

Could you please try to replace around line 18 in `src/FlowMeter.cpp'...

   attachInterrupt(digitalPinToInterrupt(this->_pin), this->_interruptCallback, (PinStatus) this->_interruptMode);

...with...

   attachInterrupt(digitalPinToInterrupt(this->_pin), this->_interruptCallback, (PinStatus) this->_interruptMode);

This already solved a similar problem (see #22).

In case you can confirm that this works, I'll merge it into the main (master) branch.

The calibration parameters are completely different.

That should no longer be the case once everything is working as intended.

Particularly getCurrentVolume() is a measure of accumulated volume over the duration since the last call to the reset() function.

This parameter does not accumulate measurements for me.

A call to getCurrentVolume() (see: https://github.com/sekdiy/FlowMeter/blob/master/src/FlowMeter.cpp#L28) just returns the internal volume state of the FlowMeter driver.

This internal state gets updated on every call of tick() (see: https://github.com/sekdiy/FlowMeter/blob/master/src/FlowMeter.cpp#L40) function.

It is not updated elsewhere.

The internal state of the current flow rate (and many more) are reset only in the reset() function (see: https://github.com/sekdiy/FlowMeter/blob/master/src/FlowMeter.cpp#L72).

They are not reset (to zero) elsewhere.

In order for me to understand what actually happens in your code, we'd need to look at your code.

Perhaps you could develop (and test) a minimal example that reproduces the issue...

Is this a bug?

The thing in line 18 most likely seems to be a bug, yes.

Please check if the behaviour changes after implementing the change above.

khseal commented 2 years ago

K-factor on esp32 has doubled. The readings are very stable. k-factor 180 %) Perhaps he began to count in falling and rising signal? Before that I used esp8226. I try attachInterrupt(digitalPinToInterrupt(this->_pin), this->_interruptCallback, (PinStatus) this->_interruptMode); The variable is not declared PinStatus.

C:\Users\khseal\Documents\Arduino\libraries\FlowMeter\src\FlowMeter.cpp: In constructor 'FlowMeter::FlowMeter(unsigned int, FlowSensorProperties, void (*)(), uint8_t)':
C:\Users\khseal\Documents\Arduino\libraries\FlowMeter\src\FlowMeter.cpp:18:87: error: 'PinStatus' was not declared in this scope
         attachInterrupt(digitalPinToInterrupt(this->_pin), this->_interruptCallback, (PinStatus) this->_interruptMode);

Tried without variable PinStatus. Compiled ok. Nothing changed.

Here is my code snippet getCurrentVolume(). This is not the complete code.

// enter your own sensor properties here, including calibration points
FlowSensorProperties HL3012 = {0.25f, 180.00f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
// connect a flow meter to an interrupt pin (see notes on your Arduino model for pin numbers)
FlowMeter *Meter;
int Flow = 5;
const unsigned long period = 1000;
void  ICACHE_RAM_ATTR MeterISR(){
    // let our flow meter count the pulses
    Meter->count();
}

#if OLED_DISPLAY == 1
void displayOLED(void)
 {
      display.drawString(0, 36, "Vol:" + String(Meter->getCurrentVolume()*100));
      display.drawString(80, 36, "sV:" + String(int(svolume)));
}
#endif

void setup()
{

  Meter = new FlowMeter(digitalPinToInterrupt(Flow), HL3012, MeterISR, RISING);

}

void loop()
{
  Meter->tick(period);
#if OLED_DISPLAY == 1
  displayOLED();
#endif
}

Here is a video where I use getCurrentVolume() https://youtu.be/j4CsxSbCPqg Only some indications flicker, but there is no accumulation.

sekdiy commented 2 years ago

The variable is not declared PinStatus.

PinStatus is not a variable, it's a type (see: https://www.learncpp.com/cpp-tutorial/explicit-type-conversion-casting-and-static-cast/).

There is a bug in the Arduino implementation for some platforms after the Arduino core team introduced some changes (see: https://github.com/arduino/ArduinoCore-API/issues/25).

Tried without variable PinStatus. Compiled ok. Nothing changed.

Great, I suggest we continue looking elsewhere. :)

void loop() { Meter->tick(period);

if OLED_DISPLAY == 1

displayOLED();

endif

}

That's not how it's supposed to work.

This is not the complete code.

Well, let's assume you didn't leave out the majority of the loop() function.

You define period to be 1000 (milliseconds), this is now your measurement base period. You can choose any arbitrary base period (as long as it isn't too short!).

By calling Meter->tick(period), you then tell the FlowMeter instance to update the measurement status – suggesting that a time of 1000ms has passed since the last update.

But you call tick() 'infinitely' fast, i.e. your program has no concept of time!

Have you tried the examples provided by the FlowMeter library (e.g. /examples/Simple/Simple.ino)?

Does the issue still occur using the example code?

Only some indications flicker, but there is no accumulation.

Yes, of course. :)

It's most likely because you only ever capture an infinitesimally small amount of flow inbetween two successive calls of tick().

By neither waiting (for a time of length period) nor keeping time (by measuring how much time has passed since the last call to tick()) you are very unlikely to actually capture a meter pulse inbetween two calls.

You'll most likely have calls to tick() (and updates to the OLED) thousands of times more frequent than you'll get pulses (because the ESP32 is very fast)... Additionally, by suggesting that this infinitesimally small duration was supposedly a whole second, you force the library to divide the measured ticks to an even smaller output value. ;)

That is of course assuming that you didn't leave out your time keeping code in your comment.

I suggest you take a look at the examples.

At the very least, please add the following to your loop():

    // wait between output updates
    delay(period);

If the issue still occurs with the simple example, please provide the complete example code you're using and a wiring diagram and/or picture of your setup.

khseal commented 2 years ago

Ok, I understand my problem. That's my fault. I don't like to use delay. I'm using your delay script from the calibration example. Thank you for pointing out my mistake. Tomorrow, I will write about the results of the bug fix.

khseal commented 2 years ago

Maybe I don't understand it? But, getCurrentVolume does not accumulate readings. It simply shows the current flow. No flow, no indication getCurrentVolume. I don’t see in the code that the readings are accumulated.

double FlowMeter::getCurrentVolume() {
    return this->_currentVolume;             
}
this->_currentVolume = this->_currentFlowrate / (60.0f/seconds);        //!< get volume (in l) from normalised flow rate and normalised time

Only the total accumulates. this->_totalVolume += this->_currentVolume; With a delay, k factor needs to be further increased. Perhaps I already have a delay in my code. Too much code =) Need to try the example.

image All this is strange. This is the correct calibration for 50ml. The factor has increased even more. It turns out that k factor depends on the speed of the processor?)

You'll most likely have calls to tick() (and updates to the OLED) thousands of times more frequent than you'll get pulses (because the ESP32 is very fast)...

On the forums they write that the esp32 is slow to process interrupts ... I now understand why many do not like esp32. She also has a very disgusting ADC.

sekdiy commented 2 years ago

First things first: this is progress!

You're almost there! :smiley:

Maybe I don't understand it? But, getCurrentVolume does not accumulate readings. It simply shows the current flow. No flow, no indication getCurrentVolume. I don’t see in the code that the readings are accumulated.

Yes.

That. Is. My. Bad. :(

Here's where I made a mistake (above): I gave an explanation based on the wrong code branch.

Only the total accumulates.

That is correct – in the version that you have...

It is not the case in the version that I was looking at...

That's because I'm currently working on a solution to #1, where exactly this topic is discussed.

I failed to check (online, on Github) which version of the code you've got.

I'm very sorry for the confusion and the additional trouble that must have caused!

On the other hand, I recommend you take a look at #1, as you'll find the explanation/solution to the accumulation problem right there (together with a discussion about what it means and why it exists).

Basically, you've got three options: • use getTotalVolume() and modify the reset() process as described in #1 (very easy to do), • just accumulate the volume yourself in your own code (also very easy), • convince me to change the library in #1 (possibly slightly harder :smile:).

This is the correct calibration for 50ml. The factor has increased even more.

I see a mismatch in units here which might explain your struggle with the k-factor.

The units of measure in the library are l (liter) and l/min (liter per minute) for all functions (as stated in the readme, in the code, in the documentation and in the examples :smile:).

So let's ignore for a moment that you print ml and ml/min (by not fully adhering to the example code).

With that being said, you're averaging around a current flow rate reading (first figure) of 4–5 l/min. That seems totally wrong when remembering the datasheet (< 0.76 l/min), but it starts to make total sense when we consider that your k-factor is too large (when compared to our initial discussion).

The current volume (second figure) has a reading in the order of 0.1 l (within every period, e.g. every second). That's also still wrong, of course (due to the wrong k-factor). But we can expect it to be around 1/60th of the current flow rate, since we know your period is one second (i.e. 1/60th of a minute). And that is absolutely the case (e.g.: 5.74 l/min divided by 1/60 s/min equals 0.0958 l/s, which rounds to 0.1 l/s).

And finally, the total volume reading (third figure) shows an accumulation. You're multiplying with 50 (*100/2), but the accumulated sum still corresponds to the sum of the individual current volumes within in each prior period (just off by your factor of 50, also accounting for some errors due to Serial.print() rounding/cutting the floating point number to two decimal digits).

So, please take a look at your units of measure and tranformations. Then please start over with the initial k-factor according to the datasheet and proceed from there.

You don't even need to transform any output values for debugging, you could simply increase the number of decimal digits in the serial output (see: https://www.arduino.cc/reference/en/language/functions/communication/serial/print/). Once you got this right, you might adapt for your OLED output later.

It turns out that k factor depends on the speed of the processor?

No, it shouldn't.

The setup could potentially miss some pulses, which would be a classical problem in sensor acquisition. I can't say, since I don't know your setup and the intricacies of the ESP32. You could easily verify that by temporarily switching to another microcontroller (like ESP2266 or ATMega).

But I recommend that you fix and rule out any other issues before going there.

On the forums they write that the esp32 is slow to process interrupts ... I now understand why many do not like esp32. She also has a very disgusting ADC.

Interesting.

I've only tested this library on (many) ATMega and (one) ESP2866, not on the ESP32 platform.

khseal commented 2 years ago

Basically, you've got three options: • use getTotalVolume() and modify the reset() process as described in https://github.com/sekdiy/FlowMeter/issues/1 (very easy to do), • just accumulate the volume yourself in your own code (also very easy), • convince me to change the library in https://github.com/sekdiy/FlowMeter/issues/1 (possibly slightly harder 😄).

You just confused me a little. Ok. I, did a reset a long time ago getTotalVolume() =)

So let's ignore for a moment that you print ml and ml/min (by not fully adhering to the example code).

I entered the units of measurement incorrectly. Sorry.

The current volume (second figure) has a reading in the order of 0.1 l (within every period, e.g. every second). That's also still wrong, of course (due to the wrong k-factor). But we can expect it to be around 1/60th of the current flow rate, since we know your period is one second (i.e. 1/60th of a minute). And that is absolutely the case (e.g.: 5.74 l/min divided by 1/60 s/min equals 0.0958 l/s, which rounds to 0.1 l/s).

And finally, the total volume reading (third figure) shows an accumulation. You're multiplying with 50 (*100/2), but the accumulated sum still corresponds to the sum of the individual current volumes within in each prior period (just off by your factor of 50, also accounting for some errors due to Serial.print() rounding/cutting the floating point number to two decimal digits).

Rounding the print output should not affect the calculations, right? Here is my mistake, I divided everything by 2. It's not in the main code. Must be Serial.print(Meter->getTotalVolume()*1000);

This is how it would be better

image

Didn't calibrate accurately. The factor flew into the sky. I initially multiplied liters incorrectly.

sekdiy commented 2 years ago

You just confused me a little.

Yes, and I'm sorry! 😦

Rounding the print output should not affect the calculations, right?

That's correct.

But trying to understand the source of the problem was more difficult due to the rounding...

This is how it would be better

Okay, I see ≈ 300 ml/min at ≈ 5 ml/s.

Was the measured total volume of 60 ml correct?

What was the pressure?

The factor flew into the sky.

It's still a possibility that the datasheet has mislead us...

I should clarify some more in the documantation that the k-factor can somtimes be difficult or impossible to obtain from datasheets.

Do you still experience any more issues?

Edit: In the wiki (https://github.com/sekdiy/FlowMeter/wiki/Properties#k-factor) I already clarify that sometimes datasheets give a factor based on frequency per flow rate, sometimes based on pulses per volume...

khseal commented 2 years ago

But trying to understand the source of the problem was more difficult due to the rounding...

Initially, I was confused about liters and milliliters.

Was the measured total volume of 60 ml correct? What was the pressure?

The volume is correct. Unfortunately, I can't measure the pressure.

I should clarify some more in the documantation that the k-factor can somtimes be difficult or impossible to obtain from datasheets. Do you still experience any more issues?

All issues resolved.