[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