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
Jon Wilder

8051 - Setting Up The UART For Serial Communications

The Intel 8051, as old as it is, is still a very popular processor in use today. Some say it's due

  1. Jon Wilder
    • This update is currently awaiting approval.
    The Intel 8051, as old as it is, is still a very popular processor in use today. Some say it's due to the abundance of legacy code that is available for the 8051. Others say that there are so many products designed around it and it's cheaper to keep using the 8051 rather than redesign everything that originally used it. But still, there are others who are just in love with the 8051 (like myself) and its resources/abilities. One of the things that makes it a wonderful processor is the ALU's multiply/divide functions and its on chip Boolean processor. Throw in the fact that it uses vectored interrupts rather than a single IRQ like the PIC's do, and you have yourself a very powerful processor that is simple to use and program.

    Unlike modern processors that feature tristate I/O ports and high source/sink currents directly from its I/O pins, the 8051 can sink up to 15mA on each of its I/O pins, but cannot source very much. They are also bistate...they can either be high or low. In the case of the P0 I/O pins, they are either low or floating unless they have an external pull up resistor on them (they internally drive both high and low when used as the low order address/data port on external memory accesses).

    Here I will demonstrate how to set up the serial port on an 8051. The 8051 features a full duplex Universal Asynchronous Receiver Transmitter, or more simply, the UART. It can both transmit and receive serial data at the same time on two different I/O pins. These pins are P3.0 and P3.1, which function as RxD and TxD respectively. This allows the 8051 to communicate with other embedded processors as well as a serial port equipped PC on just two pins.

    The first thing that needs to be done is to write 1's to bits P3.0 and P3.1 in the P3 register. These pins are automatically 1's on any power up or reset condition.

    The UART on the 8051, unlike the PIC, does not have a dedicated baud rate timer. It must use one of its on chip timers as the baud rate generator. On the 8051, there are only two timers available while the 8052 has 3 timers available. Only timer 1 on the 8051 can be used as the baud rate generator while on the 8052, both timer 1 and timer 2 can be used as the baud rate generator. We will first cover using timer 1 as the baud rate generator.

    Timer 1 Mode 2 - 8 bit Auto Reload

    Timer 1 has 4 modes of operation. Of these 4 modes, we are only interested in mode 2, which is the 8-bit auto reload mode. A value is stored in timer register TH1 while TL1 is the 8-bit timer counter. On each overflow of TL1 (a rollover from 255-0, or 0xFF to 0x00), TL1 is automatically reloaded with the value stored in TH1 and the count repeats. Serial data is sent out on TxD and received in on RxD upon each overflow/reload of TL1.

    To set the timer modes for timer 1, we must write a value to the upper 4 bits of the TMOD SFR, which is the Timer Mode register. Writing the value 0x20 will disable timer 1 gating, set the timer up as an internally incremented timer, and set timer 1 for 8-bit auto reload -

    mov TMOD,#0x20 ;timer 1 in 8-bit auto reload

    Now we must write a reload value to register TH1. In order to calculate this value, we need to know what our desired baud rate is. Also, for certain baud rates, we may need to set the double baud rate bit, which is bit 7 in the PCON SFR.

    To calculate the TH1 reload value for a given baud rate, there are two equations we can use. The first equation is used when PCON.7 = 0 -

    TH1 Value = 256 - ((Crystal MHz / 384) / Baud)

    The second equation is used when PCON.7 = 1 -

    TH1 Value = 256 - ((Crystal MHz / 192) / Baud)

    For a baud rate of 4800bps and a crystal frequency of 11.059MHz, we will first use the first equation to calculate it -

    256 - ((11059000 / 384) / 4800) = 250

    With a 11.059MHz crystal, we can simply write the value 250, or hex 0xFA, to register TH1 and not have to set bit PCON.7. But what if we were using a 12MHz crystal?

    256 - ((12000000 / 384) / 4800) = 249.49

    Register TH1 can only accept whole integer values, so we must round up to the nearest integer value, which would be 249, or hex 0xF9. But...this would give us a baud rate of -

    Actual Baud = Crystal MHz / 384(256-TH1 Value)

    12000000 / 384(256-249) = 4464.29

    Which results in an error percentage of -

    100((Actual Baud - Desired Baud) / Desired Baud) = 7%

    This is too great of an error percentage and will result in framing errors. But what if we set PCON.7? Let's see what happens there -

    PCON.7 = 1

    256-((12000000 / 192) / 4800) = 242.98

    This gives us a decimal value that is a bit closer to a whole integer value of 243, or hex 0xF3. What would our actual baud rate and error percentage be with that?

    Actual Baud Rate

    12000000 / 192(256-243) = 4807.69

    Error Percentage

    100((4807.69 - 4800) / 4800 = 0.16%

    This reduces our baud rate error immensely, and we now have a forgivable baud rate that we can work with without having to use a different crystal.

    If, however, we were using a baud rate of 9600 with the 8051, we would be forced to use a 11.059MHz crystal. This is because the error percentage would be too great regardless of the PCON.7 bit setting. However, if we were using the 8052, we could use timer 2 in 16-bit autoreload mode, which offers greater baud rate tuning resolution and negates the need to change to a different crystal. By using a reload value of 65497 (0xFFD9 in hex), this would give us a baud rate of 9615.38 and an error percentage of 0.16%, again tolerable for 9600 baud. More on timer 2's baud rate generator capabilities later.

    So for our example baud rate of 4800 with a 12MHz crystal -

    Code (text):

               mov         TH1,#0xF3          ;reload value for 4800bps w/12MHz xtal
               mov         TL1,#0xF3          ;optional, TL1 gets loaded with TH1 on overflow
               mov         TMOD,#0x20         ;timer 1 8-bit auto reload mode
               mov         TCON,#0x40         ;timer 1 active in timer mode
                                              ;timer 0 disabled
     
    Now we can configure the UART itself. For standard 8N1 asynchronous format (8 data bits/No parity/1 stop bit) -

    Code (text):

               mov         SCON,#0x50         ;UART active, 8N1 asynchronous mode
     
    Once the UART is active, we can send data out by simply writing a character to register SBUF. The hardware takes over from there. Interrupt flag bit TI in register SCON, if previously cleared in software, will remain clear until the hardware resets it upon the character transferring to the shift register from the buffer. This means we can write one character to the SBUF buffer while another character is still shifting out of the shift register.

    To prevent overwriting a character that is waiting to shift out, we can clear the TI flag, then poll it until it returns high with the following instructions -

    Code (text):

                mov        SBUF,<CHAR>         ;send character
                clr        TI                  ;clear TI interrupt flag
                jnb        TI,$                ;wait here until TI flag returns high
     
    A receive procedure would be similar to the above. The RI flag is high once a valid stop bit has been detected on the RxD pin. It must be cleared in software upon transferring the received character from SBUF. Assuming bit RI started out clear, we can receive data using the following instructions -

    Code (text):

                jnb        RI,$                 ;wait for character
                clr        RI                   ;character received, clear RI flag
                mov        A,SBUF               ;transfer character to accumulator (or register of your choice)
     
    Since data sent to the 8051 would be unpredictable (after all, how is it supposed to know when to expect data), making the processor sit and wait for an incoming character would be impractical as that would be all it would be able to do. Fortunately for us, the 8051 has an interrupt vector assigned to the UART. We can tie the processor to doing other tasks if we enable the serial interrupt.

    The serial IRQ vector is at program memory address 0x0023. At the top of our code, we must place these directives and instructions -

    Code (text):

                      org           0x0000         ;reset vector
                      ajmp          START          ;jump to start of main code

                      org           0x0023         ;UART IRQ vector
                      ajmp          UARTInt        ;jump to UART interrupt handler


                      org           0x0100         ;main code

    START:            ;place main code here

    UARTInt:          push          ACC            ;store accumulator
                      push          PSW            ;store program status word

                      jnb           RI,UARTIntExit ;exit interrupt handler if no received character
                      clr           RI             ;clear RI interrupt flag

    ;place rest of interrupt handler here

    UARTIntExit:      pop         PSW        ;restore program status word
                      pop         ACC        ;restore accumulator
                      reti                   ;return to main code