Blink.c demystified

Not that this is a complicated program, but I'll try to explain as much as possible of the GNU compiler gizmos, which are sprinkled around. If you have never seen a C program before, I suggest you start looking for a good book on the subject - www.amazon.com has plenty.

Well on the top of the program (Listing 1) sits the mandatory GNU License text. I have not changed the original copyright but this is not the code that Stephane Carrez wrote. Down to the first important line is:

#include <sys/ports.h>

The ports.h file is part of the GEL library. It contains useful definitions of the standard MCU resources like IO port addresses etc. Next there is this chunk of code:

#define BIT_0 (1<<0)

#define BIT_1 (1<<1)

#define BIT_2 (1<<2)

#define BIT_3 (1<<3)

#define BIT_4 (1<<4)

#define BIT_5 (1<<5)

#define BIT_6 (1<<6)

#define BIT_7 (1<<7)

This defines constants for setting bits 0 to 7 of any byte. You can define BIT_0 like: #define BIT_0 0x01 if you with.

Next there are tree forward declarations of the functions we will use in our program:

int __attribute__((noreturn)) main (void);

void _start (void);

void delay_ms (unsigned ms);

 

Note the yellow highlight. This is an attribute of the function. This attribute is defined by the GCC compiler and in this case instructs the compiler that the function main will never return. The other two declarations are standard C style functions. So we came to the implementation if the _start function which looks like this:

 

void _start()

{

__asm__ __volatile__ ("lds #0xFF");

__asm__ __volatile__ ("bra main");

}

 

Well this looks like anything but a normal C code - at least for me that is. There is something special about the _start function: it must be the first function in the file. It actually must be the first statement which will generate any code in this C source file. This is critical. If you read my elaborate compile and build steps, you will notice that we relay on the way the GCC compiler and the GNU linker order the code. The thing is that we need to control what code goes in address 0, because that is the address from which our boot loader starts the program. Do not concern yourself with it, just make sure this is the first function an bare with me.

 

The body of the _start function is written in assembly. Well I have to confess as much as I like to write in in pure C, I do not know a way. However the handy __asm__ statement comes to save the day. The first instruction loads the stack pointer, to point to the end of the build-in RAM, so we have a valid stack pointer from this point on. The second line is equivalent of "goto main" - simply transfers the control to the main function.

 

Both assembly instructions are marker __volatile__, this instructs the compiler that it can use the memory space to place data variables. Since those two instructions are going to be executed only once - in the beginning of the program, we can use the memory space for something else later on.

 

Next there is the delay_ms function. All the credit for this code should go to the original blink.c program author. In short all that this particular code does is to waste CPU cycles, so you can perform some controlled delay between the steps in the main algorithm.

 

There are some things to note about the delay_ms function:

At last we have our main function. There are some notable differences to the traditional main routines you will find while reading a general C language course. Most notable of all the main function has no parameters - this is understandable, there is no way you can get a command line parameters from a ROM based code - at least not a real ones. Second important thing is that this function is written so it newer completes - that is it contains a newer ending loop. We have instructed the compiler that we do not intend to return from the main function, and the main logic is that the code you write for the CPU will execute as long as the power is on.

 

Inside the "main" loop we have the following 4 statements, which complete out "blink" algorithm:

 

    _io_ports[M6811_PORTA] |= BIT_6;

    delay_ms (1);

    _io_ports[M6811_PORTA] &= ~BIT_6;

    delay_ms (1);

 

Well the first statement: _io_ports[M6811_PORTA] |= BIT_6 can be translated as "get the byte at address PA, perform bitwise OR with the value BIT_6 and store the byte back to address PA", where PA is the address of the IO port A of the MCU. This will effectively set the bit 6 of the port A to high value. The array _io_ports and the value M6811_PORTA are defined in the include file <sys/ports.h> which is part of the GEL library. If you are unfamiliar with the IO ports of the 68HC11 family I suggest you pay a visit to the Motorola web site and read some of the datasheets for 68HC11. I recommend MC68HC11RM as particularly useful in this case.

 

After setting the bit 6 of port A to level high (I'll use 1 from this point on). We call our delay function, then we set the bit 6 of port A to 0 (low) and we call the delay function again. If the delay function is correct and we live in the ideal world, the result of this program is a square wave signal on the pin 15 (on 52-pin PLCC package) with period 2ms or frequency 500Hz. Since our delay function is not precise and there is some code overhead for the loop and port IO statements, we get a signal with frequency close to 500Hz, but not precise. I did not bother to calculate the exact value, but you can easily verify it using a scope.

 

If you do not have a scope at hand, you can connect a LED to the pin using a 1k resistor and verify the program visually. In this case I suggest you put 500 instead of 1 for the delay function, then you will see approximately half a second delay between the on and off condition of the LED.

 

Now that you have an idea how the program works and what it does, let's compile and run it.

 

Go Back