|
Half Duplex Software
UART In order to
send and receive both text and audio data we decided to implement a software
protocol which emulated the workings of a hardware
UART. This meant that we would be
sending information in digital form over a serial data link. The laser would essentially constitute the
“wire” in this link connecting our two software UARTs. We could easily reserve two pins of a port to
be the receive and transmit pins for our software UART
(in the final version of the code pins D.0 and D.1 for receive and transmit
respectively). What remained was an
implementation of a receive subroutine and a transmit subroutine. The development of these subroutines is
described below. UART 101 A Universal
Asynchronous Receiver Transmitter (UART) operates on the premise that one can
transmit an 8-bit (sometimes 9-bit) data message through a single wire link
without time synchronization between source and destination. The resultant single wire link is known as a
Serial Data Link because the bits of the 8-bit message are transmitted in
series. The asynchronous part of the
UART (which is what attracted us most to the idea of emulating a UART in our
software) is achieved by sending known bits to denote the beginning and end of
the data message. These bits are called
start and stop bits and are 0 and 1 in the UART protocol respectively. When the UART is not transmitting data it
maintains a logical high (1) on the wire.
Thus, when a logical low (0) start bit is transmitted, the receiver
knows that a message is being transmitted and starts “paying attention”. The 8 bits following the start bit are the
contents of the message. The 8 bits are
followed always by a logical high (1) stop bit (and in more complicated
implementations a parity bit which we shall ignore for our purposes). In the hardware implementation of the UART on
most Atmel chips the UART samples received bits 3
times in the middle of the bit and uses a “majority rule” process to determine
the value of the bit. By contrast, our
software UART will sample received bits only once in the middle to determine
their value. Also,
another key difference between our software UART and most hardware UARTs is that it is a half duplex UART. This means that it can only transmit or
receive at any given time rather than both transmit and receive at the same
time (full duplex). A full duplex
software UART would be much more complicated and is unnecessary for our
application. The measure
of the rate at which bits are transmitted by a UART over a Serial Data Link is
known as a baud rate (the term “baud” does not stand for anything but rather is
a shortened form of the name of Emile Baudot, a
pioneer in early telegraph systems whose work paved the way for the Modulator
Demodulator (Modem)). Moreover, a data
rate of 300 baud is analogous to a rate of 300 bits per second (bps). This data rate is important for two
reasons: It determines how long the
transmitting UART must maintain a bit’s value on the wire, and it determines
how long the receiving UART must wait between sampling bits. These two lengths of time are actually the
same and will be implemented in our software UART with a software delay call
(referred to hereafter as a “delay”). To
calculate the length of time we need the delay to last we must know that
getting into each of the transmit and receive subroutines and doing the
necessary logical operations to change bit values or to determine received bit
values take a total of about 1μs (or 16 operations) and so we must
subtract this amount from our end result.
In the following calculations we will denote our end delay as “delay”
and our desired baud as “baud”: We first
must calculate the time length of a bit at the desired baud: Then get
the period in μs: We then
round this result and subtract 1μs to account for overhead and logical
operations. This is our final delay: This delay
is denoted as “buadnum”
in the final code. We also require “halfbuadnum” for
the receiver (as a rule of thumb buadnum/2,
round result up if buadnum
is odd). Any timing errors associated
with these calculations only persist for 8 bits because the messages are only 8
bits long. Such errors would be “reset”
at the beginning of the next transmission, and as a result we noticed that the
software UART was not terribly sensitive to errors on the μs
level (especially at lower bauds). These
discussions of delay calculations will make more sense to the reader after
reading the descriptions of the transmit and receive
subroutines. A table of some common
delays is given below.
It should
be noted that (although the code is nowhere near the same) many of the concepts
we used in creating our software UART came from the Atmel
Application Note on Half Duplex Compact Software UART for the AVR 8-bit RISC
microcontroller family (App Note AVR305 at http://www.atmel.com/dyn/products/app_notes.asp?family_id=607
). The code provided with this App Note
was written entirely in assembler language and so we were presented with the
task of implementing a C version which mimicked its operation. What follows is a description of our end
product. The Transmit
Subroutine put() To transmit
a byte (8 bits) over the serial data link we developed a subroutine called
put. This subroutine takes the 8 bits of
data in the character variable Txbyte and shifts each bit out on to the serial data link
one at a time starting with the LSB. The data byte desired to be sent should be
placed into Txbyte
and then put must be called in order to send that data. It is important to note that a call to put
destroys the contents of Txbyte. We felt it
was a good idea to invert Txbyte at the beginning of the put subroutine and then treat
all 0s as 1s and all 1s as 0s in order to make what is shifted into Txbyte when we
perform a right shift (which is a 0) look like a stop bit of value 1. This eliminates some logic at the end of the
subroutine to make a stop bit. We also
have a variable called carry which essentially keeps track of the last bit
shifted out of Txbyte
and is representative (i.e. the opposite) of the value of the bit currently
being sent. Lastly, we have a variable
called bitcnt
which is a counter to keep track of what bit number we are currently
sending. It is set to 9 + sb at the
beginning of the subroutine and is decremented each time a bit is sent (sb is the number
of stop bits being used which in the final version of the code is set to
1). The first
three things we do in put is to set the value of bitcnt, invert Txbyte, and set
carry to 1 (for the start bit which should be 0). We then enter a while loop which terminates when bitcnt reaches 0. Upon entering the loop we send the current
bit. This is done by setting the
transmit pin (PORTD.1) to the opposite value of carry. We then want to maintain that value on the
pin for the appropriate amount of time (determined by the desired baud) and so
we delay for buadnum
μs (using a call to delay_us()).
We then set carry to the value of the LSB of Txbyte, we shift Txbyte right 1
place¸ and we decrement bitcnt
because we have sent a bit. At this
point the loop will repeat until bitcnt reaches 0 and the stop bit has been sent. The reader should note that the time it takes
to perform any of the logic, branching, shifting, or other calculations in the
loop is accounted for in the delay (by subtracting 1 μs). We paid very close attention to the resulting
assembler language to determine this correction and we even balanced the number
of cycles required regardless of whether a 1 or a 0 was sent (the reason for
the inserted nop). A flow
diagram of this subroutine can be seen below: The Receive Subroutine
get() To receive
a byte of data from the serial data link we developed a subroutine called
get. This subroutine waits for a start
bit and, when one is detected, receives bits from the serial data link one at a
time and shifts them into the character variable Rxbyte starting with the LSB. After all 8 bits have been received the
contents of Rxbyte
represent the complete received byte. It
is important to note that the next call to get will overwrite the contents of Rxbyte with the
next byte to be received. Timing is
very important in the get subroutine because we must poll the receive pin
(PIND.0) at the appropriate time to get the correct values for the received
bits. To this end we must calculate two
different delay lengths: one is the full
bit delay length denoted as buadnum and the other is a half bit delay length denoted as halfbuadnum. The calculation of both of these delays is
explained above. The first
thing we do in get is to set bitcnt to 8 + sb. The reason we use
8 here instead of 9 (as in put) is because we will already be past the start
bit when we start to decrement bitcnt. We then enter
a while loop which continuously polls
the receive pin (PIND.0) checking for a 0 (i.e. the start bit). Thus, the subroutine can only continue once a
start bit has been detected (consequently get must be called before the start
bit is to be received). Once the start
bit has been detected we then delay for half a bit to get to the middle of the
start bit by calling delay_us(halfbuadnum).
At this point we enter a while
loop which will terminate only when bitcnt has reached 1.
Upon entering this loop we delay for an entire bit to get to the middle
of the first bit (and each successive bit on successive iterations of the loop)
by calling delay_us(buadnum).
After this delay we shift Rxbyte right 1 place.
We then set the MSB of Rxbyte according to what we are receiving on the receive pin PIND.0 (0 if receiving 0 and 1 if receiving 1). We then decrement bitcnt because we have received a
bit. The while loop will terminate when bitcnt reaches 1 (instead of 0 as
in put) because we do not wish to shift into Rxbyte the stop bit which is not
part of the data, however we must still delay for one bit after the while loop to get past the stop
bit. As in put the time required for
logic, branching, and shifting in get is accounted for in the calculation of
the delay. A flow
diagram of this subroutine can be seen below: The Programs In order to
test our software UART and our hardware setup we wrote 3 programs. One of these programs was used to test our
system’s ability to transmit and receive text data from a HyperTerm
session. This program was called echoer.c and
contained both the put() and get() subroutines. The other
two programs were used to test our system’s ability to transmit and receive audio. The program which transmits audio was called tx.c and
contained the put() subroutine. The program which receives audio was called rx.c and contained
the get() subroutine. A brief explanation of these programs is
provided. The Text Program echoer.c As
mentioned this program makes use both the put() and get()
subroutines. The main method is fairly
simple. We first set the data direction
of the pins of PORTD to the appropriate settings. We indicate the use of 1 stop bit. We then send ASCII 0x12 to clear the HyperTerm window.
Finally we enter an infinite while
loop which executes a get() then transfers the received byte Rxbyte to the
transmit byte Txbyte
and then executes a put(). This essentially echoes whatever is received
by the software UART. Our test setup was
to program 2 Mega32s with this program.
We would then have one of the Mega32s software UART receive pin
connected (via RS232) to a computer with a running HyperTerm
session and the transmit pin connected to the laser transmitter circuit. We would then connect the software UART
receive pin of the second Mega32 to the photo diode circuit and connect the
software UART transmit pin (via RS232) to a computer running a HyperTerm session. A
user could then type on the first HyperTerm session
and see the typed text appear on the second HyperTerm
session. The Audio Transmitter
Program tx.c The tx.c program is
responsible for two different things.
The first of these is to sample the output voltage of the microphone
circuit at the desired frequency and the second is to send the 8-bit result
over the serial data link (thus it only requires put()). This process makes use
of two features of the Mega32:
Timer/Counter 0 and the Analog to Digital Converter (ADC). Timer/Counter 0 will essentially (through use
of the overflow interrupt) control the frequency at which the ADC is told to
make a conversion of the microphone voltage.
Two interrupts are involved in this process: the Timer/Counter 0 overflow interrupt and
the ADC conversion complete interrupt.
The Timer/Counter 0 clock prescaler is set to
256 which effectively yields a clock period of: The reload
value (i.e. the value the counter is reset to in the overflow interrupt service
routine) is 256-21. Thus Timer/Counter 0
overflows every: We also
have a task (task1) which we can have
execute every t1 overflows of
Timer/Counter 0. This task orders the
ADC to begin a single conversion (i.e. take a sample) by setting bit 6 (ADSC)
in the ADCSR register. If we desire a
sampling frequency of about 3kHz then we want task1 to execute every time
Timer/Counter 0 overflows and so we set t1
to 1. When the ADC completes the
conversion it throws the ADC conversion complete interrupt. In the interrupt service routine for this
interrupt we transfer the result of the 8-bit microphone voltage conversion (in
ADCH) to the transmit byte Txbyte and then execute a put() to send the 8-bit voltage over the serial data link. The 8-bit voltage value produced by the ADC
represents a voltage between 0 and 5V because the reference voltage of the ADC
is set to AREF which is 5V. The main
method here is again rather simple.
After the appropriate initialization of the Timer/Counter 0 and ADC
registers and indicating the use of 1 stop bit we fall into an infinite while loop. There is one line of code in this loop: an if statement that assures that task1 executes every t1
overflows of Timer/Counter 0. The
interrupt service routines do all the rest of the work. The Audio Receiver
Program rx.c The Audio
Receiver rx.c
is very simple and does not require the use of any timers or the ADC. Its sole responsibility is to receive the
8-bit voltage values off the serial data link and blast them out 8 bits at a
time through a port (in the final version of the code PORTB) to the DAC
circuit. Because of this rx.c only
requires the get() subroutine. After the appropriate initialization
(appropriate data direction on the ports and 1 stop bit) we fall into an
infinite while loop where we execute get() continuously. The get() subroutine
will wait for the start bit of a transmission before executing. After get() executes we
blast the received byte Rxbyte
out through PORTB to the DAC circuit before the next call to get().
Because get() waits for start
bits the frequency of the output to the DAC (and in turn to the speaker) is all
determined by the frequency of incoming transmissions which is in turn
determined by the sampling frequency at the transmitter. Thus, it all fits nicely together. The Audio Setup The setup
for audio transmission required the use of two Mega32 chips. One of the chips was programmed with the
transmitter code tx.c
and the other with the receiver code rx.c. The transmitter
was connected to the microphone circuit through the ADC input pin (PORTA.0) and
to the laser transmitter circuit through the software UART transmit pin (PORTD.1). The receiver was connected to the photodiode
circuit through the software UART receive pin (PORTD.0) and to the DAC circuit
through the output port (PORTB). |