Skip to content

Bit Bang Serial 19200 Baud Receive

March 22, 2016

It works, but ick.  I’m doing this because I started it but I’m doubtful it’s going to work well enough to use.  Adjusting the timing isn’t that hard but it occurs to me that, since this is unbuffered, I would have to deal with each character received within a very  short time or put some pacing on the sender which might obviate the benefit.  Also, I need to add a timeout which complicates things further.  Even testing it seems tricky.

On the left below is the output from a simple test routine, it prints a ! then calls bbrecv and prints what it gets.  The T’s indicate a timeout but you can see me input 1,2,3.  On the right is the trace of my reads of the serial line.  The RX signal has dot’s in it where the logic analyzer is sampling the data bits.  My reads line up almost perfectly with them.  There is an extra read with a short gap that happens pretty well every time that is something to do with the monitor responding to an interrupt.  I tried disabling interrupts altogether and the extra read went away.

 

My code is below for the bbrecv routine and the test program.  Again, thanks and apologies to Jack Ganssle. http://www.ganssle.com/articles/auart.htm
Also thanks to http://wikiti.brandonw.net/index.php?title=Z80_Instruction_Set for the z80 instruction timing tables.

;
;  CIN - input a character to C within 1 second.
;  trying for 19.2 kbps, bit time is 52 uS, 208 cycles
;  character is returned in C with carry set if timeout occurs (also returns 'T')
;
cin:	di			;disable interrupts for timing
	ld	b,8		; bit count
	ld	hl,186*256	; timeout limit
ci1:	in	a,(0x40) 	; 11 read serial line
	and	0x80		; 7  isolate serial bit
	jp	z,gotit		; 10 wait till serial data comes
	in	a,(0x40) 	; 11 read serial line
	and	0x80		; 7  isolate serial bit
	jp	z,gotit		; 10 wait till serial data comes

	dec	l		; 4  check timeout limit
	jp	nz,ci1		; 10 go test again
	dec	h		;  4 check rest of to limit
	jp	nz,ci1		; 10 go test again
	jp	cto		; no character within tolimit*(84)/4 - approx 1s
gotit:	;we are now at the beginning of the start bit plus maybe 28 to 45 cycles

	ld	l,3		;  7 now need to wait 104 cycles - (45+7) already = 52 cycles
ci2:	dec	l		;  4
	jp	nz,ci2		; 10 wait till middle of start bit (3*14 is 42 more)
	jp	cnxt		; waste an extra 10 cycles (+10 makes 45+7+42+10=104)
cnxt:

ci3:	ld	l,14		;  7 next bit in 208-7 cycles, loop is 14, count is 14
ci4:	dec	l		;  4
	jp	nz,ci4		; 10 now wait one entire bit time
	nop			;  4 so total delay 7+14*14+4=207

	in	a,(0x40)	; 11 read serial character
	rla			;  4 shift into carry bit
	ld	a,c		;  4 this is where we assemble char
	rra			;  4 rotate it into the character from carry
	ld	c,a		;  4
	dec	b		;  4 dec bit count
	jp	z,ci5		; 10 j if done all bits (41 cycles to here)
	ld	l,10		;  7 next bit is in 208-68 cycles, loop above is 14, count is 10
	jp	jpci4		; 10 waste 10 cycles to balance out
jpci4:	jp	ci4		; 10 do next bit (68 cycles here!)
ci5:	ld	l,c		; return location per fastcall protocol

	or	a		; clear carry flag for success
	ei			; enable interrupts
	ret

cto:	ld	l,'T'		; failure is timeout
	scf			; set carry flag to show timeout
	ei			; enable interrupts
	ret

/*
   19.2kbps serial sending routine
   Thanks and apologies to Jack Ganssle
*/
#include <olduino.h>
#include <stdio.h>
void  bbsend(char c) __z88dk_fastcall{
	__asm
	;
	; Output the character in L
	;
			di	;disable interrupts for timing
			ld		c,l				; move input to c
			ld		hl,#0x0909		;preload bit time constants
	cout:   ld      b,10            ; # bits to send
	                                ; (start, 8 data, 2 stop)
	        xor     a               ; clear carry for start bit
	co1:    jp      nc,cc1          ; if carry, will set line high
			ld		a,0xFF			;set the line high
			out		(0x40),a
	        jp      cc2
	cc1:	ld		a,0x7F			; set serial line low
			out		(0x40),a		;set the line low
	        jp      cc2             ; idle; balance # cycles with those
	                                ; from setting output high
	cc2:    ld      l,h	     		; times per bit
	co2:    dec     l
	        jp      nz,co2          ; idle for most of one bit time
	        nop
	        nop
	        nop			          	; fill out bit time
	        scf                     ; set carry high for next bit
	        ld      a,c             ; a=character
	        rra                     ; shift it into the carry
	        ld      c,a
	        dec     b               ; --bit count
	        jp      nz,co1          ; send entire character
			ei	;enable interrupts
	        ret
	__endasm;
	c;
}
unsigned char bbrecv() __z88dk_fastcall{
	__asm
#include "bbrecv.inc"
	__endasm;
	return 'K';	//keep compiler happy
}
void main()
{
	unsigned char x;
	unsigned int j;
	while(1){
		delay(1000);
		bbsend('!');
		x=bbrecv();
		bbsend(x);
		bbsend(' ');
	}
}
#include <olduino.c>

From → Olduino/Z

One Comment
  1. John Payson permalink

    A useful trick I’ve found is to write a “receive and send” routine which waits for an incoming character and then transmits a character at the same time as the incoming character is received (it outputs a start just under half a bit time after it sees one, and then at one-bit-time intervals it sends a data bit and receives one. Inter-character marking starts halfway through the first received stop bit. Reliable operation at higher baud rates would require that the remote device use two stop bits, but 38400-N-2 will be much faster than 19200-N-1 while leaving more inter-character time available to the application.

Leave a comment