;    Filename:      palm3x.asm
;
;    Author:        Chuck Olson,  WB9KZY
;    Company:       Jackson Harbor Press
;                   jacksonharbor@.att.net
;                   http://jacksonharbor.home.att.net/
;
;
; palm3x.asm is a VERY basic Morse code keyboard program that allows a
;       PIC 16f628a to get input from a Palm portable keyboard (for the
;       Palm m100, III, VII series of Palm handhelds) and transmit the
;       keyed in characters as Morse code.  
;
;       The code speed can be set from 5 to 51 wpm by pressing the ctrl 
;       switch and then entering the code speed.  The code speed is  
;       stored in EEPROM and is initialized to 16 WPM.  If the code
;       speed is changed, the new speed is written to EEPROM and it
;       will be loaded at the next powerup sequence.
;
;       Key presses (on make, release is ignored except for the ctrl key) 
;       are buffered in a 92 character first in - first out buffer.  
;
;       The sidetone is fixed at about 600 Hz, see the labels tone2 and
;       tone2a to change the sidetone to about 750 Hz.  Other frequencies
;       may be programmed by consulting PWM section in the 16F628a manual.
;
;       Mouser #: 665-AT-142 is an excellent though large, piezo speaker
;       suitable for use with this project.
;
;       If a Palm keyboard is attached, the PIC will send:  FB after
;       power is turned on.  Note that the FB is sent through the
;       output transistor.  If no keyboard is attached, no FB is sent.
;
;       All letters and numbers are supported and also 5 special 
;       characters are supported:
;
;         key   character in Morse
;         ---   ------------------
;          ,    --**--
;          .    *-*-*-
;          =    -***-
;          \    -**-*
;          /    **--**   (unshifted /? key)
;
;       More characters can be added OR the keys to which the above
;       are associated with can be changed by editing the numbers table.
;
; Ideas for possible features that the user might add:
;       memory play/record, use date, phone, todo, memo keys on right ?
;       beacon mode, for CQ sends and regular beacons
;       external serial eeprom for larger/more memories
;       command mode to allow non-transmitted characters
;       command sidetone, to distinguish between normal sending and commands
;       variable sidetone, add a routine to set/save the sidetone frequency
;       higher speeds ?
;       add kill to flush buffer and stop memory play
;       add sidetone OFF mode, for use with rigs that have sidetone
;       LCD display of the sent code ?
;       add last character delete (use back space) for buffer
;       weight control
;       Farnsworth lower speeds
;       change program to imitate a PC keyboard, allows hooking the Palm
;           keyboard to an existing PC keyboard based Morse keyboard project.
;       buffer "fullness" status LEDs - turn on when x bytes are left ?
;       try other brands of keyboard for the Palm m100, III and VII series ?
;       pot speed control ?
;       keyer functionality
;
; 16F628a Pinout: (see the file: palmkeybd.pdf for a full schematic)
;
;       pin #   name    connection
;       -----   -----   ----------
;        1      RA2     
;        2      RA3     
;        3      RA4     
;        4      RA5     10k pullup to +3V
;        5      VSS     Ground
;        6      RB0     
;        7      RB1     RX - rs232 input from Palm keyboard thru 2n7000 inv
;        8      RB2     
;        9      RB3     sidetone output to Piezo speaker
;       10      RB4     
;       11      RB5    
;       12      RB6     
;       13      RB7     hotsync input 
;       14      VDD     +3V supply
;       15      osc1    4 Mhz crystal plus 27pF capacitor to ground
;       16      osc2    4 Mhz crystal plus 27pF capacitor to ground
;       17      RA0     RTS output to keyboard
;       18      RA1     Morse keyed output to 2n7000 keying transistor
;        
;
; List of Whys (in other words, why did he do it like that ?):
;
;       The 2n7000 inverter used to feed pin 7 was added because a
;       hardware uart generally expects that the data has been
;       inverted by some interface hardware (such as the MAX232 chip).
;       The 10k pulldown resistor on the 2n7000 gate is required.
;       A software uart wouldn't need the 2n7000 MOSFET.
;
;       The Hotsync (HS) signal from the keyboard is connected to
;       RB7 to allow the easy use of the PORT B interrupt on change
;       to wake up the PIC, assert the RTS pin and to jump to the 
;       serial input interrupt routine.  Subsequent serial inputs 
;       can use serial input (uart) interrupt instead of the hotsync 
;       blip if needed.
;
;       A 4 MHz crystal was used, a 4 MHz resonator could also be used
;       but an RC clock may not have the accuracy or stability needed
;       for a 9600 baud serial link.
;
;       A 3 volt supply was used to keep the power usage low, below
;       1 ma total for both the keyboard and PIC, much less ( < 20 uA)
;       when they are both sleeping (clock stopped).  The circuit may
;       work at voltages up to 5.5 volts but hasn't been tried by me.
;
;       The RS-232 circuit on the interface board was not used because
;       this circuit can use more power than the PIC/Palm Keyboard and
;       it also is NOT required as the Palm keyboard does work with 
;       normal 0 to 3 volt levels.
;
;       The sidetone is generated with the PWM circuitry - this allows
;       the user to easily perform other tasks while the sidetone is on.  
;
;       The dit and dah timing is performed using timer 1.  The
;       interrupt flag for timer 1 is polled rather than enabling
;       the timer 1 interrupt.  This makes for a slightly simpler 
;       interrupt routine.  Hey, I was taught microcomputers by
;       a guy who said: "never use interrupts !" (this was in the
;       days of the Intel 8080) so consider me brave for using
;       interrupts at all ;)
        

        
        list      p=16F628a            ; list directive to define processor
        #include <p16F628a.inc>        ; processor specific variable definitions
 
 __FUSES _PWRTE_OFF & _CP_OFF & _WDT_OFF & _XT_OSC & _LVP_OFF & _MCLRE_ON & _BODEN_OFF
 
; powerup timer off - still not sure why
; copy protect is off
; watchdog timer is off 
; the oscillator uses an external 4 MHz crystal or resonator
; turn off the low voltage programming capability - RB4 can be used as I/O
; turn on the mclr pin 
; turn off the brown out detect (boden) as operation at 3V anticipated



;Variable locations in bank0
        CBLOCK  0x20
        temp:   1       ;used in bin2dec
        char:   1       ;holder for character data - used in playchr, strtbit
        size:   1       ;char size counter - used in playchr, strtbit
        third:  1       ;holder for number of dit length spaces => ditsp
        temp2:  1       ;holder for element length => outdit, outdah, ditsp
        sammo:  1       ;temp location for speed2
        tonef2: 1       ;temp location for tone frequency calcs
        spd:    1       ;current Morse code speed
        tens:   1       ;temp location used in playchr
        EEADDR: 1       ; used in internal eeprom routines                
        recv:   1       ;pointer to next byte to be read                
        xmit:   1       ;pointer to next byte to be sent
        inbyte: 1       ;used in interrupt routine
        keycod: 1       ;keycode from keyboard or buffer
        downcnt: 1      ;down counter for main loop prior to sleeping
        flags:  1       ;ram flags for various funtions
        newsped: 1      ;new speed holder for spedset
        digit:  1       ;digit holder for spedset
        eedatb0: 1      ;eeprom data holder byte for write (wreep)
                ENDC
        
        
; note that placing saves, savef and savew at the top of bank 0
;       enables easy access in any of the other banks
;       these three are used in the interrupt routine to hold the
;       calling state of the PIC during the actual interrupt operations
        CBLOCK  0x7D
        saves:  1       ;interrupt temp storage
        savef:  1       ;interrupt temp storage
        savew:  1       ;interrupt temp storage
                ENDC


; the keyboard buffer is placed into bank 1 where it is easily
;       aceessed using indirection even if bank 0 is selected
bufstrt equ     H'A0'   ;start of character buffer    A0 to FC = 92 chars
bufstop equ     H'FC'   ;end of buffer


                                                                   
; bit definitions
w       equ     0       ;destination is w register
f       equ     1       ;destination is NOT the w register
zero    equ     2       ;zero bit in STATUS register
carry   equ     0       ;carry bit
keypr   equ     0       ;key press bit



;PORTA bit definitions:
owt     equ     1       ;porta, bit 1 is the keyed output
RTS     equ     0       ;port a bit 0 is the RTS output to the palm 



;PORTB bit definitions:
CCP1    equ     3       ;output to piezo (tone - PWM)
hs      equ     7       ;hotsync input


        
        ORG     0x000             ; processor reset vector
        bcf     STATUS,RP0      ;select bank 0 
        bcf     STATUS,RP1      ;              for direct addressing
        bcf     STATUS,IRP      ;select bank 0 and 1 for indirect addressing
        goto    init            ; start here by initializing the PIC



        ORG     0x004             ; interrupt vector location
        goto    rupt            ;jump into the interrupt routine
                                ;why not have the routine in-line ?
                                ;  to be sure to allow the tables 
                                ;       to exist in page 0
                                ;  the int. routine doesn't need page 0
                                ;  table usage is simpler in page 0



; speed - a lookup table for the code speed.  it translate the speed
;       setting to a dit length.  The table entry is two bytes
;       wide and represents a count loaded into timer 1 - when the count
;       rolls over from FFFF to 0, the element is finished - so the
;       table entry is basically 65536-element length.  At 4 MHz clock,
;       each tick of timer1 is 1/125000 or .008 ms or 8 microseconds
speed   addwf   PCL,f   ;compute the jump value
        retlw   d'138'   ; 0 wpm    (actually 5 wpm)
        retlw   d'208'
        retlw   d'138'   ; 1 wpm    (actually 5 wpm)
        retlw   d'208'
        retlw   d'138'   ; 2 wpm    (actually 5 wpm)
        retlw   d'208'
        retlw   d'138'   ; 3 wpm    (actually 5 wpm)
        retlw   d'208'
        retlw   d'138'   ; 4 wpm    (actually 5 wpm)
        retlw   d'208'
        retlw   d'138'   ; 5 wpm    
        retlw   d'208'
        retlw   d'158'   ; 6 wpm    
        retlw   d'88'
        retlw   d'172'   ; 7 wpm    
        retlw   d'75'
        retlw   d'182'   ; 8 wpm    
        retlw   d'194'
        retlw   d'190'   ; 9 wpm    
        retlw   d'229'
        retlw   d'197'   ; 10 wpm    
        retlw   d'104'
        retlw   d'202'   ; 11 wpm    
        retlw   d'188'
        retlw   d'207'   ; 12 wpm    
        retlw   d'44'
        retlw   d'210'   ; 13 wpm    
        retlw   d'238'
        retlw   d'214'   ; 14 wpm    
        retlw   d'38'
        retlw   d'216'   ; 15 wpm    
        retlw   d'240'
        retlw   d'219'   ; 16 wpm    
        retlw   d'97'
        retlw   d'221'   ; 17 wpm    
        retlw   d'136'
        retlw   d'223'   ; 18 wpm    
        retlw   d'115'
        retlw   d'225'   ; 19 wpm    
        retlw   d'41'
        retlw   d'226'   ; 20 wpm    
        retlw   d'180'
        retlw   d'228'   ; 21 wpm    
        retlw   d'25'
        retlw   d'229'   ; 22 wpm    
        retlw   d'94'
        retlw   d'230'   ; 23 wpm    
        retlw   d'134'
        retlw   d'231'   ; 24 wpm    
        retlw   d'150'
        retlw   d'232'   ; 25 wpm    
        retlw   d'144'
        retlw   d'233'   ; 26 wpm    
        retlw   d'119'
        retlw   d'234'   ; 27 wpm    
        retlw   d'76'
        retlw   d'235'   ; 28 wpm    
        retlw   d'19'
        retlw   d'235'   ; 29 wpm    
        retlw   d'204'
        retlw   d'236'   ; 30 wpm    
        retlw   d'120'
        retlw   d'237'   ; 31 wpm    
        retlw   d'25'
        retlw   d'237'   ; 32 wpm    
        retlw   d'176'
        retlw   d'238'   ; 33 wpm    
        retlw   d'63'
        retlw   d'238'   ; 34 wpm    
        retlw   d'196'
        retlw   d'239'   ; 35 wpm    
        retlw   d'66'
        retlw   d'239'   ; 36 wpm    
        retlw   d'185'
        retlw   d'240'   ; 37 wpm    
        retlw   d'42'
        retlw   d'240'   ; 38 wpm    
        retlw   d'149'
        retlw   d'240'   ; 39 wpm    
        retlw   d'250'
        retlw   d'241'   ; 40 wpm    
        retlw   d'90'
        retlw   d'241'   ; 41 wpm    
        retlw   d'181'
        retlw   d'242'   ; 42 wpm    
        retlw   d'13'
        retlw   d'242'   ; 43 wpm    
        retlw   d'96'
        retlw   d'242'   ; 44 wpm    
        retlw   d'175'
        retlw   d'242'   ; 45 wpm    
        retlw   d'251'
        retlw   d'243'   ; 46 wpm    
        retlw   d'67'
        retlw   d'243'   ; 47 wpm    
        retlw   d'137'
        retlw   d'243'   ; 48 wpm    
        retlw   d'203'
        retlw   d'244'   ; 49 wpm    
        retlw   d'11'
        retlw   d'244'   ; 50 wpm    
        retlw   d'72'
        retlw   d'244'   ; 51 wpm    
        retlw   d'131'



; table of equivalent morse characters
; character structure from Dave Gwillym, KB2TQX
; right justified storage of the character:
;                                       dit = 0
;                                       dah = 1
; the leftmost 1 is a start bit - the byte is left shifted until a
;       1 is seen, then the dits and dahs are shifted out for a
;       total of 8 shifts - this holds most normal characters.
; note that the table position is what is stored in the eeprom
; 
; the exceptions to the above are non-morse characters which
; have a table entry of all zeroes AND the two space keys
; which have table entries of all ones.  The all zero and all
; one entries are either ignored or decoded by the calling routines.
;
;                            character  hex      table position
numbers addwf   PCL,f   ;compute the jump value
        ;                       ascii              Y  X    dec
        retlw   b'00101111'     ; 1              0000000   0
        retlw   b'00100111'     ; 2              0000001   1
        retlw   b'00100011'     ; 3              0000010   2
        retlw   b'00011100'     ; Z              0000011   3
        retlw   b'00100001'     ; 4              0000100   4
        retlw   b'00100000'     ; 5              0000101   5
        retlw   b'00110000'     ; 6              0000110   6
        retlw   b'00111000'     ; 7              0000111   7
        retlw   b'00000000'     ; command        0001000   8
        retlw   b'00011101'     ; Q              0001001   9
        retlw   b'00001011'     ; W              0001010  10
        retlw   b'00000010'     ; E              0001011  11
        retlw   b'00001010'     ; R              0001100  12
        retlw   b'00000011'     ; T              0001101  13
        retlw   b'00011011'     ; Y              0001110  14
        retlw   b'00000000'     ; tilde          0001111  15
        retlw   b'00011001'     ; X              0010000  16
        retlw   b'00000101'     ; A              0010001  17
        retlw   b'00001000'     ; S              0010010  18
        retlw   b'00001100'     ; D              0010011  19
        retlw   b'00010010'     ; F              0010100  20
        retlw   b'00001110'     ; G              0010101  21
        retlw   b'00010000'     ; H              0010110  22
        retlw   b'11111111'     ; space1         0010111  23
        retlw   b'00000000'     ; caps lock      0011000  24
        retlw   b'00000000'     ; tab            0011001  25
        retlw   b'00000000'     ; ctrl           0011010  26
        retlw   b'00000000'     ;                0011011  27
        retlw   b'00000000'     ;                0011100  28
        retlw   b'00000000'     ;                0011101  29
        retlw   b'00000000'     ;                0011110  30
        retlw   b'00000000'     ;                0011111  31
        retlw   b'00000000'     ;                0100000  32
        retlw   b'00000000'     ;                0100001  33
        retlw   b'00000000'     ; FN             0100010  34
        retlw   b'00000000'     ; alt            0100011  35
        retlw   b'00000000'     ;                0100100  36
        retlw   b'00000000'     ;                0100101  37
        retlw   b'00000000'     ;                0100110  38
        retlw   b'00000000'     ;                0100111  39
        retlw   b'00000000'     ;                0101000  40
        retlw   b'00000000'     ;                0101001  41
        retlw   b'00000000'     ;                0101010  42
        retlw   b'00000000'     ;                0101011  43
        retlw   b'00011010'     ; C              0101100  44
        retlw   b'00010001'     ; V              0101101  45
        retlw   b'00011000'     ; B              0101110  46
        retlw   b'00000110'     ; N              0101111  47
        retlw   b'00000000'     ; -              0110000  48
        retlw   b'00110001'     ; =              0110001  49
        retlw   b'00000000'     ; backspace      0110010  50
        retlw   b'00000000'     ; spec fn 1      0110011  51
        retlw   b'00111100'     ; 8              0110100  52
        retlw   b'00111110'     ; 9              0110101  53
        retlw   b'00111111'     ; 0              0110110  54
        retlw   b'11111111'     ; space2         0110111  55
        retlw   b'00000000'     ; [              0111000  56
        retlw   b'00000000'     ; ]              0111001  57
        retlw   b'00110010'     ; \              0111010  58
        retlw   b'00000000'     ; spec fn 2      0111011  59
        retlw   b'00001001'     ; U              0111100  60
        retlw   b'00000100'     ; I              0111101  61
        retlw   b'00001111'     ; O              0111110  62
        retlw   b'00010110'     ; P              0111111  63
        retlw   b'00000000'     ; '              1000000  64
        retlw   b'00000000'     ; enter          1000001  65
        retlw   b'00000000'     ; spec fn 3      1000010  66
        retlw   b'00000000'     ;                1000011  67
        retlw   b'00010111'     ; J              1000100  68            
        retlw   b'00001101'     ; K              1000101  69
        retlw   b'00010100'     ; L              1000110  70
        retlw   b'00000000'     ; ;              1000111  71
        retlw   b'01001100'     ; ? and /        1001000  72
        retlw   b'00000000'     ; scroll up      1001001  73
        retlw   b'00000000'     ; spec fn 4      1001010  74
        retlw   b'00000000'     ;                1001011  75
        retlw   b'00000111'     ; M              1001100  76
        retlw   b'01110011'     ; ,              1001101  77
        retlw   b'01010101'     ; .              1001110  78
        retlw   b'00000000'     ; DONE           1001111  79
        retlw   b'00000000'     ; DEL            1010000  80
        retlw   b'00000000'     ; left arrow     1010001  81
        retlw   b'00000000'     ; scroll down    1010010  82
        retlw   b'00000000'     ; right arrow    1010011  83
        retlw   b'00000000'     ;                1010100  84
        retlw   b'00000000'     ;                1010101  85
        retlw   b'00000000'     ;                1010110  86
        retlw   b'00000000'     ;                1010111  87
        retlw   b'00000000'     ; shift left     1011000  88
        retlw   b'00000000'     ; shift right    1011001  89
        retlw   b'00000000'     ;                1011010  90
        retlw   b'00000000'     ;                1011011  91
        retlw   b'00000000'     ;                1011100  92
        retlw   b'00000000'     ;                1011101  93
        retlw   b'00000000'     ;                1011110  94
        retlw   b'00000000'     ;                1011111  95
       


; rupt -  the interrupt routine - go here when the hotsync is pulsed
;               OR if the RCIF uart flag is set
;       this interrupt service routine will:
;
;       1) get the key code
;       2) convert the key code to Morse
;          a) if it's a ctrl key press, go and set the code speed
;       3) fill the circular buffer
;               two pointers are used
;               recv points to the next location to be filled with data
;               xmit points to the next location to be played
;       4) increment a buffer fill counter byte
;
; per an email from Steve Weber, (KD1JV):
;
; at powerup, zero the buffer - both pointers to start
;
; when you get a char from the keyboard (via interrupt)
;       look at byte pointed to by recv - if <> 0 then you've overflowed
;               if = 0 then it's OK, put the key code into the byte
;
; when you send a key code, write a zero into it's location
;       
;
rupt    ;save the status and w registers before proceeding to the int service
        movwf   savew           ;save the w register
        swapf   STATUS,w        ;load the status register into w
        movwf   saves           ;save the status register
        swapf   FSR,w           ;load the indirect address register
        movwf   savef           ;save the indirect address register
;        
; explicitly set the ram pointers to the base banks
        bcf     STATUS,RP0      ;be sure we are in bank 0 for direct 
        bcf     STATUS,RP1      ;be sure we are in bank 0 for direct
        bcf     STATUS,IRP      ;be sure we are using bank 0 & 1 for indirect
;
;  get a byte from the keyboard
        bsf     PORTA,RTS       ;raise RTS since hotsync has gone high, 
        call    in232           ;get the byte from the uart
;
; next, store that key code temporarily
        movwf   inbyte          ;save the key code in inbyte
        btfsc   inbyte,7        ;is bit 7 high ?
        goto    cbreak          ;yes, skip this char, it's a break
;
; is this a control key press ?
        sublw   b'00011010'     ;test for a 
        btfsc   STATUS,zero     ;       ctrl key press
        call    spedset         ;yes, do a speed set
;
; test the buffer, is it full ?
bufit   movf    recv,w          ;load up the recv pointer
        movwf   FSR             ;put recv pointer into the indirect address
        movf    INDF,w          ;get the contents of the buffer
        btfss   STATUS,zero     ;is this byte a zero?
        goto    cbreak          ;  no, bail
;
;  store the key code in the circular buffer and test the next buffer loc.
storchr movlw   d'96'           ;highest possible address in numbers table + 1
        subwf   inbyte,w        ;perform inbyte - 96
        btfsc   STATUS,carry    ;test the carry bit
        goto    cbreak          ;inbyte is either 96 or more, skip this keycode
        movf    inbyte,w        ;load up the current key code
        call    numbers         ;convert it to Morse
;
; test the resulting Morse before putting it into the buffer
        movwf   inbyte          ;reuse inbyte to hold the Morse
        movf    inbyte,w        ;reload inbyte to perform a zero test
        btfsc   STATUS,zero     ;is this byte a zero?
        goto    cbreak          ;  yes, bail
        movwf   INDF            ;save it in the buffer
        bsf     flags,keypr     ;set the key press flag
        incf    recv,f          ;  no, increment the recv pointer
        movlw   bufstop+1       ;load up the last possible buffer location + 1
        subwf   recv,w          ; is the recv pointer      (recv-(bufstop+1))
        btfss   STATUS,carry    ;    less than the last possible buff loc + 1?
        goto    cbreak          ; yes, just exit the interrupt routine
        movlw   bufstrt         ; no, reset the recv pointer 
        movwf   recv            ;                       to the bufstrt
;
cbreak  ;restore the status and w registers
        ;so that the movwf & swapf instructions don't affect STATUS bits
        swapf   savef,w         ;unswap the saved FSR register into w
        movwf   FSR             ;put it back into FSR
        swapf   saves,w         ;unswap the saved status register into w
        movwf   STATUS          ;put it back into STATUS
        swapf   savew,f         ;swap the nibbles of the saved w register
        swapf   savew,w         ;swap them again into the w register
        bcf     INTCON,RBIF     ;clear the port b change interrupt flag
        retfie                  ;return from interrupt,  end of rupt routine



; in232 - get the serial character (ascii), put it into w
in232   btfsc   RCSTA,OERR      ;has a overrun error happened ?
        call    ovrerr          ;yes, handle the error
        btfss   RCSTA,FERR      ;has a framing error happened ?
        goto    ovrferr         ;no, skip the frame error handling
        movf    RCREG,w         ;reading the uart clears the FERR flag
        goto    in232           ;try it again
ovrferr btfss   PIR1,RCIF       ;is the buffer full ?
        goto    in232           ;no, wait until it is full
        movf    RCREG,w         ;load w with the received character
        return                  ;end of in232 routine



; ovrerr - a way to clear the overflow error bit
ovrerr  bcf     RCSTA,CREN      ;turn off cren
        movf    RCREG,w         ;flush 
        movf    RCREG,w         ;      the
        movf    RCREG,w         ;          fifo
        bsf     RCSTA,CREN      ;turn on cren again
        return                  ;end of ovrerr routine



; speed2 - a routine to save a few bytes by putting a 3 statement segment
;       into a subroutine that is called 3 times.
speed2  bcf     STATUS,carry    ;clear the carry bit
        rlf     spd,w           ;multiply speed table pointer by 2, put in w
        movwf   sammo           ;save it temporarily in sam
        call    speed           ;get the high byte
        movwf   TMR1H           ;save it in the timer 1 high byte
        incf    sammo,w         ;increment the pointer and load into w
        call    speed           ;get the low byte
        movwf   TMR1L           ;save it in the timer 1 low byte
ovradj  bcf     PIR1,TMR1IF     ;clear the timer 1 interrupt flag
        bsf     T1CON,TMR1ON    ;turn on timer 1
        return                  ;end of speed2 routine



; outdah - a routine to send a dah
outdah  call    toneon          ;turn on the tone.
        bsf     PORTA,owt       ;actuate the keyed output
ok2     movf    spd,w           ;load the current code speed
        call    speed2          ;convert the spd pos to dit cycles
loop4   btfss   PIR1,TMR1IF     ;poll the timer 1 overflow flag - is it set?
        goto    loop4           ;no, keep polling
        movf    spd,w           ;load the current code speed
        call    speed2          ;convert the spd pos to dit cycles
loop4a  btfss   PIR1,TMR1IF     ;poll the timer 1 overflow flag - is it set?
        goto    loop4a          ;no, keep polling
        goto    dahent2         ;yes, enter the dit routine



; outdit - a routine to send a dit
outdit  bsf     PORTA,owt       ;actuate the keyed output
        call    toneon          ;turn on the tone.
dahent2 movf    spd,w           ;load the current code speed
        call    speed2          ;convert the spd pos to dit cycles
loop5   btfss   PIR1,TMR1IF     ;poll the timer 1 overflow flag - is it set?
        goto    loop5           ;no, keep polling
        ;yes, end the dit
ditsp   bcf     PORTA,owt       ;turn off the keyed output
        call    toneoff         ;turn off the sidetone
        movlw   0x01            ;setup the loop count for a dit space
        movwf   third           ;  save it in third
        movf    spd,w           ;load the current code speed
        call    speed2          ;convert the spd pos to dit cycles
ovrsub2 goto    ditsp3  ;enter the space routine



; wdsp - a routine to wait 6 dit spaces + 1 from last dit or dah = 7 total
; wdsp2 - a routine to wait 4 dit spaces + 3 from last char = 7 total
; chsp - a routine to wait 2 dit spaces + 1 from last dit or dah = 3 total
;       mem loc used: third
wdsp    movlw   0x7             ;load up 6 dit spaces
        goto    loadet          ;branch in
wdsp2   movlw   0x5             ;load up 4 dit space (an additional delay after chsp)
        goto    loadet          ;branch into the routine with an alterante
chsp    movlw   0x3             ;load up 2 dit spaces
loadet  movwf   third           ;save it in third - a temp reg
decet   decfsz  third,f         ;decrement the dit space counter - is it 0 ?
        goto    ditsp2          ;no, start waiting
        goto    dun2            ;yes, end the dit
ditsp2  movf    spd,w           ;load the current code speed
        call    speed2          ;convert the spd pos to dit cycles
ditsp3  btfss   PIR1,TMR1IF     ;poll the timer 1 overflow flag - is it set?
        goto    ditsp3          ;no, keep polling
        goto    decet           ;yes, check the dit space counter
dun2    return                  ;end of wdsp routine



; toneoff - a routine to turn off the hardware PWM tone on pin 13 (RC2)
toneoff bcf     T2CON,TMR2ON    ;turn off timer 2 (and tone?)
        clrf    CCP1CON         ;set the output to a zero
        bcf     PORTB,CCP1      ;set the sidetone low ?
        bsf     STATUS,RP0      ;select bank 1
        bcf     TRISB,CCP1      ;turn on the output pin
        bcf     STATUS,RP0      ;select bank 0
        return                  ;end of toneoff routine


        
        

; toneon - a routine to turn on the hardware PWM tone 
toneon  bcf     STATUS,RP0      ;set bank 0 
        bcf     STATUS,RP1      ;        for sure
tone2   movlw   d'103'          ; 601 hz    pr2                         
;        movlw   d'82'           ; 753 hz    pr2                        
ovtone2 bsf     STATUS,RP0      ;select bank 1
        movwf   PR2             ;save PR2
        ; bank 0
        bcf     STATUS,RP0      ;select bank 0
tone2a  movlw   d'52'           ; ccpr1l =  52; ccp1con =  0;  601 Hz
;        movlw   d'169'         ; ccpr1l =  41; ccp1con =  2;  753 Hz
        movwf   tonef2          ;save them temp in tonef2 for ccp1con setting
        andlw   b'01111111'     ;and out the ccp1con bit
        movwf   CCPR1L          ;set the duty cycle
        movlw   b'00001100'     ;5:4=0:0 for 50% duty, pwm mode
        btfsc   tonef2,7        ;test the high order bit, if 0, skip the 11 setting
        movlw   b'00111100'     ;5:4=1:1 for 50% duty, pwm mode        
        movwf   CCP1CON         ;set the duty cycle and other bits
tone3   bsf     STATUS,RP0      ;select bank 1
        bcf     TRISB,CCP1      ;turn on the output pin
        bcf     STATUS,RP0      ;select bank 0
        bsf     T2CON,TMR2ON    ;turn on timer 2 (and tone?)
        return                  ;enf of toneon routine
        
        

; deboun - wait for 20 ms
; delay = 2 (call) + 2 (return) + 2 setup + (2000 x 10 ) - 1 = 20,005
deboun  movlw   d'10'           ;1 1 load up a counter
        movwf   temp2           ;1 2 store it in temp2
dbounc  call    halfcy2         ;wait 1997 usec
        decfsz  temp2,f         ;1 3 decrement the loop count - skip at 0
        goto    dbounc          ;2 5 count not zero, loop back
        return                  ;2 end of debounce routine



; halfcy2 - a routine to wait a specified period of time: 1997 us
;       at PIC clock = 4 MHz, each PIC instruction takes 1 or 2 usec.
; delay = 2 (call) + 2 (return) + 5  (routine) + ((freq x 9) - 1)  loop delay
freq2   equ     d'221'          ; 8 + (221 x 9) = 1997
halfcy2 movlw   freq2           ;1   1 load up the 1/2 cycle delay constant
        movwf   temp            ;1   2 store it in the temporary location
        goto    waste3          ;2   4 waste 2 cycles
waste3  nop                     ;1   5 waste a cycle
loopy2  goto    fake1           ;2      waste 2 cycles
fake1   goto    fake2           ;2 and another 2 cycles
fake2   goto    fake3           ;2 and another 2 cycles
fake3   decfsz  temp,f          ;1     decrement the temp location - is it 0 ?
        goto    loopy2          ;2     no, keep decrementing
        return                  ;2 500 yes, end of half cycle routine



; spedset - a routine to set the Morse code speed of the keyboard
;     while this routine does work, it's kinda big and clumsy, feel
;     free to streamline it as you'd like ;)
spedset clrf    newsped         ;clear the new speed holder
spedse1 call    in232           ;get the next key press
        movwf   inbyte          ;save the key code in inbyte
        btfsc   inbyte,7        ;is bit 7 high ?
        goto    ctrlrls         ; yes, go and see if it's a ctrl release
        ;no, it's not a ctrl release, but what is it ?
        movf    inbyte,w        ;load the key code
        btfss   STATUS,zero     ;is it equal to zero ?
        goto    test2           ;no, skip to the next test
        movlw   d'1'            ;yes, the key code is zero
savcalc movwf   digit           ; which means that the key pressed is a 1
        movf    newsped,w       ;test newsped        
        btfsc   STATUS,zero     ;is it zero ?
        goto    justsav         ;yes, just save digit as newsped
        addwf   newsped,f       ;no, multiply newsped by 10 and add digit
        addwf   newsped,f       ;3x
        addwf   newsped,f       ; 4x
        addwf   newsped,f       ;  5x
        addwf   newsped,f       ;   6x
        addwf   newsped,f       ;    7x
        addwf   newsped,f       ;     8x
        addwf   newsped,f       ;      9x
        addwf   newsped,f       ;      10x
justsav movf    digit,w         ;load digit
        addwf   newsped,f       ;add digit to newsped
        goto    spedse1         ;loop back for another key
test2   sublw   d'1'            ;test for a 1 
        btfss   STATUS,zero     ;       key code
        goto    test3           ;no, it's not = 1, do the next test
        movlw   d'2'            ;yes, the key code is one
        goto    savcalc         ; so save the 2 and then calculate the speed
test3   movlw   d'2'            ;test for a 2
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test4           ;no, it's not = 1, do the next test
        movlw   d'3'            ;yes, the key code is one
        goto    savcalc         ; so save the 3 and then calculate the speed
test4   movlw   d'4'            ;test for a 4
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test5           ;no, it's not = 1, do the next test
        movlw   d'4'            ;yes, the key code is 4
        goto    savcalc         ; so save the 4 and then calculate the speed
test5   movlw   d'5'            ;test for a 5
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test6           ;no, it's not = 1, do the next test
        movlw   d'5'            ;yes, the key code is 5
        goto    savcalc         ; so save the 5 and then calculate the speed
test6   movlw   d'6'            ;test for a 6
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test7           ;no, it's not = 1, do the next test
        movlw   d'6'            ;yes, the key code is 6
        goto    savcalc         ; so save the 6 and then calculate the speed
test7   movlw   d'7'            ;test for a 7
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test8           ;no, it's not = 1, do the next test
        movlw   d'7'            ;yes, the key code is 7
        goto    savcalc         ; so save the 7 and then calculate the speed
test8   movlw   d'52'           ;test for a 52
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test9           ;no, it's not = 1, do the next test
        movlw   d'8'            ;yes, the key code is 52
        goto    savcalc         ; so save the 8 and then calculate the speed
test9   movlw   d'53'           ;test for a 53
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    test0           ;no, it's not = 1, do the next test
        movlw   d'9'            ;yes, the key code is 53
        goto    savcalc         ; so save the 7 and then calculate the speed
test0   movlw   d'54'           ;test for a 54
        subwf   inbyte,w        ;       key code
        btfss   STATUS,zero     ; perform the comparision
        goto    spedse1         ;no, it's not any number, get another key
        movlw   d'0'            ;yes, the key code is 54
        goto    savcalc         ; so save the 0 and then calculate the speed
ctrlrls sublw   b'10011010'     ;test for
        btfss   STATUS,zero     ;       ctrl key release
        goto    spedse1         ;loop back for another key
        ;now test newsped to see if it makes sense, if 0 or > 51, bail out
        movf    newsped,f       ;test newsped
        btfsc   STATUS,zero     ;is newsped 0 ?
        goto    finss           ;yes, skip the spd update
        movlw   d'52'           ;ctrl has been released
        subwf   newsped,w       ; now test newsped against max speed + 1
        btfsc   STATUS,carry    ;if the carry is set, newsped is too big
        goto    finss           ;exit without changing spd
        movf    newsped,w       ;newsped is less than 52
        movwf   spd             ;save it as the keyboard Morse code speed
        clrf    EEADDR          ;clear the EEPROM address
        call    wreep           ;save the new speed in eeprom address 0
finss   return                  ;end of spedset



; rdeep - a routine to read the data in the eeprom pointed to by EEADDR
;       and put it into w & eedatb0
rdeepi  incf    EEADDR,1        ;increment the EEPROM address
rdeep   bcf     STATUS,RP0      ;set bank 0 for sure - prob not needed
        bcf     STATUS,RP1      ;
        movf    EEADDR,0        ;load up the internal eeprom address
rdeebri bsf     STATUS,RP0      ;set bank 1
        movwf   EEADR           ;store it in the EEPROM address buffer
        bsf     EECON1,RD       ;EEPROM read
        movf    EEDATA,0        ;load up eedata into w
        bcf     STATUS,RP0      ;bank 0
        return                  ;end of read eeprom routine
        


;    wreep - a routine to write the data in w into the address EEADDR
; write sequence from the 16F87x spec - for the internal 16f87x eeprom.
;       This routine was modififed from the one used in the keybdf84 program.
wreep   movwf   eedatb0         ;save the byte to be written temporarily
        movf    EEADDR,0        ;load up the internal eeprom address
        bsf     STATUS,RP0      ;set bank 1
        movwf   EEADR           ;store is in the EEPROM address buffer
        bcf     STATUS,RP0      ;set bank 0
        movf    eedatb0,0       ;load up the data to be written
        bsf     STATUS,RP0      ;set bank 1
        movwf   EEDATA          ;store in the eeprom data buffer
        bsf     STATUS,RP0      ;set the bank bit - 1
        bsf     EECON1,WREN     ;enable write
        movlw   H'55'           ;load up a 55h per the 16f84 spec
        movwf   EECON2          ;write the 55h 
        movlw   H'AA'           ;load up AAh per the 16f84 spec
        movwf   EECON2          ;write the AAh 
        bsf     EECON1,WR       ;set the WR bit and thus begin writing to eeprom
        bcf     EECON1,WREN     ;disable write
        bcf     STATUS,RP0      ;reset the bank bit to 0
checkit btfss   PIR1,EEIF       ;see if the last write is complete
        goto    checkit         ;no, check it again
        bcf     PIR1,EEIF       ;clear the write complete interrupt flag bit
        return                  ;end of wreep routine



; init - initialize the keyer from a powerup or reset
init    ; bank 0
        clrf    EEADDR          ;clear the EEPROM address
        call    rdeep           ;get eeprom byte 0
        movwf   spd             ;save it in spd
        clrf    downcnt         ;init various 
        clrf    flags           ;   ram variables
        clrf    EEADDR          ;clear the EEPROM address
        clrf    CCP1CON         ;CCP Module is off
        clrf    TMR2            ;Clear Timer 2
        clrf    PORTA           ;clear all bits of PORTA
        bcf     PORTA,RTS       ;set the RTS low 
        clrf    PORTB           ;clear all bits of PORTB
        movlw   b'00001000'     ;b3=1, port b int on chg
        movwf   INTCON          ;set the interrupts for wake on GP change
        movlw   b'00110000'     ;prescale = 8, int clock, timer off
        movwf   T1CON           ;setup timer 1
        ;50 % duty cycle at PR2=10 => 1/2 * 416 = 208 = 11010000 b
        ;       so CCPR1L = 52 (110100) and CCP1CON(5:4) = 0 (00)
        movlw   b'00001100'     ;5:4=0:0 for 50% duty, pwm mode
        movwf   CCP1CON         ;set the duty cycle and other bits
        clrf    PIR1            ;clear peripheral interrupt flags
        ; bank 1
        bsf     STATUS,RP0      ;select bank 1
        clrf    PIE1            ;disable peripheral interrupts
        clrf    TRISA           ;all port a pins are outputs
        movlw   b'10000010'     ;RX pin (rb1), rb7 are inputs
        movwf   TRISB           ;replace the tris instruction
        movlw   b'10000000'     ;port b pullups disabled
        movwf   OPTION_REG      ;replace the option instruction makes assem hap
        ; bank 0
        bcf     STATUS,RP0      ;select bank 0
        movlw   b'00000010'     ;timer 2 off, 1:1 postscale, 16 prescale
        movwf   T2CON
        ; bank 1
        bsf     STATUS,RP0      ;select bank 1
;
; setup the usart - asynch
rate    equ      0x19           ;constant for 9600 baud @ 4 MHz
        movlw   rate            ;load up the baud constant
        movwf   SPBRG           ;set the baud
        movlw   b'00100100'     ;TXSTA bit settings, meaning
                  ; .  : |------TX9D = 0 => 9th sent bit
                  ; .  :        TRMT = 0 => tsr full
                  ; .  :....... BRGH = 1 => high speed
                  ; .           unused
                  ; .           SYNC = 0 => asynch mode
                  ; . . . . . . TXEN = 1 => enable xfer
                  ;             TX9  = 0 => 8 bit xfer
                  ;-------------CSRC = 0 => don't care
        movwf   TXSTA           ;set the transmit register
        bsf     PIE1,RCIE       ;enable the UART receive interrupt
        ; bank 0
        bcf     STATUS,RP0      ;select bank 0
        movlw   b'10010000'     ;constant for receive and serial port enabled
                  ; .| : |------RX9D = 0 => 9th received bit
                  ; .| :        OERR = 0 => overrun error bit
                  ; .| :....... FERR = 0 => framing error bit
                  ; .|          ADEN = 0 => address detect enable bit, unused
                  ; .+--------- CREN = 1 => enables continuous receive
                  ; . . . . . . SREN = 0 => single receive enable bit, unused
                  ;             RX9  = 0 => 8 bit reception
                  ;------------ SPEN = 1 => serial port enabled
        movwf   RCSTA           ;set the receive register
        movlw   b'00000111'     ;turn off the 
        movwf   CMCON           ;    analog comparator pins
pollhs  btfss   PORTB,hs        ;test the hotsync pin
        goto    pollhs          ;wait until it goes high
        bsf     PORTA,RTS       ;raise RTS for a while
        call    deboun          ;wait a little        
getinit call    in232           ;get a key code from the Palm keyboard
        movwf   keycod          ;save key code temporarily (should it be tested ?)
secbyte btfss   PIR1,RCIF       ;is the buffer full ?
        goto    secbyte         ;wait for the second byte
        call    in232           ;get a key code from the Palm keyboard
        movwf   keycod          ;save key code temporarily (should it be tested ?)
        bcf     PORTA,RTS       ;lower RTS for a while
; are these deboun calls needed ?        
        call    deboun
        call    deboun
        call    deboun
        call    deboun
        call    deboun
        call    initbuf         ;initialize the buffer
        movlw   b'00010010'     ;load an F
        call    pc2             ;play it
        movlw   b'00011000'     ;load a B
        call    pc2             ;play it
        bsf     INTCON,PEIE     ;enable the peripheral interrupts
        

        
; mainlup - the main program loop
mainlup movlw   d'50'           ;load up 1 second count
        movwf   downcnt         ;       of 20 ms calls
mainlu2 call    deboun          ;wait 20 ms
        btfsc   flags,keypr     ;test the key press flag
        goto    ml3             ;a key was pressed, start emptying the buffer
        decfsz  downcnt         ;decrement the down counter
        goto    mainlu2         ;loop until the count is 0
        call    ovrerr          ;clear the RCIF flag
        bsf     INTCON,GIE      ;enable the interrupts
        bcf     INTCON,RBIF     ;clear the port b change interrupt flag
        bcf     PORTA,RTS       ;lower RTS to power down the keyboard
        sleep                   ;konk out until an hs pulse happens
        call    deboun          ;wait a while for xmit to get loaded the
ml3     movf    xmit,w          ;load up the xmit pointer
        movwf   FSR             ;put xmit pointer into the indirect address
        movf    INDF,w          ;get the contents of the buffer
        btfsc   STATUS,zero     ;is this byte a zero?
        goto    mainlup         ;yes the buffer is empty, loop back and sleep
        ;no, it's a good character so drop through to play232



; play232 - a routine to get a char from rs232 input and then play it
play232 call    pc2             ;play the Morse character
        clrf    INDF            ;zero the buffer
        incf    xmit,f          ;increment the xmit pointer
        movlw   bufstop+1       ;load up the last possible buffer location + 1
        subwf   xmit,w          ; is the xmit pointer 
        btfss   STATUS,zero     ;       equal to the last possible buffer loc?
        goto    ml3             ;no, loop back for another character
        movlw   bufstrt         ;yes, reset the xmit pointer 
        movwf   xmit            ;                       to the bufstrt
        goto    ml3             ;no, loop back for another character



; playchr - a routine to play a character stored in w
;       mem loc used:   char, tens, size
playchr call    numbers         ;lookup the value        
pc2     movwf   char            ;save it in char
        movwf   tens            ;save it in tens
        comf    tens,w          ;invert the character
        btfsc   STATUS,zero     ;skip playing the char if it is zero (FF before inv)
        goto    spacey          ;play a word space
getdig3 call    strtbit         ;rotate char left until the start bit comes up
shout3  rlf     char,f          ;rotate the msb into carry
        btfsc   STATUS,carry    ;test the carry - 0=dit, 1=dah
        goto    dahsnd3         ;it's a one, send a dah
        call    outdit          ;send a dit
        goto    sizdec          ;go check the size variable
dahsnd3 call    outdah          ;send the dah
sizdec  decfsz  size,f          ;decrement the char size counter, is it 0?
        goto    shout3          ;no, go back for the next bit
        call    chsp            ;yes, send a character space
pcend   bcf     flags,keypr     ;clear the key press flag
        return                  ; end of playchr routine
spacey  call    wdsp2           ;send a word space
        goto    pcend           ;exit the routine

        

; strtbit - a routine to rotate a char left until a 1 appears
strtbit movlw   0x8             ;load up the maximum char size + 1
        movwf   size            ;save it temp
strbit2 decf    size,f          ;decrement the element counter
xrotit  rlf     char,f          ;rotate the msb into carry
        btfss   STATUS,carry    ;test the carry - 0=placeholder, 1=start bit
        goto    strbit2         ;it's a just a placeholder (0), keep shifting
        return                  ;you're done - end of the startbit routine



; initbuf - clear the buffer to all zero and set the pointers
initbuf movlw   bufstrt         ;load up the start of the buffer
        movwf   FSR             ;set the buffer pointer
agn     clrf    INDF            ;clear the current buffer location
        incf    FSR,f           ;increment the memory pointer
        movlw   bufstop+1       ;lets look at the element counter
        subwf   FSR,w           ;is the element counter 
        btfss   STATUS,zero     ;        less than the message end address?
        goto    agn             ;yes, they are unequal - keep going
        movlw   bufstrt         ;load up the starting address of the buffer
        movwf   xmit            ;set the transmit buffer pointer
        movwf   recv            ;set the receive buffer pointer
        movwf   FSR             ;set the buffer pointer, too
        return                  ;end of initbuf routine



; preload the 16F628a internal eeprom
        org     0x2100          ;starting address for 16F628 internal eeprom
        de      d'16'           ;16 wpm code speed at first powerup


        
        end     ;end of the palm3x.asm, Morse code Palm keyboard program

