[GreenKeys] low cost general purpose PIC controller board

Tom Jennings [email protected]
Thu, 30 May 2002 11:09:22 -0700 (PDT)


My PIC code that does ascii/tty is contained below. It'll work on
a PIC16C84 or F84.

Relays aren't much use, however. For motor AC control they will
hash too much, even if they are rated at 110VAC. You can get
solid-state relays for $9 new from Digikey, or less surplus. For
loop control they're probably too slow, and transistors are cheap,
and don't hash either...

For output-only, I run the loop at abuot 14VDC, and it works fine,
meaning no detectable print errors in >> 20 feet of single-spaced
text at 60wpm.

The code below does ASCII to ITA2 translation, 2400 in, 60wpm out.
It will drive a solid-state relay, turning the motor on (with
a suitable startup delay) to print and off after 20+ seconds
of idle. It drives the bell with a separate output bit, so you
don't need a stunt box (and will ring the bell without starting
the motor...) After 2X idle time, it even turns the loop magnet
current off.

To drive the loop and the bell I use a DS2003 high-current driver
chip, there are lots of them out there, Toshiba makes a better
one, they are abuot $1 each.  They have commutating diodes built
in. I just parallel up a couple of them, run the loop between
the driver and unregulated DC and that's it (the driver chips
are open collector).

Serial input is trivial: a 10K resistor from the RxD input pin
to +5V, and 100 ohm in series to the DB-25 connector. Ditto RTS.

Note that this code REQUIRES strictly correct RTC/CTS handling
to work! Windows will *NOT* work -- it has a brain-dead serial
driver. If you are familiar with Fido type BBS code, FOSSIL
drivers work fine. Back when I used WIndows/DOS for this stuff,
I made a special cable that ran the PIC's CTS pin to CD (pin 8)
on the PC, and wrote a program that looked for CD (CTS) high
before issuing a character. Without a large programming effort
to implement an interrupt driven software UART on the PIC this
is required (besides, there's not enough memory in a PIC to do
any useful buffering).

For historical reasons, I write PIC code in "PIC BASIC", using
the microEngineering Labs compiler, which I run in a DOSEMU
window on linux, but win/dos is fine. For even poorer reasons I
use MicroMint PIC boards but that's stupid and I'm moving away
from them, but they are really just plain un-augmented PICs,
so this code will run as-is on any PIC.

The I/O pin assignments are in the code.

One last thing: this code also supports a
block-oriented data stream for my Story Teller system
(http://wps.com/products/Story-Teller), you can ignore that code,
or remove it. It looks at an input pin (which I have a switch on)
to determine if it should act like a tty or a File Type A machine.

This software is copyright me, Tom Jennings, but you can use
it for any non-commercial purpose. If anyone has any particular
legal needs I'll turn this into GNU licensing, but I'm too lazy
to do it without prodding.





' !!!! NOTE: teletype keyboard code commented out, ran out of debug time.

'
' WPS Model 2 Teletype Translator.
'
' Supports simple "TTY mode" ASCII to ITA2 conversion, and WPS record
' format "A", determined by reading a toggle switch at power up.  Handles
' TTY motor and selector-magnet power control (eg. idle and explicit
' power off).
' 
' Control codes supported: CR, LF, BEL, SO. BELL rings the bell via direct
' output bit.  SO turns the motor off immediately, rather than waiting for
' the timeout (currently 30 seconds).  When the switch is ON (towards the
' right), then record format "A" is expected; see the specification for
' details. The encapsulated data is treated the same as in TTY mode.
' 
' NOTE: the loop voltage is too low for proper loop current flow, so local
' keyboard characters are not formed well enough for the printer to print
' -- though the software picks them up OK. The loop should run with 100VDC
' and a 2K resistor for proper waveform.
' 
' mru 22 Apr 2002
'	Added motor-off timer call to file mode state 2+.
' 	Shortened motor-off timer to 20 sec.
'
' mru 10 Mar 1999
'	Using new serinp routine to fix many dropped chars 
'	(not the fault of PTR). Motor-off idle timer is only run 
'	in (1) teletype mode or (2) file mode waiting for next
'	record start (eg. doesn't run in file mode STX/data/ETX section).
'
' mru 9 Mar 1999
' 	Tried to run 9600; failed. Don't know why. Revert to 2400.
'
' mru 8 Mar 1999
'	Added SO/explicit motor off, and removed a debug serout!
' 
' mru 25 Feb 1999
'	Built new hardware, used DS2003 as driver instead of 
'	discrete transistors. Added WPS file system (record 
'	type A) compatibility, and a mode switch. Added BELL driver.
'
' mru 11 oct 98
'	Driven from Win95 Generic Printer, every other 
'	character is lost; 1st, 3rd, etc prints, 2nd, 4th, etc 
'	lost. It works fine, no lost chars, perfect printing, 
'	with a FOSSIL driver.  Some Win9x thing (serial? Win 
'	driver? Generic Printer?) is not handling CTS properly.
'	Tried every thing I could think of; pulsing CTS, true 
'	for duration of serin, etc; checking for 2nd char 
'	following. No avail.
'
' mru 8 oct 98
'	- Shortened startup code (pins= instead of high ...) etc. 
'	Delay after setup of rs232 line so clean start bit on power-up.
'
' mru 6 Oct 98
'	- clear idle timer when TTY keyboard character received
'	- Fixed: there is only one FIGS/LTRS case; in and out 
'	are not separate. The Model 28 is *half duplex*; FIGS/LTRS 
'	from either source affects the Model 28. Hence only
'	one state machine is needed.  
'	- added Control-E idle/off control
'
' mru 27 Sep 98
'	added SSR and power control; meta commands. (meta speed 
'	commands do not work!)
'
' written 4 Aug 98
' Copyright Tom Jennings, 1998, 1999, 2000, 2001, 2002.
'
'
' ASCII(RS-232) to/from ITA2(60mA current loop) interface and translator.
' Based upon a Model 01 CPU-I/O card.
'
' See p 30.34, 1998 ARRL Handbook for ITA2 and speed info.
'
'	DATA	STOP	SPEED (WPM)
'	22mS	26	60
'	17	21	75	(UNTESTED)
'	13	16	100	(UNTESTED)
'

symbol	RXSPEED= N2400			' RS-232 serial line speed
symbol	TXSPEED= N2400			' RS-232 serial line speed

' ASCII characters
symbol	NUL= 0
symbol	SOH= 1
symbol	STX= 2
symbol	ETX= 3
symbol	BEL= 7
symbol	LF= 10				' ASCII line feed
symbol	CR= 13				' ASCII carriage return
symbol	SO= 14				' ASCII SO: turn motor off immediately
symbol	SI= 15				' ASCII SI: delay 2 seconds 

' ITA2
symbol	FIGS= 27			' ITA2 FIGS char and state value
symbol	LTRS= 31			' ITA2 LTRS char and state value

symbol	DATAP= 21			' ITA2 output, data bit width
symbol	STOPP= 33			' ITA2 output, stop bit width
symbol	BSKIPP= 33			' 1.5 * DATAP (start bit + 1/2 data bit)

' Teletype AC motor timer.
symbol	TIMEOUT1= 2			' idle motor-off timer fudge factor
symbol	TIMEOUT=44000			' for approx. 10 seconds in geta()

symbol	TTYMODE= 1			' MODE switch off: TTY mode 
symbol	FILEMODE= 0			' MODE switch on: File type A mode

symbol	poweron= bit0			' (b0.0) mimics PWR pin
symbol	indata= bit1			' (b0.1) STX..ETX state machine

symbol	astate= b1			' ASCII state machine
symbol	case= b2			' (char) FIGS or LTRS character
symbol	n= b3
symbol	i= b4
symbol	c= b5
symbol	j= b6
symbol	serflag= b7			' (unsigned) serinp() control/status
'symbol	unused2= b8
symbol	timer2= b9			' (unsigned) # of timer1's for motor-off
symbol	timer1= w10			' (b20, b21) timer for AC power control

symbol	BOUT= 0				' ITA2 serial output (1=idle)
symbol	BOUTpin= pin0			' ITA2 serial output
symbol	BIN= 1				' ITA2 serial input
symbol	BINpin= pin1			' ITA2 serial input
symbol	PWR= pin2			' solid state relay, teletype AC power
symbol	BELL= 3				' Teletype bell
symbol	BELLpin= pin3			' Teletype bell
symbol	MODEpin= pin4			' TTY/WPS A mode
symbol	CTS= 5				' ASCII Clear To Send
symbol	CTSpin= pin5			' ASCII Clear To Send
symbol	TXD= 6				' ASCII serial output
symbol	TXDpin= pin6			' ASCII serial output
symbol	RXD= 7				' ASCII serial input
symbol	RXDpin= pin7			' ASCII serial input

symbol	DBUG= 3					' 'scope trigger 


'              76543210
	dirs= %01101101				' ins and outs...
	pins= %00000101				' BOUT high, TXD high, CTS low,
	poweron= 0				' assume TTY power on,
	serout TXD, TXSPEED, (CR, LF)		' chars to the ASCII port,
	serflag= 1				' force serial init

	case= FIGS				' powers up in FIGS state
	c= CR : gosub AtoB			' print on new line
	c= LF : gosub AtoB
	c= BEL: gosub AtoB

	if MODEpin= FILEMODE then fm1		' if TTY mode,

' TELETYPE mode
tt0:	gosub timer				' do motor off timer,
	call serinp : if serflag = 0 then tt0a
	gosub AtoB 				' output it,

'
' This isn't working, and I have run out of debug time.
'
tt0a:	goto tt0
'tt0a:	if poweron = 0 then tt0			' if loop current on,
	if BINpin = 1 then tt0			' and TTY keyboard activity,
	gosub inITA2				' read it,
	gosub BtoA				' translate and output it,
	goto tt0				' repeat.

' FILE mode
fm1:	gosub timer
	call serinp : if serflag = 0 then fm1	' wait for SOH,
	if c <> SOH then fm1			' 
fm1a:	gosub timer
	call serinp : if serflag = 0 then fm1a	' wait for 'A',
	if c <> 65 then fm1

fm2:	indata= 0				' no STX yet,
fm2a:	call serinp : if serflag = 0 then fm2a	' process char stream,
	 ' NUL:restart 1:eh 2:STX 3:ETX 4:EOT
	branch c, (fm1, fm2a, fm2c, fm2, fm1)
fm2b:	if indata = 0 then fm2a			' wait for STX, else fall through,
fm2c:	indata= 1				' got STX, (AtoB will ignore it),
	gosub AtoB				' output data character,
	goto fm2a				' repeat.

'
' Count down the motor timer.
'
timer:	timer1= timer1 - 1 			' count down motor timer,
	if timer1 <> 0 then ret			' exit if not zero yet.
	timer1= TIMEOUT
	timer2= timer2 - 1			' decrement number of ten-sec
	if timer2 <> 0 then ret			' intervals
'
' Turn motor off.
'
off:	timer1= TIMEOUT				' reset timer,
	timer2= TIMEOUT1
	if poweron = 1 then of1			' if motor AC already off,
	BOUTpin= 0				' turn off magnet current,
of1:	PWR= 0 : poweron= 0			' turn off AC motor,
ret:	return

'
' Convert ASCII character C to ITA2 character N. If the new character is not
' in the current case, issue the right case character. The table does all
' the work; it is the entire US TTY ITA2 character set in ASCII order in the
' lower six bits; the upper two bits contain the case the character resides
' in: $80=LTRS, $40=FIGS, 0=both (eg. CR, etc). $ff=unprintable character.
' 
' US TTY, type basket RS. Note that we make the following mappings:
'
'	ASCII	ITA
'	!	.
'	+	WRU (modified basket)
'
'	0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
'00	$ff,$ff,$ff,$ff,$ff,$ff,$ff,$05,$ff,$ff,$02,$ff,$ff,$08,$ff,$ff,
'16	$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,
'32	$04,$5c,$51,$54,$49,$04,$5a,$4b,$4f,$52,$04,$09,$4c,$43,$5c,$5d,
'48	$56,$57,$53,$41,$4a,$50,$55,$47,$46,$58,$4e,$5e,$04,$04,$04,$59,
'64	$04,$83,$99,$8e,$89,$81,$8d,$9a,$94,$86,$8b,$8f,$92,$9c,$8c,$98,
'80	$96,$97,$8a,$85,$90,$87,$9e,$93,$9d,$95,$91,$04,$04,$04,$04,$04,
'
' Keep the above table as master, edit into nasty lookup() command.
'
' Modifies N, C, J.
'
AtoB:	if c = SO then off			' control char SO: motor off
	if c <> BEL then ab0			' control char BEL: ding-a-ling
	pulsout BELL, 2500			' bell has own solenoid,
	pause 200				' let it ringgggggggg...
	return
'
' Process the character; convert to upper case, determine FIGS or LTRS 
' case from the table; if different than the current case, issue 
' case character first.
'
ab0:	if c < 96 then ab1			' if lower case,
	c= c - 32				' force to upper case
ab1:	lookup c, ($ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $02, $ff, $ff, $08, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff, $04, $4d, $51, $54, $49, $04, $5a, $4b, $4f, $52, $04, $04, $4c, $43, $5c, $5d, $56, $57, $53, $41, $4a, $50, $55, $47, $46, $58, $4e, $5e, $04, $04, $04, $59, $04, $83, $99, $8e, $89, $81, $8d, $9a, $94, $86, $8b, $8f, $92, $9c, $8c, $98, $96, $97, $8a, $85, $90, $87, $9e, $93, $9d, $95, $91, $04, $04, $04, $04, $04), c
	if c = $FF then ret			' if unprintable, done.
	if poweron = 1 then ab1a		' else if TTY power off...
'
' The Teletype is turned off; turn it on, and issue the correct FIGS or
' LTRS command.  Issuing a NUL simply runs the print/keyboard mechanism for
' the case where a key was pressed while motor off or garbage was issued
' by a bouncing RS232 line (such as when a laptop sleeps, ahem). Reissuing
' the case character is probably worthless, except in the marginal case
' where the garbage looks like FIGS or LTRS.
' 
	BOUTpin= 1				' turn on magnet loop current,
	pause 100				' let settle
	PWR= 1 : poweron= 1			' turn on big AC motor,
	pause 750				' let motor start up,
	n= NUL : gosub outITA2			' send a NUL to clear,
	n= case : gosub outITA2			' restore FIGS/LTRS case,

ab1a:	timer1= TIMEOUT				' continuously reset the timer,
	timer2= TIMEOUT1
	n= c / 64				' N= case indicator bits,
	c= c & $1F				' C= ITA2 character,
	if n = 0 then ab2			' if in both cases, output now,
	lookup n, (0, FIGS, LTRS, 0), n		' else gen case char 
	if n = 0 then ab2			' if in both cases, just output;
	if n = case then ab2			' or same case, just output;
	case= n : gosub outITA2			' else save, output case char,
ab2:	n= c 					' then output data character.

'
' Output the LS 5 bits of N to the teletype. 
' Modifies N, J.
'
outITA2:
	BOUTpin= 0					' do start bit
	pulsout DBUG, 4
	pause DATAP
	for j= 1 to 5					' five data bits,
		BOUTpin= n : n= n / 2			' assert data, LS bit first
		pulsout DBUG, 1
		pause DATAP				' generate data bit,
	next j
	BOUTpin= 1					' do stop bit
	pulsout DBUG, 1
	pause STOPP
	return

'
' Convert ITA2 character N to ASCII and output to the ASCII port. 
' This keeps track of LTRS/FIGS state. 
' Modifies N.
'
BtoA:	if n = FIGS then ba2			' handle FIGS & LTRS separately
	if n = LTRS then ba2
	if case = LTRS then ba1 : n= n + 32	' select LTRS or FIGS table
'                  FIGS                                                       LTRS
ba1:	lookup n, (0,"E",10,"A SIU",13,"DRJNFCKTZLWHYPQOBG",255,"MXV",255,    0,"3",10,"- ",7,"87",13,"$4',!:(5",34,")2#6019?&",255,"./;",255), n
	if n = 255 then ret			' if translatable,
	serout TXD, TXSPEED, (n)		' output to ASCII port,
	return
ba2:	case= n : n= 255 : return
'
' Read a ITA2 character into N. There are five data bits followed by one
' stop bit.  The start bit is now under way. We sample the middle of each
' bit period; therefore we delay initially 1-1/2 data bit times (skip start
' bit, half of 1st data bit), sample the data, then to read the next four
' data bits, delay one bit time and sample, four times.  After the last
' data bit, we delay until the end of the stop bit -- because the teletype
' is half-duplex, if we hurry up and read an ASCII character and output
' it before the key board character is complete, we'll generate garbage.
' 
' Modifies J, N.
' 
inITA2:
	timer1= TIMEOUT				' reset the timer,
	timer2= TIMEOUT1
	n= 0					' clear built-up character,
	pulsout DBUG, 4
	pause BSKIPP				' skip to middle of 1st data bit
	j= 1					' loop counter and bit value
ib1:		pulsout DBUG, 1
		if BINpin = 0 then ib2		' if data bit present,
		n= n + j			' add in weighted bit
ib2:		pause DATAP			' delay into next bit time,
		j= j + j			' increase bit weight
	if j < 32 then ib1			' do 5 times (eg. 2^^5 == 32)
	pause BSKIPP				' skip 2nd half of last data bit
	pulsout DBUG, 1
	return

END


ASM

;
; Poll for a serial character, returns serflag=1 and character in C
; and CTS 0 if one is present (eg. start bit visible on serial line). If
; serflag=1 on entry, CTS is set and the serin() call arguments are
; set up before the serin() call; if serflag=0 on entry then this is
; skipped, which allows for greater speed in tight loops IFF no other
; serial I/O is done between calls. Note that upon character receipt,
; if serflag is left set then serin() will be initialized properly on
; the first call.
;
_serinp	movf _serflag, f
	btfss Z				; if serflag set,
	goto __sp1			; go init.
	btfss portB, _RXD		; else if start bit not present,
	goto done			; exit now.

; Start bit has been asserted onto the serial line; read the
; character then turn off CTS and prepare for return.
__sp2	call serin@W			; read character,
	bcf portb, _CTS			; deassert CTS,
	movwf _c			; store W in C,
	movlw 1
	movwf _serflag			; set serflag
	goto done

; Assert CTS, and ready the args for the later serin() call.
__sp1	bsf portB, _CTS			; assert CTS,
	movlw _RXD			; Rx pin number,
	call BP@Pin
	movlw _RXSPEED			; port speed,
	movwf GOP			; (local to ass'y code?)
	clrf _serflag			; set serflag false,
	goto done



ENDASM