attachInterrupt: Flow meters trigger button interrupt

Using an electron, Device OS: 1.5.0, flashing with Workbench. Serial connected.

Using two flow meters, two push buttons, and a motorized valve, I want to control the flow through one pipe as a percentage of the flow through the other pipe. The valve will be adjusted to open and close based on user’s button input.

The valve is 12V, and is connected to a relay. Relay is connected to the particle at C0 to open the valve, and C1 to close the valve.

Main flow meter is on on D5, the other on D6. They are powered by 5V from the same power source that powers the electron.

Button1 increases the ratio, and is on B2. Button2 decreases ratio, and is on B3. Their V+ leg comes from 3V3 from the electron. On the other side of each switch is a 10kOhm resistor to ground, and a conductor back to the electron pin.

When there is no flow through the flow meters, my button presses respond perfectly, and correctly open and close the valve. When there is flow, the buttons are acting like they are getting input, and call their interrupt function.

This results in phantom button presses, which start adjusting the valve.

I can’t provide all the code, but I’ll take a quick shot at pseudo code, mixed with some actual. I’ll use actual code for the initialization of the buttons and the meters.

I’m hoping this has something to do with how I’m using the pins, but the reference guide showed interrupt usable without restrictions on D4 and D5, and then one of each grouping for B2 and B3, so I don’t see any overlap there.

If I need to condense this down to a single source file, I can, but I’m hoping this can at least show where the issue may be.

Thanks.

The main setup and loop is as follows:

SYSTEM_MODE(MANUAL);

WidgetCollection *widgetCollection = nullptr;

void setup(void) 
{  
    uint16_t closeValvePin = C1;

    widgetCollection = new WidgetCollection();

    widgetCollection->AddWidget(new FlowController(
        new FlowMeterWidget(D4, 450.0f, true, "MainFlowMeter"),
        new FlowMeterWidget(D5, 450.0f, true, "ToolFlowMeter"),
        B2, // increase tool flow button pin
        B3, // decrease tool flow button pin
        C0, // open tool valve pin
        closeValvePin // close tool valve pin
    ));

    widgetCollection->Initialize(closeValvePin);
}

void loop(void) 
{  
    widgetCollection->Execute();
}

What I’m doing here is creating a collection of widgets. Basically, an array I can load up with various sensors as needed via widgetCollection->AddWidget(*aWidget);

widgetCollection->Initialize(closeValvePin) handles some basic startup housekeeping, such as closing the valve for 10 seconds to ensure it is closed before turning pumps and things on.

Each widgetCollection->Execute(); loops through my array of widgets, and calls an implemented abstract classes’s function called DoWork(void);

DoWork(void)'s various implementations use a _IsTimeToFire() boolean function, such that if the difference between last execution time of the widget, and millis() is greater than some defined number of milliseconds, the widget’s work is executed. Otherwise, we move on to the next widget.

Such as:

<WhateverTheWidgetClassIsNamed>::DoWork(void)
{
     if (AWidget::_IsTimeToFire())
     {
          // execute the widget's work
     }
}

That’s basically the control.

Here is some button code:

FlowController::FlowController(FlowMeterWidget *mainFlowMeter, FlowMeterWidget *toolFlowMeter, uint16_t increaseToolFlowButtonPin, uint16_t decreaseToolFlowButtonPin, uint16_t openToolValvePin, uint16_t closeToolValvePin)
{
    this->__MainFlowMeter = mainFlowMeter;
    this->__ToolFlowMeter = toolFlowMeter;

    // ...
    
    this->__InitializeButtons();
}

void FlowController::__InitializeButtons(void)
{    
    pinMode(this->__IncreaseToolFlowButtonPin, PinMode::INPUT);
    attachInterrupt(this->__IncreaseToolFlowButtonPin, &FlowController::__IncreaseToolFlow, this, InterruptMode::RISING);

    pinMode(this->__DecreaseToolFlowButtonPin, PinMode::INPUT);
    attachInterrupt(this->__DecreaseToolFlowButtonPin, &FlowController::__DecreaseToolFlow, this, InterruptMode::RISING);
}

void FlowController::__IncreaseToolFlow(void)
{    
    if ((millis() - this->__ButtonLastFireTime) > this->__DebounceDelay)
    {        
        this->__ButtonLastFireTime = millis();

        if (this->__RequestedToolFlowRatio < 100)
        {
            this->__RequestedToolFlowRatio++;
            this->__IsInterrupted = true;        
        }                            
    }
}

void FlowController::__DecreaseToolFlow(void)
{
    if ((millis() - this->__ButtonLastFireTime) > this->__DebounceDelay)
    {        
        this->__ButtonLastFireTime = millis();

        if (this->__RequestedToolFlowRatio > 0)
        {
            this->__RequestedToolFlowRatio--;
            this->__IsInterrupted == true;        
        }          
    }
}

Here is some flow meter code:

FlowMeterWidget::FlowMeterWidget(uint16_t sensorPin, float pulsesPerLiter, bool isReturnMetric, const char *widgetName)
{        
    this->SetPin(sensorPin);

    // ... 

    pinMode(this->GetPin(), PinMode::INPUT);    

    this->__DoAttachment();    
}

void FlowMeterWidget::__DoAttachment(void)
{
    this->ResetPulseCount();
    this->__LastReadMillis = millis();
    attachInterrupt(this->GetPin(), &FlowMeterWidget::__IncrementPulseCount, this, InterruptMode::RISING);
}

void FlowMeterWidget::__IncrementPulseCount(void)
{    
    if (this->__PulseCount < 65535)    
        this->__PulseCount++;
}

void FlowMeterWidget::ResetPulseCount(void)
{
    this->__PulseCount = 0;
}

float FlowMeterWidget::GetFlowRatePerMinute(void)
{	
     if ((millis() - this->__LastReadMillis) > this->_MillisecondsDelayBetweenFiring)
     {
         detachInterrupt(this->GetPin());	                  
            
	 this->__FlowRateLitersPerMinute = // some calculation;	        

         FlowMeterWidget::__DoAttachment();
     }			

    return (this->__IsReturnMetric) ? this->__FlowRateLitersPerMinute : this->__FlowRateLitersPerMinute * 0.264172f;
}

FlowController’s DoWork(void)ish:

void FlowController::DoWork(void)
{
    if (AWidget::_IsTimeToFire() || this->__IsInterrupted)
    {                                 
        this->__IsInterrupted = false;

        float toolFlowRate = this->__ToolFlowMeter->GetFlowRatePerMinute();       
        float mainFlowRate = this->__MainFlowMeter->GetFlowRatePerMinute();

        // logic to handle opening and closing our valve based on these rates                         
    }    
}

Can you probe the button pins with an oscilloscope to see whether you get some cross talk from your flow meter signals and/or the valves?

You could opt for lower value pull-down resistors and/or some filter caps to short out any high frequency noise.

Ehhh, o-scope is a bridge too far at the moment. Like half of the rest of humanity, I’m stuck at the house.

I’ll see if I can order up a portable one that I can justify expensing.

In the mean time, I guess I can sure up all the connections, and play with some resistors, if nothing in the pin choice and initialization is jumping out as the cause.

Got a good portable o-scope recommendation?

Not sure about “good” but I always have a cheap DS213 Mini on me for quick checks.