Oscillating a Pin

I'm having a problem getting a wavemaker to turn on and off at a specific interval. Currently it's suppose to switch every two seconds.

My setup is simple. I have an 8 outlet power box controlled by an 8 channel solid state relay board. 110v goes in, Arduino flips the switch to turn devices on and off. I thought it would be nice to simply plug in a powerhead and have the arduino turn that outlet on and off every two seconds.

Simple code

void updateWave() {
  if(millis() % 1000 == 0) {
    int state = digitalReadFast(waveMaker);
    digitalWriteFast(waveMaker, !state);
  }
}

But it doesn't always switch when it needs to. Often times it skips a switch. Any thoughts on what may cause this?

Thanks

Two thoughts:

  • The code relies on millis() returning a value that's an exact multiple of 1000. millis() doesn't strictly return the number of milliseconds that have passed since the program started. It calculates the number of milliseconds by counting overflows of Timer0, which happen every 1.024 milliseconds, and it calculates the error between its current count and the actual number of milliseconds that have passed. When it gets more than one millisecond behind, it bumps its counter twice, and it appears to skip a millisecond. Your code looks for a specific value of millis(), and it's possible that the value you're looking for is occasionally skipped.

  • This code reverses the state of the output whenever it finds millis() equal to an integer multiple of 1000. There's nothing in this snippet that will stop the function from executing twice within the same millisecond, while the value of millis() hasn't changed from the previous examination. When that happens, the program will turn the output, say, off, then turn it on , and then turn it off again, within a millisecond. It may do that more than once, and it may do it a whole bunch of times, depending on how long it takes to execute the rest of the code that isn't shown. The relay probably wouldn't have time to switch, and, if it did switch, you might not notice that the output had changed state for a fraction of a millisecond.

You'll be happier if you do this:

[Edit: The method described here will work for almost 50 days before it fails, when millis() approaches the maximum value for an unsigned long. See further discussion below.]

  • When you start, get the value of millis(), add 1000 to it, and save it somewhere. For this discussion, let's say you call that variable switchTime
  • When you're looping, don't look for equality. Look for the value of millis() to be greater than or equal to switchTime. That way, if you take longer to get around to checking the time, you won't miss a switching event, and you won't miss one if millis() skips the precise value you're looking for.
  • When it's time to switch, switch, and then update the value of switchTime to be 1000 milliseconds later.

You might be even happier to store the current state of the output in memory, rather than reading it back from the pin.

Eager as I am to write working code for this application, I resist that temptation, for your edification. You'll be happiest if you implement this function yourself.

Most definitely. I've written most of this project myself and like to understand what all is going on.

Thank you so much for the incite, it definitely clarifies millis(). I've had some trouble before in the past with that function, but the projects were so small and insignificant I didn't think too much about it. I'll work on a function to follow as you've explained and let you know how if it works out.

Thanks again! :slight_smile:

Thanks for your kind words. They may be premature. As clever as my description might have seemed to me as I typed it, it has a tragic flaw: it'll fail when the value of millis() gets close to its maximum value. When the next switching time exceeds 2^32, or 4294967296, it will appear as a small number. It will test as less than the current value of millis() until millis() itself rolls to zero, and the output will switch as fast as the program can run for that period of time.

The way around this problem is to refrain from calculating the next switch time, and instead save the value of millis() at the time of the most recent switch. Subtract that value from millis() while looping, and when the difference reaches 1000 - or whatever interval you decide to use - switch the output, save the current value of millis(), and keep going. The magic of two's complement arithmetic will provide correct results. I encourage you to work that out for yourself, in order to be sure that the devece will handle the millis() rollover properly.

You can see this technique implemented properly in the tutorial, "blink without delay," accessible from the "Learning" page of arduino.cc, and in the Digital folder under examples in the Arduino folder in the IDE. And, you can find this subject discussed on the forum by searching for stuff like "millis rollover."

So I've updated my code:

void updateWave() {
  if(millis() >= switchTime) {
    waveState = !waveState;
    switchTime = millis() + waveFrequency;
  }
  
  if(waveState)
    digitalWrite(waveMaker, HIGH);
  else
    digitalWrite(waveMaker, LOW);
    
  Serial.print(waveState);
  Serial.print(", ");
  Serial.print(switchTime);
  Serial.print(", ");
  Serial.println(millis());
  
}

And it works based off the info you gave me. I'm sure it could use some cleaning up, but that will happen over time as I continue my project. I am curious what will happen when millis() turns over however. Had some fun learning that 'switchTime' needed to be unsigned long as well, hehe, dur. I'm assuming since switchTime is the same type as millis, they'll both turn over the same and keep going. I guess we'll find out in 50 days unless you have some insight on that as well. I'm not really concerned about it anyway, my bet's on that they both turn over at the same time and keep going.

Thanks again

Unfortunately that code won't work on millis() wrap-around, the way that actually works is
this:

void updateWave() {
  if (millis() - switchTime >= interval)  // #define interval as appropriate
  {
    switchTime += interval ;  // bump switchTime on for next time
    ....
  }
  ...

You need to subtract two time values from each other to get a value that
is always correct whether or not millis() is wrapping-around back to zero.

Interval has to be less than about 49 days for millis() and less than 1h12m
if using micros()

Of course if you are using millis() and you never run the sketch for periods of
49 days or more it won't matter.

Ok, that makes more sense after doing some math behind it. Mine actually would still handle it, but the system would freak out at that turn over for a little while till millis caught up to the turn over and then became lower than the switchTime. An easy change though, thanks for the insight. I can actually convert this concept over to my dosing machine as well instead of using delay() and stop absolutely everything. At the moment it doesn't really affect anything, but as I add more stuff, it probably will.

Not to nitpick, but just in case anyone else plans on using this, I believe it should be <= instead of >=

void updateWave() {
  if (millis() - switchTime <= interval)  // #define interval as appropriate
  {
    switchTime += interval ;  // bump switchTime on for next time
    ....
  }
  ...