list p=16C71, r=HEX, c=132, w=2 ; ; Automatic morse keyer using PIC16C71 ; (c) Brian Kelly GW6BWX ; ;pinouts as follows: ; RA0 - input - speed voltage RB0 - input - message select bit 0 ; RA1 - input - tone voltage RB1 - input - message select bit 1 ; RA2 - output - keyer output RB2 - input - message select bit 2 ; RA3 - output - tone output RB3 - o/p - eeprom & pot power (active -) ; RB4 - input - message playback switch (active -) ; RB5 - input - straight key (active -) ; RB6 - input - auto key dah (active -) ; RB7 - input - auto key dit (active -) ;processor working registers: IND0 equ 00h ;indirect address data register RTCC equ 01h ;clock/counter PCL equ 02h ;low bits of program counter STATUS equ 03h ;processor flags FSR equ 04h ;file select register PORTA equ 05h ;analogue i/p & keyer o/p port PORTB equ 06h ;key, switch & I2C port ADCON0 equ 08h ;ADC control register ADRES equ 09h ;ADC result register PCLATH equ 0Ah ;top bits of PC during calls/goto's INTCON equ 0Bh ;interrupt control register OPTREG equ 081h ;configuration register TRISA equ 085h ;port A tristate/direction control TRISB equ 086h ;port B tristate/direction control ADCON1 equ 088h ;ADC configuration register ; status / destination bit definitions: W equ 0 ;destination is W register SAME equ 1 ;destination is source register C equ 0 ;carry bit position D equ 1 ;digit carry / borrow Z equ 2 ;zero bit PD equ 3 ;power down bit TO equ 4 ;time-out bit RP0 equ 5 ;register page select bit 0 RP1 equ 6 ;register page select bit 1 IRP equ 7 ;indirect register page bit ;program working registers: MODE equ 0Dh ;bitmapped acording to mode PERIOD equ 0Eh ;value to set delay length RATE equ 0Fh ;storage for speed voltage PITCH equ 10h ;storage for pitch value TONE equ 11h ;pitch ADC voltage MEMPTR equ 12h ;pointer to message storage MSGNUM equ 13h ;number of selected message store TEMP equ 14h ;workspace ;constants silence equ 00h dit equ 04h dah equ 07h done equ 0Ah ; to make life easy, the PCLATH register is kept at 00 and subroutines are ; stored at the low end of the address space after the reset vector. org 0 ;reset address reset goto start org 4 ;interrupt vector bcf INTCON,0 ;clear RB7:4 changed flag movf PORTB,W ;dummy read to clear port retfie ; subroutines start here get_period movlw B'00000011' ;ADC on with channel 0 selected movwf ADCON0 bsf ADCON0,2 ;start conversion ch0_wait btfsc ADCON0,2 ;skip next if still converting goto ch0_wait bcf STATUS,C ;ensure a zero is rotated into bit 7 rlf ADRES,SAME ;divide ADC result by 2 bcf STATUS,C ;ensure a zero is rotated into bit 7 rlf ADRES,SAME ;divide ADC result by 2 (by 4 now) movf ADRES,W movwf RATE ;RATE value used as timing source RETURN get_tone movlw B'00001011' ;ADC on with channel 1 selected movwf ADCON0 bsf ADCON0,2 ;start conversion ch1_wait btfsc ADCON0,2 ;skip next if still converting goto ch1_wait movf ADRES,W ;pick up and store the result... movwf PITCH ;PITCH used as delay when spkr toggled return send_dit bcf MODE,0 ;record dit as current mode comf RATE,W ;transfer -RATE to RTCC movwf RTCC bsf PORTA,2 ;go to key down state dit_loop movf PITCH,W ;set the monitor tone period movwf TONE spkr_off_dly1 decf TONE,SAME ;delay by counting down TONE variable btfss STATUS,Z ;skip next if not yet zero goto spkr_off_dly1 bsf PORTA,3 ;turn loudspeaker output on. movf PITCH,W ;set the monitor tone period movwf TONE spkr_on_dly1 decf TONE,SAME ;delay by counting down TONE variable btfss STATUS,Z ;skip next if not yet zero goto spkr_on_dly1 bcf PORTA,3 ;turn loudspeaker output off. movf RTCC,W ;see if "on" period was long enough btfss STATUS,Z ;skip next if period is finished goto dit_loop bcf PORTA,2 ;unkey return send_dah bsf MODE,0 ;record dah as current mode bsf PORTA,2 ;go to key down state movf RATE,W ;transfer -(3*RATE) to RTCC addwf RATE,SAME addwf RATE,SAME comf RATE,W movwf RTCC dah_loop movf PITCH,W ;find out the monitor tone period movwf TONE spkr_off_dly2 decf TONE,SAME ;delay by counting down TONE variable btfss STATUS,Z ;skip next if not yet zero goto spkr_off_dly2 bsf PORTA,3 ;turn loudspeaker output on. movf PITCH,W ;find out the monitor tone period movwf TONE spkr_on_dly2 decf TONE,SAME ;delay by counting down TONE variable btfss STATUS,Z ;skip next if not yet zero goto spkr_on_dly2 bcf PORTA,3 ;turn loudspeaker output off. movf RTCC,W ;see if "on" period was long enough btfss STATUS,Z ;skip next if period is finished goto dah_loop bcf PORTA,2 ;unkey return ; This routine causes the delay between "key down" periods. Timing is derived ; from the value in RTCC. If in record mode, a pause is put in the memory ; sequence. send_pause call get_period ;get the current timing value comf RATE,W ;transfer -RATE to RTCC movwf RTCC pause_loop movf RTCC,W ;see if period was long enough btfss STATUS,Z ;skip next if period is finished goto pause_loop ;wait until RTCC = FF return ;------------------------------------------------------------------------- start clrw ;initial register conditions movwf INTCON ;disallow interrupts during setup movwf PCLATH ;becomes top bits in jump/call inst. movlw 20H ;switch to register page 1 movwf STATUS movlw B'00000111' ;enable pull-ups, int on falling edge, ;prescaler to RTCC, prescale = 1:256 movwf OPTREG movlw B'00000010' ;RA0,1 = analog i/p RA2,3 = dig. o/p movwf ADCON1 movlw B'00000011' ;RA0,1 = inputs RA3,4 = outputs movwf TRISA movlw B'11110111' ;RB3 = output, others = inputs movwf TRISB clrf STATUS ;switch back to register page 0 clrf PORTA ;clear the ports clrf PORTB clrf MODE clrf INTCON ;disable interrupts for now main bsf PORTB,3 ;provide +ve for pots & pull-ups nop call get_tone call get_period main1 movf PORTB,W ;read the switches into W movwf TEMP movlw B'11000000' ;mask for dit and dah keys andwf TEMP,SAME ;if b7 & b6 = 0, set iambic mode btfsc STATUS,Z ;Z flag set if both 0 goto iambic chk_bit7 btfsc PORTB,7 ;start by checking bit 7 goto chk_bit6 ;if bit 7 = 1, try bit 6 call send_dah ;send dah then a pause call send_pause goto main chk_bit6 btfsc PORTB,6 ;check bit 6 goto chk_bit5 ;if bit 6 = 1, try bit 5 call send_dit ;send dit then a pause call send_pause goto main chk_bit5 btfsc PORTB,5 ;check bit 5 goto chk_bit4 ;if bit 5 = 1, try bit 4 str1 comf RATE,W ;transfer -RATE to RTCC movwf RTCC bsf PORTA,2 ;go to key down state str_loop movf PITCH,W ;set the monitor tone period movwf TONE spkr_off_dly3 decf TONE,SAME ;delay by counting down TONE variable btfss STATUS,Z ;skip next if not yet zero goto spkr_off_dly3 bsf PORTA,3 ;turn loudspeaker output on. movf PITCH,W ;set the monitor tone period movwf TONE spkr_on_dly3 decf TONE,SAME ;delay by counting down TONE variable btfss STATUS,Z ;skip next if not yet zero goto spkr_on_dly3 bcf PORTA,3 ;turn loudspeaker output off. movf RTCC,W ;see if "on" period was long enough btfss STATUS,Z ;skip next if period is finished goto str_loop btfss PORTB,5 ;skip next if key is up goto str1 ;still down so back for more bcf PORTA,2 ;unkey goto main chk_bit4 btfsc PORTB,4 ;check bit 4 goto shutdown ;no switches closed, start shutdown movlw H'ff' ;preset the message pointer movwf MEMPTR bcf PCLATH,0 btfss PORTB,2 ;check message selection bits goto message2 btfss PORTB,1 goto message1 btfss PORTB,0 goto message0 goto main ;no message selected ; if this point is reached, no keys or switches are active and we are not ; in record or playback mode. Start shutdown timer and if no keys operated ; before time-out, unkey then power down. shutdown clrw ;maximise shutdown delay movwf RTCC ;load it into RTCC incf RTCC,SAME ;ensure not zero before checking. shut_loop movf RTCC,W ;set status flags btfss STATUS,Z ;Z flag is set if RTCC = 0 goto final_check ;abort shutdown if key operated ;before timer reaches 0 bcf PORTA,2 ;timed out so unkey bcf PORTA,3 ;ensure tone output is disabled bcf PORTB,3 ;power down pots & RB2 pull-up clrf ADCON0 ;disable ADC to save power nop ;allow time for RB3 to discharge movlw B'10001000' ;enable interrupt on RB4:7 change movwf INTCON nop sleep ;power down until interrupted. nop ;resumes here after interrupt clrf INTCON ;disable further interrupts goto main final_check movlw B'11110100' ;mask bits for all switches andwf PORTB,W sublw B'11110100' ;if result = 0, all switches are open btfss STATUS,Z ;skip next if switches open goto main ;switch operated, abort shutdown goto shut_loop ; If both keyer inputs are low together, (both paddles closed) the keyer is ; in Iambic mode. The send_dit and send_dah routines are called alternately, ; starting with whichever was NOT sent last. iambic btfss MODE,0 ;skip next if in dah mode goto iambic1 call send_dit call send_pause goto main iambic1 call send_dah call send_pause goto main ; these are the routines that produce the predefined character sequences. ; Note: the switch inputs are active low (switches connect PORTB pins to 0v) ; therefore a single pole 3-way switch will produce codes 110 (=6), 101 (=5) ; or 011 (=3). A code of 111 (=7) implies no switch is fitted so no message ; will be sent. message0 ; all switches closed bsf PCLATH,0 incf MEMPTR,SAME movf MEMPTR,W call table0 ;table of characters for message 0 bcf PCLATH,0 addwf PCL,SAME ;jump ahead by steps from the table call send_pause ; 0 = intercharacter pause (3*dit time) call send_pause ; 1 call send_pause ; 2 goto message0 ; 3 call send_dit ; 4 = send dit and a pause call send_pause ; 5 goto message0 ; 6 call send_dah ; 7 = send dah and a pause call send_pause ; 8 goto message0 ; 9 goto main ; A = message finished message1 ;switches 1 & 2 closed, 3 open bsf PCLATH,0 incf MEMPTR,SAME movf MEMPTR,W call table1 ;table of characters for message 1 bcf PCLATH,0 addwf PCL,SAME ;jump ahead by steps from the table call send_pause ; 0 = intercharacter pause (5*dit time) call send_pause ; 1 call send_pause ; 2 goto message1 ; 3 call send_dit ; 4 = send dit and a pause call send_pause ; 5 goto message1 ; 6 call send_dah ; 7 = send dah and a pause call send_pause ; 8 goto message1 ; 9 goto main ; A = message finished message2 ;switches 1 & 3 closed, 2 open bsf PCLATH,0 incf MEMPTR,SAME movf MEMPTR,W call table2 ;table of characters for message 2 bcf PCLATH,0 addwf PCL,SAME ;jump ahead by steps from the table call send_pause ; 0 = intercharacter pause (5*dit time) call send_pause ; 1 call send_pause ; 2 goto message2 ; 3 call send_dit ; 4 = send dit and a pause call send_pause ; 5 goto message2 ; 6 call send_dah ; 7 = send dah and a pause call send_pause ; 8 goto message2 ; 9 goto main ; A = message finished org H'0100' table0 addwf PCL,SAME retlw dah ;GW6BWX retlw dah retlw dit retlw silence retlw dit retlw dah retlw dah retlw silence retlw dah retlw dit retlw dit retlw dit retlw dit retlw silence retlw dah retlw dit retlw dit retlw dit retlw silence retlw dit retlw dah retlw dah retlw silence retlw dah retlw dit retlw dit retlw dah retlw done table1 addwf PCL,SAME retlw dit ;Risca retlw dah retlw dit retlw silence retlw dit retlw dit retlw silence retlw dit retlw dit retlw dit retlw silence retlw dah retlw dit retlw dah retlw dit retlw silence retlw dit retlw dah retlw silence retlw silence retlw dit ;IO81LO retlw dit retlw silence retlw dah retlw dah retlw dah retlw silence retlw dah retlw dah retlw dah retlw dit retlw dit retlw silence retlw dit retlw dah retlw dah retlw dah retlw dah retlw silence retlw dit retlw dah retlw dit retlw dit retlw silence retlw dah retlw dah retlw dah retlw done table2 addwf PCL,SAME retlw dah ;Brian retlw dit retlw dit retlw dit retlw silence retlw dit retlw dah retlw dit retlw silence retlw dit retlw dit retlw silence retlw dit retlw dah retlw silence retlw dah retlw dit retlw done end