Create  Your  Serial  Port  and  Build  This

 

 

          Using Bit-Banging technique to create a software Serial Port

               for your Microcontroller is rewarding

 

 

                                By  G.Y. Xu

 

 

    Microcontrollers(MCUs) can be classified in many ways by different criteria. But as this article is concerned, they can be divided into only two categories: the ones that already have a built-in serial port, and the ones that don't.

 

    We all know the importance of serial data communications to our daily life,

and the convenience of using microprocessors in achieving this goal. So what do

you do if you have a microcontroller (usually it also is a cheap one) that does not have a built-in serial port?

 

    The answer is not to abandon it, nor avoid using it. But rather, create a software serial port for it. This is not a daunting task as you might think, it only takes you some time to learn and try. You will find the results are very interesting and rewarding.

 

    I will present two practical examples below to show, from simple to complex, how easy (or how difficult) it is to do so. First let’s go to review something on the serial port.

 

 

 

                Serial Port Basics: 9600 bps, 8N1

 

 

    We're all familiar with the PC serial port(s). Compared to parallel port,

the main difference is it transmits data not a byte, but one bit at a time. That can save a bunch of wires. So there are only two wires needed: TX (transmit) and RX (receive) for data transmission. Actually, a common ground (GND) wire is also needed to make it work.

 

    The CPU of a microprocessor does not handle data one bit at a time (it's too

inefficient!); so the task of serial data transmission is relegated to a special hardware part called UART (Universal Asynchronous Receiver/Transmitter).

 

   The UART is the heart of serial port, where a mecchanism is provided to convert a byte into a series of bits for transmission, and vice versa for reception. And to make this happen, first and foremost, the UART must have an accurate bit rate (BaudRate) generator. The serial port speed is defined as bit per second (bps), governed by BaudRate generator.

 

    Of course the UART also has other hardware to check for transmission status and errors, flow control, etc. But the essential parts are the above-mentioned three in order to make it up and run.

 

 

   Each byte of data is transmitted in such sequence: bit 0 or Least Significant Bit (LSB) at first, then bit 1, ... and so on, then bit 7 or Most Significant Bit (MSB) at last. In order to signify the beginning and end of each byte transmission, the RS-232 protocol adds a Start bit before LSB, and a Stop bit after MSB, respectively. The Start bit is a logic Low (0), the Stop bit is logic High (1). Figure 1 shows how an ASCII “X” (0x58 Hex, or Binary 01011000) char is transmitted, notice the sequence is reversed to 00011010.

 

                      

 

                        Figure 1. Transmit “X”

 

 

    In the simplest and most popular use as we talk in this article, the serial port is configured as 9600 bps, 8N1. That means BaudRate is 9600 bit per second, data is 8 bits in one transaction, there is no error checking, one start bit and one stop bit attached to each transaction. So it actually transmits 10 bits instead of 8 bits for a byte of data transaction.

 

 

 

           A Simple Hardware and Software Implementation

 

 

    The Atmel ATtiny11 is the most inexpensive MCU as I know (only 52 cents each or $38/100). It’s an 8-pin tiny chip, of course without a built-in serial port. But believe or not, using the circuit shown in Figure 2 and the simple assembly language program in Listing 1, we can demonstrate that a working serial port had been created for it.

 

    This circuit is so simple that anyone can build it on a breadboard. And the LED is not required, it only serves as a reminder when you forget to plug in the power supply and found it’s not working. Furthermore, you can even dispense with the 1 MHz crystal (XTAL) and use the chip’s internal 1 MHz RC-Oscillator. In my experiment the demo still works.

   

    Refer to both Figure 2 and Listing 1 and notice that, ATtiny11 pin-7 (PB2) is configured as RX, and pin-6 (PB1) is TX. The Dallas Semiconductor’s DS275 is a voltage level shifter which shifts TTL level to RS-232 level, and vice versa.

 

    J1 is a female DB9 connector, as shown only three wires are necessary to connect to the PC serial port and match its DB9 male connector. You can use three longer wires starting from breadboard and directly plug each end of wire into the female connector’s corresponding pin holes, this facilitates the connection between DB9 and breadboard.

 

    Now let’s look at the software program Listing 1. It contains four sections. The first section starting at RESET is the main program. The remaining sections are three subroutines, their detailed executions will be analyzed later, but note that each subroutine corresponds to an essential part of the UART: bit rate generation, transmission, and reception. So we have a software UART here.

 

 

          

 

 

                 Figure 2. Software Serial Port Demo Circuit

 

 

    To program the ATtiny11, you first compile (assemble) the source code using Atmel’s free assembler:

 

       AVRASM  -I  UARTAVR.ASM  UARTAVR.LST  UARTAVR.HEX

 

and then use an ATtiny11 programmer (such as the one published in February, 2006 issue of Nuts & Volts). You can download all UARTAVR files from the Nuts & Volts website (www.ntsvolts.com) and notice that the size of this demo program is quite small: it takes only 94 bytes of program memory.

 

    By the way, if you don’t have an ATtiny11 or its programmer, but you have an AT90S1200, which is a 20-pin Flash MCU and doesn’t have a built-in serial port, you can still use the same program source code. The only change you need to make is Figure 2, change component U2 to AT90S1200 and the corresponding pin numbers.

 

    Any Windows PC has a terminal emulation program called HyperTerminal, which is the one we will use to interact with the demo circuit. So from the Windows Start button, go ahead to open HyperTerminal, and configure it as connected to: COM1(or COM2), 9600 bps, 8N1. When the HyperTerminal window and a “-“ cursor appear, it’s ready to serve.

 

    Power up the demo circuit. You’ll see an “X” appear on the screen. Typing any character from keyboard will also be echoed on the screen. That’s what the program is expected to do. So the software serial port is functioning OK.

 

 

 

LISTING 1.  Software Serial Port Demo Program

 

; UARTavr.ASM: 1 MHz AVR BitBanging UART Demo Program (9600 bps, 8N1)

; Interact with PC software: HyperTerminal

; 8/12/2006 Result: "X" echoed, and it works for both Tiny11/1200

 

.equ    RX = 2                ; RX is PB2: pin7 of Tiny11, pin14 of 1200

.equ    TX = 1                ; TX is PB1: pin6 of Tiny11, pin13 of 1200

.equ    LED = 0               ; LED is PB0:pin5 of Tiny11, pin12 of 1200

 

.def   RCVREG = R17

.def   XMTREG = R18

.def   COUNT = R19

 

.include "TN11def.inc"        ; Port definitions for ATtiny11

.cseg                         

.org 0

;------------------------------------------------------------------------

RESET:

        Cbi     DDRB, RX      ; config DDRB bit-2 as INput

        Sbi     DDRB, TX      ; config DDRB bit-1 as OUTput

        Sbi     DDRB, LED     ; config DDRB bit-0 as OUTput

        NOP

        cbi     PORTB, LED    ; LED=ON (active Low)

        sbi     PORTB, RX     ;\

        sbi     PORTB, TX     ;/ initialize RX/TX as High

 

        ldi     XMTREG, 0x58  ; this is ASCII "X"

        rcall   XMT_CHR       ; send "X" to PC at beginning

Main:

        rcall   RCV_CHR       ; recive Chr from Port RX line

        MOV     XMTREG, RCVREG

        rcall   XMT_CHR       ; transmit Chr to Port TX line

        rjmp    Main

;------------------------------------------------------------

DLY49us:                      ; 49 us Delay  (~Half_Bitime)

 

        LDI     R20, 14       ;                 1us

LOOP1:

        DEC     R20           ;       14 * 1 = 14

        BRNE    LOOP1         ;   13 * 1 + 1 = 27

        RET                   ;        4 + 3 =  7

;---------------------------------------------------------

XMT_CHR:

        LDI     COUNT, 8     ; 8-bit Data

        CBI     PORTB, TX    ; StartBit='0'

        rcall   DLY49us      ; ~Half_Bitime

        rcall   DLY49us      ; ~Half_Bitime

NEXTX:

        LSR     XMTREG       ; shift right LSB to Carry

        brcc    FORWARD

        sbi     PORTB, TX    ; set TX='1' if Carry='1'

        RJMP    F2          

FORWARD:

        Cbi     PORTB, TX    ; set TX='0' if Carry='0'

F2:

        rcall   DLY49us      ; ~Half_Bitime

        rcall   DLY49us      ; ~Half_Bitime

        DEC     COUNT

        brne    NEXTX

STOPBIT:

        sbi     PORTB, TX    ; StopBit = '1'

        rcall   DLY49us      ; ~Half_Bitime

        rcall   DLY49us      ; ~Half_Bitime

        RET             

;-----------------------------------------------------------

RCV_CHR:

        LDI     COUNT, 8     ; 8-bit Data  

wait:

        sbic    PINB, RX     ; wait for StartBit='0'

        rjmp    wait

 

        rcall   DLY49us      ; ~Half_Bitime

NEXRX:

        rcall   DLY49us      ; ~Half_Bitime

        rcall   DLY49us      ; at the Middle of next bit

 

        CLC                  ; clear Carry

        ROR     RCVREG

        sbic    PINB, RX     ; Test RX='0' or '1'?

        SBR     RCVREG, $80  ; set bit7='1' if RX='1'

                             ; otherwise don't change

        DEC     COUNT

        brne    NEXRX

        RET             

;---------------------------------------------------------

 

 

 

 

                            Timing  is  Everything

 

 

    First and foremost, our UART program must have a way to generate bit rate. This is done by calling the DLY49us routine, which delays 49 microseconds (us).

 

    Why do we choose 1 MHz crystal? Because at 1 MHz frequency, 1 clock cycle = 1 us, and for all AVR MCUs, most of their instructions take just 1 clock cycle or 1us to execute. That makes timing calculation much easier.

 

    Now take a closer look at DLY49us. This routine uses only four instructions. From the AVR datasheet you can find out how many clock cycle each instruction takes. The calculation is listed there. Total delay time is exactly 49us.

 

    This routine is used to “waste” 49us when it is needed by the UART. For a BaudRate of 9600 bps, a bit is 1/9600 = 104us. Because we always want to sample the incoming bit at the middle of it (that will avoid any jitter error), so half of this value, the so-called half_bitime = 104/2 = 52us is used more often in calculation.

 

    Notice that 49us is not equal to, but a little less than half_bitime, or we may call it “almost half_bitime.” The reason for keeping a difference between 49us and 52us will be understood in next section.

 

 

 

                             How  Bit-Banging  is  done

 

 

    Now let’s look at XMT_CHR routine, which transmits a byte of data loaded in register R18 or XMTREG to the outside world, one bit at a time. Read the code line by line, it’s straightforward that it just does the things as described.

 

    At the beginning, it pulls the TX line Low and delay two 49us to signify a Start bit. After that, register R18 is shifted right one bit and leaves this bit (that is LSB) to Carry bit by instruction LSR XMTREG. Depending on whether Carry bit is now set or clear, TX pin will be set or clear accordingly. Then delay two 49us. Such action effectively transmits a High or Low bit out.

 

    The above shifting process is repeated 8 times until count=0. So a byte of data is completely shifted out. Then TX line is set High for two 49us to signify a Stop bit, the end of transmission.

      

    Because there are several instructions executed from one bit transmitted to the next, each instruction takes 1us, or 2us in this routine, and the bit output logic is not fixed (due to different bit value at different time), in order to keep the BaudRate = 9600 bps or bitime = 104us, the delay routine must allowed for these instructions execution time. That’s why we created DLY49us.

 

    Similarly, the RCV_CHR routine receives a byte of data from outside word and stores it in register R17 or RCVREG. The receiver keeps sampling its RX line until a logic Low is detected, that is the beginning of Start bit.

 

    To find out the value of LSB, the routine pauses for 3 DLY49us. By doing so it samples the middle of LSB. This is the best position for correct sampling. The incoming bit value is read, and Carry bit is set or clear accordingly. The instruction ROR RCVREG rotates that bit into register RCVREG bit7 each time, until all 8 bits transmitted and count=0, so RCVREG has converted the bits into a byte.

 

    The technique of converting a byte into a series of bits for transmission, or converting a series of bits into a byte for reception, is usually known as “Bit-Banging.”

 

 

 

                        A Full-fledged Application: WWLSEEP-1 Programmer

 

 

    You might think that even though the above demo program works in simplistic case such as playing char typing with HyperTerminal, it may not work in more complex situation.

 

    I had the same concern. Because I have not seen any other product that’s built using a software serial port, all I’ve seen are just simple demo circuits. So I was very ambitiously determined to give it a try: I must make a product that uses a software serial port. Specifically, I decided to make an I2C EEPROM programmer.

 

    Because I was not sure I can succeed, I had to think what if I fail. And fortunately Atmel has two very similar MCUs: the AT90S1200 and AT90S2313, both are 20-pin chips, the differences are the 2313 has 2K flash memory and built-in serial port; but the 1200 has only 1K flash and no serial port (hence cheaper).

 

    My strategy was two steps: first, build an I2C EEPROM programmer using the 2313, then try to migrate to using 1200 with my software serial port. It only took a few days to complete the first step, and a working programmer already at hand.

 

    The migration took much longer time, and was actually an adventure for me. Originally, the 1200’s RX and TX pins were not assigned the same pin numbers as the 2313’s RX=pin2 and TX=pin3. But then experiment told me those pins can only get EEPROM write correctly, Read was incorrect.

 

    UP to this writing I still don’t know why it can’t Read correctly, but finally I discovered that, if the 1200 pin assignment was chosen the same as 2313 (RX=pin2, TX=pin3), then both Read and Write are correct. So I stick with that, and the 1200-based programmer completely works. This programmer schematic is shown in Figure 3.

 

 

 

 

 

              Figure 3. Schematic of Wall-wart-less Serial EEPROM Programmer

 

 

    Notice this serial EEPROM programmer is wall-wart-less, meaning the power supply is taken from PC serial port itself. This is a convenience to the user. It’s possible because the circuit contains only three small chips, the total current consumption is much less than 10 mA.

 

    To program the 24CXX I2C EEPROM, only two pins are needed: the serial clock pin SCL, and serial data pin SDA which is bidirectional. The programming command or data is applied to SDA from 1200, and the SDA also outputs data to 1200 when requested. The SCL pin is applied clock signal generated by 1200 to synchronize I/O operation.

    The complete programmer development includes writing Windows XP executable program to communicate with the programmer, and writing the 1200 firmware. Of course, in addition to creating software serial port and communicating with PC, the firmware must perform all Read/Write programming chores through SCL and SDA. The firmware is totally 854 bytes, including the software serial port overhead.

 

 

                  

       

                         Photo 1.  The WWLSEEP-1 programmer

 

 

    Photo 1 shows the WWLSEEP-1 programmer. Notice that all signals and power supply are provided through DB9 connector, there is no separate power supply.

Also note that if the PC is not running the programmer software but running HyperTerminal program, then it can demo char echo effect as in first example.

 

 

 

                          Summing  up

 

 

    You have seen from the two examples that software serial port is useful. It can save you some bucks. It can do the same job just as the hardware serial port. To do so needs some memory space, but from the first example you know the overhead is less than 100 bytes, for the 1K byte 1200 there is still a lot of program space left.

 

    And the timing requirement for the asynchronous communication is not very strict, but rather forgiving at the speed of 9600 bps.  Feel free to examine what will happen if you change the DLY49us to 50us by adding a NOP (that’s equivalent to 1us) to the beginning of the routine. You’ll note it still OK.

 

    Of course there are limitations. The 1 MHz frequency is too low if you want to get higher BaudRate. Because at higher speed the half_bitime is reduced, then it will be hard to keep the sampling at middle of each bit.

 

    We are not chip makers, we can’t make an MCU. But we are software users and writers, we can create a software serial port for any MCU when we need it. For this purpose, I have created a website containing the software serial port program source code for different MCUs, from 8051, PIC to AVR to demonstrate such idea. Please visit my website at www.oocities.org/microappnotes

 

 

    Note: The following item is available from:

    G.Y. Xu, P.O.Box 14681, Houston, TX 77021, USA.

    Phone: (713) 741-3125

 

1.       Assembled and tested WWLSEEP-1 Programmer - $24.95 (with one free 24CXX)