Motor Control With Zero Crossing and Mode Select

Currently I am working a project that has some unique objectives.

1) I am controlling a pump that must be fired only on the falling edge of an incoming rectified A/C square wave. This is the inline power. 2) I have to select 3 modes of speed for this pump to operate at. These speeds are determined by pulses per second that the pump operates at. Sometimes it fires every falling edge, while other modes fire every 3 falling edges or every 10 and so on. 3) The modes are selected by user input via push button.

The inline A/C square wave operates at 60Hz.

I started off with polling commands such as digitalRead, but now I've realized that the frequency is too fast for polling. I am attempting to write interrupt commands in order to detect the falling edge and fire the pump.

I'm having a difficult time rapping my head around the order of commands in order to accomplish this task.

So far this is the outline of the program I think I want

// Setup inline voltage variable - this can be treated like a button changing state int inlineVoltageInt = 0 //Interrupt Digital Pin 2

//setup constants for pump and push button const int buttonPin = 3; const int pumpPin = 5;

//volatiles setup some volatile variables for interrupt commands

void setup() - declare pinModes and interrupt commands

void loop() // detects push button // selects pump mode

void interrupt command() use interrupt to detect falling edge and firepump

Any help is appreciated.

Thanks Arduino Community!

johnpaulperron: The inline A/C square wave operates at 60Hz.

the frequency is too fast for polling.

I'm surprised to hear that -- 1/60th of a second is an eternity for an Arduino. 16.6ms is enough time to run thousands of instructions. Are you using delay() or other blocking code?

(Not to ignore your ISR questions, but I don't know the answer.)

const int pumpPin = 3;
const int inlineVoltage_INT=0;

void setup()
{
pinMode(pumpPin, OUTPUT);

attachInterrupt(inlineVoltage_INT, swap, FALLING);

}

void loop ()
{
noInterrupts()
{
}

}

void swap()
{

digitalWrite(pumpPin, LOW));

}

Currently Im just running this program as my trial. On my Tektronix Scope I’m seeing misses… could be the scope, I hate this thing. But you are correct 16.6ms is nothing for Arduino. Which makes me wonder if I am using interrupts correctly.

I would like to know everything I can about interrupts. Ive pretty much Googled the hell out of the topic. I see many examples for push button interrupts. But what I want is to use a botton to select modes and then use the inline voltage as my interrupt trigger. So I know I can't use interrupts inside of interrupts so Im really lost at what to do. I can have my interrupt function continually running because I don't want the pump to continually run.

void loop ()
{
  noInterrupts()
  {
  }


}

Defining a function inside another function is not allowed. Defining a function with the same name as an existing function is a bad idea.

I would like to know everything I can about interrupts. Ive pretty much Googled the hell out of the topic.

Have you read Nick Gammon's page on interrupts? It's written specifically for Arduinos.

and then use the inline voltage as my interrupt trigger.

What "inline voltage"?

So I know I can't use interrupts inside of interrupts

Then you don't know squat. Enabling interrupts inside an interrupt handler is generally not a good idea, but it can be done. IF you know what you are doing.

Though why you think you need to to detect zero crossings is a mystery.

I can see I left the noInterrupt function left in by accident. I was just messing around with the code.

This is the current code

const int pumpPin = 3; const int inlineVoltage_INT=0;

define e 255

void setup() { pinMode(pumpPin, OUTPUT);

attachInterrupt(inlineVoltage_INT, swap, CHANGE); }

void loop () {

}

void swap() {

digitalWrite(pumpPin, !digitalRead(pumpPin));

}

the reason I am using zero crossing is because I have a solenoid pump that doesn't like a full AC spike.

Also from what I read Interrupt inside an Interrupt can be done, but it's not advised.

And no I haven't seen Nick Gammon's site, but thanks for telling me about it

johnpaulperron: I would like to know everything I can about interrupts. Ive pretty much Googled the hell out of the topic. I see many examples for push button interrupts. But what I want is to use a botton to select modes and then use the inline voltage as my interrupt trigger.

Your use of an interrupt to detect zero crossings is fine. But you don't need an interrupt to respond to button presses -- humans don't have microsecond latency, so polling for button presses in loop() is entirely reasonable. The human will never notice it takes 300uS instead of 1uS for the button press to be recognized.

By the way, this:

digitalWrite(pumpPin, !digitalRead(pumpPin));

Is pretty clever. :)

digitalRead() is not a terribly fast function.

pinState = !pinState;

is orders of magnitude faster.

digitalWrite() is not a terribly fast function, either. You may need to use direct port manipulation (that's a search phrase).

Now, about your mysterious circuit. What, exactly, is connected to pin 2?

Thanks for the input!

My circuit isn't terribly complex but let me see if I can explain...

I have 120V A/C 60Hz - 'Merica! coming into a square wave rectifying circuit. Aka Diodes cutting up an A/C Sin wave. I have this square wave going into an Opto isolater that sends a 0 - 5V pulse to my Arduino Pin 2 for external interrupt. This lets my Arduino know when zero crossing is happening via edge rising or falling.

From Pin 3 I have my pump signal out. This signal goes into another opto isolater that keeps the 120V A/C separate from Arduino. The Opto fires a Triac that sends the A/C voltage to the pump.

Is the code working?

[tylernt][Your use of an interrupt to detect zero crossings is fine. But you don't need an interrupt to respond to button presses -- humans don't have microsecond latency, so polling for button presses in loop() is entirely reasonable. The human will never notice it takes 300uS instead of 1uS for the button press to be recognized. ]

How would I control the interrupt withing my loop function? I only want the interrupt to run during a mode selection. Each mode selection will change the number of pulses/min the pump operates at.

PaulS: Is the code working?

How do I implement that code? I tried replacing it word for word but it didn't work, so I think I am doing something wrong.

I tried replacing it word for word but it didn't work, so I think I am doing something wrong.

I have no idea what you mean by the lame expression "it didn't work". The code did something. You haven't explained what. You have some expectations. You haven't explained what they are.

PaulS: I have no idea what you mean by the lame expression "it didn't work". The code did something. You haven't explained what. You have some expectations. You haven't explained what they are.

I dont know what you mean by pinState = !pinState . How is this implemented ?

I dont know what you mean by pinState = !pinState . How is this implemented ?

Declare a global variable, type byte, called pinState. Instead of reading the state of the LED pin, remember it, in pinState. Toggle pinState and then call digitalWrite() with pinState.

johnpaulperron:
How would I control the interrupt withing my loop function? I only want the interrupt to run during a mode selection. Each mode selection will change the number of pulses/min the pump operates at.

The answer is, you don’t control the ISR in the loop. :slight_smile: Inside the ISR, you decide on whether to turn on/off the pump.

So, in your loop, read your button press and use a global variable like

volatile byte userMode // variables used in ISRs should be volatile

to store how many cycles (such as 0 or 3 or 10) the pump should remain off between on cycles. Then, in your ISR, compare another global variable like

volatile byte cycle // bytes process faster than ints, important in an ISR (pumpPin should be a byte too)

to userMode, to see if you should be turning the pump on or off.

Something like:

void pumpISR()
{
if(cycle < userMode) // haven't been off for long enough yet
  {
  digitalWrite(pumpPin, LOW); // turn off or stay off
  cycle++; // keep track of how long we've been off
  }
else // been off for long enough now
  {
  digitalWrite(pumpPin, HIGH); // turn on or stay on
  cycle = 0; // this will turn the pump off again next cycle (but only if userMode is > 0)
  }
}

(Disclaimer: the above code is not guaranteed to be bug-free :wink: )

Below is what I have, could be wrong.

Is it possible to only fire the Triac for 2ms instead of the whole pulse? Is it possible to set a 2ms pulse every 3 cycles? All of your help has been great. Much appreciated.

const byte pumpPin = 3;
const int inlineVoltage_INT=0;
const int buttonPin = 5;

boolean lastButton = LOW;         //Last Button State
boolean currentButton = LOW;      //Current Button State
int pumpMode = 0;                 //Cycle between pump Modes


volatile byte pinState = LOW;
volatile byte userMode = 0;
volatile byte cycle = 0;

void setup()
{
  pinMode(pumpPin, OUTPUT);
  
  attachInterrupt(inlineVoltage_INT, pumpISR, FALLING);  
  
  Serial.begin(9600);
}


/*
*Debouncing Function
*Pass it the previous button state, and get back the current debounced button state.
*/

boolean debounce(boolean last)
{
  boolean current = digitalRead(buttonPin);    //Read the button state
  if (last != current)                       //if different ...
  {
    delay(5);                                // wait 5ms
    current = digitalRead(buttonPin);         //read it again
  }
  return current;                            // return the current value
}

/*Pump Mode Selection
*Pass a number for the Pump state and set it accordingly
*/


void setMode(int pumpMode)

{
  //Mode 1 - 12 grams / min
  if (pumpMode == 1)
  {  
       userMode = 6;
     Serial.print("pump mode:  ");
     Serial.println(pumpMode);
  }
  
  //Mode 2 - 18 grams / min
  else if (pumpMode == 2)
  {
     userMode = 3;      
     Serial.print("pump mode:  ");
     Serial.println(pumpMode);
  }
  
  //Mode 3 - 20 grams / min
  else if (pumpMode ==3)
  {  
     userMode = 1;    
     Serial.print("pump mode:  ");
     Serial.println(pumpMode);
  }
  
  //OFF MODE (mode = 0)
  else 
  { 
          digitalWrite(pumpPin,HIGH);
          Serial.println("Pump Off");
   
  }
  

}

void loop ()
{
   currentButton = debounce(lastButton);        //Read debounced state
  if (lastButton == LOW && currentButton == HIGH)     //if it was pressed...
  {
    pumpMode++;                                //increment the Pump value
  }
  lastButton = currentButton;                  //reset button value
  //if you've cycled through the different options, reset the counter to 0
  if (pumpMode == 4) pumpMode = 0;
  
  setMode(pumpMode);

}


void pumpISR()
{
if(cycle < userMode) // haven't been off for long enough yet
  {
  digitalWrite(pumpPin, HIGH); // turn off or stay off
  cycle++; // keep track of how long we've been off
  }
else // been off for long enough now
  {
  digitalWrite(pumpPin, LOW); // turn on or stay on
  cycle = 0; // this will turn the pump off again next cycle (but only if userMode is > 0)
  }
}]

johnpaulperron: Below is what I have, could be wrong.

Looks ok to me, but I don't claim to be an expert. ;)

Is it possible to only fire the Triac for 2ms instead of the whole pulse? Is it possible to set a 2ms pulse every 3 cycles?

My understanding is that you can only turn a Triac off on a zero crossing. But, I believe (?) you can turn it on any time you like.

Is it possible to only fire the Triac for 2ms instead of the whole pulse? Is it possible to set a 2ms pulse every 3 cycles?

My understanding is that you can only turn a Triac off on a zero crossing. But, I believe (?) you can turn it on any time you like. [/quote]

The Triac does open on the Zero Cross and will close once the wave passes through. What my Arduino does is sends a 2ms pulse to turn the Triac on @ zero crossing. The Arduino doesn't need to maintain a signal for the whole period. So I want my Arduino to send a 2ms pulse every so many periods.

Easy enough with a delay(2); statement (except you can't use delay() in an ISR). Not sure how that's better than leaving the pin HIGH the whole cycle though?