FriendlyWire.com


beginner-friendly electronics tutorials and projects


Discover the joy of understanding electronics!

Programming a 1Hz clock signal

July 5, 2019 tutorial

Electronic clocks are everywhere, so it is probably a good idea to learn how they work. And the most important part of a clock is, of course, the clock signal. In this tutorial we will learn how to generate a reliable 1Hz signal using a PIC microcontroller.

In the end, we will write a small program that makes an LED blink precisely once a second. And soon enough, we will be able to build our first digital clock using this technique!

If this is your first article you are reading on FrieldyWire, welcome! If you have time, it is a good idea to have a look at these previous articles as well:

  1. Your first microcontroller program!
  2. Make an LED blink: your first simple PIC microcontroller project!
  3. What makes a microcontroller tick?

Alright, let's get to it :)

Why don't we just use __delay_ms(1000)?

This is a very good question! As you might remember, in our first program where we made an LED blink we actually made it blink once a second, using the following code:

#define LED RB3
#define _XTAL_FREQ 4000000

void main(void) {
	
	TRISB3 = 0;
	
	while (1) {
	
		LED = 1;
		__delay_ms(1000);
		LED = 0;
		__delay_ms(1000);

	}
	
	return;
}

How does that code work? In line 2 we define the internal clock speed of the PIC microcontroller, and the command __delay_ms(1000); then implements a delay of precisely one second. If that is all we want, we are golden.

But, as you can tell, there is a problem: it is a delay, which means that the controller doesn't do anything while it executes the command __delay_ms(1000);. It goes on a break!

For a blinky LED program that is all fine and well. But imagine that we have a more complicated software that does many other things as well. For example, it could read out pushbuttons, drive LEDs or other displays, connect to the internet, send and receive data, monitor sensor data, and so forth, you name it. Fact is: a delay of one second wastes computing resources and should be avoided!

Interrupts to the rescue!

So what else can we do? I am glad you ask! The magic keyword here is interrupt. Interrupts are a powerful tool that is used in many microcontrollers, and it is essential to understand pretty much any advanced microcontroller program out there.

As the name suggests, when an interrupt gets activated they interrupt the main program for a short amount of time, so that some short action can be performed. Anything can activate an interrupt, and we can call that the source of the interrupt.

But let's take it one step at a time and look at an example that we all know :)

Interrupts don't only exist on microcontrollers, they were first invented for use with desktop computers. A very famous example is that of the mouse cursor (which explains the image above). Have you ever notices that your mouse cursor moves quite smoothly, even when your Microsoft Word or your current video game is running slowly? That is because the display of the mouse cursor is independent of the main program! It is displayed by a so-called interrupt routine!

Whenever the mouse gets moved, this acts as a source of the interrupt. Then the computer gets interrupted by the interrupt (yeah...) and draws the mouse cursor at the new, updated position of the mouse. Only then the interrupt finishes, and the program carries on as before.

Let's remember the main structure of all microcontroller programs:

// main function
void main (void) {
	
	// some other stuff,
	// initialization of variables and what not.
	
	// IMPORTANT: the main part of a microcontroller
	// program is an infinite loop!
	while (1) {
	
		// do some stuff
		
		// OK, do some other stuff
		
		// this can be very complex and take
		// many seconds to complete

	}
	
	return;
}

It is an infinite loop! Everything encapsulated by while (1) { and } gets repeated over and over again. Here is a simplified graphical representation of this:

As we said: All an interrupt does is, well, interrupt the program if something happens (the source of the interrupt). Here is a diagram of how it works:

To summarize:

  • The main program is an infinite loop (as always with microcontrollers). It goes about its business and does not care about anything.
  • The interrupt looks for an external event, called the source. I visualized that with Futurama's suspicious-looking Fry in the above.
  • When something at the source happens, the main program gets interrupted. The program jumps into the so-called interrupt service routine and executes a few short commands.
  • After that, the program resumes in the main loop where it left off before the interrupt was triggered.

Okay, I hope we now all have a vague idea of what interrupts are. Let's try to apply this to something useful in a concrete example, shall we?

Using an interrupt to make an LED blink once a second

Time for an example! Here is our beloved PIC16F627A connected to an LED and a 4.194304MHz crystal:

The part encircled in orange is the only difference to the LED blink program from a few weeks ago. For this project we need our PIC16F627A to run precisely at 4.194304 MHz, and this is important for us to extract a 1Hz signal so that the LED will blink precisely once a second. We will explain that shortly!

And as we all know, a microcontroller project is only complete with a program that tells the microcontroller what to do. You can find the full source code of our program in the appendix. Here we will go through the relevant pieces, step by step.

// CONFIG
#pragma config FOSC = XT        // Oscillator Selection bits (XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)
#pragma config WDTE = ON        // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

Here, we set up the configuration bits of the PIC16F627A. Only line 9 is important: we tell the PIC16F627A to work with an external crystal of intermediate frequency (this is what the XT stands for).

// LED is connected to RB3
#define LED RB3

// global buffer variable
int buffer = 0;

The LED is connected to port RB3 (as visible in the schematic). Also, we declare a variable called buffer that will become important later. When this variable reaches the value 4095, one second has passed. We will explain that later :)

This is the main function:

// main function
void main (void) {

    // LED is a digital OUTPUT
    TRISB3 = 0;
    
	// TIMER0 works with 1/4 of the internal clock
    // (that amounts to 1.048576 MHz)
	T0CS = 0;

	// set the TIMER0 prescaler to 1:1
	PSA = 1;
	PS0 = 1;
	PS1 = 1;
	PS2 = 1;

	// create an interrupt when TIMER0 overflows
	T0IE = 1;

    // enable interrupts
	GIE = 1;
    
    // main loop
    while (1) {
        
        // and yes, nothing happens here!

    }
    
    return;
}

In line 30 we set port RB3 (that the LED is connected to) as an output. Then it becomes interesting. We initialize a so-called timer (and in this case, this timer has the incredibly interesting name TIMER0). The concept of timers goes beyond the scope of our article here, but we can think of a timer as a simple counter. Whenever the crystal oscillates and the microcontroller executes another line of code, the timer value increases.

This is precisely what we do in line 34: for technical reasons, the timer will count with only a quarter of the crystal's frequency, which is 4.194304 MHz / 4 = 1.148546 MHz. It means that 1,148,546 times per second the value of the TIMER0 gets increased.

Lines 37-40 are not so important, they control the input frequency of the timer. With this choice, the input frequency is a quarter of the clock frequency, a.k.a. our 1.148546 MHz. We could divide this further by factors of two using the prescaler, but we will not really need it here, so we can just forget about it for this project.

There is one important detail: TIMER0 can only take values from 0 to 255! What does that mean?

It means that it is an 8-bit quantity, and it can never arrive at 1,148,546! It can count up to 253, 254, 255, but if you add one more, it overflows and goes back to 0! This overflow happens how often? Well, it happens 1,148,546 / 256 = 4096 times per second!

And this is how we can get our one Hertz signal! We can create an interrupt whose source is the the overflow of TIMER0! This is exactly what we do in line 43. T0IE stands for Timer 0 Interrupt Enable, and when you read the datasheet of the PIC16F627A (page 45) you see that it corresponds to an overflow of TIMER0 (when it goes from 255 to 0).

In line 46 we enable interrupts globally. This is a common pitfall when working with interrupts, because if we don't enable interrupts globally our line 43 has no effect. Good to know.

The rest is fairly standard. Lines 49-53 constitute the main loop of the program. But wait! Where do we make the LED blink? Wasn't that our goal all along?

Yes, it was, and it still is! This is where the interrupt service routine (or isr for short) comes into play:

// the interrupt service routine
void __interrupt () isr (void) {    

    // has TIMER0 reached its maximum?
	if (T0IF) {
        
        // increase the buffer variable
        // whenever TIMER0 overflows
        buffer++;
        
        // has it overflown 4096 times?
        // then one second has passed!
        if (buffer >= 4095) {
            
            // reset buffer
            buffer = 0;
            
            // toggle LED
            if (LED) {
                LED = 0;
            } else {
                LED = 1;
            }
            
        }
        
		// IMPORTANT: reset the interrupt flag!
		T0IF = 0;

	}

}

Don't panic, let's go through the code step by step :)

As we told the microcontroller in line 43, whenever Timer0 overflows (goes from 255 to 0), and interrupt should be triggered. And the controller does that by jumping into the isr. In this case, it means that the program gets interrupted and the controller jumps to line 59.

But what happens then?

In line 62 we check if the bit T0IF is set, which stands for Timer 0 Interrupt Flag. You see, on a controller many different things can cause an interrupt, not just Timer0. So by checking if T0IF is equal to 1 we can make sure that the isr was called because of Timer0 (and not because of something else). We don't really need this line in our example, but it is good programming style, so I decided to keep it.

Okay, now what?

As we have said, the Timer0 overflows 4096 times per second. So whenever it does, we increase the variable buffer by 1. When it reaches 4095 (we start counting at 0), one second has passed! Only then we toggle the LED from 1 to 0 or 0 to 1. And now we also know why we had to choose a 4.194304MHz crystal. Had we chosen a 4MHz crystal (a nice round number), everything would work the same, but one second would pass when buffer reaches a value of 3921.56862745... And this is not an even number! We could round it up to 3922, but this would be very imprecise. It is only an error of 0.15%, but it corresponds to a 12 seconds error in 24 hours, which is quite big!

We are almost done! We now need to reset the Timer0 interrupt flag in line 85, because otherwise the program would keep jumping to line 59 over and over and over again. We need to tell the controller: it is OK, we deal with your interrupt. Now please carry on with the rest of the program :)

Final thoughts

I know, I know, this is a lot to digest. If you are confused: welcome to the club. Take some time, read it again, and if you have any questions, get in touch with me and let me know what I messed up in my explanation :)

I hope that all of this will become clearer in the future when we build our first electronic clock using a microcontroller! Instead of putting everything in one project, I believe that it is useful to divide a large project into smaller pieces. Today's article is one of these pieces.

But interrupts are useful beyond clock signals. They are everywhere in microcontrollers, and it is a good idea to get used to them. Thanks for reading!

Appendix: The complete source code

Here is the complete source code for your reference, feel free to copy & paste it into MPLAB IDE X and have fun with it! Let me know if something doesn't work. You can also download the C source code as well as the .hex file under resources.

/*
 * File:   main.c
 * Author: boos
 *
 * Created on July 4, 2019, 9:10 PM
 */

// CONFIG
#pragma config FOSC = XT        // Oscillator Selection bits (XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)
#pragma config WDTE = ON        // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include 

// LED is connected to RB3
#define LED RB3

// global buffer variable
int buffer = 0;

// main function
void main (void) {

    // LED is a digital OUTPUT
    TRISB3 = 0;
    
	// TIMER0 works with 1/4 of the internal clock
    // (that amounts to 1.048576 MHz)
	T0CS = 0;

	// set the TIMER0 prescaler to 1:1
	PSA = 1;
	PS0 = 1;
	PS1 = 1;
	PS2 = 1;

	// create an interrupt when TIMER0 overflows
	T0IE = 1;

    // enable interrupts
	GIE = 1;
    
    // main loop
    while (1) {
        
        // and yes, nothing happens here!

    }
    
    return;
}

// the interrupt service routine
void __interrupt () isr (void) {    

    // has TIMER0 reached its maximum?
	if (T0IF) {
        
        // increase the buffer variable
        // whenever TIMER0 overflows
        buffer++;
        
        // has it overflown 4096 times?
        // then one second has passed!
        if (buffer >= 4095) {
            
            // reset buffer
            buffer = 0;
            
            // toggle LED
            if (LED) {
                LED = 0;
            } else {
                LED = 1;
            }
            
        }
        
		// IMPORTANT: reset the interrupt flag!
		T0IF = 0;

	}

}

About FriendlyWire

Beginner-friendly electronics tutorials and projects. Discover the joy of electronics! Keep reading.

Let's build a community

How did you get interested in electronics? What do you want to learn? Connect and share your story!

Tag Cloud

  • clock
  • 1 Hertz
  • quartz crystal
  • 4.194304MHz
  • microcontroller
  • timer
  • prescaler
  • interrupts
  • C code
  • .hex file