Encoder Front Page
SRS Home | Front Page | Monthly Issue | Index
Google
Search WWW Search seattlerobotics.org

Need more serial output ports? 68HC11 Bit Bang

Otherwise know as "Trials and Tribulations of a Robotist"

Jeff Davis

jdavis2@mmcable.com

lcd.jpg (31775 bytes)

While in my ongoing attempt at evolution with my bot I hit the preverbal wall. I’m sure all of you know this wall, it doesn’t 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.

bot_top.jpg (37521 bytes)

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 I’ve been dreaming about. My solution? Bit Bang.

lcdcntrl.jpg (30280 bytes)

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 isn’t 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 wasn’t much help and also managed to insult me at the same time. I’m 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 don’t 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 Code

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.

 

bit_bang
	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
bottomlsr
	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

Delay Subroutine

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 doesn’t 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

Baud   Bit Time
1200   833.33 usec
2400   416.67 usec
4800   208.33 usec

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

 

Hints and things to avoid:

  1. If you have a V8600A voice module don’t debug your serial code to this device. It seems that if you hit just the right sequence of bits you can clear the internal memory of the unit and the sucker wont do anything until you re-upload the unit with a good OS. (sorry for the headache Randy at RC Systems) Don’t ask me how I know this, just take comfort in the fact that I do….
  2. Its not a good idea at 2:00 am when you have your code debugged and its really working to wake your wife to come see your display or voice module operating. Again don’t ask......, just trust meJ.
  3. What I found worked well for me was to use 2 serial ports on my PC. I built a RS232 converter that I feed the bit banged serial output from the 68hc11 into and then into the PC. I then used another port to program the bot. This configuration allowed me to open one hyper-terminal window for incoming test data and another window for programming the bot’s 68hc11.
  4. If you have access to a decent Scope it will ensure that your bit time is what it’s really supposed to be. A storage scope would be even better, wish I had had one.
  5. If your main program uses RTI or other interupts you probably need to disable them on entry into this code section or your timing may be off.

In Conclusion

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.

Jeff Davis