TimerV assisted serial output on a Basic Atom Pro

As I have had problems in the past with My LCDS not displaying properly when I have interrupts enabled in my program and it has frustrated me for awhile, I decided to see if I could write my own serial output function, that would use a timer to assist in generating pulses. I would have loved to use WTimer as it is a 16 bit timer and it would be easier to code it up and make it work properly. But HServo uses wtimer and it resets the values and the like so I ended up using TimerV. I am still in the process of testing and cleaning it up, but at least it looks like it might have a chance of working at least for not long duration interrupts.

I will include my current test program at the end of this post, which includes the current sources. Still needs to be cleaned up. Watching the output with the Logic Analyzer, I did 4 different outputs to different IO pins. Three of them with my function and the fourth with serout. The calls looked like:

b1 = "A"
ab2 = "XYZ",15
gosub TASerout[0, i9600, @b1, 1]
gosub TASerout[1, n9600, @b1, 1]
gosub TASerout[2, i19200, @ab2, 4]
Serout p3, i9600, "ABCDEF"][/code]
The result with no interrupts enabled:
http://i416.photobucket.com/albums/pp245/Kurts_Robots/HASerialoutput.png

Now with a simple TimerA overlflow counter with it incrementing the timer on every 32nd clock:
http://i416.photobucket.com/albums/pp245/Kurts_Robots/HASerialwithinterrupt.png
You will notice the first three lines, which were generated by my code still look like they got the right pulses, but the 4th one that was generated by Serout was corrupted.

Again please note this a work in progress,  I have not actually tried  outputing to my real devices yet, but I was just glad to get it to this point

Kurt

P.S. - Here is the complete code...
[code];------------------------------------
; test program for Timer Assisted Serout.
; This program will see if is possible to use TimerV on the BAP
; to generate the pulses for serial output and handle external
; interrupts more reliably then the simple b it bang versions.
;									
;------------------------------------

b1	var byte
ab2 var byte(4)

; BUGBUG:: debug information
DTBM		var word
DBTCORA		var	byte
BTCRV1		var	byte
DBTCRV0		var	byte
DBPINPORT	var	word
DBPINBIT	var	byte
DBINVERT	var	byte
DBCNT		var	word
DBOUTPUTBYTE	var byte

ONINTERRUPT TIMERAINT,IntHandler 
timer var long 


;==============================================================================
; Complete initialization
;==============================================================================
b1 = "A"
ab2 = "XYZ",15

TMA = (TMA & 0xF0) | 0x6 ;Sets TimerA to increment once every 32 instructions
ENABLE TIMERAINT 
ENABLE
;==============================================================================
; Main Loop
;==============================================================================
	sound 9,[50\4400]
	high p12
	high p13
	high p14
	serout S_OUT, i9600, "9600:", dec n9600, " 192:", dec n19200, " 115200: ", dec n115200, " 300: ", dec n300, " 2400:", dec n2400, " S_OUT=",dec s_out,  13]
main:
	gosub TASerout[0, i9600, @b1, 1]
	gosub TASerout[1, n9600, @b1, 1]
	gosub TASerout[2, i19200, @ab2, 4]
	Serout p3, i9600, "ABCDEF"]

; dump some debug information out to serial port to see our status
	serout S_OUT, i9600, [hex DTBM, ":", hex DBTCORA," ",hex BTCRV1, " ",hex DBTCRV0, " ",hex DBPINPORT, " ",hex DBPINBIT, " ", hex DBINVERT, " ", hex DBCNT," ",hex DBOUTPUTBYTE, "(",DBOUTPUTBYTE,")",13]
	
	pause 100
	 
	goto main


;==============================================================================
; Dummy interrupt handler...
;==============================================================================

IntHandler: 
	timer = timer + 1 
	resume

;==============================================================================
; TASerout(pin, baudmode, buffer, bufferlen
;
; First attempt will be to use TimerV which is only 8 bits, not sure exactly how 
; accurate some of these need to be, but will try to scale the timer counter to
; keep the value we are checking to be under 256.  The scaller for the clock has
; only the valid values of: 4, 8, 16, 32, 64, 128, so our bit baud rate can maybe
; work with a min baud rate of: 490, I wish I could use TimerW...
;
;==============================================================================
TASPin var	byte
TASBM	var	word
TASPBUF var	POINTER
TASCnt var	word
TASerout[TASPin, TASBM, TASPBUF, TASCNT]
								; BUGBUG: could do this in assembly as well

	; ok need to initialize TimerV
; transistion to assembly...
;	bset.b	#4,@PCR8:8			; make sure set to output, basic should have done earlier!
;	bset.b	#5,@PCR8:8			; make sure set to output, basic should have done earlier!
;	bset.b	#6,@PCR8:8			; make sure set to output, basic should have done earlier!

	mov.w	@TASBM:16, r1			; get the baudmode.
	mov.w	r1, @DTBM				; see what our baud mode was...
	bld.b	#6, r1h					; Get the normal/invert bit into C
	subx.b	r3h, r3h				; r3h = 0 if Normal or ff if inverted
	and.w	#0x1fff, r1				; extract the bit time
	mov.w	#3,e1					; start off assuming clock will be divided by 8 as the baud mode is set this way
_TASOMCL:	
	or.b	r1h, r1h				; see if our count is < 256
	beq		_TASOBMLD:8				; yep done with this loop
	shlr.w	r1						; nope divide by 2
	add.w	#1, e1					; change clock input for TimerV
	jmp		_TASOMCL:8				; Check again

_TASOBMLD:	
	; BUGBUG: should verify that we are in a valid range...
	mov.b	r1l, @TCORA:8			; save away the value that we should count up to for each bit
	mov.b	r1l, @DBTCORA:16			; BUGBUG: Debug
	mov.w	e1, r1
	mov.b	#0, r1h					; zero out the high byte
	rotr.w	r1						; so low order bit now in high bit of high word
	rotl.b	r1h						; now in low bit of r1h
	mov.b	r1h, @TCRV1:8			; OK so should have the low bit of timer calc stored properly away.
	mov.b	r1h, @BTCRV1:16			; BUGBUG::: debug infor...
	or.b	#0x08, r1l				; Added in the bits: CCLR0 for the TCRV0
	mov.b	r1l, @TCRV0:8			; save away the generated configuration byte
	mov.b	r1l, @DBTCRV0:16		; BUGBUG:: DEBUG
		
; first lets use the Porttable that BAP has to convert logical pin to Port/pin
	xor.l	er0, er0				; zero out er0
	mov.b	@TASPIN:16, r0l			; move the logical pin number into r0l
	and.b	#0x3f, r0l				; make sure we are in the range 0-63
	shll.b	r0l						; double the offset as the port table is 2 bytes per entry.
	mov.w	@(_PORTTABLE:16, er0), r2	; OK R2 now has the two bytes high being the bit, and low being the port...
	mov.b	r2h, r3l				; Ok r3l now has the pin number
	mov.b	#0xff, r2h				; setup 32 bit address to port register PDRx
	
	; make sure it is setup for output
	mov.b	@(0x70-0xD0, er2), r0l	; get the current value for PCRx byte from the shadow location
	bset.b	r3l, r0l				; set the appropriate bit for our port
	mov.b	r0l, @(0x70-0xD0, er2)	; save away the updated value to the shadow location
	mov.b	r0l, @(0x10, er2)		; save away in the actual PCRx byte
		
	; Make sure the IO line is correct to start off with
	or.b	r3h, r3h
	beq		_TASOINORMAL:8			; OK will start up normal
	bset.b	r3l,@er2				; Inverted mode start high
	jmp		_TASOINITBUF:8			; continue to setup pointers to buffers
_TASOINORMAL:
	bclr.b	r3l, @er2				; this sets the IO line to low...

	; Now setup data pointer and counter.
_TASOINITBUF:	
	mov.l	@TASPBUF:16, er4			; OK now ER4 has pointer to the buffer to output.
	mov.w	@TASCNT:16, e3			; Now E3 has the count of bytes to output

	; put up to one bit delay at the start to get everything in sync...
_TASOWAB:	
	bld.b	#6, @TCSRV:8			; see if we had a timer cycle go through
	bcc		#_TASOWAB:8				; nope
	bclr.b	#6, @TCSRV:8			; clear out the status

	;BUGBUG: -------------- save some debug information here...
	mov.w	r2, @DBPINPORT:16
	mov.b	r3l, @DBPINBIT:16
	mov.b	r3h, @DBINVERT:16
	mov.w	e3, @DBCNT:16
	
;------------------------------------------------
; Main output loop: helps to know what each register is doing:
; r2	- Word address for PDRx for the output pin
; r3l	- bit number of IO PIN
; r3h	- 0 if Normal communication, FF if inverted.
; e3	- Count of bytes left to output	
; er4 	- Pointing to the current byte to output
	
_TASOBYTELOOP:
	or.w	e3, e3					; see if more bytes left to go
	beq		_TASODONE:16			; NO BYtes left go to do the cleanup
	mov.b	@er4+, r0l				; Ok have the next byte to output
	mov.b	r0l, @DBOUTPUTBYTE:16	; BUGBUG::: Debug save away the byte to debug
	xor.b	r3h,r0l					; Hack if inverted mode we will invert all of the bits here...
	mov.b	#8, r0h					; Ok we now have 8 bits to output.
	
	; align our self with the bit clock to make sure we don't give to small of a start pulse
_TASOWSB0:	
	bld.b	#6, @TCSRV:8			; see if we had a timer cycle go through
	bcc		#_TASOWSB0:8			; nope
	bclr.b	#6, @TCSRV:8			; clear out the status
	
	
	; need to do the start bit, for the heck of it we will wait until we get a counter match/reset before we do anything
	; The value depends on if we are inverted or not...
	or.b	r3h, r3h
	beq		_TASOSBNORMAL:8			; OK will start up normal
	bclr.b	r3l, @er2				; this sets the IO line to low...
	jmp		_TASOWSB:8			; continue to setup pointers to buffers
_TASOSBNORMAL:
	bset.b	r3l,@er2				; Inverted mode start high

_TASOWSB:	
	bld.b	#6, @TCSRV:8			; see if we had a timer cycle go through
	bcc		#_TASOWSB:8				; nope
	bclr.b	#6, @TCSRV:8			; clear out the status
	
_TASOBITLOOP:
;	bnot.b	#6,@PDR8:8	
	; now lets process the next bit
	shlr.b	r0l						; ok lets shift it down lowest bit goes into carry.
	bcc		_TASOBIT0:8				; not set so was zero
	bclr.b	r3l,@er2				; The bit is a zero
	jmp		_TASOBITDELAY:8			; 
_TASOBIT0:
	bset.b	r3l, @er2				; Ok this bit goes high
_TASOBITDELAY:
	bld.b	#6, @TCSRV:8			; see if we had a timer cycle go through
	bcc		#_TASOBITDELAY:8			; nope
	bclr.b	#6, @TCSRV:8			; clear out the status
	
	; now see if we finished doing all of the bits
	dec.b	r0h
	bne		_TASOBITLOOP:8			; nope still more bits to output
	bset.b	#5,@PCR8:8			; make sure set to output, basic should have done earlier!
;	bclr.b	#5, @PDR8:8			; Ok lets try setting one LED ON to debug
	
	; now we need to output the Stop bit
	; need to do the start bit, for the heck of it we will wait until we get a counter match/reset before we do anything
	; The value depends on if we are inverted or not...
	or.b	r3h, r3h
	beq		_TASOSTNORMAL:8			; OK will start up normal
	bset.b	r3l,@er2				; Inverted mode start high
	jmp		_TSOSTOPDELAY:8			; continue to setup pointers to buffers
_TASOSTNORMAL:
	bclr.b	r3l, @er2				; this sets the IO line to low...
_TSOSTOPDELAY:
	bld.b	#6, @TCSRV:8			; see if we had a timer cycle go through
	bcc		#_TSOSTOPDELAY:8		; nope
	bclr.b	#6, @TCSRV:8			; clear out the status

	; now jump up to see if there are any more bytes left to output
	dec.w	#1,e3						; decrement the count and go back up to check to see if we are done
	jmp		_TASOBYTELOOP:8
		

_TASODONE:	
; transistion back to basic	
return

So would this serial out code be better than using the serout command built in? Seems to me this code should replace the current serout backend code!

As I think I mentioned this is a work in progress. Would it be better to use than the builtin serout code? That is hard to say. A lot of work went into writing and optimizing the normal serout code. Currently it is better than my code for making sure each of the pulses of the exact width that is needed. Also it handles a wider range of baud ranges than I currently support. For example my code will not work properly at 300 baud. Of course probably no one uses that low of value any more.

Where this gets for more interesting is when you have background interrupts going on. For example if you have a simplist TimerOverflow interrupt. That simply adds 1 to a variable each time an overflow happens, it can cause problems. Why? This is because the current code is very good at counting all of the instructions it executes in it’s own loops the exact number of times to come up with the right pulse width. But: it does not take into account all of the instructions that were robbed from it while processing an interrupt.

The simplist assembly language handler would, push a register, load current value, increment, store, pop, return from interrupt, which would take approximately 60 clock cycles in assembly code. The Basic version would take quite a bit more at a minimum I think it pushes all 8 registers and then restores them, which adds something like: 148 clocks. Now at 9600 baud, pulse is about 1664 clock cycles, so it can probably handle a low frequency hit of 60 cycles, but if this happens a lot, this corrupts the output. As you try to serialize at higher speeds like for example 11520, which we like to do to the SSC-32, we only have something like 136 clocs per pulse and needles to say the basic pulse out is probably not going to work with even the simplist interrupt handlers.

The approach I am using is to try to synchronize the output using a hardware timer instead of counting clocks. Again currently I am not as precise as the current code, but if some time gets robbed from me, I easily will resynch. It appears to be working at lower baud rates, but I still think there are issues with it for doing the higher baud rate like 11520, so I think I am going to try to have some of the work done at a timer interrupt level. My first attempt will be to only do the IO transistions for only one byte up there and leave most of the code at the main loop. What this will do is if the other interrupt handlers are cooperative (the enable interrupts during their processing), then I should be able to get a pretty fast clean output. But now I just need to sit down and try it out…

Kurt

P.S. - Sorry for this long winded answer.

I now have a version that is using the Compare A interrupt on TimerV, which appears to be working some.

As I was asked if it could work at the baud rate 115200, I thought I would go ahead and try. Here is an image showing it. The first line is using my function at 9600 baud, the second line is using my function at 115200, the third line is with Serout at 115200 and the 4th line is with HSEROUT at 115200. It was interesting that the logic Analyzer was able to decode mine properly but not serout or hserout.

http://i416.photobucket.com/albums/pp245/Kurts_Robots/TASerout115200-nointerrupts.png.

My guess is that the pulse widths are slightly different between the three different implementations. The baud rate of 115200 generates a baud mode of 17, which I believe is 8.5uS pulses. Currently mine is not 100% consistent, but averages about 8.75uS, where as serout appears to average somewhere beteen 8 and 8.25uS and the hserout appears to be a consistent 8uS.

I have found that even a simple interrupt handler like:
TimerA:
x = x + 1
resume.

Can cause problems at this speed, I have not verified if the ASM version does as well, probably not as much. Also I have not tried making the handler more friendly for other interrupts, by enabling interrupts. ie.
TimerA:
enable
x = x + 1
resume

I am not sure if anyone is interested so for now I won’t post the code.

Kurt