Click here to Skip to main content
15,867,488 members
Articles / Internet of Things / Arduino

Digital Egg Timer...

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
1 Aug 2018CPOL8 min read 20.3K   123   3   8
Maximize your Arduino with some fun...

Introduction

It is very easy to start an Arduino project. To light some LEDs. However, very quickly, you will eat up the available pins. You can see the article, linked to the original 'Summer Fun with Arduino', challenge 1 here: Connecting an Arduino to a Breadboard to Light Up LEDs[^].

As you can see, it lights a sequence of eight LEDs according to the bits of a number. But what if you want to show a 16 bit number? You have only 14 digital pins on the Arduino UNO used in the sample...

While you can go with other boards (like MEGA), you can hit a size limit whenever you go for larger board/controller, and pay more too. So after all, it can be very helpful to know how to use additional chips to maximize the potential of the microcontroller...

Starting Point

The final project will contain three seven-segment displays and a piezo (buzzer). To demonstrate the problem, I created (tried at least) a diagram of those components connected to the board...

Image 1

While the display shows perfectly the "answer to the Ultimate Question of Life, the Universe, and Everything", it has a problem... Not a single pin left to connect the piezo (not to mention the third seven segment display). It also ate rx/tx line so could be problem to initiate serial communication (it is a very cool option to hook the Arduino to your PC/Tablet and configure the running application via serial communication - a kind of command-line interpreter).

The project I intend to create is not a big one (like driving a printer or washing machine) and even so, I managed to break the boundaries of the Arduino...

Shift Register

To resolve the problem, I want you to meet the 74HC595 shif-register chip. A shift register is a serial-in parallel-out (it can be serial-out too, but that's not really relevant now :-)) gate between the microcontroller and the device. The idea is that you use only 3 pins to write an 8-bit value. It happens in a bit-by-bit fashion, where the shift register will receive a bit with every clock pulse and push the values into the some internal storage (which overflows when full). When the shift register gets the 'end-of-transmittion' signal it pushes the internal storage to the pins and lights the display...

A nice addition of the 595, that it has a 'overflow' pin, that enables chaining multiple shift registers and save even more Arduino pins. So when you write out 16 bits (in two steps of 8 bits each), the first 8 pulses will set the 8 storage pins of the 595 and the pulses from 9 to 19 will pushe them out to the 'overflow' pin - one by one, and take their places. And you can go on to 24 or 32 or more bits, writing them in groups of 8 but within the same loop. If you connect the 'overflow' pin to the data pin of another 595, you actually write both chips via the very same Arduino pin.

Let's see it on the board...

Image 2

Now the white and yellow and turquoise lines are connected to the shift registers. All shift registers' clock and latch lines are connected to pin 8 and 9 as we want to synchronize the writing and timing for all displays. Pin 13 is connected to the data line of the first shift register (the right one) and the 'overflow' pin of that is connected to the data line of the second shift register (the middle one), and the 'overflow' pin of that is connected to the data line of the last shift register (the left one).

Now I had the free pin to connect the piezo (purple) and still 10 free pins left, including the rx/tx pair for serial communication... What is actually happening here is that I can drive 3 seven-segment displays (that is 21 LEDs!) using only 3 pins of my Arduino (and it can go on, even to the crazy thing of driving shift registers with shift registers)...

Let's Code It

Driving the Shift Register

C++
// rightmost bit is always 0 as we do not use the first pin
// of the shift registers (it is on the far side)
const byte ledValues[10] = {
  //GFEDCBA - pins
  0b01111110, // 0
  0b00001100, // 1
  0b10110110, // 2
  0b10011110, // 3
  0b11001100, // 4
  0b11011010, // 5
  0b11111010, // 6
  0b00001110, // 7
  0b11111110, // 8
  0b11011110, // 9
};

void registerWrite(byte left, byte middle, byte right) {
  digitalWrite(latchPin, LOW);

  shiftOut(dataPin, clockPin, MSBFIRST, left);
  shiftOut(dataPin, clockPin, MSBFIRST, middle);
  shiftOut(dataPin, clockPin, MSBFIRST, right);

  digitalWrite(latchPin, HIGH);
}

ledValues - it is a simple mapping of the digits to the 7 LEDs in the display (the first bit position unused because of technical reasons, I do not use the Q0 pin of the shift register). Because I push the bits starting from the most significant bit, the order of A-G is flipped here...

If you look at the registerWrite method, you can realize that I write out the leftmost byte first (the one holds the value for the hundreds), then the middle (holds the value for the tens) and finally the rightmost (that holds the value for the ones). So if I write 42 (that is 042) to the display, I send this bit sequence (in this order):

0,1,1,1,1,1,1,0,1,1,0,0,1,1,0,0,1,0,1,1,0,1,1,0

You may wonder while the seemingly reversed order? You may be thinking that the first write should be the first led (A) of the first display (rightmost), but remember that we are using shift registers!!! When a new value comes in on the data line, it pushes the previous value on (and probably out to the overflow bit). So the first bit I have to write is the value of LED G of that last display in the chain (the leftmost).

The other interesting thing is that the shiftOut method (not mine - it is native to the Arduino) can write only 8 bits - a byte - so I need three calls to write out three digits. The trick is that as long as the latch line of the shift register is low, it will be addressed as a single input. The moment I set the latch line to high, the data that came in will be sent out on the Q pins... And that also explains why I connected all the latch lines of all the shift registers to the same pin - that enables me to control their states as one. The same of course is true for the clock line, that will synchronize the writings using the same frequency for all the shift registers...

Counting Seconds

Arduino has no builtin clock to trace time, but it has a feature (common for microcontrollers) called watchdog. This feature is a ticking clock - parallel to the execution of the main loop, that is mainly used to break out from hangs. The watchdog can be programmed to fire an interrupt periodically, if the main program did not reset the watchdog before the interrupt finished, it will initiate a reboot of the controller.

In my case, I set the watchdog timer to kick in every second, then I reduce the counter and reset the watchdog to come back again after one second...

C++
#include <avr/wdt.h> // you need this for easier access of the watchdog timer

// watchdog interrupt handler
ISR(WDT_vect) {
  if(counter > 0) {
    counter--;

    wdt_start();
  }
  else {
    alarm = true;
    wdt_stop();
  }
}

void wdt_start() {
  wdt_disable();

  WDTCSR |= 0b00011000;          // enable presale settings to set the interrupt time 
                                 // in the next line (bit 5)
  WDTCSR = 0b01001000 | WDTO_1S; // enable interrupt flag (bit 7) and set timing to 1s (bits 1, 2)
                                 // bit 4 is always high to enable writing to the 
                                 // watchdog control register

  wdt_reset();
}

void wdt_stop() {
  wdt_reset();
  wdt_disable();
}

void setup() {
  wdt_stop();
}

void loop() {
  wdt_start();
}

As watchdog is a low level thing (and I do not want to load more libraries and bloat my Arduino), I initialize the timer using some bit settings...

Probably the most important thing is to disable the watchdog in the setup method, so it will not interfere with the software/hardware initialization, that may take more than 1 second, in which case we will hit an infinite loop of restarting...

The ISR method is the interrupt handler and that's the place the actual counting is done... After each interrupt, I restart the cycle or stop the timer if the counter hits zero.

Setting and Resetting

Then the next major part is starting and stopping the timer. For it to be totally autonomous, I would have add some buttons to set/start/stop the timer. However, I decided to use the rx/tx line (after all, I worked hard to save them) to hook the Arduino to my PC and configure it from there...

C++
void loop() {
  bool endRead = false;
 
  while (Serial.available())
  {
    byte val = Serial.read();

    if (val > 47 && val < 58)
    {
      value = value * 10 + (val - 48);
    }
    else if (val == 's') {
      endRead = true;
    }
    else if (val == 'r' || val == 'R')
    {
      reset();
    }
  }

  if (endRead && value > 0 && value < 1000)
  {
    setValue(value);

    wdt_start();
    
    value = 0;
  }
}

This code reads in everything sent on the rx/tx line (on the PCs end, it will probably show as a COM over USB connection). If you look closely, you will notice that this code is built to work in an environment where IO is not blocking. In most of the developer world (desktop or web or mobile), the IO is blocking by default and you have to do special efforts to overcome it. In Arduino however, Serial.read is non-blocking by definition. What it means in practice is that not all the information sent will be read in the same spring of the loop method.

My solution for that is using single characters to flag the end of input. For my need, I picked r (or R) for reset the timer, and s (seconds) to flag the end of the numeric input. Sending for instance 456s will set the timer 456 seconds and start counting. On the other hand, sending 23 will not do anything, except wait for more input.

Alarmingly Funny

When the timer runs out, it sets a flag to start the alarm phase. A piezo is not much for music, as all it can do is playing a note of specific frequency for a specific duration, and not in fantastic quality... However, that is enough to play a melody as alarm instead of simple buzzing, and that's exactly the last part of what the code does...

C++
// music notes
const int o = 0; // represents silence

const int a = 440;
const int f = 349;
const int cH = 523;
const int eH = 659;
const int fH = 698;
const int gS = 415;

const int len = 19;
const int notes[] = {a, a, a, f, cH, a, f, cH, a, o, eH, eH, fH, cH, gS, f, cH, a, o};
const int beats[] = {8, 8, 8, 6, 2, 8, 6, 2, 16, 8, 8, 8, 8, 6, 2, 8, 6, 2, 16, 8};
const int tempo = 70;

const int piezo = 2;

void play(int note, int duration)
{
  tone(piezo, note, duration);

  delay(duration);
  noTone(piezo);

  delay(tempo / 2); // fixed pause between notes
}

void setup() {
  pinMode(piezo, OUTPUT);
}

void loop() {
  if(alarm) {
    for (int i = 0; i < len; i++) {
      play(notes[i], beats[i] * tempo);
      }
  }
}

As I know nothing of music notes, I used the values from here (with some modification to make it more efficient): Arduino Star Wars Song for Piezo.

It will play you the Imperial March theme from Star Wars in a very irritating way, over and over again, until you reset the timer...

Hardware

Only to complete it.

Quantity Component
1 Arduino UNO
3 Seven Segment Display (cathode but you can switch)
3 8-Bit Shift Register (74HC595)
4 100 &#8486; Resistor
1 Piezo
  A nice amount of wire of several colors

Summary

Working with modern computers, or even with mobile devices may have left us with the feeling that there is no limit of resource in our hands. When starting to work with electronics and using microcontrollers, we can easily hit walls and break down. This project shows how to re-think and resolve problems of shortage of resources. Spending small but still thinking big...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Israel Israel
Born in Hungary, got my first computer at age 12 (C64 with tape and joystick). Also got a book with it about 6502 assembly, that on its back has a motto, said 'Try yourself!'. I believe this is my beginning...

Started to learn - formally - in connection to mathematics an physics, by writing basic and assembly programs demoing theorems and experiments.

After moving to Israel learned two years in college and got a software engineering degree, I still have somewhere...

Since 1997 I do development for living. I used 286 assembly, COBOL, C/C++, Magic, Pascal, Visual Basic, C#, JavaScript, HTML, CSS, PHP, ASP, ASP.NET, C# and some more buzzes.

Since 2005 I have to find spare time after kids go bed, which means can't sleep to much, but much happier this way...

Free tools I've created for you...



Comments and Discussions

 
Questionquestion Pin
Member 148575678-Jun-20 21:07
Member 148575678-Jun-20 21:07 
GeneralNice write up.... Pin
DaveAuld9-Sep-18 3:55
professionalDaveAuld9-Sep-18 3:55 
Questionplaying other sounds Pin
Matthew Dennis13-Aug-18 9:57
sysadminMatthew Dennis13-Aug-18 9:57 
GeneralMy vote of 5 Pin
raddevus2-Aug-18 2:21
mvaraddevus2-Aug-18 2:21 
Really great write-up. So much to learn here and a great topic of bitshift registers. Also, really cool use of watchdog timer.
Did you get this to run on TinkerCad or did you just use it to diagram? Great stuff.
GeneralRe: My vote of 5 Pin
Kornfeld Eliyahu Peter2-Aug-18 2:25
professionalKornfeld Eliyahu Peter2-Aug-18 2:25 
GeneralRe: My vote of 5 Pin
raddevus2-Aug-18 2:30
mvaraddevus2-Aug-18 2:30 
QuestionGot my 5 Pin
Zdenek Sedlak2-Aug-18 0:48
professionalZdenek Sedlak2-Aug-18 0:48 
AnswerRe: Got my 5 Pin
Kornfeld Eliyahu Peter2-Aug-18 1:43
professionalKornfeld Eliyahu Peter2-Aug-18 1:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.