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)