• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
Resource icon

Basic 8051 tutorial 5 2014-04-21

We need to start looking further afield! Now that we know other devices are controllable, we need to know what control we can implement.... Years ago I had the opportunity to program CNC machinery.... These machines made use of the RS232 connection. Serial communication takes on many guises... SPI, I2C, RS485, CANBUS, ONEWIRE and RS232 to name a couple..

The first to get our heads round is the RS232... One of the simplest ways to connect to a PC.. The basic circuit that was first built will do the job...

basic.png



Serial communication can be simpler than first thought, the idea behind serial communication stems from morse code.. Dot dot dash dash etc...

Reading a stream of bits coming into a port at a pre-determined rate. This is the format of the RS232 protocol.

Baud.png


Notice the bit sequence :- S = Start bit.. D0~D7 = data bits... P = stop bits... The X is the extension to the stop bits, the stop bits can be 1, 1.5 or 2 bits long..


The next part to understand is the “pre-determined” time... Known as the baud rate, invented by

Emile Baudot, needs to be known at both ends so bit detection can be made.. The micro can spill out the bits and the PC can read these bits and act on the byte that has been received.

Virtually every micro today has a USART or EUSART, Enhanced Universal Asynchronous Receiver Transmitter, module built in... All of the 8051's have at least one...

Once you place your data in the relevant register the micro or PC will send the data byte for you... They also include “flags” to notify you when this is completed so you don't have to sit and wait..

Lets say we want to send some data from the micro to the PC running a terminal program. The terminal programs use several extensions to ASCII to display data received...These extensions are very similar but each have a slightly different command set... But as we are going to be using straight ASCII, we need not worry too much about the protocol transport.

Now the first code we always write is the echo!! Wait for a character on the serial bus and echo it back when it does...

The baud rate is calculated as.. (2^SMOD) / 32 * (11095200 /(12*[256-TH1]))..
so by substitution TH1 = 256 – (11095200/(384 * baud )) = 253 ( 0xFD )..

Code:
    org    0        ; Reset vector
    sjmp    Start

    org    30H        ; Code starts here
Start:
    acall     SerInit        ; Initialize Serial
While: 
    acall    getch        ; wait for character
    acall    putch        ; echo back
    sjmp    While        ; Back to loop

;Initialise serial port
;------------------------
SerInit:
    mov    SCON,#52H    ; Mode 1 REN = on, TI = ready
    mov    PCON,#0H    ; SMOD = off normal baud
    mov    TH1,#0FDH    ; FDH = 9600 baud
    mov    TMOD,#20H    ; Timer 1, 8 bit with auto reload
    setb    TR1
    ret

;Get a character
;----------------
getch: 
    jnb    RI,$        ; wait for Receive interrupt flag
    clr    RI        ; Ok char here clear flag
    mov    A,SBUF    ; move into Accumulator
    ret            ; done

;Send a character
;-----------------
putch:  
    mov    SBUF,A        ; Send A to serial buffer
    jnb    TI,$        ; wait until gone
    clr    TI        ; clear interrupt
    ret            ; done

    END
Ok! Here we see the reason for the 11.0952Mhz crystal...Standard baud rates are near perfect when using crystals that were designed for serial communication...

Here is the C version
C:
#include<8051.h>        // definition file

unsigned char getch(void)
    {
    while(!RI);        // Wait here until SBUF clear
    RI = 0;
    return SBUF;
    }
 
void putch(unsigned char ch)
    {
    while(!TI);        //  Wait here until SBUF clear
    TI = 0;
    SBUF = ch;
    }
 
void SerInit(void)
    {
    SCON = 0x52;    // Mode 1...REN = on
    PCON = 0;        // SMOD = 0
    TH1 = 0xFD;        // 9600 baud
    TMOD = 0x20;    // 8 bit timer 1 with auto reload
    TR1 = 1;        // timer on
    }
 
void main(void)            // Main entry point
    {
    SerInit();            // Get port ready
 
    while(1)            // Forever loop
        {
        putch(getch());    // When something arrives, send it back
        } 
    }
Now we have a bit of a chore!! The next examples need maths ( sorry math in USA ).. If we need to start sending reasonable information, its something we have to get our heads round.. Lets assume we have a simple calculation to make... Lets say we need to display a voltage on a LCD display or even send the voltage to a PC for display! The humble 8051 only understands 8 bit math(s). This is a simple map that is used to change a voltage sampled on the ADC to a value that represents the “real” value we need to view...

Digital value representation = ( ADC input / Digital value representation span * ADC maximum resolution ) - Digital value representation minimum..

As you can see there needs to be some calculations around 16 bit values 0 ~ 65535... You will soon find that 16 bit isn't enough, but will suit our needs..

The next couple of examples are only for ASM because in C it's all built in... C comes with extensive math(s) libraries, even 32bit floating point math(s).

The first thing we need is 16 bit addition...
Code:
OP1low    equ     020H        ; Operand 1
OP1high    equ     021H
OP2low    equ     022H        ; Operand 2
OP2high    equ     023H
res1        equ     024H        ; Result registers
res2        equ     025H        ;
res3        equ     026H        ;
res3        equ     027H        ;

    mov     A,OP1low        ;
    add    A,OP2low        ; Add the two low bytes
    mov    res1,A            ; store the answer in res1

    mov    A,OP1high        ;  Add the two high bytes +
    addc    A,OP2high        ; the carry from the low bytes
    Mov    res2,A            ; Store

    mov    A,#00h        ; Clear the accumulator
    Addc    A,#00h        ; Add carry from high byte sum
    mov    res3,A             ; store
    ret                ;

; And then subtraction 16 x 16....

    mov    A,OP1low         ; Get the first low-byte
    clr    C               ; Clear carry
    subb    A,OP2low        ; Subtract the second low-byte
    mov    res1,A            ; Store
    mov    A,OP1high         ; Get the first high-byte
    subb    A,OP2high        ; Subtract the second high-byte
    mov    res2,A            ; Store
    ret

; And then multiplication 16x16...

    clr    C
    mov    A,OP1high        ; Multiply high bytes
    mov    B,OP2high
    mul    AB
    mov    res3,A
    mov    res4,B
    mov    A,OP1low        ; Multiply high bytes
    mov    B,OP2low
    mul    AB
    mov    res1,A
    mov    res2,B
    mov    A,OP1high        ; Now low and high
    mov    B,OP2low
    mul    AB
    add    A,res2            ; add in result
    mov    res2,A
    mov    A,B
    addc    A,res3            ; add in result
    mov    res3,A
    clr    A
    addc    A,res4            ; carry into MSbyte
    mov    res4,A
    mov    A,OP1low        ; Now high and low
    mov    B,OP2high
    mul    AB
    add    A,res2            ; add in result
    mov    res2,A
    mov    A,B
    addc    A,res3            ; add in result
    mov    res3,A
    clr    A
    addc    A,res4
    mov    res4,A            ; carry into Msbyte

; Lastly division.. This is quite long as it is 32 bit x 16 bit division....

    mov    R7,#0
    mov    R6,#0             ;zero out partial remainder
    mov    res1,#0
    mov    res2,#0
    mov    res3,#0            ; Clear result ready!!
    mov    res4,#0
    mov    R1,OP1_2        ;load divisor
    mov    R0,OP1_1
    mov    R5,#32                 ;loop count

Div_loop:
    acall    Shift_D        ; shift the dividend and return MSB in C
    mov    A,R6             ; shift carry into LSB of partial remainder
    rlc    A
    mov     R6,A
    mov    A,R7
    rlc    A
    mov    R7,A
                            ; now test to see if R7:R6 >= R1:R0
    clr    C
    mov    A,R7             ; subtract R1 from R7 to see if R1 < R7
    subb    A,R1            ;  A = R7 - R1, carry set if R7 < R1
    jc    Cant_sub
                            ; at this point R7>R1 or R7=R1
    jnz    Can_sub        ; jump if R7>R1
                          ;if R7 = R1, test for R6>=R0
    clr    C
    mov    A,R6
    subb    A,R0                   ; A = R6 - R0, carry set if R6 < R0
    jc    Cant_sub
Can_sub:
                          ;subtract the divisor from the partial remainder
    clr    C
    mov    A,R6
    subb    A,R0            ; A = R6 - R0
    mov    R6,A
    mov    A,R7
    subb    A,R1            ; A = R7 - R1 - Borrow
    mov    R7,A
    setb    C             ; shift a 1 into the quotient
    sjmp    Quot
Cant_sub:
                            ;shift a 0 into the quotient
    clr    C
Quot:
                    ;shift the carry bit into the quotient
    acall    Shift_Q
                            ; Test for competion
    djnz    R5,Div_loop
                            ; Now we are all done, move the TMP values back into OP
    mov    OP2_1,res1
    mov    OP2_2,res2        ; put into result!!
    mov    OP2_3,res3
    mov    OP2_4,res4
    ret                ; All done

Shift_D:
                            ;shift the dividend one bit to the left and return the MSB in C
    clr    C
    mov    A,OP2_1
    rlc    A
    mov    OP2_1,A        ; all four bytes
    mov    A,OP2_2        ; shifted right!!
    rlc    A
    mov    OP2_2,A
    mov    A,OP2_3
    rlc    A
    mov    OP2_3,A
    mov    A,OP2_4
    mov    A
    mov    OP2_4,A
    ret

Shift_Q:
                          ;shift the quotient one bit to the left and shift the C into LSB
    mov    A,res1
    rlc    A
    mov    res1,A            ; All four bytes
    mov    A,res2            ; Shifted right...
    rlc    A
    mov    res2,A
    mov    A,res3
    rlc    A
    mov    res3,A
    mov    A,res4
    rlc    A
    mov    res4,A
    ret
This will cover all our needs for the next few examples... As you will see using math(s) in C is completely automatic.

For now I wanted to send something to the PC... I have used the DS18S20 in the past... 1-wire serial connection..
ds18s20.png

Using timer 0 for the 1-wire and timer 1 for the serial connection to the PC... Our math routines and a hex2bcd utility... We have a complete application. This code will read the DS18S20, convert to ASCII and send to the PC in a readable format..

Code:
scr1    equ    020H        ; Scratch arry

tmp1    equ    02AH        ; temp storage
tmp2    equ    02BH
tmp3    equ    02CH
tmp4    equ    02DH

OP1_1    equ     030H        ; Maths Operand 1
OP1_2    equ     031H
OP2_1    equ     032H        ; Maths Operand 2
OP2_2    equ     033H
OP2_3    equ     034H
OP2_4    equ     035H
res1    equ     036H        ; Maths Result registers 
res2    equ     037H     
res3    equ     038H     
res4    equ     039H     
BCD1    equ    03AH        ; BCD results
BCD2    equ    03BH
BCD3    equ    03CH
BCD4    equ    03DH

    org    0        ; Reset vector
    sjmp    Start

    org    30H        ; Code starts here
Start:
    mov    TMOD,#21H
    acall    SerInit
    acall     OWInit

While: 
    acall    Convert        ; DS18S20 to convert
    acall    delay
    acall    GetTemp        ; read scratch register
    mov    A,scr1
    mov    OP1_1,A
    mov    OP1_2,#0H    ;
    mov    OP2_1,#5H    ; Temperature needs multiplying
    mov    OP2_2,#0H    ; .5 degrees
    acall    MUL16
    mov    R1,res2
    mov    R2,res1
    acall    hex2bcd        ; Convert to BCD so we can send to PC
    mov    A,R5
    add    A,#30H        ; First digit -> ascii -> PC
    acall    putch
    mov    A,R4
    add    A,#30H        ; Second digit -> ascii -> PC
    acall    putch
    mov    A,#2eH        ; decimal point -> ascii -> PC
    acall    putch
    mov    A,R3
    add    A,#30H        ; decimal digit -> ascii -> PC
    acall    putch
    mov    A,#0DH        ; Carraige return
    acall    putch
    sjmp    While        ; Back to loop

SerInit:
    mov    SCON,#52H    ; Mode 1 REN = on, TI = ready
    mov    PCON,#0H    ; SMOD = off normal baud
    mov    TH1,#0FDH    ; FDH = 9600 baud
    setb    TR1
    ret

putch:    mov    SBUF,A        ; Send A to serial buffer
    jnb    TI,$        ; wait until gone
    clr    TI        ; clear interrupt
    ret            ; done


; High level 1 wire
;------------------

OWInit: 
    acall    OWreset        ; Get bus
    mov    A,#0CCH        ; Skip ROM
    acall    writeOw        ;
    mov    A,#04EH        ; Read Scratch pad
    acall    writeOw
    mov    A,#00H        ; Write to scratch??
    acall    writeOw
    mov    A,#00H        ; For identifying
    acall    writeOw
    ret
 
Convert:
    acall    OWreset        ; Get bus
    mov    A,#0CCH        ; Skip ROM
    acall    writeOw
    mov    A,#044H        ; Convert
    acall    writeOw
    ret

GetTemp:
    mov    R2,#9
    mov    R1,#scr1    ; Array pointer
    acall    OWreset        ; gET BUS
    mov    A,#0CCH        ; Skip ROM
    acall    writeOw
    mov    A,#0BEH        ; Read scratch
    acall    writeOw
gt1:
    acall    readOw        ; 9 bytes
    mov    @R1,A 
    inc    R1
    djnz    R2,gt1 
    ret

; Medium level 1 wire
;---------------------
writeOw: 
    mov    R0,#8
nxtW: 
    acall    OWwrite        ; Write 8 bits LSBit first
    rrc    A
    djnz    R0,nxtW
    ret

readOw: 
    mov    R0,#7        ; Read 8 bits LSbit first
    clr    A
nxtR: 
    acall    OWread
    clr    C
    rrc    A
    djnz    R0,nxtR
    acall    OWread
    ret

; Low level 1 wire
;------------------
OWwrite:
    jnb    Acc.0,notone
    mov    TH0,#0FFH    ; 15uS
    mov    TL0,#0F9H
    setb    TR0
    clr    P3.2
    jnb    TF0,$
    setb    P3.2
    clr    TR0
    clr    TF0
    mov    TH0,#0FFH    ; rest of 70uS
    mov    TL0,#0CCH
    setb    TR0
    jnb    TF0,$
    clr    TR0
    clr    TF0
    ret
notone:
    clr    P3.2
    clr    TF0
    mov    TH0,#0FFH    ; 55uS
    mov    TL0,#0CCH
    setb    TR0
    jnb    TF0,$
    setb    P3.2
    clr    TR0
    clr    TF0 
    mov    TH0,#0FFH    ; Rest of 70uS
    mov    TL0,#0F6H
    setb    TR0
    clr    TR0
    clr    TF0
    ret

OWread:
    mov    TH0,#0FFH    ; Send 15uS pulse
    mov    TL0,#0F9H
    setb    TR0
    clr    P3.2
    jnb    TF0,$
    clr    TR0
    clr    TF0
    setb    P3.2
    mov    TH0,#0FFH    ; Wait 15uS
    mov    TL0,#0F9H
    setb    TR0
    jnb    TF0,$
    jnb    P3.2,nobit    ; High or low
    setb    Acc.7        ; set bit 7
nobit: 
    clr    TR0
    clr    TF0
    mov    TH0,#0FFH    ; Rest of 70uS
    mov    TL0,#0CCH
    setb    TR0
    jnb    TF0,$
    clr    TR0
    clr    TF0 
    ret
 
OWreset:
    clr    A
    mov    TH0,#0FEH    ; 480uS reset pulse
    mov    TL0,#045H
    setb    TR0
    clr    P3.2
    jnb    TF0,$
    clr    TR0
    clr    TF0
    setb    P3.2
    mov    TH0,#0FFH    ; Wait for device
    mov    TL0,#0B9H
    setb    TR0
    jnb    TF0,$
    jnb    P3.2,nodev
    inc    A        ; device found!!
nodev: 
    clr    TR0
    clr    TF0
    mov    TH0,#0FEH    ; rest of 960uS
    mov    TL0,#045H
    setb    TR0
    jnb    TF0,$ 
    ret

delay:                ; conversion delay @ 750mS
    mov    R7,#6
    mov    R6,#0
    mov    R5,#0
D1:    djnz    R5,D1
    djnz    R6,D1
    djnz    R7,D1
    ret

;BCD routine
Hex2bcd:
    mov     R3,#00h        ; Clear
    mov     R4,#00h     ; all
    mov     R5,#00h        ; BDC
    mov     R6,#00h     ; Registers
    mov    R7,#00h
    mov     B,#10         ; Divide low byte by 10
    mov     A,R2         ;
    div     AB
    mov     R3,B         ; Store it in R3
    mov     B,#10         ; Divide remainder
    div     AB
    mov     R4,B         ; store in R4
    mov     R5,A         ; And in R5
    cjne     R1,#0H,HighByte ; Check the high byte
    sjmp     EndD
HighByte:             ; Now you need to factor the high byte
    mov     A,#6         ; For every bit add 256 to total
    add     A,R3         ; 6
    mov     B,#10
    div     AB
    mov     R3,B
    add     A,#5         ; 5
    add     A,R4
    mov     B,#10
    div     AB
    mov     R4,B
    add     A,#2         ; 2
    add     A,R5
    mov     B,#10
    div     AB
    mov     R5,B
    cjne     R6,#00,AddIt    ; This could take a while..
    sjmp     Cont
AddIt:
    add     A,R6
Cont:
    mov     R6,A
    djnz     R1,HighByte     ; 255 iterations
    mov     B, #10
    mov     A,R6
    div     AB
    mov     R6,B         ; finally R6
    mov     R7,A         ; and R7
EndD:    ret

; Addition
ADD16:
    mov     A,OP1_1            ;
    add    A,OP2_1            ; Add the two low bytes
    mov    res1,A            ; store the answer in res1
 
    mov    A,OP1_2            ;  Add the two high bytes +
    addc    A,OP2_2            ; the carry from the low bytes
    Mov    res2,A            ; Store

    mov    A,#00h            ; Clear the accumulator
    Addc    A,#00h            ; Add carry from high byte sum
    mov    res3,A             ; store
    ret                ;

; And then subtraction 16 x 16....
SUB16:
    mov    A,OP1_1         ; Get the first low-byte
    clr    C               ; Clear carry
    subb    A,OP2_1            ; Subtract the second low-byte
    mov    res1,A            ; Store
    mov    A,OP1_2         ; Get the first high-byte
    subb    A,OP2_2            ; Subtract the second high-byte
    mov    res2,A            ; Store
    ret

; And then multiplication 16x16...
MUL16:
    clr    C
    mov    A,OP1_2            ; Multiply high bytes
    mov    B,OP2_2
    mul    AB
    mov    res3,A
    mov    res4,B
    mov    A,OP1_1            ; Multiply high bytes
    mov    B,OP2_1
    mul    AB
    mov    res1,A
    mov    res2,B 
    mov    A,OP1_2            ; Now low and high
    mov    B,OP2_1
    mul    AB
    add    A,res2            ; add in result
    mov    res2,A
    mov    A,B
    addc    A,res3            ; add in result
    mov    res3,A
    clr    A
    addc    A,res4            ; carry into MSbyte
    mov    res4,A
    mov    A,OP1_1            ; Now high and low
    mov    B,OP2_2
    mul    AB
    add    A,res2            ; add in result
    mov    res2,A
    mov    A,B
    addc    A,res3            ; add in result
    mov    res3,A
    clr    A
    addc    A,res4
    mov    res4,A            ; carry into Msbyte
    ret

; Lastly division.. This is quite long as it is 32 bit x 16 bit division....
DIV16:
    mov    R7,#0
    mov    R6,#0             ;zero out partial remainder
    mov    res1,#0
    mov    res2,#0
    mov    res3,#0            ; Clear result ready!!
    mov    res4,#0
    mov    R1,OP1_2        ;load divisor
    mov    R0,OP1_1
    mov    R5,#32                 ;loop count

Div_loop:
    acall    Shift_D        ; shift the dividend and return MSB in C
    mov    A,R6             ; shift carry into LSB of partial remainder
    rlc    A
    mov     R6,A
    mov    A,R7
    rlc    A
    mov    R7,A
                            ; now test to see if R7:R6 >= R1:R0
    clr    C
    mov    A,R7             ; subtract R1 from R7 to see if R1 < R7
    subb    A,R1            ;  A = R7 - R1, carry set if R7 < R1
    jc    Cant_sub
                            ; at this point R7>R1 or R7=R1
    jnz    Can_sub        ; jump if R7>R1
                          ;if R7 = R1, test for R6>=R0
    clr    C
    mov    A,R6
    subb    A,R0                   ; A = R6 - R0, carry set if R6 < R0
    jc    Cant_sub
Can_sub:
                          ;subtract the divisor from the partial remainder
    clr    C
    mov    A,R6
    subb    A,R0            ; A = R6 - R0
    mov    R6,A
    mov    A,R7
    subb    A,R1            ; A = R7 - R1 - Borrow
    mov    R7,A
    setb    C             ; shift a 1 into the quotient
    sjmp    Quot
Cant_sub:
                            ;shift a 0 into the quotient
    clr    C
Quot:
                    ;shift the carry bit into the quotient
    acall    Shift_Q
                            ; Test for competion
    djnz    R5,Div_loop
                            ; Now we are all done, move the TMP values back into OP
    mov    OP2_1,res1
    mov    OP2_2,res2        ; put into result!!
    mov    OP2_3,res3
    mov    OP2_4,res4
    ret                ; All done

Shift_D:
                        ;shift the dividend one bit to the left and return the MSB in C
    clr    C
    mov    A,OP2_1
    rlc    A
    mov    OP2_1,A            ; all four bytes
    mov    A,OP2_2            ; shifted right!!
    rlc    A
    mov    OP2_2,A
    mov    A,OP2_3
    rlc    A
    mov    OP2_3,A
    mov    A,OP2_4
    rlc    A
    mov    OP2_4,A
    ret

Shift_Q:
                          ;shift the quotent one bit to the left and shift the C into LSB
    mov    A,res1
    rlc    A
    mov    res1,A            ; All four bytes
    mov    A,res2            ; Shifted right...
    rlc    A
    mov    res2,A
    mov    A,res3
    rlc    A
    mov    res3,A
    mov    A,res4
    rlc    A
    mov    res4,A
    ret

    END
This is quite a large application... We shall start to place code in other files and include them when we need them.. This will make it more manageable...

From now on I can make the ASM files smaller... I will let you know what files to include....

Next up will be I2C and SPI... They will also be bit banged as some 8051 derivatives do not have a MSSP module...
Likes: sanyaade

Latest reviews

Just what I need. Good example.Little bit easier (I guess) than setting up serial communication or registers for 8 bit AVRs.
Well done, Ian.
nice!!

Latest threads

EE World Online Articles

Loading

 
Top