Wednesday, June 3, 2009

Cracking the Myths Behind the Microcontroller Clock

Hi,
I being a embedded engineer, would like to discuss something interesting with respect to a controller. Every microcontroller will have its operational frequency. This frequency is provided by a Clock Source. This can be an internal or external. Though I was working on Micro controllers for a long time, I have to admit that clocks were a big black box for me. So I decided to break all the mystery behind Clocks. This understanding is the first step to optimize your code. This is the point where hardware and firmware meet eachother. For an embedded engineer, who is doing his development in C, need to take these things into consideration.

Now that its high time to understand what a clock meant to a Microcontroller. I had a small experimental setup. This consists of my custom ATMega128L board, a tektronics oscilloscope and a multimeter. I program in AVRStudio, which uses WinAVR to compile the code and generate hex file. This ATMega128L can run at a max frequency of 8MHz and can operate at 3.3V. I used an internal RC Oscillator @8MHz.

Now lets get into some analysis.
What is this 8MHz mean? This is the speed at which our internal RC Oscillator oscillates. That is the clock oscillates 8000000 times every second, which means that it takes 125nanoseconds(=1/8000000) for one oscillation. ATMega128L being a RISC Processor, each of its instruction is executed in one clock cycle. So one instruction is executed per cycle. Fine, if this is going to be the case, then how fast can I toggle a General Purpose Input Output Pin (GPIO Pin)? This is the place where our coding style comes into picture. In the later part of the article, I am going to explain how Coding Style affects the speed. At the end of this article you will understand how the clock affects the speed.


//Example-1:
#include "avr/io.h" //Controller Specific Header File.
//Start of Main - Entry into the program.
int main()
{
DDRD = 0x01; //Direction of the PORTD0 Pin is set as Output.
while(1)
{
PORTD |= 0x01; //Make PortD0 high,keeping all other pins as it is.
PORTD &= 0xFE; //Make PortD0 low,keeping all other pins as it is.
}
}

The following is the waveform that I got when I executed this code.



This program produces a square waveform, and does nothing. Guess what is the frequency of the square waveform I got. It is hardly around 1.316MHz. Are you surprised to see a 8 MHz clock to ~1.3 MHz.

This result provoked me for further experimentations. I tried to use some directives that I got with AVRlib. Following is the code that I tried.


//Example-2:
#include "avr/io.h" //Controller Specific Header File.
//Start of Main - Entry into the program.
int main()
{
DDRD = 0x01; //Direction of the PORTD0 Pin is set as Output.
while(1)
{
sbi(PORTD,0); //Make PortD0 high,keeping all other pins as it is.
cbi(PORTD,0); //Make PortD0 low,keeping all other pins as it is.
}
}

I ended up with the same result. The waveforms looked alike. Following is the waveform I got.



My next attempt was a bit logical. I used EXOR operation, thinking that each EXOR operation will be completed in one cycle. Following is the code outcome of this experiment.

//Example-3:
#include "avr/io.h" //Controller Specific Header File.
//Start of Main - Entry into the program.
int main()
{
DDRD = 0x01; //Direction of the PORTD0 Pin is set as Output.
while(1)
{
PORTD^=0x01; //Make PortD0 high,keeping all other pins as it is.
PORTD^=0x01; //Make PortD0 low,keeping all other pins as it is.
}
}


But this infact increased the clock time period. Following is the output waveform.



This time I wanted to try something simple. So I wrote a program like this. Some may think that it is foolishness. But it really worked.

//Example-4:
#include "avr/io.h" //Controller Specific Header File.
//Start of Main - Entry into the program.
int main()
{
DDRD = 0xFF; //Direction of the PORTD0 Pin is set as Output.
PORTD = 0x01;
PORTD = 0x00;
PORTD = 0x01;
PORTD = 0x00;
PORTD = 0x01;
PORTD = 0x00;
PORTD = 0x01;
PORTD = 0x00;
PORTD = 0x01;
PORTD = 0x00;
PORTD = 0x01;
PORTD = 0x00;
return 0;
}

I was amazed to see the waveform!!! Following is the waveform.



There are two things that I changed in the Example-4 compared to other examples. They are
1. I removed the while command.
2. I removed the logical operation on the ports.

Let us understand which instruction gulped how many instruction cycles. So I tried the following code.


//Example-5:
#include "avr/io.h" //Controller Specific Header File.
//Start of Main - Entry into the program.
int main()
{
DDRD = 0xFF; //Direction of the PORTD0 Pin is set as Output.
while(1){
PORTD = 0x01;
PORTD = 0x00;
PORTD = 0x01;
PORTD = 0x00;
}
return 0;
}


Following are the Output waveforms.





Everything looks fine. An Article needs conclusion. It took me a long time to wrap things up. Here is the conclusion and reasoning of what every Example code meant.

The controller is running at 8MHz. This means that each instruction takes 125 nanoseconds.

Example-1:
In this code, we used a while loop, one OR operation, one AND operation and two assignment operations. A while loop takes two cycles, OR, AND and Assignment operation will take one cycle each. So totally there are 6 clock cycles used (2 for while loop + 1 for OR operation + 1 for AND operation + 2 for two Assignment Operation). Time taken would be 125 nanoseconds*6 = 750 nanoseconds. We are getting approximately 770 nanoseconds. Thus justified.
Example-2:
In this code, we tried using a directive that came with AVRlib. Surprisingly the sbi(), cbi() were also been implemented in the same way as previous one. Since they are #define 's, they are being handled at the preprocessor level itself. Thus 770 nanoseconds.
Example-3:
In this code, when we see from outside, it seems to be simple logical EXOR operations. But when we see this code carefully, it is different. Each logical EXOR will take two clock cycles. So to execute each while loop in this code, the controller will take 8 Clock Cycles (2 for while loop, 2 for each EXOR. So 4 Cycles and 2 Assignment). So 125 ns * 8 = 1000ns (~1.030 microSeconds).
Example-4:
In this code, one assignment operation for making the GPIO logical one and one assignment operation for making the GPIO logical zero. So only two clock cycles are used. This leads to a maximum frequency which is 4MHz. Time taken will be 125ns*2=250ns (~256 ns). Thus justified.
Example-5:
In this code, in addition to assignment operations, a while loop is also added. So time should be for this two pulses, it should take 256ns and for while loop, it will take two clock cycles (which is 512 ns).


If you like this article and be able to appreciate this, dont forget to mail me your comments. my email id is "vbalaji[dot]acs[at]gmail[dot]com". Thanks for your patience.

Search Google

Books that I refer to...

  • The Complete Reference C, Fourth Edition
  • The C Programming Language