Otherwise know as "Trials and Tribulations of a Robotist"
While in my ongoing attempt at evolution with my bot I hit the preverbal wall. Im sure all of you know this wall, it doesnt matter if you have basic stamp or 68HC11, sooner or later you need just a few more I/O lines. In my particular situation I have a 68HC11 and what I really needed was a few more serial lines.
I had recently purchased a V8600A voice module from RC Systems and had a nifty little serial driven LCD display that I scratch built with a Atmel 2313. To make matters worse I really wanted to use the SCI port for a RF modem connection that Ive been dreaming about. My solution? Bit Bang.
I started searching the Net trying to find examples of this code in 68HC11 format. From my searching efforts it would appear to me that bit bang code is like a right of passage for the programming Gods and isnt shared with the rest of us mortals. It was this lack of information that led me to share my information with my program-challenged brethren. During my search the best info I found was the Jargon definition of Bit Bang "Transmission of data on a serial line, when accomplished by rapidly tweaking a single output bit, in software, at the appropriate times. The technique is a simple loop with eight OUT and SHIFT instruction pairs for each byte. Input is more interesting. And full duplex (doing input and output at the same time) is one way to separate the real hackers from the <wannabee.html>s. ". Along with this I also found a nice description of Serial format in TTL and RS232 at http://www.seetron.com/ser_an1.htm
The Jargon definition wasnt much help and also managed to insult me at the same time. Im pretty sure I am a Wannabee now J. The information from the Scott Edwards site was much more helpful. It provided waveforms for TTL level as well as RS232. Also listed in this site was my Holy Grail. Bit Time. Armed with this information and my limited programming skill I felt pretty sure I could write my own bit bang routine.
I fired up my assembly editor and went work. All the following references are made with TTL signal levels in mind. RS232 waveforms are inverted from this level so dont get them confused. First on the agenda was a close look at what I was trying to output. The waveforms at the above referenced site gave me all I needed to know, Idle State, Start Bit, 8 Data Bits, Stop Bit and of course the time a single bit was to be active for each Baud rate.
So off we go,
The example code was written for a 68hc11, 8 Mhz running in the expanded mode, a PRU and with Buffalo OS. A complete code listing is provided after the explanation.
Set the Stack Register, [Being the amateur I am this little op-code once cost me days and days of debugging. I now keep this statement virtually write-protected in my standard equate header].
org $c000 *Code begin lds #$dfff *Set stack
We first start by setting X to the I/O base page (in my case this was $1000) for Bset and Bclr commands this is followed by taking the output line high with the bit set command. This is called the idle state and is the condition the output is in unless there is data flow. This is followed by a jsr to a bit time delay routine to make sure idle is stays set for at least one bit time before sending the start bit. More on the delay routine later.
Main ldx #$1000 *Set X for index operation for Bset and Bclr * commands. bset $04,X %00000001 *Take Idle line to high, normal state jsr bit_delay *Make sure Idle is held high for at least one bit time
The entry into this routine only requires the address of where the data string begins. The end of the string is marked with $F1. We store the address of the string start in register Y.
ldx #$1000 *set register X as index for (bclr- bset) operations
start ldaa ,y * Load A with first address to be output staa debug * For debug purpose cmpa #$F1 * If A=F1 then exit routine is done beq exit *
ldy #text * Load Y with the address for the data string
Register A is loaded with the byte from memory indexed by register Y. A test is made to see if the Byte is equal to $F1. If F1 then the routine exits else it prepares to output the byte.
Register B is loaded with #8 this equals the number of bits we are going to shift out.
start_bit ldab #$08 * Set B for a 8 bits to be output bclr $04,x %00000001 *6 3us Output the Start Bit jsr bit_delay * Call bit delay
Now that we have a byte ready to send we initiate a start bit. The start bit is a low output lasting one bit time. This is done with a Bit Clear command followed by a jsr to the Bit Delay Routine.
The heart of the entire operation is the lsra command. This little op-code allows one to shift the contents of accumulator A one bit at a time into the CC register. From here you can check the status of the bit and branch to output a high or a low signal determined by what was shifted into the CC register from the A accumulator. If CC was set then bcs to Highout, if CC was clear then bcc to Lowout.
rotate ldx #$1000 *3 1.5us Making sure X stays at index location lsra *2 1us Shift bit into carry register bcs high_out *3 1.5us If High branch to high_out bcc low_out *3 1.5us If Low branch to low_out
In the Highout and Lowout routines we simply turn off or on the output pin with the Bit Set or Bit Clear op-code and call the bit delay subroutine. On return from this subroutine we decrement our B counter by one and check to see if we have done all 8 bits. If all bits have not been sent then we loop back up to the lsra section of the code to do the next bit. If all 8 bits have been sent then we set the output back to idle and then call the bit delay subroutine (AKA setting the stop bit) and then increment Y register to point at the new byte to be shifted out and repeat the whole process.
low_out ldx #$1000 *3 1.5us Making sure X stays at index location bclr $04,x %00000001 *6 3us Turn off $1004 bit one jsr bit_delay *6 3us Call bit delay bra bottomlsr *3 1.5us Jump to end of rotate section
high_out ldx #$1000 *3 1.5us Making sure X stays at index location bset $04,x %00000001 *6 3us Turn off $1004 bit one jsr bit_delay *6 3us Call bit delay bra bottomlsr *3 1.5us Jump to end of rotate section
decb *2 1us Decrement B bne rotate *3 1.5us If B not zero then do rotate again * If zero done with this Byte set up for exit ldx #$1000 *3 Making sure X stays at index location bset $04,x %00000001 *6 Turn on stop bit jsr bit_delay *6 Call bit delay iny *4 Set Y register to next Byte to output clra * Make sure A register is clear for next set
bra start *3 Jump to begining and do next Byte
The above is fairly straightforward. Point at a memory location, get the byte and send it out one bit at a time, continue the process until you see a $F1 and exit the routine. The following delay routine is actually the most difficult part of this little program. The difficulty doesnt lie in the complexity of the code but in the amount of delay we create. Since serial communication is all about bit timing so the other end can understand what we are trying to send we must be fairly exact on how long a serial bit is High or Low. To achieve this we need first need to know a few things.
The speed of the processor running the program.
How many instructions cycles are required for each op-code.
How many usec each cycle lasts.
With the above information we can tell how much time the data moves, compares, and bit shifting take up in the main part of the program and build a delay routine that will fill in the rest of the time we need to keep a bit high or low. The cycle quantities can be found in almost any op-code sheet for the 68hc11. For our cycle time we take the
8 Mhz clock and divide by 4. 2Mhz is the speed of the internal clock for a 8 Mhz 68hc11. One cycle is the reciprical of 2Mhz ie. .5usec of time per cycle.
All of the instructions that are relevant to bit timing have 2 numbers in the comment field. The first number is how many machine cycles the instuction requires to execute. The second number is the total time in usec that the instuction takes to execute. In our example we have 30.5 usec delay. This includes time from Labels ( rotate, high_out, part of bottom lsr, and bit_delay). Since we only travel threw high_out or low_out once per round we only count the time in just one of these code sections.
From the table below we see that to hit the proper bit time our timing for a single bit must be 104.17 usec. So we take 104.17-30.5=73.67. 73.67 is the amount of time we must eat up in the bit_delay routine. The X loop we are using takes 3usec to complete so we can divide 73.67 by 3 for how many times we must run the loop to get as to to the 104.17 bit time we are looking for. At 9600 Baud this number is 24.55 times. With my controller 24 loops works, your milage may vary. To get closer we could add a nop statement at the begining of the bit_delay routine and have a result of 24.3.
Bit Times for different Baud Rates
104.17 usec *An error of more than 3 usec will probably not work at 9600 baud
The complete code listing can be found in bitbang.asc
In closing I hope my meager offerings may have been some help. I would also personally like to thank all my WWW mentors at Seattle Robotics Society for all the help and resources they have provided. I am quite sure that my bot would still be sitting in the closet without them.