Bit Bang Serial 19200 Baud Receive
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>
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.