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.

 

BAUD

baudnum

halfbaudnum

110

9090

4545

9600

103

52

27777

35

18

38400

25

13

57600

16

8

115200

8

4

 

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


©2003 Keith Carter, Michael Muccio