This is a rewrite of a project I created in 2010 which brought me a lot of joy, so I hope you enjoy it too.Please read the entire article before starting your own. You can still make it today, the parts are easily available.
I’ve always enjoyed making Arduino-powered clocks, however over time they tended to become increasingly complex. So to counter this, allow me to introduce you to “Blinky” the one-eyed clock:
It reminds me of the giant killer orb “Rover” from “The Prisoner“… Using a minimal Arduino bootrom system, a DS1307 real time clock IC and an RGB diffused LED, you can make a clock that blinks the time, using the colours of the LED to note different numerical values.
For example, if the time is 12:45, the clock will blink red 12 times, then show blue for a second (think of this as the colon on a digital clock) then blink four times in green (for forty minutes), then blink three times in red for the individual minutes.
If there is a zero, blink blue quickly. Then the clock will not display anything for around forty seconds, then repeat the process. Here he (she, it?) is blinking the time:
Setting the clock is simple. It is set to start at 12:00 upon power up. So for the first use you have to wait until about five seconds before midday or midnight, then power it up. To save cost it doesn’t use a backup lithium battery on the real-time clock IC, but you could add one if required. So let’s get started.
The first thing to do was test the RGB LED for brightness levels, so I just connected it to the digital output pins of my Arduino-compatible board via suitable current-limiting resistors. Red, green and blue to D9, D10 and D11 respectively. Each LED is going to be different, so to ensure maximum brightness without causing any damage you need to calculate the appropriate resistor values.
This is quite easy, the formula is: resistor (ohms) = voltage drop / LED current So if you have a 5V supply, and LED that needs only 2 volts, and draws 20 milliamps (0.2 amps) , the calculation will be: resistor = (5-2)/0.02 = 150 ohms. To be safe I used 180 ohm resistors. The LED was tested with this simple sketch:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It was interesting to alter the value of d, the delay variable, to get an idea for an appropriate blinking speed. Originally the plan was to have the LED in a photo frame, but it was decided to mount a ping-pong ball over the LED for a retro-style look. Here is a short video of the result of the test:
If you are going to use a ping-pong ball, please be careful when cutting into it with a knife, initially it may require a lot of force, but once the knife cuts through it does so very quickly.
Now it was time to develop the sketch to convert time into blinks. The sketch itself is quite simple. Read the hours and minutes from the DS1307 timer IC; convert the hours to 12 hour time; then blink an LED for the number of hours, display another colour for the colon; divide the minutes by ten and blink that in another colour; then the modulus of minutes and ten to find the individual minutes, and blink those out. Here is the first test sketch:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Finally, the code was tested using the Arduino-compatible board and my home-made DS1307 real time clock shield (hey it was 2010, DS32xx were expensive). It is best to use existing hardware while testing, before committing to purchasing new hardware and so on. So here it is on the breadboard:
Here is the prototype in action:
If you’re wondering why the videos are potato-cam quality, smartphones couldn’t record using 4K Ultra HD in 2010.
But perhaps the first version was a little bland. By using analogWrite() we can control the brightness of the LED segments. So I’ve added two more functions, whiteGlow() and blueGlow(); whose purpose is to make the display “glow” by increasing then decreasing the brightness.
And I’ve scaled back the amount of blinking, to make blinky less obvious. So now the display will glow white to announce the forthcoming display of time, wait a second, blink the time (with a blue glowing colon) then stay dark for ten seconds before repeating the process. Here is a quick demonstration of this display style:
Here is the sketch for the above demonstration, and the final one I will use with the hardware prototype:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Once happy with the sketch, I put a fresh ATmega328P-PU with Arduino bootloader in the board and programmed it with the sketch, to be used in the final version. The next step is to build my own hardware. The last hardware unknown is the amount of current the circuit draws. Once I know this the correct voltage regulator and power supply can be decided upon.
I had a fair idea it would be less than 100 milliamps, so I put a 6V battery onto supply duty via a 78L05 5V regulator (data sheet), and recorded the result:
So it varies, between 20.5 and 46 mA. As it only reaches 46 mA for a short time, we could consider the constant draw to be averaged out at 30 mA. I really want this to be able to run from a battery, but without having an external lead-acid battery lurking around, it will need a plug-pack with an output voltage greater than 7V DC.
Another alternative would be to run it from a USB socket, a nice source of 5V. If doing so, there wouldn’t be a need for the 78L05 regulator. Which brings us to the circuit diagram, which includes the power regulator. I’ve also altered the resistors to suit the RGB LED used, your values may be different:
And since it’s 2022, not 2010 – I’ve replaced the DS1307 circuit with a RTC module. Y1 is a three pin 16MHz ceramic resonator, we used those in 2010 as they were cheaper and easier than a crystal and two 22pF capacitors.
The circuit does not allow for uploading code, so you will need to program the microcontroller on another Arduino or compatible board, then transfer it to the blinky circuit board as described above. At this stage you should test it again – but using a solderless breadboard. In doing so you can make final hardware checks, and generally make sure everything works as it should. This is also a good stage to double-check you are happy with the display behaviour, default time and so on.
Used the Duemilanove as a lazy 5V for testing.
Time to solder up the circuit on some stripboard. Blank stripboard varies, but luckily I found this and a nice box to hold it in:
Stripboard does vary between retailers and so on, so you will need to work out the layout with your own board. In doing so, please double-check your work – follow the layout against the schematic and so on.
Have a break, then check it again. There is nothing worse than soldering away to realise you are one strip too far over or something. My hand-eye coordination is not the best, therefore my soldering isn’t pretty, but it works:
Note that the images above are using the 2010 circuit – which had a DS1307 sub-circuit.
One would say that there is a good argument for making your own PCBs… and I would agree with that. In 2010 it wasn’t that easy or inexpensive. Now you have KiCAD and Chinese PCB fabs tripping over themselves to give you cheap boards.
The LED is soldered to some short leads to give it a bit of play, and some heatshrink over the legs to keep them isolated:
And finally, to add a DC socket to feed blinky some power:
The last thing was to check the soldering once more under natural light, to check for bridges or shorts, then have a cup of tea. Upon my return I drilled out a hole in the enclosure lid for the LED, and one one the side for the DC socket, and fitted the lot together… and success! It worked.
I hope you enjoyed making this or at least reading about it. If you find this sort of thing interesting, please consider ordering one or both of my books from No Starch Press, or other book sellers:
In this tutorial we look at how to use the neat LED Real Time Clock Temperature Sensor Shield for Arduino from PMD Way. That’s a bit of a mouthful, however the shield does offer the following:
four digit, seven-segment LED display
DS1307 real-time clock IC
three buttons
four LEDs
a active buzzer
a light-dependent resistor (LDR)
and a thermistor for measuring ambient temperature
The shield also arrives fully-assembled , so you can just plug it into your Arduino Uno or compatible board. Neat, beginners will love that. So let’s get started, by showing how each function can be used – then some example projects. In no particular order…
The buzzer
A high-pitched active buzzer is connected to digital pin D6 – which can be turned on and off with a simple digitalWrite() function. So let’s do that now, for example:
voidsetup(){// buzzer on digital pin 6pinMode(6,OUTPUT);}// the loop function runs over and over again forevervoidloop(){digitalWrite(6,HIGH);// turn the buzzer on (HIGH is the voltage level)delay(1000);// wait for a seconddigitalWrite(6,LOW);// turn the buzzer off by making the voltage LOWdelay(1000);// wait for a second}
If there is a white sticker over your buzzer, remove it before uploading the sketch. Now for a quick video demonstration. Turn down your volume before playback.
The LEDs
Our shield has four LEDs, as shown below:
They’re labelled D1 through to D4, with D1 on the right-hand side. They are wired to digital outputs D2, D3, D4 and D5 respectively. Again, they can be used with digitalWrite() – so let’s do that now with a quick demonstration of some blinky goodness. Our sketch turns the LEDs on and off in sequential order. You can change the delay by altering the variable x:
voidsetup(){// initialize digital pin LED_BUILTIN as an output.pinMode(2,OUTPUT);// LED 1pinMode(3,OUTPUT);// LED 2pinMode(4,OUTPUT);// LED 3pinMode(5,OUTPUT);// LED 4}intx=200;voidloop(){digitalWrite(2,HIGH);// turn on LED1delay(x);digitalWrite(2,LOW);// turn off LED1. Process repeats for the other three LEDsdigitalWrite(3,HIGH);delay(x);digitalWrite(3,LOW);digitalWrite(4,HIGH);delay(x);digitalWrite(4,LOW);digitalWrite(5,HIGH);delay(x);digitalWrite(5,LOW);}
And in action:
The Buttons
It is now time to pay attention to the three large buttons on the bottom-left of the shield. They look imposing however are just normal buttons, and from right-to-left are connected to digital pins D9, D10 and D11:
They are, however, wired without external pull-up or pull-down resistors so when initialising them in your Arduino sketch you need to activate the digital input’s internal pull-up resistor inside the microcontroller using:
pinMode(pin, INPUT_PULLUP);
Due to this, buttons are by default HIGH when not pressed. So when you press a button, they return LOW. The following sketch demonstrates the use of the buttons by lighting LEDs when pressed:
voidsetup(){// initalise digital pins for LEDs as outputspinMode(2,OUTPUT);// LED 1pinMode(3,OUTPUT);// LED 2pinMode(4,OUTPUT);// LED 3// initalise digital pins for buttons as inputs// and initialise internal pullupspinMode(9,INPUT_PULLUP);// button K1pinMode(10,INPUT_PULLUP);// button K2pinMode(11,INPUT_PULLUP);// button K3}voidloop(){if(digitalRead(9)==LOW){digitalWrite(2,HIGH);delay(10);digitalWrite(2,LOW);}if(digitalRead(10)==LOW){digitalWrite(3,HIGH);delay(10);digitalWrite(3,LOW);}if(digitalRead(11)==LOW){digitalWrite(4,HIGH);delay(10);digitalWrite(4,LOW);}}
You can see these in action via the following video:
The Numerical LED Display
Our shield has a nice red four-digit, seven-segment LED clock display. We call it a clock display as there are colon LEDs between the second and third digit, just as a digital clock would usually have:
The TM1636 itself is an interesting part, so we’ll explain that in a separate tutorial in the near future. For now, back to the shield.
To control the LED display we need to install an Arduino library. In fact the shield needs four, so you can install them all at once now. Download the .zip file from here. Then expand that into your local download directory – it contains four library folders. You can then install them one at a time using the Arduino IDE’s Sketch > Include library > Add .zip library… command:
The supplied library offers five functions used to control the display.
.num(x);
…this displays a positive integer (whole number) between 0 and 9999.
.display(p,d);
… this shows a digit d in location p (locations from left to right are 3, 2, 1, 0)
.time(h,m)
… this is used to display time data (hours, minutes) easily. h is hours, m is minutes
.pointOn();
.pointOff();
… these turn the colon on … and off. And finally:
.clear();
… which clears the display to all off. At the start of the sketch, we need to use the library and initiate the instance of the display by inserting the following lines:
#include <TTSDisplay.h>
TTSDisplay rtcshield;
Don’t panic – the following sketch demonstrates the five functions described above:
#include<TTSDisplay.h>TTSDisplayrtcshield;inta=0;intb=0;voidsetup(){}voidloop(){// display some numbersfor(a=4921;a<5101;a++){rtcshield.num(a);delay(10);}// clear displayrtcshield.clear();// display individual digitsfor(a=3;a>=0;--a){rtcshield.display(a,a);delay(1000);rtcshield.clear();}for(a=3;a>=0;--a){rtcshield.display(a,a);delay(1000);rtcshield.clear();}// turn the colon and offfor(a=0;a<5;a++){rtcshield.pointOn();delay(500);rtcshield.pointOff();delay(500);}// demo the time display functionrtcshield.pointOn();rtcshield.time(11,57);delay(1000);rtcshield.time(11,58);delay(1000);rtcshield.time(11,59);delay(1000);rtcshield.time(12,00);delay(1000);}
And you can see it in action through the video below:
The LDR (Light Dependent Resistor)
LDRs are useful for giving basic light level measurements, and our shield has one connected to analog input pin A1. It’s the two-legged item with the squiggle on top as shown below:
The resistance of LDRs change with light levels – the greater the light, the less the resistance. Thus by measuring the voltage of a current through the LDR with an analog input pin – you can get a numerical value proportional to the ambient light level. And that’s just what the following sketch does:
#include<TTSDisplay.h>TTSDisplayrtcshield;inta=0;voidsetup(){}voidloop(){// read value of analog inputa=analogRead(A1);// show value on displayrtcshield.num(a);delay(100);}
The Thermistor
A thermistor is a resistor whose resistance is relative to the ambient temperature. As the temperature increases, their resistance decreases. It’s the black part to the left of the LDR in the image below:
We can use this relationship between temperature and resistance to determine the ambient temperature. To keep things simple we won’t go into the theory – instead, just show you how to get a reading.
The thermistor circuit on our shield has the output connected to analog input zero, and we can use the library installed earlier to take care of the mathematics. Which just leaves us with the functions.
At the start of the sketch, we need to use the library and initiate the instance of the thermistor by inserting the following lines:
#include <TTSTemp.h>
TTSTemp temp;
… then use the following which returns a positive integer containing the temperature (so no freezing cold environments):
.get();
For our example, we’ll get the temperature and show it on the numerical display:
And our thermometer in action. No video this time… a nice 24 degrees C in the office:
The Real-Time Clock
Our shield is fitted with a DS1307 real-time clock IC circuit and backup battery holder. If you insert a CR1220 battery, the RTC will remember the settings even if you remove the shield from the Arduino or if there’s a power blackout, board reset etc:
The DS1307 is incredibly popular and used in many projects and found on many inexpensive breakout boards. We have a separate tutorial on how to use the DS1307, so instead of repeating ourselves – please visit our specific DS1307 Arduino tutorial, then return when finished.
Where to from here?
We can image there are many practical uses for this shield, which will not only improve your Arduino coding skills but also have some useful applications. An example is given below, that you can use for learning or fun.
Temperature Alarm
This projects turns the shield into a temperature monitor – you can select a lower and upper temperature, and if the temperature goes outside that range the buzzer can sound until you press it.
Here’s the sketch:
#include<TTSDisplay.h>#include<TTSTemp.h>TTSTemptemp;TTSDisplayrtcshield;booleanalarmOnOff=false;inthighTemp=40;intlowTemp=10;intcurrentTemp;voidLEDsoff(){// function to turn all alarm high/low LEDs offdigitalWrite(2,LOW);digitalWrite(4,LOW);}voidsetup(){// initalise digital pins for LEDs and buzzer as outputspinMode(2,OUTPUT);// LED 1pinMode(3,OUTPUT);// LED 2pinMode(4,OUTPUT);// LED 3pinMode(5,OUTPUT);// LED 4pinMode(6,OUTPUT);// buzzer// initalise digital pins for buttons as inputs// and initialise internal pullupspinMode(9,INPUT_PULLUP);// button K1pinMode(10,INPUT_PULLUP);// button K2pinMode(11,INPUT_PULLUP);// button K3}voidloop(){// get current temperaturecurrentTemp=temp.get();// if current temperature is within set limts// show temperature on displayif(currentTemp>=lowTemp||currentTemp<=highTemp)// if ambient temperature is less than high boundary// OR if ambient temperature is grater than low boundary// all is well{LEDsoff();// turn off LEDsrtcshield.num(currentTemp);}// if current temperature is above set high bounday, show red LED and// show temperature on display// turn on buzzer if alarm is set to on (button K3)if(currentTemp>highTemp){LEDsoff();// turn off LEDsdigitalWrite(4,HIGH);// turn on red LEDrtcshield.num(currentTemp);if(alarmOnOff==true){digitalWrite(6,HIGH);// buzzer on }}}// if current temperature is below set lower boundary, show blue LED and// show temperature on display// turn on buzzer if alarm is set to on (button K3)if(currentTemp<lowTemp){LEDsoff();// turn off LEDsdigitalWrite(2,HIGH);// turn on blue LEDrtcshield.num(currentTemp);if(alarmOnOff==true){digitalWrite(6,HIGH);// buzzer on }}}// --------turn alarm on or off-----------------------------------------------------if(digitalRead(11)==LOW)// turn alarm on or off{alarmOnOff=!alarmOnOff;if(alarmOnOff==0){digitalWrite(6,LOW);// turn off buzzerdigitalWrite(5,LOW);// turn off alarm on LED}// if alarm is set to on, turn LED on to indicate thisif(alarmOnOff==1){digitalWrite(5,HIGH);}delay(300);// software debounce}// --------set low temperature------------------------------------------------------if(digitalRead(10)==LOW)// set low temperature. If temp falls below this value, activate alarm{// clear display and turn on blue LED to indicate user is setting lower boundaryrtcshield.clear();digitalWrite(2,HIGH);// turn on blue LEDrtcshield.num(lowTemp);// user can press buttons K2 and K1 to decrease/increase lower boundary.// once user presses button K3, lower boundary is locked in and unit goes// back to normal statewhile(digitalRead(11)!=LOW)// repeat the following code until the user presses button K3{if(digitalRead(10)==LOW)// if button K2 pressed{--lowTemp;// subtract one from lower boundary// display new value. If this falls below zero, won't display. You can add checks for this yourself :)rtcshield.num(lowTemp);}if(digitalRead(9)==LOW)// if button K3 pressed{lowTemp++;// add one to lower boundary// display new value. If this exceeds 9999, won't display. You can add checks for this yourself :)rtcshield.num(lowTemp);}delay(300);// for switch debounce}digitalWrite(2,LOW);// turn off blue LED}// --------set high temperature-----------------------------------------------------if(digitalRead(9)==LOW)// set high temperature. If temp exceeds this value, activate alarm{// clear display and turn on red LED to indicate user is setting lower boundaryrtcshield.clear();digitalWrite(4,HIGH);// turn on red LEDrtcshield.num(highTemp);// user can press buttons K2 and K1 to decrease/increase upper boundary.// once user presses button K3, upper boundary is locked in and unit goes// back to normal statewhile(digitalRead(11)!=LOW)// repeat the following code until the user presses button K3{if(digitalRead(10)==LOW)// if button K2 pressed{--highTemp;// subtract one from upper boundary// display new value. If this falls below zero, won't display. You can add checks for this yourself :)rtcshield.num(highTemp);}if(digitalRead(9)==LOW)// if button K3 pressed{highTemp++;// add one to upper boundary// display new value. If this exceeds 9999, won't display. You can add checks for this yourself :)rtcshield.num(highTemp);}delay(300);// for switch debounce}digitalWrite(4,LOW);// turn off red LED}}
Operating instructions:
To set lower temperature, – press button K2. Blue LED turns on. Use buttons K2 and K1 to select temperature, then press K3 to lock it in. Blue LED turns off.
To set upper temperature – press button K1. Red LED turns on. Use buttons K2 and K1 to select temperature, then press K3 to lock it in. Red LED turns off.
If temperature drops below lower or exceeds upper temperature, the blue or red LED will come on.
You can have the buzzer sound when the alarm activates – to do this, press K3. When the buzzer mode is on, LED D4 will be on. You can turn buzzer off after it activates by pressing K3.
Display will show ambient temperature during normal operation.
You can see this in action via the video below:
Conclusion
This is a fun and useful shield – especially for beginners. It offers a lot of fun and options without any difficult coding or soldering – it’s easy to find success with the shield and increase your motivation to learn more and make more.
You can be serious with a clock, or annoy people with the buzzer. And at the time of writing you can have one for US$14.95, delivered. So go forth and create something.
A little research has shown that this shield was based from a product by Seeed, who discontinued it from sale. I’d like to thank them for the library.
This post brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.
To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.
Another option for real-time clock ICs is the PCF8563 real-time clock IC from NXP – so this is a tutorial on how to use it for time, date, alarm clock and square-wave generation purposes.
The PCF8563 is another inexpensive RTC that can be used with an Arduino or other platforms due to the wide operating voltage (1 to 5.5V DC), I2C interface, and very low power consumption (when powered by a backup battery it only draws 0.25 μA). If you aren’t up to speed on the I2C interface, please review the I2C tutorials before moving forward. And please download the data sheet (.pdf).
The PCF8563 is available in various chip packages, for the curious we’re using the TSSOP8 version mounted on a breakout board:
Don’t panic – you can also get it in a breadboard-friendly DIP (through-hole) package as well, and also on a pre-built module from the usual suspects.
Demonstration Circuit
If you have a pre-made module, you can skip to the next section. However if you’re making up the circuit yourself, you will need:
One 32.768 kHz crystal
Two 1N4148 diodes*
One 3V coin cell (with holder)*
Two 10kΩ resistors
One 0.1 uF capacitor
And here’s the schematic:
* You can skip the diodes and battery if you don’t want a backup power supply when the main power is turned off or removed. Pin 3 is for the interrupt output (we’ll consider that later) and pin 7 is for the square-wave oscillator output.
Communicating with the PCF8563
Now to get down into the land of I2C once more. When looking through the data sheet NXP mentions two bus addresses, which have the same 7-bits finished with either a 1 for read or 0 for write. However you can just bitshift it over one bit as we don’t need the R/W bit – which gives you a bus address of 0x51.
Next you need to know which registers store the time and date – check the register map (table 4) on page 7 of the data sheet:
There will be a few other registers of interest, but we’ll return to those later. For now, note that the time and date start from 0x02. And one more thing – data is stored in the BCD (binary-coded- decimal) format. But don’t panic, we have a couple of functions to convert numbers between BCD and decimal.
Writing the time and date is a simple matter of collating the seconds, minutes, hours, day of week, day of month, month and year into bytes, converting to BCD then sending them to the PCF8563 with seven Wire.write() functions. Reading the data is also easy, just set the pointer to 0x02 and request seven bytes of data – then run them through a BCD to decimal conversion. With a catch.
And that catch is the need to sort out unwanted bits. Revisit table 4 in the data sheet – if you see an x that’s an unused bit. If any of them are a 1 they will mess up the BCD-decimal conversion when reading the register, so they need to be eliminated just like a whack-a-mole. To do this, we perform an & (bitwise AND) operation on the returned byte and mask out the unwanted bits with a zero. How does that work?
Example – the byte for dayOfMonth is returned – we only need bits 5 to 0. So 6 and 7 are superfluous. If you use (dayOfMonth & B00111111) the & function will set bits 6 and 7 to zero, and leave the other bits as they were.
Now to put all that together in a demonstration sketch. It puts everything mentioned to work and simply sets the time to the PCF8563, and then returns it to the serial monitor. The data is kept in global variables declared at the start of the sketch, and the conversions between BCD and decimal are done “on the fly” in the functions used to send or retrieve data from the PCF8563. Read through the following sketch and see how it works for yourself:
// Example 54.1 - PCF8563 RTC write/read demonstration
#include "Wire.h"
#define PCF8563address 0x51
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
String days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
byte bcdToDec(byte value)
{
return ((value / 16) * 10 + value % 16);
}
byte decToBcd(byte value){
return (value / 10 * 16 + value % 10);
}
void setPCF8563()
// this sets the time and date to the PCF8563
{
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
void readPCF8563()
// this gets the time and date from the PCF8563
{
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(PCF8563address, 7);
second = bcdToDec(Wire.read() & B01111111); // remove VL error bit
minute = bcdToDec(Wire.read() & B01111111); // remove unwanted bits from MSB
hour = bcdToDec(Wire.read() & B00111111);
dayOfMonth = bcdToDec(Wire.read() & B00111111);
dayOfWeek = bcdToDec(Wire.read() & B00000111);
month = bcdToDec(Wire.read() & B00011111); // remove century bit, 1999 is over
year = bcdToDec(Wire.read());
}
void setup()
{
Wire.begin();
Serial.begin(9600);
// change the following to set your initial time
second = 0;
minute = 28;
hour = 9;
dayOfWeek = 2;
dayOfMonth = 13;
month = 8;
year = 13;
// comment out the next line and upload again to set and keep the time from resetting every reset
setPCF8563();
}
void loop()
{
readPCF8563();
Serial.print(days[dayOfWeek]);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/20");
Serial.print(year, DEC);
Serial.print(" - ");
Serial.print(hour, DEC);
Serial.print(":");
if (minute < 10)
{
Serial.print("0");
}
Serial.print(minute, DEC);
Serial.print(":");
if (second < 10)
{
Serial.print("0");
}
Serial.println(second, DEC);
delay(1000);
}
And a quick video of this in operation:
If all you need to do is write and read the time with the PCF8563, you’re ready to go. However there’s a few more features of this unassuming little part which you might find useful, so at least keep reading…
Square-wave output
As with any clock or RTC IC, an oscillator is involved, and as mentioned earlier you can take this from pin 7 of the PCF8563. However – it’s an open-drain output – which means current flows from the supply voltage into pin 7. For example if you want to blink an LED, connect a 560Ω resistor between 5V and the anode of the LED, then connect the cathode to pin 7 of the PCF8563.
The frequency is controlled from the register at 0x0D. Simply write one of the following values for the respective frequencies:
10000000 for 32.768 kHz;
10000001 for 1.024 kHz;
10000010 for 32 kHz;
10000011 for 1 Hz;
0 turns the output off and sets it to high impedance.
The following is a quick demonstration sketch which runs through the options:
And the resulting waveforms from slowest to highest frequency. Note the sample was measured from a point between the LED and resistor, so the oscillations don’t vary between the supply voltage and zero:
Self-awareness of clock accuracy
The PCF8563 monitors the oscillator and supply voltage, and if the oscillator stops or the voltage drops below a certain point – the first bit of the seconds register (called the VL bit) is set to 1.
Thus your sketch can tell you if there’s a chance of the time not being accurate by reading this bit. The default value is 1 on power-up, so you need to set it back to zero after setting the time in your sketch – which is done when you write seconds using the code in our example sketches. Then from that point it can be monitored by reading the seconds register, isolating the bit and returning the value.
Examine the function checkVLerror() in the following example sketch. It reads the seconds byte, isolates the VL bit, then turns on D13 (the onboard LED) if there’s a problem. The only way to restore the error bit to “OK” is to re-set the time:
// Example 54.3 - PCF8563 RTC write/read demonstration with error-checking
#include "Wire.h"
#define PCF8563address 0x51
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
String days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
byte bcdToDec(byte value)
{
return ((value / 16) * 10 + value % 16);
}
byte decToBcd(byte value){
return (value / 10 * 16 + value % 10);
}
void setPCF8563()
// this sets the time and date to the PCF8563
{
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
void readPCF8563()
// this gets the time and date from the PCF8563
{
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(PCF8563address, 7);
second = bcdToDec(Wire.read() & B01111111); // remove VL error bit
minute = bcdToDec(Wire.read() & B01111111); // remove unwanted bits from MSB
hour = bcdToDec(Wire.read() & B00111111);
dayOfMonth = bcdToDec(Wire.read() & B00111111);
dayOfWeek = bcdToDec(Wire.read() & B00000111);
month = bcdToDec(Wire.read() & B00011111); // remove century bit, 1999 is over
year = bcdToDec(Wire.read());
}
void checkVLerror()
// this checks the VL bit in the seconds register
// and turns on D13 if there's a possible accuracy error
{
byte test;
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(PCF8563address, 1);
test = Wire.read();
test = test & B10000000;
if (test == B10000000)
{
// error
digitalWrite(13, HIGH);
Serial.println("Uh-oh - possible accuracy error");
} else
if (test != B10000000)
{
digitalWrite(13, LOW);
}
}
void setup()
{
Wire.begin();
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
Serial.begin(9600);
// change the following to set your inital time
second = 0;
minute = 42;
hour = 11;
dayOfWeek = 2;
dayOfMonth = 13;
month = 8;
year = 13;
// comment out the next line and upload again to set and keep the time from resetting every reset
// setPCF8563();
}
void loop()
{
readPCF8563();
Serial.print(days[dayOfWeek]);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/20");
Serial.print(year, DEC);
Serial.print(" - ");
Serial.print(hour, DEC);
Serial.print(":");
if (minute < 10)
{
Serial.print("0");
}
Serial.print(minute, DEC);
Serial.print(":");
if (second < 10)
{
Serial.print("0");
}
Serial.println(second, DEC);
checkVLerror();
delay(1000);
}
And now for a demonstration of the error-checking at work. We have the PCF8563 happily returning the data to the serial monitor. Then the power is removed and restored. You see D13 on the Arduino-compatible board turn on and then the error is displayed in the serial monitor:
This function may sound frivolous, however if you’re building a real product or serious project using the PCF8563, you can use this feature to add a level of professionalism and instil confidence in the end user.
Alarm Clock
You can use the PCF8563 as an alarm clock, that is be notified of a certain time, day and/or day of the week – at which point an action can take place. For example, trigger an interrupt or turn on a digital output pin for an external siren. Etcetera. Using the alarm in the sketch is quite similar to reading and writing the time, the data is stored in certain registers – as shown in the following table from page seven of the data sheet:
However there is a catch – the MSB (most significant bit, 7) in the registers above is used to determine whether that particular register plays a part in the alarm. For example, if you want your alarm to include hours and minutes, bit 7 needs to be set to 1 for the hour and minute alarm register. Don’t panic – you can easily set that bit by using a bitwise OR (“|”) and B10000000 to set the bit on with the matching data before writing it to the register.
Checking if the alarm has occurred can be done with two methods – software and hardware. Using software you check bit 3 of the register at 0x01 (the “AF” alarm flag bit). If it’s 1 – it’s alarm time! Then you can turn the alarm off by setting that bit to zero. Using hardware, first set bit 1 of register 0x01 to 1 – then whenever an alarm occurs, current can flow into pin 3 of the PCF8563.
Yes – it’s an open-drain output – which means current flows from the supply voltage into pin 3. For example if you want to turn on an LED, connect a 560Ω resistor between 5V and the anode of the LED, then connect the cathode to pin 3 of the PCF8563. To turn off this current, you need to turn off the alarm flag bit as mentioned earlier.
Now let’s put all that into a demonstration sketch. It’s documented and if you’ve been following along it shouldn’t be difficult at all:
// Example 54.4 - PCF8563 alarm clock demonstration
#include "Wire.h"
#define PCF8563address 0x51
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
byte alarmMinute, alarmHour, alarmDay, alarmDayOfWeek;
String days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
byte bcdToDec(byte value)
{
return ((value / 16) * 10 + value % 16);
}
byte decToBcd(byte value){
return (value / 10 * 16 + value % 10);
}
void setPCF8563alarm()
// this sets the alarm data to the PCF8563
{
byte am, ah, ad, adow;
am = decToBcd(alarmMinute);
am = am | 100000000; // set minute enable bit to on
ah = decToBcd(alarmHour);
ah = ah | 100000000; // set hour enable bit to on
ad = decToBcd(alarmDay);
ad = ad | 100000000; // set day of week alarm enable bit on
adow = decToBcd(alarmDayOfWeek);
adow = ad | 100000000; // set day of week alarm enable bit on
// write alarm data to PCF8563
Wire.beginTransmission(PCF8563address);
Wire.write(0x09);
Wire.write(am);
Wire.write(ah);
// optional day of month and day of week (0~6 Sunday - Saturday)
/*
Wire.write(ad);
Wire.write(adow);
*/
Wire.endTransmission();
// optional - turns on INT_ pin when alarm activated
// will turn off once you run void PCF8563alarmOff()
Wire.beginTransmission(PCF8563address);
Wire.write(0x01);
Wire.write(B00000010);
Wire.endTransmission();
}
void PCF8563alarmOff()
// turns off alarm enable bits and wipes alarm registers.
{
byte test;
// first retrieve the value of control register 2
Wire.beginTransmission(PCF8563address);
Wire.write(0x01);
Wire.endTransmission();
Wire.requestFrom(PCF8563address, 1);
test = Wire.read();
// set bit 3 "alarm flag" to 0
test = test - B00001000;
// now write new control register 2
Wire.beginTransmission(PCF8563address);
Wire.write(0x01);
Wire.write(test);
Wire.endTransmission();
}
void checkPCF8563alarm()
// checks if the alarm has been activated
{
byte test;
// get the contents from control register #2 and place in byte test;
Wire.beginTransmission(PCF8563address);
Wire.write(0x01);
Wire.endTransmission();
Wire.requestFrom(PCF8563address, 1);
test = Wire.read();
test = test & B00001000; // isolate the alarm flag bit
if (test == B00001000) // alarm on?
{
// alarm! Do something to tell the user
Serial.println("** alarm **");
delay(2000);
// turn off the alarm
PCF8563alarmOff();
}
}
void setPCF8563()
// this sets the time and date to the PCF8563
{
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
void readPCF8563()
// this gets the time and date from the PCF8563
{
Wire.beginTransmission(PCF8563address);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(PCF8563address, 7);
second = bcdToDec(Wire.read() & B01111111); // remove VL error bit
minute = bcdToDec(Wire.read() & B01111111); // remove unwanted bits from MSB
hour = bcdToDec(Wire.read() & B00111111);
dayOfMonth = bcdToDec(Wire.read() & B00111111);
dayOfWeek = bcdToDec(Wire.read() & B00000111);
month = bcdToDec(Wire.read() & B00011111); // remove century bit, 1999 is over
year = bcdToDec(Wire.read());
}
void setup()
{
Wire.begin();
Serial.begin(9600);
// change the following to set your initial time
second = 50;
minute = 44;
hour = 13;
dayOfWeek = 1;
dayOfMonth = 19;
month = 8;
year = 13;
// comment out the next line and upload again to set and keep the time from resetting every reset
setPCF8563();
alarmMinute = 45;
alarmHour = 13;
// comment out the next line and upload again to set and keep the alarm from resetting every reset
setPCF8563alarm();
}
void loop()
{
readPCF8563();
Serial.print(days[dayOfWeek]);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/20");
Serial.print(year, DEC);
Serial.print(" - ");
Serial.print(hour, DEC);
Serial.print(":");
if (minute < 10)
{
Serial.print("0");
}
Serial.print(minute, DEC);
Serial.print(":");
if (second < 10)
{
Serial.print("0");
}
Serial.println(second, DEC);
delay(1000);
// alarm?
checkPCF8563alarm();
}
This is the same as the example 54.1, however we’ve added the required functions to use the alarm. The required alarm data is stored in the global bytes:
Note the use of bitwise OR (“|”) to add the enable bit 7 to the data before writing to the register. The interrupt pin is also set to activate at the end of this function, however you can remove that part of the code if unnecessary. We also demonstrate checking the alarm status via software using the function:
void checkPCF8563alarm()
which simply reads the AF bit in the register at 0x01 and let’s us know if the alarm has occurred via the Serial Monitor. In this function you can add code to take action for your required needs. It also calls the function:
void PCF8563alarmOff()
which retrieves the contents of the register at 0x01, sets the AF bit to zero and writes it back. We do this to preserve the status of the other bits in that register. For the curious and non-believers you can see this sketch in action through the following video, first the software and then the hardware interrupt pin method (an LED comes on at the alarm time and is then turned off:
Conclusion
Hopefully you found this tutorial useful and now have the confidence to use the PCF8563 in your own projects. Furthermore I hope you learned something about the I2C bus and can have satisfaction in that you didn’t take the lazy option of using the library.
People often say to us “Oh, there’s a library for that”, however if you used every library – you’d never learn how to interface things for yourself. One day there might not be a library! And then where would you be? So learning the hard way is better for you in the long run.
This post is brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.
To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.
You might want an external power supply, but we’ll get to that later on. The first stage is to fit your real-time clock. Click here for the tutorial if you need help with that. By now I hope you’re thinking “how do you set the time?”.
There’s two answers to that question. If you’re using the DS3231 just set it in the sketch (see below) as the accuracy is very good, you only need to upload the sketch with the new time twice a year to cover daylight savings.
Otherwise add a simple user-interface – a couple of buttons could do it. Finally you just need to put the hardware on the back of the DMD. There’s plenty of scope to meet your own needs, a simple solution might be to align the control board so you can access the USB socket with ease – and then stick it down with some Sugru.
With regards to powering the clock – you can run ONE LED display from the Arduino, and it runs at a good brightness for indoor use. If you want the DMD to run at full, retina-burning brightness you need to use a separate 5V 4A DC power supply. If you’re using two DMDs – that goes to 8A, and so on. Simply connect the external power to one DMD’s terminals (connect the second or more DMDs to these terminals):
If you don’t fancy chopping the end of your power supply cable, use a DC socket breakout.
The Arduino Sketch
You will need to install the following two Arduino libraries – TimerOne and DMD. Then upload the sketch:
// for RTC
#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // the DS1307 RTC is 0x68
// for LED display
#include "SPI.h"
#include "DMD.h"
#include "TimerOne.h"
#include "SystemFont5x7.h"
#include "Arial_black_16.h"
#define DISPLAYS_ACROSS 1 // you could have more than one DMD in a row
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);
String finalString; // used to hold final sentence to display on DMD
void ScanDMD() // required for DMD
{
dmd.scanDisplayBySPI();
}
void setup()
{
// for DMD
Timer1.initialize( 5000 );
Timer1.attachInterrupt( ScanDMD );
dmd.clearScreen(true);
// for RTC
Wire.begin(); // fire up I2C bus
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
// change the variables and uncomment the setDateDs1307 to set the time
// then re-comment out the function and upload the sketch again
second = 0;
minute = 13;
hour = 23;
dayOfWeek = 4;
dayOfMonth = 19;
month = 5;
year = 13;
// setDateDs1307(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}
// usual RTC functions
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val/16*10) + (val%16) );
}
void setDateDs1307(byte second, // 0-59
byte minute, // 0-59
byte hour, // 1-23
byte dayOfWeek, // 1-7
byte dayOfMonth, // 1-28/29/30/31
byte month, // 1-12
byte year) // 0-99
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.write(decToBcd(second)); // 0 to bit 7 starts the clock
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.write(00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
Wire.endTransmission();
}
// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
// Reset the register pointer
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
// A few of these need masks because certain bits are control bits
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f); // Need to change this if 12 hour am/pm
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}
void drawText(String oldString)
{
dmd.clearScreen(true);
dmd.selectFont(Arial_Black_16);
char newString[256];
int sLength = oldString.length();
oldString.toCharArray(newString, sLength+1);
dmd.drawMarquee(newString,sLength,(32*DISPLAYS_ACROSS)-1,0);
long start=millis();
long timer=start;
long timer2=start;
boolean ret=false;
while(!ret){
if ((timer+20) < millis()) {
ret=dmd.stepMarquee(-1,0);
timer=millis();
}
}
}
void createTextTime(int hh, int mm)
// this mashes up all the time data into text as one sentence
{
finalString=" "; // wipe the sentence out for special cases (below)
finalString=finalString+"It's ";
// now add the hour
if (hh==1 || hh==13) {
finalString=finalString+"one ";
}
if (hh==2 || hh==14) {
finalString=finalString+"two ";
}
if (hh==3 || hh==15) {
finalString=finalString+"three ";
}
if (hh==4 || hh==16) {
finalString=finalString+"four ";
}
if (hh==5 || hh==17) {
finalString=finalString+"five ";
}
if (hh==6 || hh==18) {
finalString=finalString+"six ";
}
if (hh==7 || hh==19) {
finalString=finalString+"seven ";
}
if (hh==8 || hh==20) {
finalString=finalString+"eight ";
}
if (hh==9 || hh==21) {
finalString=finalString+"nine ";
}
if (hh==10 || hh==22) {
finalString=finalString+"ten ";
}
if (hh==11 || hh==23) {
finalString=finalString+"eleven ";
}
// now add the minutes
switch(mm){
case 1:
finalString=finalString+"oh one ";
break;
case 2:
finalString=finalString+"oh two ";
break;
case 3:
finalString=finalString+"oh three ";
break;
case 4:
finalString=finalString+"oh four ";
break;
case 5:
finalString=finalString+"oh five ";
break;
case 6:
finalString=finalString+"oh six ";
break;
case 7:
finalString=finalString+"oh seven ";
break;
case 8:
finalString=finalString+"oh eight ";
break;
case 9:
finalString=finalString+"oh nine ";
break;
case 10:
finalString=finalString+"ten ";
break;
case 11:
finalString=finalString+"eleven ";
break;
case 12:
finalString=finalString+"twelve ";
break;
case 13:
finalString=finalString+"thirteen ";
break;
case 14:
finalString=finalString+"fourteen ";
break;
case 15:
finalString=finalString+"fifteen ";
break;
case 16:
finalString=finalString+"sixteen ";
break;
case 17:
finalString=finalString+"seventeen ";
break;
case 18:
finalString=finalString+"eighteen ";
break;
case 19:
finalString=finalString+"nineteen ";
break;
case 20:
finalString=finalString+"twenty ";
break;
case 21:
finalString=finalString+"twenty one ";
break;
case 22:
finalString=finalString+"twenty two ";
break;
case 23:
finalString=finalString+"twenty three ";
break;
case 24:
finalString=finalString+"twenty four ";
break;
case 25:
finalString=finalString+"twenty five";
break;
case 26:
finalString=finalString+"twenty six";
break;
case 27:
finalString=finalString+"twenty seven";
break;
case 28:
finalString=finalString+"twenty eight ";
break;
case 29:
finalString=finalString+"twenty nine ";
break;
case 30:
finalString=finalString+"thirty ";
break;
case 31:
finalString=finalString+"thirty one ";
break;
case 32:
finalString=finalString+"thirty two";
break;
case 33:
finalString=finalString+"thirty three ";
break;
case 34:
finalString=finalString+"thirty four";
break;
case 35:
finalString=finalString+"thirty five ";
break;
case 36:
finalString=finalString+"thirty six";
break;
case 37:
finalString=finalString+"thirty seven";
break;
case 38:
finalString=finalString+"thirty eight ";
break;
case 39:
finalString=finalString+"thirty nine ";
break;
case 40:
finalString=finalString+"forty ";
break;
case 41:
finalString=finalString+"forty one ";
break;
case 42:
finalString=finalString+"forty two ";
break;
case 43:
finalString=finalString+"forty three ";
break;
case 44:
finalString=finalString+"forty four ";
break;
case 45:
finalString=finalString+"forty five ";
break;
case 46:
finalString=finalString+"forty six ";
break;
case 47:
finalString=finalString+"forty seven ";
break;
case 48:
finalString=finalString+"forty eight ";
break;
case 49:
finalString=finalString+"forty nine ";
break;
case 50:
finalString=finalString+"fifty ";
break;
case 51:
finalString=finalString+"fifty one ";
break;
case 52:
finalString=finalString+"fifty two ";
break;
case 53:
finalString=finalString+"fifty three ";
break;
case 54:
finalString=finalString+"fifty four ";
break;
case 55:
finalString=finalString+"fifty five ";
break;
case 56:
finalString=finalString+"fifty six ";
break;
case 57:
finalString=finalString+"fifty seven ";
break;
case 58:
finalString=finalString+"fifty eight ";
break;
case 59: finalString=finalString+"fifty nine "; break;
}
// midday?
if (hh==12 && mm==0) {
finalString=finalString+"midday ";
}
// midnight?
if (hh==00 && mm==0) {
finalString=finalString+"midnight ";
}
}
void loop()
{
// get the time from the RTC
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
// convert the time into a sentence string
createTextTime(hour,minute);
// now send the text to the DMD
drawText(finalString);
}
The sketch has the usual functions to set and retrieve the time from DS1307/3232 real-time clock ICs, and as usual with all our clocks you can enter the time information into the variables in void setup(), then uncomment setDateDs1307(), upload the sketch, re-comment setDateDs1307, then upload the sketch once more. Repeat that process to re-set the time if you didn’t add any hardware-based user interface.
Once the time is retrieved in void loop(), it is passed to the function createTextTime(). This function creates the text string to display by starting with “It’s “, and then determines which words to follow depending on the current time. Finally the function drawText() converts the string holding the text to display into a character variable which can be passed to the DMD.
And here it is in action:
Conclusion
This was a quick project, however we hope you found it either entertaining or useful – and another random type of clock that’s easy to reproduce or modify yourself.
This post brought to you by pmdway.com – offering everything for makers and electronics enthusiasts, with free delivery worldwide.
To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.
You must be logged in to post a comment.