For this project, I created an LCD clock. This project took me 5 weeks to complete. The code for the LCD clock was written in C on MIDE-51 and, as always, I used Tera Term, along with the LCD screen, to display the results of the code.
In creating this project, I technically combined two previous projects (system integration), the Keypad project (C language) and the Clock project (C language). My goal was to, instead of typing in a desired time using a computer keyboard, use the keypad (attached to the circuit board) to enter the numbers.
#include <8052.h>
#include <stdio.h>
#include <stdlib.h>
static __xdata unsigned char *cmdWr;
static __xdata unsigned char *datWr;
static __xdata unsigned char *statRd;
static __xdata unsigned char *datRd;
void LCD_delay(void)
{ int i;
for (i=0;i<500;i++)
{}
// unsigned char s;
// s=*statRd;
// while (s&0x80==0x80);
// {
// s=*statRd;
// s=s&0x80;
// }
//
}
char kbhit(void)
{
P1=0xf0;
if (P1==0xf0)
{
return 0;
}
else {
return 1 ;
}
}
void delay20ms(void)
{ int i;
for (i=0;i<2000;i++);
}
void InitSerialPort(void)
{
SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr */
TMOD |= 0x20; /* TMOD: timer 1, mode 2, 8-bit reload */
TH1 = 0xf3; /*TH1=0xf3; reload value for 2400 baud @ 12MHz*/
PCON=0x80; /* Smod1=1 related to 4800, 12MMHz*/
TR1 = 1; /* TR1: timer 1 start to run */
TI = 1; /* TI: set TI to send first char of UART */
}
char scankey(void)
{ P1=0xfe;
if (P1==0xee) return '1';
if (P1==0xde) return '2';
if (P1==0xbe) return '3';
if (P1==0x7e) return 'A';
P1=0xfd;
if (P1==0xed) return '4';
if (P1==0xdd) return '5';
if (P1==0xbd) return '6';
if (P1==0x7d) return 'B';
P1=0xfb;
if (P1==0xeb) return '7';
if (P1==0xdb) return '8';
if (P1==0xbb) return '9';
if (P1==0x7b) return 'C';
P1=0xf7;
if (P1==0xe7) return '*';
if (P1==0xd7) return '0';
if (P1==0xb7) return '#';
if (P1==0x77) return 'D';
return 1;
}
char keyPad(void)
{char k;
here: while(kbhit()==0); //if no key is pressed, wait.
delay20ms();
if (kbhit()==0) goto here;
scankey();
k=scankey();
if (k==1) goto here;
while(kbhit()==1);
return k;
}
int getchar()
{
return ((int)keyPad());
/*
while (!RI); assumes UART is initialized
RI = 0;
return SBUF;*/
// return 0;
}
int putchar (int c)
{
int count;
count=*statRd & 0x7f;
if (count>40)
{ count=0;
*cmdWr=0x80;
LCD_delay();
}
if (c=='\n')
{
// count=*statRd & 0x7f;
if (count<=20)
{ *cmdWr=0x80+count+20;
LCD_delay();
}
if (count>20)
{ *cmdWr=0x80 + count-20;
LCD_delay();
}
}
else if (c=='\r')
{
// count=*statRd & 0x7f;
if (count<=20)
{ *cmdWr=0x80;
LCD_delay();
}
if (count>20)
{ *cmdWr=148;
LCD_delay();
}
}
else
{ *datWr=(char)c;
LCD_delay();
}
while (!TI); /* assumes UART is initialized */
TI = 0;
SBUF = c;
return c;
}
/*int putchar (int c)
{ while (!TI); assumes UART is initialized
TI = 0;
SBUF = c;
return c;
}*/
void gets(char *str)
{ while(1)
{ *str=(char)getchar();
if ((*str=='\n')||(*str=='\r')||(*str=='#')) break;
putchar(*str);
str++;
}
}
void delay(void)
{ int i,j;
for (j=0;j<1500;j++)
{ for (i=0;i<100;i++)
{
}
}
}
/*int kbhit(void) //check keyboard, if keyed 1; else 0
{
return (int)RI;
if (RI==0)
{return 0;
}
else
{
return 1;
}
}*/
void LCDinit(void)
{ cmdWr= (__xdata unsigned char *)0x8000;
datWr= (__xdata unsigned char *)0x8002;
statRd=(__xdata unsigned char *)0x8001;
datRd= (__xdata unsigned char *)0x8003;
*cmdWr=0x01; //clear screen
LCD_delay();
*cmdWr=0x30;
LCD_delay();
*cmdWr=0x0e;
LCD_delay();
*cmdWr=0x06;
LCD_delay();
}
void clearLCD(void)
{ cmdWr= (__xdata unsigned char *)0x8000;
datWr= (__xdata unsigned char *)0x8002;
statRd=(__xdata unsigned char *)0x8001;
datRd= (__xdata unsigned char *)0x8003;
*cmdWr=0x01; //clear screen
LCD_delay();
}
void main(void)
{ int h;
int m;
int s;
char kbuf[10];
InitSerialPort();
LCDinit();
printf("LCD Clock V1.1\n");
h=0;
m=0;
s=0;
while (1)
{
if (kbhit()==0)
{ clearLCD();
printf("%02d:%02d:%02d\n\r",h,m,s);
delay();
s++;
if(s>59)
{ s=0;
m++;
}
if(m>59)
{ m=0;
h++;
}
if(h>12)
{ h=1;
}
}
else
{
clearLCD();
printf("\ninput HH:");
gets(kbuf);
h= atoi(kbuf);
clearLCD();
printf("\ninput mm:");
gets(kbuf);
m= atoi(kbuf);
clearLCD();
printf("\ninput ss:");
gets(kbuf);
s= atoi(kbuf);
printf("\n");
}
}
}
How much coding time can you save if you change from assembly language to C language in a microprocessor application?
To write the code for our clock project in both C and assembly, I spent a total of 3 weeks working on it. It took me a lot longer to write the code in Assembly than in C.
A few reasons why this happened:
In C language, you only need to write one line to finish a standard IO interface, such as printf(). Lines like these can simplify the code. For example, writing printf() is much easier than calling getchar and putchar all the time.
In C language, it's easier to write the code, but the code length is bigger and the program is slow.
In C language, variable memory location can be done by C compiler, assembly can not do so.
C does not need to assign labels in the programming. The standard loops (e.g. for(), while(), if() ) already embed the labels automatically.
In Assembly, it's harder to write the code, but the code length is short and the program is fast.
In Assembly, you have to manually plan out the memory location of each variable in the code. This can get tiring and confusing if you're working on a large-scale project.
You don't need to write semicolons after each line, which is required in C. You also don't need any parenthesis.
If your code is complicated, the more advantaged you will be if you use C language. Our count-up was a simple application; therefore, the advantages of using C are not significant when writing the code. Our clock project was a complicated project with assembly language, after we change to C, it is much easier.
In conclusion, it depends on your code. If your code is complicated, you should consider using C language. If the code is simple, you can use either C or Assembly. For our code, using C was easier since we only used 1.5 hours to finish it.
org 0000h
here1: mov R7,#0
here5: lcall display
lcall Delay2s
inc R7
mov a,R7
clr c
SUBB a,#10H ;count to this number, 02h-10h
JZ here1 ; if R7 reaches 12, then R7=0 if R7==0xc lcall kbhit
sjmp here5
Display: MOV DPTR,#Segments ; #0100 is the start address where we put the digit BYTE
MOV A, R7
MOVC A,@A+DPTR ; Look up the digit BYTE table to A
MOV P1,A ; display number on 7 segment LED diplay
RET
Delay2s: mov r3,#10H
here3: mov r2,#0
here2: mov r1,#0
here: djnz r1,here
djnz R2,here2
djnz r3,here3
ret
Segments: DB 03H
db 9FH ;"1"
db 25H ;2
db 0DH ;3
db 99H ;4
db 49H ;5
db 41H ;6
db 1FH ;7
db 01H ;8
db 19H ;9
db 11H ;A
db 0C1H ;b
db 63H ;C
db 85H ;d
db 61H ;e
db 71H ;F
putchar: jnb ti,$
clr ti
mov sbuf,R7 ;the ASCII to be displayed in R7
ret
getchar: jnb ri,$ ;read char from teraterm and put in R7
mov r7,sbuf
clr ri
ret
kbhit: jnb ri,nokey ;check if key is pressed, if so, return R7=1, otherwise R7=0
mov r7,#1 ; if key is pressed, return to 1
ret
nokey: mov r7,#0 ; if no key, retun to 0
ret
InitUart: mov scon,#50H
mov a,tmod
orl a,#20H
mov tmod,a
mov TH1,#253 ; 57600, 33MHz
mov PCON,#80H ;57600 33MHz
mov TH1,#0f3h ; 9600, 24MHz
mov PCON,#80H ; 9600, 24MHz
setb tr1 ; timer 1 start
setb ti ; send first char of UART
ret
;clr p1.7
;here9: sjmp here9
END
#include <8052.h>
#include <stdio.h>
void InitSerialPort(void)
{
SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr */
TMOD |= 0x20; /* TMOD: timer 1, mode 2, 8-bit reload */
TH1 = 0xf3; /*TH1=0xf3; reload value for 2400 baud @ 12MHz*/
PCON=0x80; /* Smod1=1 related to 4800, 12MMHz*/
TR1 = 1; /* TR1: timer 1 start to run */
TI = 1; /* TI: set TI to send first char of UART */
}
int getchar()
{ while (!RI); /* assumes UART is initialized */
RI = 0;
return SBUF;
}
int putchar (int c)
{ while (!TI); /* assumes UART is initialized */
TI = 0;
SBUF = c;
return c;
}
void gets(char *str)
{ while(1)
{ *str=(char)getchar();
if ((*str=='\n')||(*str=='\r')) break;
putchar(*str);
str++;
}
}
void delay(void)
{ unsigned int i;
for (i=0;i<65533;i++)
{
}
}
void main(void)
{ unsigned char digt_0toF[16]={0x3,0x9f,0x25,0xd,0x99,0x49,0x41, 0x1f, 0x01, 0x19, 0x11, 0xc1, 0x63, 0x85, 0x61, 0x71};
int i;
InitSerialPort();
printf_tiny("SW_7seg_count_up project V1.0\n");
//P1=0x3
i=0;
while (1)
{
printf_tiny("SW_7seg_count_up project V1.1\n");
switch(i)
{
case 0: P1=0x3;
break;
case 1: P1=0x9f;
break;
case 2: P1=0x25;
break;
case 3: P1=0x0d;
break;
case 4: P1=0x99;
break;
case 5: P1=0x49;
break;
case 6: P1=0x41;
break;
case 7: P1=0x1f;
break;
case 8: P1=0x01;
break;
case 9: P1=0x19;
break;
case 0xa: P1=0x11;
break;
case 0xb: P1=0xc1;
break;
case 0xc: P1=0x63;
break;
case 0xd: P1=0x85;
break;
case 0xe: P1=0x61;
break;
case 0xf: P1=0x71;
break;
default: break;
}
delay();
i++;
if (i>0xf)
{ i=0;
}
}
}
Using an interruption in a microprocessor means that you are creating a temporary halt of the code in order for the program to go and execute something else. You can think of it as a real-life scenario. You're cooking dinner and all of a sudden, you get a phone call. The phone call is the interruption. After you're done with the phone call, you go back to cooking dinner. This is just like the scenarios in a microprocessor.
The purpose of the interrupt system is to improve the efficiency of the microprocessor when multi-event exists in an application. The interrupt system is important because some code executions are urgent, and need to be top priority. The user would have to wait for an important part of the code to be executed.
In a microprocessor, the machine will be running the main code in an endless loop. If an interrupt occurs, the main code will halt and the microprocessor will start to run a code, called ISR - interrupt service routine, from a code memory location called interrupt vector. Normally, the ISR is a short code and needs a very short amount of time. After ISR is finished the microprocessor will return back to the main code where it was interrupted and continue to execute the main code.
For example, we can look above this text. The main code is running until it hits "lcall display". It jumps to Display, executes the code, and returns back to the main code.
In summation, interruptions are crucial to the microprocessor.
org 0000h
mov R6,#0
lcall InitUart
loop: lcall kbhit
cjne r7,#1,here5
lcall getchar
lcall putchar
; count up main loop start here
here5: mov a,R6
mov r7,a
lcall display
lcall Delay2s
inc R6
mov a,R6
clr c
SUBB a,#10H ;cuont to this number, 02h-10h
JZ here1 ;loop ends here
sjmp loop
here1: mov R6,#0
sjmp loop ; if R7 reaches 12, then R7=0 if R7==0xc lcall kbhit
; count up mai
Display: MOV DPTR,#Segments ; #0100 is the start address where we put the digit BYTE
MOV A, R7
MOVC A,@A+DPTR ; Look up the digit BYTE table to A
MOV P1,A ; display number on 7 segment LED diplay
RET
Delay2s: mov r3,#10H
here3: mov r2,#0
here2: mov r1,#0
here: djnz r1,here
djnz R2,here2
djnz r3,here3
ret
Segments: DB 03H
db 9FH ;"1"
db 25H ;2
db 0DH ;3
db 99H ;4
db 49H ;5
db 41H ;6
db 1FH ;7
db 01H ;8
db 19H ;9
db 11H ;A
db 0C1H ;b
db 63H ;C
db 85H ;d
db 61H ;e
db 71H ;F
;----------------------------------------------------------------------------------------------------
putchar: jnb ti, $
clr ti
mov sbuf,R7 ; The ASCII to be displayed is in R7
ret
getchar: jnb ri,$ ;read char from teraterm and put into R7
mov r7,sbuf
clr ri
ret
kbhit: jnb ri,nokey ;check if kepboard is pressed
mov r7,#1 ; if key is pressed, return 1
ret
nokey: mov r7,#0; ; if no key, return 0
ret
InitUart: mov scon,#50H
mov a,tmod
orl a,#20H
mov tmod,a
mov TH1,#253 ; 57600, 33MHz
mov PCON,#80H ; 57600, 33MMHz
mov TH1,#0f3H ; 9600, 24MHz 0f3=243;
mov PCON,#80H ; 9600, 24MMHz
setb tr1 ;timer 1 start
setb ti ;send first char of UART
ret
END
org 0000h
ljmp start
org 0023h
ljmp uartInt
org 0100h
start: lcall InitUart
lcall intInit
;mainloop: nop
;ljmp mainloop
here1: mov R7,#0
here5: lcall display
lcall Delay2s
inc R7
mov a,R7
clr c
SUBB a,#10H ;count to this number, 02h-10h
JZ here1 ; if R7 reaches 12, then R7=0 if R7==0xc lcall kbhit
sjmp here5
Display: MOV DPTR,#Segments ; #0100 is the start address where we put the digit BYTE
MOV A, R7
MOVC A,@A+DPTR ; Look up the digit BYTE table to A
lcall Delay2s
MOV P1,A ; display number on 7 segment LED diplay
RET
Delay2s: mov r3,#20H
here3: mov r2,#0
here2: mov r1,#0
here: djnz r1,here
djnz R2,here2
djnz r3,here3
ret
Segments: DB 03H
db 9FH ;"1"
db 25H ;2
db 0DH ;3
db 99H ;4
db 49H ;5
db 41H ;6
db 1FH ;7
db 01H ;8
db 19H ;9
db 11H ;A
db 0C1H ;b
db 63H ;C
db 85H ;d
db 61H ;e
db 71H ;F
uartInt: push acc
mov a,R7
mov r6,a
mov r7,sbuf
clr ri
mov sbuf, R7
jnb ti, $
clr ti
mov a, r6
mov r7,a
pop acc
reti
IntInit:
mov ie,#90h
ret
InitUart: mov scon,#50H
mov a,tmod
orl a,#20H
mov tmod,a
mov TH1,#253 ; 57600, 33MHz
mov PCON,#80H ;57600 33MHz
mov TH1,#0f3h ; 9600, 24MHz
mov PCON,#80H ; 9600, 24MHz
setb tr1 ; timer 1 start
; setb ti ; send first char of UART
ret
END
From this project, I learned the concept of system integration. During integration, some problems in the submodules were found and had to be fixed. Other problems could occur when changing from the Tera Term standard terminal to a dedicated hardware LCD and keypad. In order for the code to work smoothly, these problems needed to be fixed.
Debugging: I saw several problems after putting these 3 modules together. One problem was that the screen kept mixing the displayed characters with the new printed digit, making the display appear extremely confusing. In order to solve this, I created a clearscreen() to clear all of the unused characters. Another mistake was that I saw 2 unidentified characters after the clock display on the LCD screen. After further debugging, it was revealed that the problem was the incorrect expression of \n and \r (I had written \n as "/n") 🤦
Another example was the incorrect coding of the 3 necessary delays (essential in order for the code to work). I ended up forgetting to write one delay and having to recode another delay that was not updated in the past.
Changing the name of the function, especially when they use same name in the submodules.
I had a delay() function in LCD, keypad, and clock. They had different time constants: 1ms, 20ms, and 1s. When I merged the 3 modules to LCD clock project, they were very confusing, so I had to rename them.