Create Your Serial Port and Build This

†††††† CreateYourSerialPortandBuildThis



††††††††† Using Bit-Banging technique to create a software Serial Port

†††††††††††††† for your Microcontroller is rewarding



††††††††††††††††††††††††††††††† ByG.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:




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 ( 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 ""††††††† ; Port definitions for ATtiny11

.cseg††††††††††††† †††††††††††

.org 0



††††††† 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


††††††† 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


††††††† DEC†††† R20†††††††††† ;†††††† 14 * 1 = 14

††††††† BRNE††† LOOP1†††††††† ;†† 13 * 1 + 1 = 27

††††††† RET††††† †††††††††††††;††††††† 4 + 3 =7



††††††† LDI†††† COUNT, 8†††† ; 8-bit Data

††††††† CBI†††† PORTB, TX††† ; StartBit='0'

††††††† rcall†† DLY49us††††† ; ~Half_Bitime

††††††† rcall†† DLY49us††††; ~Half_Bitime


††††††† LSR†††† XMTREG†††††† ; shift right LSB to Carry

††††††† brcc††† FORWARD

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

††††††† RJMP††† F2††††††††††


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


††††††† rcall†† DLY49us††††† ; ~Half_Bitime

††††††† rcall†† DLY49us††††† ; ~Half_Bitime

††††††† DEC†††† COUNT

††††††† brne††† NEXTX


††††††† sbi†††† PORTB, TX††† ; StopBit = '1'

††††††† rcall†† DLY49us††††† ; ~Half_Bitime

††††††† rcall†† DLY49us††††† ; ~Half_Bitime

††††††† RET†††††††††††††



††††††† LDI†††† COUNT, 8†††† ; 8-bit Data††


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

††††††† rjmp††† wait


††††††† rcall†† DLY49us††††† ; ~Half_Bitime


††††††† 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†††††††††††††






††††††††††††† ††††††††††††††TimingisEverything



††† 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.




†††††††††††††††††††††††††††† HowBit-Bangingisdone



††† 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.




††††††††††††††††††††††††† Summingup



††† 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



††† 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)