The 8051 micro has been on the scene for a number of years now since the early 80's.
There are literally hundreds of derivatives.. Ti, Silicon, Atmel, Microchip, Cypress and many many more have come up with several products using this core..... I used Phillips personally. These chips came with a tight boot-loader... With a free program called “Flash Magic” you could simply write a program, compile or assemble to a hex file and flash magic puts it on the device through the serial port.
Boot-loading isn't a new thing, many devices out today employ a boot-loader... The good old Arduino is a good example of this.
I am going to try and show you how to program an Intel based 8051 micro..
For ease of manufacturing a little development board can be made so you can begin playing.
Begin with a P89C51RD2.. This comes in a 40pin PDIP package and is compatible with flash magic.
Once you have made you development board, and you have connected it via a serial cable ( You'll need a USB / Serial for newer PC's). You are ready to go... Incidentally my board was laid out using a P89C668HBA in a 44 pin PLCC package and not the P89C51RD2, but from here on in it doesn’t matter.
I have also put connectors on every pin so I can use them.. You could for the purpose of the tutorials make three or four other boards that have LED's, switches, an LCD 16x2 screen and some memory... The memory can be SPI or I2C as I'll do a software (bit bang) version of both.
So far I have planned...
A hello world program... Just manipulating an LED..
Using input switches to manipulate some LED's..
Reading a 4x3 keypad..
Writing to a stepper motor..
Writing to a servo motor..
A bit of serial communication..
Writing data to an LCD module 16x2..
Writing data to a dual 7 segment LED display..
Writing and reading to a I2C ADC/DAC..
Writing and reading to a I2C EEprom..
Writing and reading to a SPI ADC..
Writing and reading to a SPI Eeprom..
Writing data to a graphical mono screen..
After the initial tutorials we'll combine some of the routines into a library so we can call them whenever needed..
Then a tutorial taking an I2c memory, a I2C ADC, a Toshiba graphical display and a 12 key keypad to make a system that can be used to measure differing voltages and displayed on a graphical screen.
Okay we'll start with the basics.....
The whole purpose of a micro-controller is to manipulate IO to perform specific tasks.. Lighting an LED connected to a port is one of the easiest tasks to perform.. Lets assume that port 1 has an LED connected to pin 0. Looking to the datasheet, the actual pin would be pin 1...
Also from the datasheet we can see that the port is poorly equipped to supply any sort of usable current... One port pin can consume or supply no more that 15mA, however more than one cannot consume or supply more that 26mA on one port and All ports combined cannot consume or supply more than 71mA... This leaves us with a choice... Either use low current or use switching transistors to boost the current..
So our first example we'll use a single LED and limit the current to 10mA... This should be more than enough to see the LED light..
In assembler most operations than needs doing requires the accumulator.. The accumulator is the only register connected to the ALU ( Arithmetic Logic Unit ).. However! To place simple data onto a port pin only requires a direct call..
setb P1.0 ..... Set the bit in register P1 bit 0.
Similarly to clear the data on the pin is just as simple.
clr P1.0 ... Clear the bit in register P1 bit 0.
Add an LED to the circuit... I have also added a switch for the next exercise.
So this simple program will turn the LED on and off...
Code 1
Now before you run ahead and try this for real, remember to current limit the LED. A simple equation to find the correct limiting resistor is needed.... We have “Ohms Law”
Wattage = voltage * current. And Voltage = resistance * current.
We look at the data sheet of a simple LED and see that the forward voltage is about 2 volts, the current we need is 10mA..
The voltage supplied from the micro supply rail is 5 volts we need to drop the voltage to 2 volts to light the LED. A voltage drop of 3 is needed at 10mA..
If voltage = resistance * current, then resistance = voltage / current. Therefore 3volts / 10mA = 300 ohms.. the nearest value of “Off the shelf” resistor is 330, so the current will be smaller at around 9mA.. This will be fine.
If we place the resistor on the anode of the LED and the 5v rail.. The cathode of the LED to the port pin, then when we write a logic 0 to the pin the LED will light... If we write a logic 1 the LED will extinguish..
Next we need an assembler.. There are many assemblers on the net but ASEM51 will be the best. If you also want to follow the tutorials in C then I suggest SDCC ( Small Device C Compiler ). Both are free... The best way to use these tools is with an IDE ( Integrated Development Environment )
I use MCU8051IDE available from Sourceforge.. The SDCC is also available from the same place.
Download and install SDCC and MCU8051IDE and you will be ready to go.. You will also need “Flash Magic” To download hex files to your development board.
If you open MCU8051IDE and type in the program.... Then assemble, you will create a small hex file in the project directory. Here you can simulate the program before you write to the chip. The MCU8051IDE comes equipped with a simulator... This helps with debugging, if the program doesn't behave the way its supposed to.
Now! The astute amongst you may have noticed that the clock speed of the micro is 11.0592Mhz and the switching on and off of the LED will be so fast it will not be able to light up... As the commands will be executed just over 1uS the complete code will have run just under 6uS..
We will need to slow it down a tad.... If you need to see the LED switching on and off you need to do it below the human threshold which is about 15hz that's 15 times a second.. But to really see a flashing LED you would need it to be 3 or 4 times a second... so if we stop the micro for 125mS each we will get nearly 4 flashes per second..
So to do this on the micro... To do this there are two ways..
Code 2
The clock cycle on this system is 1.0815uS
TOSC = 1 / FOSC and these micro's need 12 tick cycles per instruction
( 1 / 11.0952Mhz ) * 12 = 1.0815uS
6+115200+450 = 115656 = 125.08mS … Close enough to 125mS..
Ok! Tested on the hardware and the LED flashes 4 times a second, just as required..
In the code we have introduced several commands
ACALL = Absolute CALL... This can call a subroutine from within page boundary..
11 bit address space..
SJMP = Short JuMP.. Jump relative within 256 byte boundary..
8 bit address space..
MOV = MOVe data to / from a register..
DJNZ = Decrement register and Jump if register is Not Zero..
8 bit address space..
There are more jumps and calls depending on the circumstances...
Conditional jumps... JNZ, JZ, JNC and JC.. These work on the Zero flag and Carry flag
Other conditional jumps... DJNZ and CJNE Decrease jump... Compare jump..
Non conditional jumps are SJMP, AJMP and LJMP...
Routine calls are ACALL and LCALL..
I will now show the same code in C... This is SDCC format!
Code 3
C is a higher level language than assembler. Using a more readable syntax helps with debugging also the automated low level access make it quicker to write a program.
If you look at the two programs side by side, it doesn't look any easier. As the tutorials move on you'll begin to see huge differences in the size and time it takes to program...
We can move on to switched input!! Toggle a switch to light the LED..
Code 4
Notice that a using constants we need to use the pound '#' to denote a value, otherwise if this is omitted then it uses the address of the register...
mov P0, #0FFH.. will place 255 on Port 0, whereas.. mov P0, 0FFH will try and place the value in memory location 0FFH into Port 0.
To use a pin on the 8051 as an input you must set it high.. The data latch must disable the output FET in the port pin schematics to prevent it being low while an external device pulls it high..
mov P0, #0FFh …. will put a logic high on all the port 0 pins..
The above program could be achieved without a micro controller, lets make it more interesting!!!
We can now look at using a switch to change LED sequences.. Moving images created by a block of LED's.. Making it looks as if the LED is moving... We will use lookup table to store different patterns, using the switch we can traverse through the patterns to achieve basic animation.. Remember that the all 8 LED's cannot consume more that 25mA or the micro will fry! You will need to connect each led via a suitable transistor or current limit all the LED's to 3mA each, which will be very dim.
Code 5
To use lookup tables we need to use the data pointer ( DPTR ) register.
Using this register and the accumulator as a offset we can pick each data value as we need it..
Before the analysis, I'll post the same code in C...
Code 6
Two Identical programs ( well nearly ). The ASM routine changes the DPTR routine to point at the data byte table ( array ), then loops through all 8 values placing each in turn on the LED port..
Using pointers in C is considered “advanced” programming. However! Using arrays we can get to grips with the way pointers actually work.. I have purposely used three array's each with 8 elements so I can show how simply pointers can be used... In the ASM program we use..
movc A,@A+DPTR ; MOVe Code byte into Accumulator at number currently held in the ; Accumulator plus the address currently stored in the DataPoinTeR ; register..
We see that DPTR is pointing to Pat1 ( the first data table ) a contains 8 through 1 in each itteration of the loop. All we need to do is swap the value the DPTR register is pointing to to change the array.
In C we have..
unsigned char* patternPtr; // This will hold the address of each array being used.
PatternPtr = &Pattern1[0]; // This will point at the first byte in the array so we can retrieve it.
The ampersand is a reference operator... '&' this means “The address of”.. Once we have created the pointer and pointed to the array.. patternPtr and Pattern1[0] are essentially the same thing. Pattern1[3] will retrieve the same value as patternPtr[3]... We only need to swap the address of the pointer to access all three arrays. Why do we need the pointer? Consider this code..
void printLcd( char * string)
Pass the array to the function and as we don't need to access individual elements we can just increment the pointer rather than changing the element reference making it easier to traverse through the array.
The next tutorial is working on the keypad, 7 segment displays and motors..
I'll get it posted soon.
There are literally hundreds of derivatives.. Ti, Silicon, Atmel, Microchip, Cypress and many many more have come up with several products using this core..... I used Phillips personally. These chips came with a tight boot-loader... With a free program called “Flash Magic” you could simply write a program, compile or assemble to a hex file and flash magic puts it on the device through the serial port.
Boot-loading isn't a new thing, many devices out today employ a boot-loader... The good old Arduino is a good example of this.
I am going to try and show you how to program an Intel based 8051 micro..
For ease of manufacturing a little development board can be made so you can begin playing.
Begin with a P89C51RD2.. This comes in a 40pin PDIP package and is compatible with flash magic.
Once you have made you development board, and you have connected it via a serial cable ( You'll need a USB / Serial for newer PC's). You are ready to go... Incidentally my board was laid out using a P89C668HBA in a 44 pin PLCC package and not the P89C51RD2, but from here on in it doesn’t matter.
I have also put connectors on every pin so I can use them.. You could for the purpose of the tutorials make three or four other boards that have LED's, switches, an LCD 16x2 screen and some memory... The memory can be SPI or I2C as I'll do a software (bit bang) version of both.
So far I have planned...
A hello world program... Just manipulating an LED..
Using input switches to manipulate some LED's..
Reading a 4x3 keypad..
Writing to a stepper motor..
Writing to a servo motor..
A bit of serial communication..
Writing data to an LCD module 16x2..
Writing data to a dual 7 segment LED display..
Writing and reading to a I2C ADC/DAC..
Writing and reading to a I2C EEprom..
Writing and reading to a SPI ADC..
Writing and reading to a SPI Eeprom..
Writing data to a graphical mono screen..
After the initial tutorials we'll combine some of the routines into a library so we can call them whenever needed..
Then a tutorial taking an I2c memory, a I2C ADC, a Toshiba graphical display and a 12 key keypad to make a system that can be used to measure differing voltages and displayed on a graphical screen.
Okay we'll start with the basics.....
The whole purpose of a micro-controller is to manipulate IO to perform specific tasks.. Lighting an LED connected to a port is one of the easiest tasks to perform.. Lets assume that port 1 has an LED connected to pin 0. Looking to the datasheet, the actual pin would be pin 1...
Also from the datasheet we can see that the port is poorly equipped to supply any sort of usable current... One port pin can consume or supply no more that 15mA, however more than one cannot consume or supply more that 26mA on one port and All ports combined cannot consume or supply more than 71mA... This leaves us with a choice... Either use low current or use switching transistors to boost the current..
So our first example we'll use a single LED and limit the current to 10mA... This should be more than enough to see the LED light..
In assembler most operations than needs doing requires the accumulator.. The accumulator is the only register connected to the ALU ( Arithmetic Logic Unit ).. However! To place simple data onto a port pin only requires a direct call..
setb P1.0 ..... Set the bit in register P1 bit 0.
Similarly to clear the data on the pin is just as simple.
clr P1.0 ... Clear the bit in register P1 bit 0.
Add an LED to the circuit... I have also added a switch for the next exercise.
So this simple program will turn the LED on and off...
Code 1
Code:
org 0
sjmp Start
org 30H
Start:
setb P1.0
nop
clr P1.0
sjmp Start
end
Now before you run ahead and try this for real, remember to current limit the LED. A simple equation to find the correct limiting resistor is needed.... We have “Ohms Law”
Wattage = voltage * current. And Voltage = resistance * current.
We look at the data sheet of a simple LED and see that the forward voltage is about 2 volts, the current we need is 10mA..
The voltage supplied from the micro supply rail is 5 volts we need to drop the voltage to 2 volts to light the LED. A voltage drop of 3 is needed at 10mA..
If voltage = resistance * current, then resistance = voltage / current. Therefore 3volts / 10mA = 300 ohms.. the nearest value of “Off the shelf” resistor is 330, so the current will be smaller at around 9mA.. This will be fine.
If we place the resistor on the anode of the LED and the 5v rail.. The cathode of the LED to the port pin, then when we write a logic 0 to the pin the LED will light... If we write a logic 1 the LED will extinguish..
Next we need an assembler.. There are many assemblers on the net but ASEM51 will be the best. If you also want to follow the tutorials in C then I suggest SDCC ( Small Device C Compiler ). Both are free... The best way to use these tools is with an IDE ( Integrated Development Environment )
I use MCU8051IDE available from Sourceforge.. The SDCC is also available from the same place.
Download and install SDCC and MCU8051IDE and you will be ready to go.. You will also need “Flash Magic” To download hex files to your development board.
If you open MCU8051IDE and type in the program.... Then assemble, you will create a small hex file in the project directory. Here you can simulate the program before you write to the chip. The MCU8051IDE comes equipped with a simulator... This helps with debugging, if the program doesn't behave the way its supposed to.
Now! The astute amongst you may have noticed that the clock speed of the micro is 11.0592Mhz and the switching on and off of the LED will be so fast it will not be able to light up... As the commands will be executed just over 1uS the complete code will have run just under 6uS..
We will need to slow it down a tad.... If you need to see the LED switching on and off you need to do it below the human threshold which is about 15hz that's 15 times a second.. But to really see a flashing LED you would need it to be 3 or 4 times a second... so if we stop the micro for 125mS each we will get nearly 4 flashes per second..
So to do this on the micro... To do this there are two ways..
- We need to get the program counter to loop round and round for 125mS..
- We set a timer to count the time taken and pause the program until it has..
Code 2
Code:
org 0 ; Reset vector
sjmp Start
org 30H ; Code starts here
Start:
setb P1.0 ; Set LED pin ( turn off LED )
acall delay
clr P1.0 ; Clear LED pin ( turn on LED )
acall delay
sjmp Start ; do it again ( Forever loop )
delay:
mov R2,#225 ; 2 clock cycles (call) = 2
mov R1,#0 ; 2 clock cycles (loading) = 2
d1: djnz R1,d1 ; 2 * 256 clock cycles *225 = 115200
djnz R2,d1 ; 2 * 225 clock cycles = 450
ret ; 2 clock cycles (return) = 2
end
The clock cycle on this system is 1.0815uS
TOSC = 1 / FOSC and these micro's need 12 tick cycles per instruction
( 1 / 11.0952Mhz ) * 12 = 1.0815uS
6+115200+450 = 115656 = 125.08mS … Close enough to 125mS..
Ok! Tested on the hardware and the LED flashes 4 times a second, just as required..
In the code we have introduced several commands
ACALL = Absolute CALL... This can call a subroutine from within page boundary..
11 bit address space..
SJMP = Short JuMP.. Jump relative within 256 byte boundary..
8 bit address space..
MOV = MOVe data to / from a register..
DJNZ = Decrement register and Jump if register is Not Zero..
8 bit address space..
There are more jumps and calls depending on the circumstances...
Conditional jumps... JNZ, JZ, JNC and JC.. These work on the Zero flag and Carry flag
Other conditional jumps... DJNZ and CJNE Decrease jump... Compare jump..
Non conditional jumps are SJMP, AJMP and LJMP...
Routine calls are ACALL and LCALL..
I will now show the same code in C... This is SDCC format!
Code 3
C:
#include<8051.h> // definition file
__sbit __at (0x90) LED; // Pin LED is connected to..
void delay(void) // How to get 125mS..
{
int x = 10500; // The while statement consumes 11.89uS (11 clock cycles )
while(x--); // So 10500 * 11.89uS = nearly 125mS
}
void main(void) // Main entry point
{
while(1) // Forever loop
{
LED = 1; // Using identifiers make the code easy
delay(); // to understand
LED= 0;
delay();
}
}
C is a higher level language than assembler. Using a more readable syntax helps with debugging also the automated low level access make it quicker to write a program.
If you look at the two programs side by side, it doesn't look any easier. As the tutorials move on you'll begin to see huge differences in the size and time it takes to program...
We can move on to switched input!! Toggle a switch to light the LED..
Code 4
Code:
org 0 ; Reset vector
sjmp Start
org 30H ; Code starts here
Start:
mov P0,#0FFH ; P0 as input
while:
jnb P0.0,off ; If the switch is on
clr P1.0 ; Clear LED pin ( turn off LED )
sjmp on
off:
setb P1.0 ; Set LED pin ( turn on LED )
on:
sjmp while ; do it again ( Forever loop )
end
Notice that a using constants we need to use the pound '#' to denote a value, otherwise if this is omitted then it uses the address of the register...
mov P0, #0FFH.. will place 255 on Port 0, whereas.. mov P0, 0FFH will try and place the value in memory location 0FFH into Port 0.
To use a pin on the 8051 as an input you must set it high.. The data latch must disable the output FET in the port pin schematics to prevent it being low while an external device pulls it high..
mov P0, #0FFh …. will put a logic high on all the port 0 pins..
The above program could be achieved without a micro controller, lets make it more interesting!!!
We can now look at using a switch to change LED sequences.. Moving images created by a block of LED's.. Making it looks as if the LED is moving... We will use lookup table to store different patterns, using the switch we can traverse through the patterns to achieve basic animation.. Remember that the all 8 LED's cannot consume more that 25mA or the micro will fry! You will need to connect each led via a suitable transistor or current limit all the LED's to 3mA each, which will be very dim.
Code 5
Code:
Mode equ 20H
org 0 ; Reset vector
sjmp Start
org 30H ; Code starts here
Start:
mov A,#1 ; Mode 1 to start
mov Mode,A ; Save in variable
mov R0,#8 ; eight LED's
mov DPTR,#Pat1 ; Start with pattern 1
clr A ; A = 0
While:
mov A,R0 ; get count
movc A,@A+DPTR ; Use this to get digit
mov P1,A
acall delay ; delay so we see it
acall button ; new mode?
djnz R0,While ; loop through all LED's
mov R0,#8 ; re-set counter
sjmp While ; do it again ( Forever loop )
button:
jb P0.0,done ; no button pressed
acall delay
inc Mode ; next mode
clr C ; We don't need the carry
mov A,Mode ; If we get to four there's
subb A,#4 ; too many.. Reset to one
jz one
clr C ; We don't need the carry
mov A,Mode ; Restore A
subb A,#3 ; mode 3
jz three
clr C ; We don't need the carry
mov A,Mode ; Restore A
subb A,#2 ; mode 2
jz two
one:
mov A,#1 ; mode 1
mov Mode,A ; save new mode
mov DPTR,#Pat1 ; pattern 1
sjmp done
two:
mov DPTR,#Pat2 ; pattern 2
sjmp done
three: mov DPTR,#Pat3 ; pattern 3
done: ret
delay:
mov R2,#225 ; 2 clock cycles (call) = 2
mov R1,#0 ; 2 clock cycles (loading) = 2
d1:
djnz R1,d1 ; 2 * 256 clock cycles *225 = 115200
djnz R2,d1 ; 2 * 225 clock cycles = 450
ret ; 2 clock cycles (return) = 2
Pat1: db 000H,080H,040H,020H,010H,008H,004H,002H,001H
Pat2: db 000H,081H,042H,024H,018H,018H,024H,042H,081H
Pat3: db 000H,0A0H,050H,028H,014H,00AH,005H,002H,001H
end
To use lookup tables we need to use the data pointer ( DPTR ) register.
Using this register and the accumulator as a offset we can pick each data value as we need it..
Before the analysis, I'll post the same code in C...
Code 6
C:
#include<8051.h> // definition file
unsigned char Pattern1[] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char Pattern2[] = {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81};
unsigned char Pattern3[] = {0xA0,0x50,0x28,0x14,0x0A,0x05,0x02,0x01};
unsigned char* patternPtr;
unsigned char mode ;
void delay(void) // How to get 125mS..
{
int x = 10500; // The while statement consumes 11.89uS (11 clock cycles )
while(x--); // So 10500 * 11.89uS = nearly 125mS
}
char button(void)
{
if(P0_0) return 0;
delay();
if(++mode == 3) mode = 0;
if(mode == 0) patternPtr = &Pattern1[0];
if(mode == 1) patternPtr = &Pattern2[0];
if(mode == 2) patternPtr = &Pattern3[0];
return 1;
}
void main(void) // Main entry point
{
char x;
mode = 0;
patternPtr = &Pattern1[0];
while(1) // Forever loop
{
for(x=0;x<8;x++)
{
P1 = patternPtr[x]; //
delay(); // To see LED's
if(button()) x = 0; // Check button
}
}
}
Using pointers in C is considered “advanced” programming. However! Using arrays we can get to grips with the way pointers actually work.. I have purposely used three array's each with 8 elements so I can show how simply pointers can be used... In the ASM program we use..
movc A,@A+DPTR ; MOVe Code byte into Accumulator at number currently held in the ; Accumulator plus the address currently stored in the DataPoinTeR ; register..
We see that DPTR is pointing to Pat1 ( the first data table ) a contains 8 through 1 in each itteration of the loop. All we need to do is swap the value the DPTR register is pointing to to change the array.
In C we have..
unsigned char* patternPtr; // This will hold the address of each array being used.
PatternPtr = &Pattern1[0]; // This will point at the first byte in the array so we can retrieve it.
The ampersand is a reference operator... '&' this means “The address of”.. Once we have created the pointer and pointed to the array.. patternPtr and Pattern1[0] are essentially the same thing. Pattern1[3] will retrieve the same value as patternPtr[3]... We only need to swap the address of the pointer to access all three arrays. Why do we need the pointer? Consider this code..
void printLcd( char * string)
{
while(*string != 0 )
while(*string != 0 )
LcdDataOut(*string++)
}Pass the array to the function and as we don't need to access individual elements we can just increment the pointer rather than changing the element reference making it easier to traverse through the array.
The next tutorial is working on the keypad, 7 segment displays and motors..
I'll get it posted soon.