1. 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.
    Dismiss Notice
Ian Rogers

Basic 8051 tutorial 5

Serial communication in ASM and C

  1. Ian Rogers
    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 (asm):

        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
    Code (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 (asm):

    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 (asm):

    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...
    RonaldB, vead, DonPriceTech and 2 others like this.

Recent Reviews

  1. RonaldB
    RonaldB
    5/5,
    Just what I need. Good example.Little bit easier (I guess) than setting up serial communication or registers for 8 bit AVRs.
  2. absf
    absf
    5/5,
    Well done, Ian.
  3. vinnnie
    vinnnie
    5/5,
    nice!!