Instructables

Intermediate Arduino: Inputs and Outputs

Featured

Continuing from my Intro to Arduino post, this Instructable will go over some slightly more advanced topics with Arduino, specifically relating to controlling and managing many inputs and outputs. The next class covers how to connect the Arduino's inputs and outputs to MIDI.

Parts List:

(1x) Arduino Uno Amazon or you can pick one up at a local Radioshack

(1x) usb cable Amazon

(1x) breadboard Amazon

(1x) jumper wires Amazon

(8x) red LEDs Digikey C503B-RCN-CW0Z0AA1-ND

(8x) 220Ohm resistors Digikey CF14JT220RCT-ND

(1x) 10kOhm resistor Digikey CF14JT10K0CT-ND

(1x) tact button Digikey 450-1650-ND

(1x) 595 shift register Digikey 296-1600-5-ND

(1x) red LED dot matrix Adafruit 454

 
Remove these adsRemove these ads by Signing Up

Step 1: Blink without Delay()

Picture of Blink without Delay()
Screen Shot 2014-02-03 at 8.57.53 PM.png

So far we've been using the delay() function to pause the Arduino sketch momentarily so that a little time can pass between two Arduino commands. In the LED blink sketch, we used delay() to set the amount of time the Arduino was lit and the amount of time it was turned off:

digitalWrite(ledPin, HIGH);//turn LED on
delay(1000);// wait for 1000 milliseconds (one second)
digitalWrite(ledPin, LOW);//turn LED off
delay(1000);//wait one second

Sometimes using delay() is not a great option because the Arduino can't perform any secondary tasks while the delay is happening. Imagine we wanted to blink an LED and detect a button press at the same time using delay():

loop(){

digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
boolean buttonState = digitalRead(7);

}

In the code above, we are only measuring the button once every two seconds, so it may take up to two seconds before a button press is detected, and very brief presses might not ever get detected at all.

millis() gives us control over when events happen without putting pauses in the sketch. Each time we call millis() in an Arduino sketch, it returns the number of milliseconds since the Arduino was turned on.


Run the following code to see how millis() works:


//recording time with Arduino millis()

void setup() {
  Serial.begin(9600);    
}

void loop()
{
  unsigned long currentMillis = millis();
  Serial.println(currentMillis);
}

Here's how to use millis() to blink an LED without using delay().


//blink led without delay()

int ledPin =  7;

int ledState = LOW;//current state of the LED
unsigned long timeOfLastLedEvent = 0;//the last time the LED was updated

int intervalON = 1000;//how long we want the LED to stay on
int intervalOFF = 500;//how long we want the LED to stay off

void setup() {
  pinMode(ledPin, OUTPUT);  
  digitalWrite(ledPin, ledState);  
}

void loop() {

  unsigned long currentMillis = millis();
  
  if (ledState == LOW){//if the LED is already off
    if (currentMillis - timeOfLastLedEvent > intervalOFF){//and enough time has passed
      digitalWrite(ledPin, HIGH);//turn it on
      ledState = HIGH;//store its current state
      timeOfLastLedEvent = currentMillis;//update the time of this new event
    }
  } else {//if the LED is already on
    if (currentMillis - timeOfLastLedEvent > intervalON){
      digitalWrite(ledPin, LOW);
      ledState = LOW;
      timeOfLastLedEvent = currentMillis;
    }
  }
}

The sketch above introduces a few new things:

unsigned long is another data type (so far we've seen int and boolean). Unsigned long is like int, but larger, I'll explain... Each data type requires a certain amount of space in the Arduino's memory, and the amount of space that the Arduino frees up for a given variable dictates the min and max values that the variable can store. For example, int's can range from -32,768 to 32,767, if you tried to do something like this:

int myVariable = 100,000;

You would end up with a very strange bug in your code. This may seem like an arbitrary range, but it comes from the fact that int's require 16 bits of space in the Arduino's memory, and with 16 bits of binary you can store numbers from 0 to (2^16-1) = 65535. But people decided that int should be able to store negative numbers too, so one of the bits in the 16 bit number is used to store the sign (positive or negative) and the remaining 15 bits store the value : 2^15 = 32768. Including 0, we end up with the range -32,768 to 32,767. Another data type called an insigned int does not store sign, so it gets the 0 to 65535 range that I calculated before, but you cannot store a negative number in an insigned int.

When we need to use numbers larger than 65535 or less than -32768, we use a data type called long. Long is allocated 32 bits of space in the Arduino's memory. 2^32 = 4,294,967,296, center this around zero to get a range of : -2,147,483,648 to 2,147,483,647. Unsigned long's, like unsigned int's are always positive, so they range from 0 to 4,294,967,295.

There is no larger data type for storing numbers than long, so if you need to store a number larger than 4,294,967,295, you'll have to come up with a different way to store it (maybe the first 9 bits in one number and the last nine in another?). This limitation has some interesting consequences for the millis() function. Since millis returns unsigned longs, and it's constantly counting up in milliseconds, millis() will actually reset back to zero once it reaches:

4,294,967,295 ms

= 4,294,967seconds

= 49.71 days

If you use millis() and you plan on keeping you project running for extended periods of time without ever turning it off or resetting, you should be mindful of this.

One more comment about data types: We could have been using long's or unsigned long's this whole time when we declare pin numbers or other variables in the example sketches so far, but generally it's a good idea to use the smallest data type possible for a variable, that way you have plenty of extra space in the Arduino's memory for other things. In Arduino, longs are rarely used, but millis() is a good example of when they come in handy.

Getting back to the sketch, the general idea is to store the last time you toggled the LED on or off and compare that with the current time returned by millis(). Once the difference between those two times is greater than some interval, you know it's time to toggle the LED again. To do this I've set up some new storage variables:

int ledState = LOW;//current state of the LED
unsigned long timeOfLastLedEvent = 0;//the last time the LED was updated
int intervalON = 1000;//how long we want the LED to stay on
int intervalOFF = 500;//how long we want the LED to stay off

In the loop() there's a bunch of logic that checks to see if enough time has passed, and if so, toggles the LED, updates the variable "timeOfLastLedEvent", and toggles the stored state of the LED. The logic is repeated twice, once for the case that the LED is HIGH, and once for the case that the LED is low, I'll repeat the LOW case below:

if (currentMillis - timeOfLastLedEvent > intervalOFF){//and enough time has passed

digitalWrite(ledPin, HIGH);//turn it on
ledState = HIGH;//store its current state
timeOfLastLedEvent = currentMillis;//update the time of this new event

}

currentMillis is an unsigned long representing the current time that is updated each time the Arduino's loop() function starts. (currentMillis - timeOfLastLedEvent) gives the time sine the LED's state was last changed, we compare this against the intervalOFF to see if it's time to turn off the LED, if it's not the Arduino will keep updating currentMillis and re-checking until it's time.

nheck3 months ago

i love your tutorials! keep it up :)

schel5 months ago

Most awesome! Good job Amanda!! I have a kinda related question maybe you or someone can direct me to the 'ible or info. I have 2 diy UNOs..I want one to send signals to the other to operate various servos etc a tx AND rx capability between them would be great too. Both need to do there own things until UNO1 tells UNO2 to do something else....make sense? ANY help is super appreciated! Lots in your instructable has helped me to get closer to a solution! Much Thanks!

amandaghassaei (author)  schel4 months ago

http://www.instructables.com/id/I2C-between-Arduinos/

maewert5 months ago

The rollover of millis() is a problem to consider when making programs that execute for long periods of time. Does your code properly manage this? I have not found a good writeup demonstrating how it is best managed. In your code:

'if (currentMillis - timeOfLastLedEvent > intervalON){'

I might expect once the roll over to occur that your program would appear to stop working since it would take 49 days for left side to be larger than the right side (if ever).

One can easily detect when the rollover occurs (currentMillis<timeOfLastEvent) and can compute the actual time since the previous time ( = currentMillis + (rolloverTime-timeOfLastEvent). So I'm thinking the best approach would be to create a function which return the time since last like so:

int delta_Time(long unsigned int timeOfLastEvent)

{

long unsigned int currentMillis = millis();

if (currentMillis>= timeOfLastEvent)

return currentMillis-timeOfLastEvent;

else

return currentMillis+ (4294967295-timeOfLastEvent);

}

I haven't tried it, but think someone should document it for the masses ;-)

Nice Instructable!

Best Wishes.

I actually take care of the 50 day rollover in my Vampire Killer project.

Feel free to raid my code.

As SuperTech-IT pointed out: easy way to handle the rollover is to cast the subtraction like so and the math all works out:

if ((unsigned long)(current_time - previous_time) >= time_interval)

{

previous_time = current_time;

// time interval was exceeded

}

Thanks!

Looks suspiciously a lot like my code....LOL! Thanks for the implied kudos! LOL. Because the Vampire Killer is intended as an "always on" device, and uses both a 2 second and a 1 minute timer which cannot be set up as delays, millis() had to be utilized AND the 50 day rollover had to be addressed - otherwise it'd screw up about every 50 days - and with 8 power outlets being timed, this could be a disaster if it wasn't coded like this.

amandaghassaei (author)  maewert5 months ago

That function looks good to me, I'll mention it in the article when I get a chance. Thanks!

That looks over complicated and explicitly typing out maxint instead of using MAXINT could get you into trouble. I think you might want this instead:

unsigned long waitUntilMillis;

void setup() { waitUntilMillis = millis() + someInterval; }

void loop() { if (millis() - waitUntilMillis >= 0) { time's up, set waitUntilMillis } }

lapsmith5 months ago

Wow, this is awesome! Bookmarking this for future reference, thanks!

lemaxnut5 months ago

I just got my ARDUINO Leonardo yesterday and was looking for some projects to practice code, so the timing of this Instructable was perfect - Thanks. You are a very good teacher!

neo35875 months ago

You also can use internal timers instead of delay() and millis(), the precision is almost perfect and never rolls over.

Example:

void setup () {

//put here pinMode(), Serial.begin(), etc etc.

cli();

TCCR1A = 0;

TTCR1B = 0;

TCNT1 = 0;

OCR1A = 0x3E80;

TCCR1B += (1<<WGM12) + (1<<CS10) /* replace "+" for OR function, I can't put it here. */

TIMSK1 += (1<<OCIE1A); // replace "+" for OR function here too.

sei();

}

ISR(TIMER1_COMPA_vect) {

//put here the code, this code will be repeated exactly each 1 ms

}

Pro

Get More Out of Instructables

Already have an Account?

close

PDF Downloads
As a Pro member, you will gain access to download any Instructable in the PDF format. You also have the ability to customize your PDF download.

Upgrade to Pro today!