Examples 1
Examples 1
This page contains example programs (mostly in assembly, but a few in C) for excercising the EdSim51 peripherals.
First, download for free the EdSim51DI simulator - it's free and is very easy to install.
The simulator does not run in real time, of course. When a program is running, the amount of elapsed time (as far as the 8051 is aware) is displayed
in the field above the source code. The user can set the number of instructions executed between updates to the simulator GUI by selecting a value
from the Update Freq. menu. Certain update frequencies suit some programs better than others.
For example, if a program is multiplexing the four 7-segment displays, then it is best to run this program with an update frequency of 1. But if a
program is sending data to the serial port, then a higher update frequency might be used in order to see the data arrive promptly.
All of these programs have been written for 12 MHz 8051 system clock. For other clock frequencies, time delays would need to be modified
accordingly.
The peripheral logic diagram is shown below. This diagram (and the extracts further down) relates to the standard peripheral interface. The user can
move the peripherals to other port pins. A new logic diagram is then available from the simulator itself.
1. Binary Pattern on the Port 1 LEDs
; This program displays the binary pattern
; from 0 to 255 (and back to 0) on the LEDs
; interfaced with port 1.
start:
DEC P1 ; decrement port 1
JMP start ; and repeat
When running this program, best viewed with Update Freq. set to 1.
start:
MOV P1, P2 ; move data on P2 pins to P1
JMP start ; and repeat
When running this program, best viewed with Update Freq. set to 1.
3. Multiplexing the 7-segment Displays
; This program multiplexes the number 1234
; on the four 7-segment displays.
start:
SETB P3.3 ; |
SETB P3.4 ; | enable display 3
MOV P1, #11111001B ; put pattern for 1 on display
CALL delay
CLR P3.3 ; enable display 2
MOV P1, #10100100B ; put pattern for 2 on display
CALL delay
CLR P3.4 ; |
SETB P3.3 ; | enable display 1
MOV P1, #10110000B ; put pattern for 3 on display
CALL delay
CLR P3.3 ; enable display 0
MOV P1, #10011001B ; put pattern for 4 on display
CALL delay
JMP start ; jump back to start
; a crude delay
delay:
MOV R0, #200
DJNZ R0, $
RET
When running this program, best viewed with Update Freq. set to 100.
4. LCD Module
The example program for programming the LCD module is written in C. It was developed and compiled using the Keil uVision IDE. If you wish to
write your own C programs for the 8051, get the free evaluation version of uVision.
The EdSim51 simulator can only parse assembly programs. It cannot compile C programs, therefore do not try to copy and paste the program
below into EdSim51. Instead, you should compile the program in uVision3 and use the Intel HEX output file. This type of file can be loaded into
EdSim51.
When the program below is running in the simulator, the user can shift the display right and left and can return the cursor home by using switches 5,
6 and 7 (see the comment in the main program for details). Since the simulator is not real-time, to be able to see the display shift left and right, it is
best to set the simulator update frequency to 100.
#include <reg51.h>
void returnHome(void);
void entryModeSet(bit id, bit s);
void displayOnOffControl(bit display, bit cursor, bit blinking);
void cursorOrDisplayShift(bit sc, bit rl);
void functionSet(void);
void setDdRamAddress(char address);
void main(void) {
functionSet();
entryModeSet(1, 0); // increment and no shift
displayOnOffControl(1, 1, 1); // display on, cursor on and blinking on
sendString("EdSim51 LCD Module Simulation");
setDdRamAddress(0x40); // set address to start of second line
sendString("Based on Hitachi HD44780");
void returnHome(void) {
RS = 0;
DB7 = 0;
DB6 = 0;
DB5 = 0;
DB4 = 0;
E = 1;
E = 0;
DB5 = 1;
E = 1;
E = 0;
delay();
}
void functionSet(void) {
// The high nibble for the function set is actually sent twice. Why? See 4-bit operation
// on pages 39 and 42 of HD44780.pdf.
DB7 = 0;
DB6 = 0;
DB5 = 1;
DB4 = 0;
RS = 0;
E = 1;
E = 0;
delay();
E = 1;
E = 0;
DB7 = 1;
E = 1;
E = 0;
delay();
}
void sendChar(char c) {
DB7 = getBit(c, 7);
DB6 = getBit(c, 6);
DB5 = getBit(c, 5);
DB4 = getBit(c, 4);
RS = 1;
E = 1;
E = 0;
DB7 = getBit(c, 3);
DB6 = getBit(c, 2);
DB5 = getBit(c, 1);
DB4 = getBit(c, 0);
E = 1;
E = 0;
delay();
}
void delay(void) {
char c;
for (c = 0; c < 50; c++);
}
CLR P1.3 ; clear RS - indicates that instructions are being sent to the module
; function set
CLR P1.7 ; |
CLR P1.6 ; |
SETB P1.5 ; |
CLR P1.4 ; | high nibble set
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
; same function set high nibble sent a second time
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
SETB P1.6 ; |
SETB P1.5 ; |low nibble set
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
SETB P1.7 ; |
SETB P1.6 ; |
SETB P1.5 ; |
SETB P1.4 ; | low nibble set
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
finish:
JMP $
sendCharacter:
MOV C, ACC.7 ; |
MOV P1.7, C ; |
MOV C, ACC.6 ; |
MOV P1.6, C ; |
MOV C, ACC.5 ; |
MOV P1.5, C ; |
MOV C, ACC.4 ; |
MOV P1.4, C ; | high nibble set
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
MOV C, ACC.3 ; |
MOV P1.7, C ; |
MOV C, ACC.2 ; |
MOV P1.6, C ; |
MOV C, ACC.1 ; |
MOV P1.5, C ; |
MOV C, ACC.0 ; |
MOV P1.4, C ; | low nibble set
SETB P1.2 ; |
CLR P1.2 ; | negative edge on E
delay:
MOV R0, #50
DJNZ R0, $
RET
5. Ramp Signal on the DAC Output
When running this program, best viewed with Update Freq. set to 1.
The output on the scope should be something like this:
6. Taking Samples from the ADC and Displaying them on the Scope via the DAC
Note: When using the ADC, the switches in the switch bank must be open (the switches are grey when closed, grey when closed). This is because
the switch bank and the ADC share the same port.
MOV TH0, #-50 ; | put -50 into timer 0 high-byte - this reload value,
; | with system clock of 12 MHz, will result in a timer 0 overflow every 50 us
MOV TL0, #-50 ; | put the same value in the low byte to ensure the timer starts counting from
; | 236 (256 - 50) rather than 0
When running this program with Update Freq. set to 1, the student will observe the time delay between a change in the ADC input voltage
appearing on the scope. In order to see this change appear on the scope more quickly, select a higher update frequency.
A Further exercise for the student might be to display, using multiplexing, the actual input signal voltage to the ADC on the 7-segment displays.
The following program shows how to scan the keypad. The program halts (to be precise, sits in an endless loop) once a key is pressed.
; This program scans the keypad.
; +----+----+----+
; | 11 | 10 | 9 | row3
; +----+----+----+
; | 8 | 7 | 6 | row2
; +----+----|----+
; | 5 | 4 | 3 | row1
; +----+----+----+
; | 2 | 1 | 0 | row0
; +----+----+----+
; col2 col1 col0
start:
; scan row0
SETB P0.3 ; set row3
CLR P0.0 ; clear row0
CALL colScan ; call column-scan subroutine
JB F0, finish ; | if F0 is set, jump to end of program
; | (because the pressed key was found and its number is in R0)
; scan row1
SETB P0.0 ; set row0
CLR P0.1 ; clear row1
CALL colScan ; call column-scan subroutine
JB F0, finish ; | if F0 is set, jump to end of program
; | (because the pressed key was found and its number is in R0)
; scan row2
SETB P0.1 ; set row1
CLR P0.2 ; clear row2
CALL colScan ; call column-scan subroutine
JB F0, finish ; | if F0 is set, jump to end of program
; | (because the pressed key was found and its number is in R0)
; scan row3
SETB P0.2 ; set row2
CLR P0.3 ; clear row3
CALL colScan ; call column-scan subroutine
JB F0, finish ; | if F0 is set, jump to end of program
; | (because the pressed key was found and its number is in R0)
finish:
JMP $ ; program execution arrives here when key is found - do nothing
; column-scan subroutine
colScan:
JNB P0.4, gotKey ; if col0 is cleared - key found
INC R0 ; otherwise move to next key
JNB P0.5, gotKey ; if col1 is cleared - key found
INC R0 ; otherwise move to next key
JNB P0.6, gotKey ; if col2 is cleared - key found
INC R0 ; otherwise move to next key
RET ; return from subroutine - key not found
gotKey:
SETB F0 ; key found - set F0
RET ; and return from subroutine
It may appear as if this program does nothing. Remember, the program simply scans the keypad until a key is pressed. It then places the key number
in R0 and enters an endless loop, doing nothing. Therefore, to see that it has performed the task correctly, examine the contents of R0 after a key is
pressed. Also remember that, if the update frequency is set to 1, it will take a short amount of time for they pressed key's number to appear in R0.
Key number: the key number mentioned here is not the number on the key, but the key's position in the matrix. The # key is key number 0, 0 key is
key number 1, * key is key number 2, and so on.
Further exercises for the student might be:
• Modify the program so that the keypad is scanned continuously (ie: it doesn't stop after one key-press).
• Write extra code that displays the key symbol on one of the 7-segment displays. For example, if key 4 is pressed, the number 8 appears on
the display. If key 10 is pressed the number 2 appears on the display. (Note: the symbols # and * cannot be displayed on a 7-segment display,
but some special characters could be invented and displayed instead.
CLR SM0 ; |
SETB SM1 ; | put serial port in 8-bit UART mode
MOV A, PCON ; |
SETB ACC.7 ; |
MOV PCON, A ; | set SMOD in PCON to double baud rate
MOV TMOD, #20H ; put timer 1 in 8-bit auto-reload interval timing mode
MOV TH1, #243 ; put -13 in timer 1 high byte (timer will overflow every 13 us)
MOV TL1, #243 ; put same value in low byte so when timer is first started it will overflow after 13 us
SETB TR1 ; start timer 1
MOV 33H, #0 ; null-terminate the data (when the accumulator contains 0, no more data to be sent)
MOV R0, #30H ; put data start address in R0
again:
MOV A, @R0 ; move from location pointed to by R0 to the accumulator
JZ finish ; if the accumulator contains 0, no more data to be sent, jump to finish
MOV C, P ; otherwise, move parity bit to the carry
MOV ACC.7, C ; and move the carry to the accumulator MSB
MOV SBUF, A ; move data to be sent to the serial port
INC R0 ; increment R0 to point at next byte of data to be sent
JNB TI, $ ; wait for TI to be set, indicating serial port has finished sending byte
CLR TI ; clear TI
JMP again ; send next byte
finish:
JMP $ ; do nothing
Take note of what happens if the external UART is not set to Even Parity (ie: run the program with the UART on No Parity, then run it again with
it set to
Odd Parity). With the help of the ASCII table, see if you can explain the actual data that is displayed.
It takes quite some time (from the 8051's perspective of time) for data to be sent to the UART. If the user wishes to see the data arrive at the UART
quickly, it may be best, when running this program, to select an Update Freq. of 100 or 1000.
CLR SM0 ; |
SETB SM1 ; | put serial port in 8-bit UART mode
MOV A, PCON ; |
SETB ACC.7 ; |
MOV PCON, A ; | set SMOD in PCON to double baud rate
MOV TMOD, #20H ; put timer 1 in 8-bit auto-reload interval timing mode
MOV TH1, #0FDH ; put -3 in timer 1 high byte (timer will overflow every 3 us)
MOV TL1, #0FDH ; put same value in low byte so when timer is first started it will overflow after
approx. 3 us
SETB TR1 ; start timer 1
MOV R1, #30H ; put data start address in R1
again:
JNB RI, $ ; wait for byte to be received
CLR RI ; clear the RI flag
MOV A, SBUF ; move received byte to A
CJNE A, #0DH, skip ; compare it with 0DH - it it's not, skip next instruction
JMP finish ; if it is the terminating character, jump to the end of the program
skip:
MOV @R1, A ; move from A to location pointed to by R1
INC R1 ; increment R1 to point at next location where data will be stored
JMP again ; jump back to waiting for next byte
finish:
JMP $ ; do nothing
It takes quite some time (from the 8051's perspective of time) for data to be received from the UART. If the user wishes to see the data arrive in
8051 RAM quickly, it may be best, when running this program, to select an Update Freq. of 100 or 1000.
10. The Motor
CLR P3.4 ; |
CLR P3.3 ; | enable Display 0
again:
CALL setDirection ; set the motor's direction
MOV A, TL1 ; move timer 1 low byte to A
CJNE A, #10, skip ; if the number of revolutions is not 10 skip next instruction
CALL clearTimer ; if the number of revolutions is 10, reset timer 1
skip:
MOVC A, @A+DPTR ; | get 7-segment code from code table - the index into the table is
; | decided by the value in A
; | (example: the data pointer points to the start of the
; | table - if there are two revolutions, then A will contain two,
; | therefore the second code in the table will be copied to A)
MOV P1, A ; | move (7-seg code for) number of revolutions and motor direction
; | indicator to Display 0
setDirection:
PUSH ACC ; save value of A on stack
PUSH 20H ; save value of location 20H (first bit-addressable
; location in RAM) on stack
CLR A ; clear A
MOV 20H, #0 ; clear location 20H
MOV C, P2.0 ; put SW0 value in carry
MOV ACC.0, C ; then move to ACC.0
MOV C, F0 ; move current motor direction in carry
MOV 0, C ; and move to LSB of location 20H (which has bit address 0)
JMP finish ; if they are the same, motor's direction does not need to be changed
changeDir:
CLR P3.0 ; |
CLR P3.1 ; | stop motor
CALL clearTimer ; reset timer 1 (revolution count restarts when motor direction changes)
MOV C, P2.0 ; move SW0 value to carry
MOV F0, C ; and then to F0 - this is the new motor direction
MOV P3.0, C ; move SW0 value (in carry) to motor control bit 1
CPL C ; invert the carry
MOV P3.1, C ; | and move it to motor control bit 0 (it will therefore have the opposite
; | value to control bit 1 and the motor will start
; | again in the new direction)
finish:
POP 20H ; get original value for location 20H from the stack
POP ACC ; get original value for A from the stack
RET ; return from subroutine
clearTimer:
CLR A ; reset revolution count in A to zero
CLR TR1 ; stop timer 1
MOV TL1, #0 ; reset timer 1 low byte to zero
SETB TR1 ; start timer 1
RET ; return from subroutine
LEDcodes: ; | this label points to the start address of the 7-segment code table which is
; | stored in program memory using the DB command below
DB 11000000B, 11111001B, 10100100B, 10110000B, 10011001B, 10010010B, 10000010B, 11111000B, 10000000B, 10010000B
Note: The above program is a good illustration of what can go wrong if the sampling frequency is too low. Try running the program with the motor
at full speed (use the slider to the right of the motor to increase the motor speed). You should notice the motor completes a number of revolutions
before the display updates. In other words, the motor's highest speed is too fast for the 8051 running at system clock of 12 MHz.
Since the program only shows up to nine revolutions (displayed on the 7-segment display) and then starts counting again, it is best to run this
program with an Update Freq. of 1. This allows the user observe the display count up from 0 to 9 and back to 0 again. It also illustrates the delay
between the motor arm passing the sensor and the display update. Similarly, the delay between pressing the switch for a direction change and the
actual direction change occurring is evident. The student will learn that, while in real time these delays are not noticeable, to the microcontroller
these operations take quite some time.