; COPYRIGHT 2002 BY DAVID EK. PERMISSION IS GRANTED TO FREELY USE ; THIS SOFTWARE FOR NONCOMMERCIAL PURPOSES. ; ; NOTE: USE AT YOUR OWN RISK. THE AUTHOR ASSUMES NO LIABILITY FOR DAMAGE ; INCURRED FROM THE USE OF THIS SOFTWARE. ; ; See QRP Quarterly Summer 2001 issue for a complete circuit description. ; ADDENDUM: since publication, the circuit has been modified to allow ; connection of a paddle for hand sending. On the PIC, pin RB2 (pin 8) ; connects to the dit paddle, and pin RB3 (pin 9) connects to the dah ; paddle. Both these pins should be tied to +5V via a 10K resistor, and ; the paddle lines should be connected directly to the pins so that ; depressing either paddle grounds the corresponding pin. Note that ; depressing either paddle will stop any sending currently being done ; by the computer. ; ; PIC16F84 code for the Serial CW Sender project. The purpose ; of this project is to create a device which will receive ASCII strings ; via the serial port from a PC and cause the strings to be sent on a ham ; radio by keying the radio appropriately. ; ; The hardware circuit makes use of two major components: the ; PIC16F84 itself and a MAX232 TTL-RS232 level converter. ; The MAX232 converts the TTL signals from the PIC to RS232 levels ; for the PC (and vice versa). ; ; The PIC16F84 pin assignments are as follows: ; ; RA1: serial out to PC ; RA2: CW out to rig key jack ; RA3: inverted CW out (inverted RA2) to drive LED if desired ; RB0: serial in from PC (generates interrupt) ; RB2: dit paddle connection ; RB3: dah paddle connection ; ; Written using Microchip MPLAB v5.40 ; ; Please direct any questions, comments, or suggestions to the ; author: David Ek ; ekdave@earthlink.net ; ; References: ; - the method for representing the CW characters in memory was adapted from ; KEYER1.ASM by W. Buescher (DL4YHF). See www.qsl.net/dl4yhf/pic_key.html ; - the logic for handling iambic paddle keying was adapted from K1EL's K8 ; keyer source code. See http://www.k1el.com/. ; ; History: ; ; Rev 1 (17 Mar 2001): ; Creation. Serial routines stolen from my DSC project. ; Rev 2 (9 Sept 2001): ; Shortened the length of the space from three dahs to two ; dahs and a dit. Changed version to 1.01. Changed version ; string to have new call sign. ; Rev 3 (16 Jan 2002): ; Added manual keyer function. Paddle can now be connected for ; manual (non-computer) keying. Depressing a paddle key will ; interrupt any computer sending. Dits are sent by grounding ; RB2 (PIC pin 8), dahs by grounding RB3 (PIC pin 9). Changed ; version to 1.10. Updated the comments. Rearranged the code ; slightly to create seperate subroutines for sending dits and ; dahs, so they could be called either by the routines that ; send computer CW or the routines that handle manual keying. ; Rev 4 (5 Feb 2002): ; Added Curtis Mode A and B. Select Mode A by grounding PIC ; pin 7 (RB1). Select Mode B by tying pin 7 high using a 10K ; resistor. Changed version to 1.15. ; Rev 5 (9 Mar 2002): ; Fixed up Curtis Mode A (A's and N's not sent correctly). ; changed version to 1.16. ; Rev 6 (16 Mar 2002): ; More work on the paddle handling. Needed to enhance the logic ; for cases where the opposite paddle was pressed while a ; dit/dah was being sent. Changed version to 1.17. ; Rev 7 (29 Mar 2002): ; Added inverted CW out to RA3 to optionally drive an LED CW ; indicator. Connect other end of LED to +5V through a 10K ; resistor, so that the LED lights when RA3 is taken low. ; Changed to version 1.18. ; Rev 8 (26 Sep 2002): ; Modified Mainloop slightly--replaced the call to GetAChar ; with a statement to clear the Received Char flag in ; SerialRegs, since I was already checking to see whether a ; character was there before making the call to GetAChar. For ; some reason, when the paddle capabilities were added, the ; sender would occasionally miss a command to send. This seems ; to fix it, but I'm not sure why. Changed version to 1.19. ; ;---------------------------------------------------------------- ; ; Description of communications protocol (NOTE: double quotes are ; used in the description to set off the characters to send or be ; received, but are not part of the protocol): ; ; 1) All communication is 9600/8/1/N with no flow control. ; ; 2) To get the version of the software, send "^". ; Returned will be a string terminated by a CR. ; ; 3) A string of characters to send must not exceed (currently) 54 ; bytes and must be prefaced with "<" and end with a carriage ; return (ASCII 13). When finished sending, the interfacw will ; send back "r". ; ; 4) To interrupt sending, send any character. Wait for the interface ; to acknowledge by sending back "r" before sending more commands. ; ; 5) To set the speed send ">" followed by two unsigned bytes. The first ; byte is the dit length in milliseconds for characters. The second ; byte is the dit length in milliseconds for intercharacter spacing ; (to allow Farnsworth-style spacing). The value of the second byte ; should be greater than or equal to that of the first byte, or else ; the space between letters will be too short. NOTE: timing here is ; approximate, not perfect. ; ; EXAMPLE: to set character dit length to 40 ms, and character space ; dit length to 50 ms, send ">(2" because "(" is the character whose ASCII ; value is 40, and "2" is the character whose ASCII value is 50. ; ; 6) The following characters are supported for CW: ; ; A through Z (upper or lower case) ; 0 through 9 ; period (.) ; comma (,) ; slash (/) ; BT prosign (=) ; AR prosign (+) ; question mark (?) ; SK prosign (*) ; KN prosign (:) ; AS prosign (-) ; ;-------------------------------------------------------------------- list p=16f84 radix dec __config _CP_OFF & _WDT_OFF & _XT_OSC include "p16f84.inc" ; defines: ; these are constants used for sending the CW characters. The MSB indicates ; whether this is a space/special character (if set) or a regular character ; (if clear). Spaces are currently the only special character, and if the MSB ; is set a space will be sent. If all bits are set, the character is not ; supported in the CW character set and will be skipped. ; ; If the MSB is clear, then the remaining bits ; indicate the dots and dashes to be sent. The bits are sent in order from most ; to least significant, with 1 meaning dash and 0 meaning dot. A leading 1 is ; required as a start bit (and is not sent as a dash) to indicate the start ; of the dots and dashes (since not all characters have the same number of ; dots and dashes). The LSB is the last dot/dash in the string. ; ; NOTE: This method of representing the CW characters in memory was adapted from ; KEYER1.ASM (revision 3/2000), written by Wolfgang Buescher (DL4YHF). _Char_Space equ b'10000000' ; space _Char_Ignore equ b'11111111' ; skip this character _Char_Period equ b'01010101' ; period _Char_Comma equ b'01110011' ; comma _Char_Slash equ b'00110010' ; slash _Char_Equals equ b'00110001' ; equals, or BT _Char_Plus equ b'00101010' ; plus, or AR _Char_Question equ b'01001100' ; question mark _Char_Asterisk equ b'01000101' ; SK _Char_KN equ b'00110110' ; KN _Char_AS equ b'00101000' ; AS (wait) _Char_0 equ b'00111111' ; zero _Char_1 equ b'00101111' ; one _Char_2 equ b'00100111' ; two _Char_3 equ b'00100011' ; three _Char_4 equ b'00100001' ; four _Char_5 equ b'00100000' ; five _Char_6 equ b'00110000' ; six _Char_7 equ b'00111000' ; seven _Char_8 equ b'00111100' ; eight _Char_9 equ b'00111110' ; nine _Char_A equ b'00000101' ; A _Char_B equ b'00011000' ; B _Char_C equ b'00011010' ; C _Char_D equ b'00001100' ; D _Char_E equ b'00000010' ; E _Char_F equ b'00010010' ; F _Char_G equ b'00001110' ; G _Char_H equ b'00010000' ; H _Char_I equ b'00000100' ; I _Char_J equ b'00010111' ; J _Char_K equ b'00001101' ; K _Char_L equ b'00010100' ; L _Char_M equ b'00000111' ; M _Char_N equ b'00000110' ; N _Char_O equ b'00001111' ; O _Char_P equ b'00010110' ; P _Char_Q equ b'00011101' ; Q _Char_R equ b'00001010' ; R _Char_S equ b'00001000' ; S _Char_T equ b'00000011' ; T _Char_U equ b'00001001' ; U _Char_V equ b'00010001' ; V _Char_W equ b'00001011' ; W _Char_X equ b'00011001' ; X _Char_Y equ b'00011011' ; Y _Char_Z equ b'00011100' ; Z ; These are used by the serial comm routines for timing. Note that ; slower speeds may not work well because the delays might be larger ; than 255, requiring the use of the prescalar. _Clkspd equ 1000000 ;external clock / 4 _BaudRate equ 9600 ;desired baud rate _Period equ (_Clkspd/_BaudRate) ;clock cycles / bit _StartRxDelay equ (_Period/2 - 15)/3 ;this is how long to ;wait to get to the ;middle of the start ;bit. This is loops, ;not clock cycles. _BitRxDelay equ 277 - _Period ;this is what to load ;into TMR0 for correct ;interval between bits ;on RX _BitTxDelay equ 285 - _Period ;this is what to load ;into TMR0 for correct ;interval between bits ;on TX _StopTxDelay equ 272 - _Period ;this is what to load ;into TMR0 for correct ;interval betweeen last ;bit and stop bit on TX ; constants for timing the CW: _MSDelay equ (_Clkspd/10000) ;number of times to execute the ;delay loop to get a MS delay. ;assumes 10 clock cycles per ;trip through the delay loop. ; constants for PORTA: _TX equ 1 ;serial output is PORTA,1 _CW equ 2 ;CW out is PORTA,2 _INVCW equ 3 ;inverted CW out is PORTA,3 ; constants for PORTB: _RX equ 0 ;PORTB,0 receives the serial input _Mode equ 1 ;mode switch is PORTB,1 _Dit equ 2 ;dit paddle is PORTB,2 _Dah equ 3 ;dah paddle is PORTB,3 ; constants for paddle keyer (used in KeyerFlags): _DitPressed equ 0 ;if bit 0 set, dit paddle was ;pressed _DahPressed equ 1 ;if bit 1 set, dah paddle was ;pressed _BothPressed equ 2 ;if bit 2 set, both paddles are ;pressed simultaneously _DitInLast equ 3 ;if bit 3 set, last paddle pressed ;was a dit (else a dah) _DitOutLast equ 4 ;if bit 4 set, last thing sent was ;a dit (else a dah) ; memory locations used by serial send/receive routines: cblock 0x0C BitCount ;number of bits left to send or ;receive, not including start & stop ;bits RXChar ;received character while being received RXBuff ;most recently received character TXChar ;character to transmit SerialReg ;status register: ;bit 0: on if character has been ; received ;bit 1: on if busy with RX/TX ;bit 2: on if sending, off if receiving ;bit 3: on if next bit to send is stop bit WSave ;copy of W register SSave ;copy of the Status register ; memory locations used by CW sending and timing: MSDelay ;holds loop counter for MS delay loop. DitLen ;milliseconds per dit. DitWordLen ;milliseconds per dit for intercharacter/word spacing DitLoopIndex ;loop index for WaitDit. BufCount ;number of unsent characters in the buffer CharToSend ;CW character being sent CharBitCount ;counter for bits in the CW character to send KeyerFlags ;flags for manual keyer: ;bit 0: on means dit paddle was pressed ;bit 1: on means dah paddle was pressed ;bit 2: on means both paddles pressed simultaneously ;bit 3: on means last thing sent was a dit (off means dah) ;bit 4-7: not used CharBuf ;beginning of the buffer which holds characters to send ; DO NOT PUT ANY MORE VARIABLES AFTER CharBuf--THE CODE ASSUMES THAT ALL RAM AFTER ; CharBuf IS FOR THE CHARACTER BUFFER. IF YOU NEED TO ADD MORE VARIABLES, ADD THEM ; BEFORE CharBuf. endc ; more constants to set size of character buffer: _MaxChars equ (0x4F-CharBuf+1) ;max characters to hold in charbuf org 0x00 goto Main org 0x04 ;-------------------------------------------------------------------- ;-----Serial Communication Routines---------------------------------- ;-------------------------------------------------------------------- ; ; The serial comm routines generate 1 start bit, 8 data bits, 1 stop bit, ; no parity. The baud rate is determined by the delay programmed ; into the onboard timer. The sending and receiving is interrupt driven, ; meaning other tasks can be carried on while the characters are being ; sent and received. ; ;-----Main Interrupt Routine----------------------------------------- ; ; for timing: 4 cycles from interrupt time to get here. ; Save the W and STATUS registers: Int movwf WSave swapf STATUS,W ;use swapf to prevent any movwf SSave ;status flags from being changed ; cycles so far since interrupt occurred: 7 ; Okay, I'm doing something goofy here. Instead of actually checking ; the overflow bits themselves, I'm using the enable bits to determine ; which interrupt occurred. I can do this because there should only ; ever be one type of interrupt active at a time. The problem with ; checking the overflow bits is that the T0IF bit can get set even ; if that interrupt is disabled. ; Check first for a timer overflow interrupt. The overflow bit gets ; set even if the interrupt is disabled. btfsc INTCON,T0IE goto DoBit ;we're in the middle of sending or ;receiving ; 10 cycles executed on entry to DoBit ; If not a timer overflow interrupt, check for external interrupt: btfsc INTCON,INTE ;RB0 is our receive line and it goto StartRX ;generates an interrupt on a high- ;to-low transition ; 12 cycles executed on entry to StartRX ; Else, must be something we don't care about: ;do nothing for now ; Restore the W and STATUS registers: Restore swapf SSave,W movwf STATUS swapf WSave,F swapf WSave,W retfie ;------end Main Interrupt Routine------------------------------------ ;------Subroutine SerSetup------------------------------------------- SerSetup ; set up the option register for internal counting, WDT disabled, ; no prescaler. clrf TMR0 bsf STATUS,RP0 clrwdt ;set bits in OPTION_REG to movlw b'10001000' ;enable internal clock counting, movwf OPTION_REG ;disable watchdog timer. bcf STATUS,RP0 ;switch to bank 0 ; set the output line to idle (high) bsf PORTA,_TX ;set the output line to idle ;(high) state movlw b'00000001' movwf PORTB ; enable the external interrupt via RB0 movlw b'10010000' ;set bits in INTCON to enable movwf INTCON ;external interrupt ; initialize the SerialReg: clrf SerialReg return ;------end SetSetup-------------------------------------------------- ;------Subroutine StartRX-------------------------------------------- ; ; This subroutine is called by the main interrupt routine when an ; external interrupt on RB0 occurs. This means we're receiving the ; start bit for a character. We want to enable the external TMR0 ; interrupt and prepare to receive the character. StartRX ; wait halfway through the bit to see if it's real: bcf INTCON,INTF ;clear the interrupt movlw _StartRxDelay movwf BitCount ;this is the 15th instruction since ;the interrupt. Note--we're using ;BitCount for this loop purely for ;convenience. Usually it's used to ;actually count the bits we TX/RX. RXWait decfsz BitCount,F ;this loop takes 3 times the initial goto RXWait ;value of BitCount clock cycles ; now we should be at the middle of the start bit. Is the input still ; low? If not, goto Restore and ignore this interrupt. btfsc PORTB,_RX goto Restore ; if we get to here it must really be the start bit. Load TMR0, ; disable the external interrupt, and enable the TMR0 interrupt. ; load up the appropriate delay to get us to the middle of the ; first bit: movlw _BitRxDelay movwf TMR0 ;4 cycles from read of PORTB movlw b'00100000' movwf INTCON ; set the SerialReg to indicate that the routines are busy getting ; a character: movlw b'00000010' movwf SerialReg ; initialize BitCount: movlw 8 movwf BitCount ; okay, now we return. goto Restore ;------end StartRX--------------------------------------------------- ;------DoBit--------------------------------------------------------- ; ; sends or receives the next bit. Bits are sent/received from least ; to most significant bit. DoBit ; clear the TMR0 overflow interrupt flag: bcf INTCON,T0IF ; Are we receiving? btfsc SerialReg,2 goto Sending ; check to see if we're receiving the stop bit: movf BitCount,F btfsc STATUS,Z goto GetStopBit ; if we get to here, we're in the middle of receiving. Get the next ; bit: (16 cycles to get to the next instruction from the start of ; the interrupt). rrf PORTB,W ;rrf PORTB into W. This sets ;the carry bit if RB0 was high. rrf RXChar,F ;doing a rrf on RXChar brings ;in the carry bit to the MSB. ; Decrement the bit counter. decf BitCount,F ; reload TMR0 for the next interrupt, and ; go to the end of the interrupt routine. movlw _BitRxDelay movwf TMR0 ;21 cycles from start of interrupt goto Restore ; if we get to here it's because we need to check for the stop bit. GetStopBit btfss PORTB,_RX ;is the RX line low? If so, it's not goto Done ;the stop bit. Otherwise, set the movlw b'00000001' ;SerialReg to show a character has movwf SerialReg ;been received movf RXChar,W ;copy the received character to RXBuff movwf RXBuff goto Done ; We got here because we're sending. ; check to see if we're finished sending the stop bit: Sending btfsc SerialReg,3 goto Done ; check to see if we need to send the stop bit: movf BitCount,F btfsc STATUS,Z ;18th cycle goto SendStopBit ; if we get to here, we're in the middle of sending. Send the next ; bit: (16 cycles to get to the next instruction from the start of ; the interrupt). rrf TXChar,F ;doing rrf on TXChar puts the btfss STATUS,C ;least significant bit in the goto SendZero ;carry flag. nop bsf PORTA,_TX ;if carry is set, send a one. goto EndDoBit ;PORTA,_TX is set on the 24th cycle SendZero bcf PORTA,_TX ;otherwise, send a zero. (24th cycle) nop ;nop's are for taking the same time nop ;to get to reloading TMR0 as for when ;a one is sent. ; Decrement the bit counter. EndDoBit decf BitCount,F ; reload TMR0 for the next interrupt, and ; go to the end of the interrupt routine. movlw _BitTxDelay movwf TMR0 ;29th cycle goto Restore ; Here we need to send the stop bit, turn off the TMR0 interrupt, ; turn on the external interrupt, and set the SerStatus register ; flags appropriately. SendStopBit nop nop nop bsf PORTA,_TX ;no. Send the stop bit. (24th cycle) bsf SerialReg,3 ;set the "sending stop bit" flag ; reload TMR0 for the next interrupt, and ; go to the end of the interrupt routine. movlw _StopTxDelay movwf TMR0 ;27th cycle goto Restore ; we're completely done sending or receiving. Clean up. Done movlw b'00010000' ;set bits in INTCON to enable movwf INTCON ;external interrupt movlw b'00000001' andwf SerialReg,F ;clear the busy bits in SerialReg goto Restore ;------end DoBit----------------------------------------------------- ;------Subroutine SendChar------------------------------------------- ; ; This is not called by the interrupt handler. Rather, it activates ; the interrupts needed to send it. Put the character to be sent in ; the TXChar file register before calling this subroutine. ; SendChar ; send the start bit: bcf PORTA,_TX ; set the SerStatus to indicate that the routines are busy sending ; a character: movlw b'00000110' movwf SerialReg ; load up TMR0 so it overflows at the right time. nop ;for timing movlw _BitTxDelay movwf TMR0 ;5th cycle after write to PORTA ; clear the external interrupt flag, disable the external interrupt, ; and enable the TMR0 interrupt. movlw b'10100000' movwf INTCON ; set the BitCount for the eight bits to send: movlw 8 movwf BitCount return ;------end SendChar-------------------------------------------------- ;------begin GetAChar------------------------------------------------ GetAChar call Idle btfss SerialReg,0 ;wait for a character to be received goto GetAChar bcf SerialReg,0 return ;------end GetAChar-------------------------------------------------- ;------begin SendAChar----------------------------------------------- SendAChar call SendChar WaitToFinish call Idle btfsc SerialReg,1 ;wait for the character to be sent goto WaitToFinish return ;------end SendAChar------------------------------------------------- ;------begin Idle---------------------------------------------------- ; ; Idle should be called whenever the chip is waiting for something ; to happen (waiting for a character to be sent or received, for ; example). Currently, Idle doesn't do anything. Idle return ;------end Idle------------------------------------------------------ ;------begin WaitMS-------------------------------------------------- ; ; WaitMS is an approximate millisecond delay. WaitMS movlw _MSDelay movwf MSDelay RepeatWaitMS nop nop nop nop nop nop nop decfsz MSDelay,F goto RepeatWaitMS return ;------end WaitMS---------------------------------------------------- ;------begin SamplePaddles------------------------------------------- ; ; SamplePaddles checks the status of the paddles. SamplePaddles btfsc PORTB,_Dit goto DahTest btfsc KeyerFlags,_DitPressed ;if DitPressed already set, goto DahTest ;don't reset DitInLast bsf KeyerFlags,_DitPressed bsf KeyerFlags,_DitInLast DahTest btfsc PORTB,_Dah goto DoneSample btfsc KeyerFlags,_DahPressed ;if DahPressed already set, goto DoneSample ;don't reset DitInLast bsf KeyerFlags,_DahPressed bcf KeyerFlags,_DitInLast DoneSample return ;------end SamplePaddles--------------------------------------------- ;------begin WaitDit------------------------------------------------- ; ; WaitDit is a delay loop which times dits. WaitDit movf DitLen,W movwf DitLoopIndex RepeatWaitDit call WaitMS decfsz DitLoopIndex,F goto RepeatWaitDit ; check the paddle states: call SamplePaddles return ;------end WaitDit--------------------------------------------------- ;------begin WaitDah------------------------------------------------- ; ; WaitDah is a delay loop which times dahs. Dahs are three times the ; length of dits. WaitDah call WaitDit call WaitDit call WaitDit return ;------end WaitDah--------------------------------------------------- ;------begin WaitWordDit--------------------------------------------- ; ; WaitWordDit is a delay loop which times dits based on intercharacter ; spacing. WaitWordDit movf DitWordLen,W movwf DitLoopIndex RepeatWaitWordDit call WaitMS decfsz DitLoopIndex,F goto RepeatWaitWordDit return ;------end WaitWordDit----------------------------------------------- ;------begin WaitWordDah--------------------------------------------- ; ; WaitWordDah is a delay loop which times dahs based on intercharacter ; spacing. WordDahs are three times the length of Worddits. WaitWordDah call WaitWordDit call WaitWordDit call WaitWordDit return ;------end WaitWordDah----------------------------------------------- ;------begin FillBuffer---------------------------------------------- FillBuffer movlw 0x00 ;clear the buffer character count movwf BufCount movlw CharBuf movwf FSR FillBufferLoop call GetAChar ;wait for a character movf RXBuff,W ;move the rx char into W sublw 0x0D ;is this a CR (end of characters) command? btfsc STATUS,Z goto SendString ;if yes, go to send the string. movf BufCount,W ;check to see if the buffer's full. if so, sublw _MaxChars ;don't do anything. btfsc STATUS,Z goto FillBufferLoop movf RXBuff,W ;else, add it to the buffer. move the rx char into W movwf INDF ;store it in the character buffer using indirect ;addressing incf BufCount,F ;increment the counters incf FSR,F goto FillBufferLoop ;------end FillBuffer------------------------------------------------ ;------begin SendCWDit----------------------------------------------- ; ; actually sends the dit over the radio SendCWDit bsf PORTA,_CW ; turn the bit line on bcf PORTA,_INVCW ; turn on LED call WaitDit ; wait the appropriate amount of time bcf PORTA,_CW ; turn the bit line off bsf PORTA,_INVCW ; turn off LED call WaitDit ; wait for space between dits and dahs bcf KeyerFlags,_DitPressed ;clear the paddle flag bsf KeyerFlags,_DitOutLast ;set "last out was dit" flag return ;------end SendCWDit------------------------------------------------- ;------begin SendCWDah----------------------------------------------- ; ; actually sends the dah over the radio SendCWDah bsf PORTA,_CW ; turn the bit line on bcf PORTA,_INVCW ; turn on LED call WaitDah ; wait the appropriate amount of time bcf PORTA,_CW ; turn the bit line off bsf PORTA,_INVCW ; turn off LED call WaitDit ; wait for space between dits and dahs bcf KeyerFlags,_DahPressed ;clear the paddle flag bcf KeyerFlags,_DitOutLast ;clear "last out was dah" flag return ;------end SendCWDah------------------------------------------------- ;------begin SendString---------------------------------------------- SendString ;send the characters in the buffer movlw CharBuf movwf FSR movf BufCount,F ;do this to check for zero in BufCount btfsc STATUS,Z ;if zero, we're done sending. Go to DoneSending. goto DoneSending SendStringLoop ; check to see if another serial character has been received. If so, stop sending ; and reset everything. Also stop if the user depresses the paddle. btfsc SerialReg,0 ;see if a character has been received goto DoneSending ;if so, quit sending. btfss PORTB,_Dit ;dit paddle depressed? goto DoneSending ;if so, quit sending. btfss PORTB,_Dah ;dah paddle depressed? goto DoneSending ;if so, quit sending. call SendCWChar ;else, send the character. incf FSR,F ;set for the next character. decf BufCount,F ;reduce the number of characters remaining by one btfsc STATUS,Z ;if zero, we're done sending. Go to DoneSending. goto DoneSending goto SendStringLoop DoneSending movlw 0x00 ;clear the buffer character count movwf BufCount movlw CharBuf ;reset the character buffer pointer movwf FSR movlw 'r' ;send the acknowledgement. movwf TXChar call SendAChar goto MainLoop ;------end SendString------------------------------------------------ ;------begin SendCWChar---------------------------------------------- ; get the character to send: SendCWChar movlw 0x20 ;we'll subtract 32 from the ASCII value ;since we're only interested in characters ;starting with ASCII 32 (space) subwf INDF,F ;subtract 32 from the next character ASCII value btfss STATUS,C ;if carry is set, the result is zero or positive return ;else, return & get next character (because the ;ASCII value of the character was less than 32 and ;therefore of no interest) movlw 0x5B subwf INDF,W ;now check to see if the value is too high (over 122, ;except we already subtracted 32, so check for over 90) btfsc STATUS,C ;if clear, result is negative (ASCII value is too high) return ;else, it's okay. movlw HIGH Table ;preset the PCLATH for the computed goto movwf PCLATH movf INDF,W ;now put the value in W for table lookup call Table ;return value will be the bit string to send movwf CharToSend ;store the bit string in CharToSend ; is this a special character? Special characters have MSB on. btfsc CharToSend,7 goto SendSpecialChar ; no. find the Start bit. Start looking at bit 6 and continue down until a 1 is encountered. ; the rest of the bits to send will follow. rlf CharToSend,F ; get rid of bit 7. movlw 0x08 ; load the bit counter so we know when we're out of bits. movwf CharBitCount bcf STATUS,C FindCWStartBit decf CharBitCount,F ; count the bits we check. rlf CharToSend,F ; look at the next bit. is it a 1? if so, that's our start bit. btfss STATUS,C goto FindCWStartBit SendCWBit decfsz CharBitCount,F ; check to see if we're out of bits to send. goto SendNextCWBit goto DoneSendingCWBits SendNextCWBit rlf CharToSend,F ; get the next bit. 1 is a dah, 0 is a dit. btfss STATUS,C goto SendADit goto SendADah SendADit call SendCWDit ; send the dit goto SendCWBit ; do the next dit or dah SendADah call SendCWDah ; send the dah goto SendCWBit ; do the next dit or dah SendSpecialChar movlw b'10000000' ; check to see if it's a space subwf CharToSend,W btfss STATUS,Z ; is the subtraction result 0? goto CheckForIgnore ; no--see if it's a character we ignore call WaitWordDah ; yes. Wait two dahs and a dit, then do the call WaitWordDah ; next character. Use intercharacter word call WaitWordDit ; spacing. (NOTE: two dahs and a dit chosen return ; purely because it sounded good.) CheckForIgnore movlw 0xFF ; ignore character is 255 subwf CharToSend,W btfss STATUS,Z ; is the subtraction 0? goto CheckNextSpecialChar return CheckNextSpecialChar ; there aren't any more, so just return. return DoneSendingCWBits ; wait a dah length for the next character. call WaitWordDah ; use the intercharacter spacing return ;------end SendCWChar------------------------------------------------ ;------begin SetCWSpeed---------------------------------------------- SetCWSpeed ;we're expecting two more bytes: the first one is for the dit length ;and the second one is the interword spacing dit length (to allow ;for Farnsworth-style spacing). call GetAChar ;get the first byte. movf RXBuff,W ;store it. movwf DitLen call GetAChar ;get the interword spacing byte. movf RXBuff,W movwf DitWordLen ;store it. ; send the acknowledgement: movlw 'r' movwf TXChar call SendAChar goto MainLoop ;------end SetCWSpeed------------------------------------------------ ;------begin SendVersion--------------------------------------------- ; send a string with the version in it. The string comes from EEPROM ; memory and is null-terminated. The null terminator is not sent. The ; protocol dictates that the string sent is terminated by a CR, which ; is sent. SendVersion bcf STATUS,RP0 clrf EEADR ;the string we want starts at the ;beginning of EEPROM memory. GetNextVersionChar bsf STATUS,RP0 bsf EECON1,RD bcf STATUS,RP0 movf EEDATA,W btfsc STATUS,Z ;if the character in W is null, don't ;send any more. goto MainLoop movwf TXChar call SendAChar incf EEADR,F goto GetNextVersionChar ;------end SendVersion----------------------------------------------- ;------begin Paddle-------------------------------------------------- ; This section of code handles the keyer for the paddle. The iambic ; keying logic was adapted from the K8 keyer source code by K1EL. Paddle call SamplePaddles ;get paddle states first time through PaddleLoop btfss PORTB,_Dit ;are both pressed right now? btfsc PORTB,_Dah goto NotBoth ;no. check for other conditions bsf KeyerFlags,_BothPressed ;both are pressed now. set the flag. ;we'll need later for mode B Iambic btfss KeyerFlags,_DitOutLast ;was last a dit? goto DitPaddle ;no--send a dit goto DahPaddle ;yes--send a dah NotBoth btfss KeyerFlags,_BothPressed ;were both pressed before? goto Single ;no--check for single paddle presses bcf KeyerFlags,_BothPressed ;yes, but not now--clear the flag btfsc PORTB,_Mode ;are we in mode B? goto Iambic ;yes--send the trailing dit/dah goto PaddleDone ;no--finish up. ; were both paddles pressed during last dit/dah (not necessarily at the same time)? Single btfsc KeyerFlags,_DitPressed ;dit pressed? btfss KeyerFlags,_DahPressed ;and dah? goto SinglePress ;nope. Only one. Find out which one. btfsc KeyerFlags,_DitInLast ;if dit paddle was pressed last, dah goto DahPaddle ;was pressed first, so send dah. goto DitPaddle ;else, send a dit ; only one (or no) paddle was pressed. Find out which and send the dit/dah. SinglePress btfsc KeyerFlags,_DitPressed ;dit pressed? goto DitPaddle ;yes--send dit btfsc KeyerFlags,_DahPressed ;dah pressed goto DahPaddle ;yes--send dah goto PaddleDone ;nothing pressed. Finish. DitPaddle call SendCWDit goto PaddleLoop DahPaddle call SendCWDah goto PaddleLoop PaddleDone clrf KeyerFlags ;all done. clear the flags and return return ;------end Paddle---------------------------------------------------- ;------Main Program-------------------------------------------------- Main ; set up the ports as inputs and outputs as needed. bsf STATUS,RP0 ;switch to bank 1 movlw 0xFF movwf TRISB ;make Port B input movlw 0x00 movwf TRISA ;make Port A output bcf STATUS,RP0 clrf PORTA bcf PORTA,_CW ; set CW line low bsf PORTA,_INVCW ; turn off LED call SerSetup ;set up serial comm routines & int. ; set default for the CW speed: movlw 0x40 movwf DitLen movlw 0x40 movwf DitWordLen ; clear the flags for manual keyer: clrf KeyerFlags ;------begin MainLoop------------------------------------------------ ; this main program simply waits for characters to be received, then ; calls the handler for the command indicated by the received character. It ; also checks the paddle inputs and sends dits/dahs if the paddles are ; depressed. MainLoop call Paddle ;handle any manual keying btfss SerialReg,0 ;has character been received? goto MainLoop ;no, loop again bcf SerialReg,0 ;got a char, so clear the flag movf RXBuff,W ;move the rx char into W sublw '<' ;is this the "fill buffer" command? btfsc STATUS,Z goto FillBuffer ;if yes, go to the fill buffer loop movf RXBuff,W ;move the rx char into W sublw '>' ;is this the "set speed" command? btfsc STATUS,Z goto SetCWSpeed ;if yes, go to the set CW speed code movf RXBuff,W ;move the rx char into W sublw '^' ;is this the "version" command? btfsc STATUS,Z goto SendVersion ;if yes, go to SendVersion goto MainLoop ;------end MainLoop-------------------------------------------------- ;------begin Table--------------------------------------------------- org 0x380 ;begin the table on a page boundary Table addwf PCL,F ;compute the GOTO retlw _Char_Space retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Asterisk retlw _Char_Plus retlw _Char_Comma retlw _Char_AS retlw _Char_Period retlw _Char_Slash retlw _Char_0 retlw _Char_1 retlw _Char_2 retlw _Char_3 retlw _Char_4 retlw _Char_5 retlw _Char_6 retlw _Char_7 retlw _Char_8 retlw _Char_9 retlw _Char_KN retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Equals retlw _Char_Ignore retlw _Char_Question retlw _Char_Ignore retlw _Char_A retlw _Char_B retlw _Char_C retlw _Char_D retlw _Char_E retlw _Char_F retlw _Char_G retlw _Char_H retlw _Char_I retlw _Char_J retlw _Char_K retlw _Char_L retlw _Char_M retlw _Char_N retlw _Char_O retlw _Char_P retlw _Char_Q retlw _Char_R retlw _Char_S retlw _Char_T retlw _Char_U retlw _Char_V retlw _Char_W retlw _Char_X retlw _Char_Y retlw _Char_Z retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_Ignore retlw _Char_A retlw _Char_B retlw _Char_C retlw _Char_D retlw _Char_E retlw _Char_F retlw _Char_G retlw _Char_H retlw _Char_I retlw _Char_J retlw _Char_K retlw _Char_L retlw _Char_M retlw _Char_N retlw _Char_O retlw _Char_P retlw _Char_Q retlw _Char_R retlw _Char_S retlw _Char_T retlw _Char_U retlw _Char_V retlw _Char_W retlw _Char_X retlw _Char_Y retlw _Char_Z ;------end Table----------------------------------------------------- ;------Version EEPROM------------------------------------------------ org 0x2100 de "Serial CW v1.19 (c) 2002 by NK0E",0x0D,0x00 ; Version 1.19 end