;************************************************************** ;* DDK.asm "DDS Development Kit" source code * ;* * ;* Author: Bruce Hall, W8BH * ;* Email: bhall66@gmail.com * ;* Version: 09-21-2010 * ;* Language: AVR Assembler * ;* Target: Atmel ATmega328/ATmega88 microcontroller * ;* * ;* Based on the original ATmega88.asm code by: * ;* W8DIZ Dieter (Diz) Gentzow, w8diz@tampabay.rr.com * ;************************************************************** .include "m328def.inc" ;use this for ATmega328 chip ;.include "m88def.inc" ;use this for ATmega88 chip ;************************************************************** ;* PROGRAM SETTINGS ;************************************************************** .equ DefTuneRate = 3 ;default tuning rate: 3= 1KHz step .equ NumPresets = 20 ;number of VFO presets (27 max) .equ NumMessages = 7 ;number of keyer msgs (7 max) .equ DefaultSpeed = 13 ;default code speed = 13 WPM .equ MaxSpeed = 30 ;highest WPM choice (30 max) .equ MinSpeed = 5 ;lowest WPM choice (5 min) ;************************************************************** ;* I/O PIN DEFINITIONS ;************************************************************** .equ Col2 = PB0 ;keypad column output .equ Col3 = PB1 ;keypad column output .equ Row4 = PB2 ;keypad row input .equ Row3 = PB3 ;keypad row input .equ Row2 = PB4 ;keypad row input .equ Row1 = PB5 ;keypad row input ; XTAL1 = PB6 ;crystal oscillator input ; XTAL2 = PB7 ;crystal oscillator input .equ LCD_DRS = PC0 ;LCD output .equ LCD_E = PC1 ;LCD output .equ LCD_CP = PC2 ;LCD output .equ LED = PC3 ;LED output .equ RPaddle = PC4 ;Paddle input .equ LPaddle = PC5 ;Paddle input ; RESET = PC6 ;not available (Reset line) ; NOPIN = PC7 ;not available (no pin) .equ DDSenable = PD0 ;DDS output .equ DDSclock = PD1 ;DDS output .equ STATE = PD2 ;Encoder input .equ Button = PD3 ;Encoder button input .equ PHASE = PD4 ;Encoder input .equ DDSdata = PD5 ;DDS output .equ KeyOut = PD6 ;Keyer output .equ Col1 = PD7 ;Keypad column output ;************************************************************** ;* REGISTER DEFINITIONS ;************************************************************** .def hold = r15 .def temp1 = r16 .def temp2 = r17 .def temp3 = r18 .def temp4 = r19 .def temp5 = r20 .def release = r21 .def StepRate = r22 .def press = r23 .def delay = r24 .def encoder = r25 ; XL = r26 ; XH = r27 ; YL = r28 ; YH = r29 ; ZL = r30 ; ZH = r31 ;*************************************************************** ;* SRAM MEMORY USAGE ;*************************************************************** .dseg .org $100 LCDrcve0: .byte 8 ;buffer for decimal freq readout rcve0: .byte 4 ;buffer for DDS freq numbers freq0: .byte 4 ;buffer for 1 freq numbers freq1: .byte 4 ;buffer for 10 freq numbers freq2: .byte 4 ;buffer for 100 freq numbers freq3: .byte 4 ;buffer for 1,000 freq numbers freq4: .byte 4 ;buffer for 10,000 freq numbers freq5: .byte 4 ;buffer for 100,000 freq numbers freq6: .byte 4 ;buffer for 1,000,000 freq numbers freq7: .byte 4 ;buffer for 10,000,000 freq numbers cwdelay: .byte 1 ;timing delay for morse code output VFOmn: .byte 4 ;buffer for VFO magic# preset: .byte 1 ;buffer for current frequency preset # mode: .byte 1 ;0=tuning mode, 1=load presets ;2=save preset, 3=keyer memories ;4=code speed, 5=IF offset ch: .byte 1 ;ASCII character buffer msgnum: .byte 1 ;message number msgbuf: .byte 33 ;keyer message buffer flags: .byte 1 ;bit0 = hold in progress; ;bit1 = last element sent 'dah' ;bit2 = alternate submode params: ;30-byte parameter block starts here speed: .byte 1 ;code speed, in words per minute tunerate: .byte 1 ;default tuning rate (6=MHz,3=KHz,0=Hz) IFmode: .byte 1 ;mixer (0=none, 1=OF+VFO, 2=OF-VFO, 3=VFO-OF) IFfreq: .byte 8 ;intermediate frequency IFmn: .byte 4 ;buffer for IF magic# startfreq: .byte 8 ;power-on operating frequency unused: .byte 7 ;end of parameter block ;*************************************************************** ;* INTERRUPT VECTOR TABLE ;*************************************************************** ; use RJMP instructions with ATmega88 chips ; use JMP instructions for ATmega328 chips .cseg .org $000 jmp RESET ;Program Start .org INT0addr jmp EINT0 ;External Interrupt Request 0 .org INT1addr jmp EINT1 ;External Interrupt Request 1 .org OVF0addr jmp OVF0 ;Timer/Counter0 Overflow .org OVF2addr jmp OVF2 ;Timer/Counter2 overflow .org INT_VECTORS_SIZE ;*************************************************************** ;* PROGRAM START - INITIALIZATION ;*************************************************************** RESET: ; STACK POINTER SETUP ldi temp1,low(RAMEND) out SPL,temp1 ldi temp1,high(RAMEND) out SPH,temp1 ; PORT B SETUP ldi temp1,$03 ;binary 0000.0011 out DDRB,temp1 ;set PB0,1 as output ldi temp1,$3C ;binary 0011.1100 out PORTB,temp1 ;set pullups on PB2-5 ; PORT C SETUP ldi temp1,$0F ;binary 0000.1111 out DDRC,temp1 ;set PC0-PC3 as outputs ldi temp1,$38 ;binary 0011.1000 out PORTC,temp1 ;set pullups on PC4-5 & LED off ; PORT D SETUP ldi temp1,$E3 ;binary 1110.0011 out DDRD,temp1 ;set PD0,1,5,6,7 outputs ldi temp1,$BF ;binary 1011.1111 out PORTD,temp1 ;no pullup on PD6 (keyer out) ; VARIABLES clr temp1 sts mode,temp1 ;start mode0 = normal operation sts flags,temp1 ;nothing to flag yet sts preset,temp1 ;start with first preset sts speed,temp1 ;start with default code speed sts msgnum,temp1 ;start with no pending messages clr release ;no button events on startup clr hold ;no holds on startup clr encoder ;clear encoder interrupt counts clr press ;clear press interrupt counts ; COUNTER/TIMER SETUP ldi temp1, $05 ;set timer0 prescale divisor to 1024 out TCCR0B,temp1 ;using 20.48 XTAL = 50uS ldi temp1, $01 sts TIMSK0,temp1 ;enable TIMER0 overflow interrupts ldi temp1, $07 ;set timer2 prescale divider to 1024 sts TCCR2B,temp1 ldi temp1, $01 ;enable TIMER2 overflow interrupt sts TIMSK2,temp1 ; INTERRUPT SETUP ldi temp1,0b00001011 ;int1 on falling edge & int0 on rising edge sbic PIND,STATE ;test state of encoder ldi temp1,0b00001010 ;int1 on falling edge & int0 on falling edge sts EICRA,temp1 ldi temp1,$03 out EIMSK,temp1 ;enable int0 and int1 interrupts sei ;global all interrupt enable ; EEPROM STARTUP rcall EECheck ;make sure EEPROM is initialized sbis PinD,Button ;is button down on startup? rcall EEProgram ;yes, so initialize EEPROM rcall EELoadParams ;get stored code speed, IF, tune rate, etc ; AD9834 DDS STARTUP rcall DDSReset ;initialize AD9834 chip rcall Default_Freq ;move magic#'s to SRAM rcall LoadFreq ;get a new start freq from EEPROM, if any rcall ZeroMagic rcall BuildMagic rcall FreqOut2 ;output freq bits to DDS chip rcall DDSOutputA ;turn on DDS output ; SYSTEM READY ldi temp1,2 ;blink LED 2x to show system is working rcall BLINK_LED rcall INIT_LCD ;initialize LCD display ldi temp1,0 ;mode 0 = tuning mode rcall ChangeMode ;setup display for tuning mode ;*************************************************************** ;* W8BH - MAIN PROGRAM LOOP ;*************************************************************** MAIN: rcall CheckEncoder ;check for encoder action rcall CheckButton ;check for button events rcall CheckHold ;check for button holds rcall CheckKey ;check for paddle action rcall CheckKeypad ;check for keypad action rjmp Main ;loop forever CHECKENCODER: tst encoder ;any encoder requests? breq ce9 ;no, so quit lds temp1,mode cpi temp1,0 ;are we in normal mode (0)? brne ce1 ;no, skip rjmp EncoderMode0 ;yes, handle it ce1: cpi temp1,1 ;are we in mode 1? brne ce2 ;no, skip rjmp EncoderMode1 ;yes, handle it ce2: cpi temp1,2 ;are we in mode 2? brne ce3 ;no, skip rjmp EncoderMode2 ;yes, handle it ce3: cpi temp1,3 ;are we in mode 3? brne ce4 ;no, skip rjmp EncoderMode3 ;yes, handle it ce4: cpi temp1,4 ;are we in mode 4? brne ce5 ;no, skip rjmp EncoderMode4 ;yes, handle it ce5: cpi temp1,5 ;are we in mode 5? brne ce6 ;no, skip rjmp EncoderMode5 ;yes, handle it ce6: cpi temp1,6 ;are we in mode 6? brne ce7 ;no, skip rjmp EncoderMode6 ;yes, handle it ce7: cpi temp1,7 ;are we in mode 7? brne ce8 ;no, skip rjmp EncoderMode7 ;yes, handle it ce8: ce9: ret CHECKBUTTON: tst encoder ;any encoder requests? brne cb4 ;wait until encoder is done tst press ;any button down events? breq cb1 ;no, check for button up events? rcall ButtonTapDown ;do the button down dec press ;one less button tap to do cb1: tst release ;any button up events? breq cb4 ;no, so quit lds temp1,flags sbrs temp1,0 ;is there a hold in progress? rjmp cb2 ;no cbr temp1,$01 ;yes, remove hold flag sts flags,temp1 ;save un-held state rcall ButtonHoldUp ;do hold release rjmp cb3 cb2: rcall ButtonTapUp ;to the Tap Release cb3: dec release ;one less release to do cb4: ret CHECKHOLD: tst hold ;any new hold event? brpl ck1 ;no, so quit lds temp1,flags sbr temp1,$01 ;flag the hold sts flags,temp1 ;save it rcall ButtonHoldDown ;do the hold event clr hold ;reset = allow future holds ck1: ret BUTTONTAPUP: lds temp1,mode ;get mode cpi temp1,0 ;are we in mode0? brne tu1 ;no, skip rjmp TapUp0 ;yes, handle it tu1: cpi temp1,1 ;are we in mode1? brne tu2 ;no, skip rjmp TapUp1 ;yes, handle it tu2: cpi temp1,2 ;are we in mode2? brne tu3 ;no, skip rjmp TapUp2 ;yes, handle it tu3: cpi temp1,3 ;are we in mode3? brne tu4 ;no, skip rjmp TapUp3 ;yes, handle it tu4: cpi temp1,4 ;are we in mode4? brne tu5 ;no, skip rjmp TapUp4 ;yes, handle it tu5: cpi temp1,5 ;are we in mode5? brne tu6 ;no, skip rjmp TapUp5 ;yes, handle it tu6: ret BUTTONTAPDOWN: lds temp1,mode ;get mode cpi temp1,0 ;are we in mode0? brne td1 ;no, skip rjmp TapDown0 ;yes, handle it td1: cpi temp1,1 ;are we in mode1? brne td2 ;no, skip ; rjmp TapDown1 ;yes, handle it td2: cpi temp1,2 ;are we in mode2? brne td3 ;no, skip ; rjmp TapDown2 ;yes, handle it td3: cpi temp1,3 ;are we in mode3? brne td4 ;no, skip rjmp TapDown3 ;yes, handle it td4: cpi temp1,4 ;are we in mode4? brne td5 ;no, skip ; rjmp TapDown4 ;yes, handle it td5: cpi temp1,5 ;are we in mode5? brne td6 ;no, skip rjmp TapDown5 ;yes, handle it td6: cpi temp1,6 ;are we in mode6? brne td7 ;no, skip rjmp TapDown6 ;yes, handle it td7: cpi temp1,7 ;are we in mode7? brne td8 ;no, skip rjmp TapDown7 ;yes, handle it td8: ret BUTTONHOLDUP: lds temp1,mode ;get mode cpi temp1,0 ;are we in mode0? brne hu1 ;no, skip rjmp HoldUp0 ;yes, handle it hu1: cpi temp1,1 ;are we in mode1? brne hu2 ;no, skip rjmp HoldUp1 ;yes, handle it hu2: cpi temp1,2 ;are we in mode2? brne hu3 ;no, skip rjmp HoldUp2 ;yes, handle it hu3: cpi temp1,3 ;are we in mode3? brne hu4 ;no, skip rjmp HoldUp3 ;yes, handle it hu4: cpi temp1,4 ;are we in mode4? brne hu5 ;no, skip rjmp HoldUp4 ;yes, handle it hu5: cpi temp1,5 ;are we in mode5? brne hu6 ;no, skip rjmp HoldUp5 ;yes, handle it hu6: cpi temp1,6 ;are we in mode6? brne hu7 ;no, skip rjmp HoldUp6 ;yes, handle it hu7: cpi temp1,7 ;are we in mode7? brne hu8 ;no, skip rjmp HoldUp7 ;yes, handle it hu8: ret BUTTONHOLDDOWN: lds temp1,mode ;get mode cpi temp1,0 ;are we in mode0? brne hd1 ;no, skip rjmp HoldDown0 ;yes, handle it hd1: cpi temp1,1 ;are we in mode1? brne hd2 ;no, skip rjmp HoldDown1 ;yes, handle it hd2: cpi temp1,2 ;are we in mode2? brne hd3 ;no, skip rjmp HoldDown2 ;yes, handle it hd3: cpi temp1,3 ;are we in mode3? brne hd4 ;no, skip rjmp HoldDown3 ;yes, handle it hd4: cpi temp1,4 ;are we in mode4? brne hd5 ;no, skip rjmp HoldDown4 ;yes, handle it hd5: cpi temp1,5 ;are we in mode5? brne hd6 ;no, skip rjmp HoldDown5 ;yes, handle it hd6: cpi temp1,6 ;are we in mode6? brne hd7 ;no, skip rjmp HoldDown6 ;yes, handle it hd7: cpi temp1,7 ;are we in mode7? brne hd8 ;no, skip rjmp HoldDown7 ;yes, handle it hd8: ret CHANGEMODE: ; call this routine with new mode in temp1 ; main action is to change the message on Line 1 sts mode,temp1 ;save the new mode cpi temp1,0 ;mode 0? brne cm1 ;no, skip ldi temp1,1 rcall DisplayLine1 ;yes, show normal title rjmp ShowTuning ;display tuning freq cm1: cpi temp1,1 ;mode 1? brne cm2 ;no, skip ldi temp1,2 rjmp DisplayLine1 ;yes, show mode 1 title cm2: cpi temp1,2 ;mode 2? brne cm3 ;no, skip ldi temp1,3 rjmp DisplayLine1 ;yes, show mode 2 title cm3: cpi temp1,3 ;mode 3? brne cm4 ;no, skip ldi temp1,4 rjmp DisplayLine1 ;yes, show mode 3 title cm4: cpi temp1,4 ;mode 4? brne cm5 ;no, skip ldi temp1,5 rjmp DisplayLine1 ;yes, show mode 4 title cm5: cpi temp1,5 ;mode 5? brne cm6 ;no, skip ldi temp1,6 rjmp DisplayLine1 ;yes, show mode 5 title cm6: cpi temp1,6 ;mode 6? brne cm7 ;no, skip ldi temp1,7 rjmp DisplayLine1 ;yes, show mode 6 title cm7: cpi temp1,7 ;mode 6? brne cm8 ;no, skip ldi temp1,8 rjmp DisplayLine1 ;yes, show mode 7 title cm8: ret QUICKBLINK: cbi PORTC,LED ;turn LED on ldi delay,15 ;keep on 20 ms rcall wait sbi PORTC,LED ;turn LED off ret WAITBUTTON: ; waits for button press tst press breq waitbutton ;loop until button pressed clr press ;ignore the press push temp1 ;save register ldi temp1,2 rcall Blink_LED ;user feedback pop temp1 ;restore register ret ENCODERVALUE: ; increment/decrement a value, depending on encoder rotation ; call with temp1 = current value ; temp2 = lower limit (0-254) ; temp3 = upper limit (0-255) ; returns with new value in temp1 tst encoder ;which way did encoder turn? brmi ev1 ;negative = CCW rotation cp temp1,temp3 ;CW rotation------------ brge ev2 ;hard stop at upper limit inc temp1 ;go to next higher preset rjmp ev2 ev1: cp temp2,temp1 ;CCW rotation------------ brge ev2 ;hard stop at lower limit dec temp1 ;go to next lower preset ev2: clr encoder ;ignore any more requests ret ;*************************************************************** ;* W8BH - MODE 0 (VFO TUNING) ROUTINES ;*************************************************************** ENCODERMODE0: ; This code taken from original program loop. ; Called when there is a non-zero value for encoder variable. ; Negative encoder values = encoder has turned CCW ; Positive encoder values = encoder has turned CW ; In mode 0, encoder should increase/decrease the DDS freq tst encoder brpl e02 ;which way did encoder rotate? inc encoder ;remove 1 negative rotation rcall DecFreq0 ;reduce displayed frequency cpi temp1,55 ;55 = all OK brne e01 rcall IncFreq0 ;correct freq. underflow rjmp e05 e01: rcall DecFreq9 ;reduce magic number rjmp e04 e02: dec encoder ;remove 1 positive rotation rcall IncFreq0 ;increase displayed frequency cpi temp1,55 ;55 = all OK brne e03 rcall DecFreq0 ;correct freq. overflow rjmp e05 e03: rcall IncFreq9 ;increase magic number e04: rcall FREQ_OUT ;update the DDS rcall ShowFreq ;display new frequency e05: rcall QuickBlink ret TAPDOWN0: ; This code taken from original program loop. ; Called when there is a non-zero value for press variable. ; Non-zero value = number of times button has been pressed ; In mode 0, button should advance cursor to the right dec StepRate ;advance cursor position variable brpl b01 ;position >= 0 (Hz position) ldi StepRate,7 ;no, so go back to 10MHz position b01: rcall ShowCursor rcall QuickBlink ;flash the LED ret TAPUP0: lds temp1,msgnum ;get message counter tst temp1 ;was a message number entered? breq t98 ;no rcall SendMorseMsg ;send the message clr temp1 ;done, so remove msg# sts msgnum,temp1 ;and save it. t98: ret HOLDDOWN0: ; Called when button has been held down for about 1.6 seconds. ; In mode 0, action should be to invoke mode1 = scrolling freq. presets ldi temp1,1 rcall ChangeMode ;go to next mode ret HOLDUP0: ; Called when entering this mode from another mode rcall ShowTuning ret ;*************************************************************** ;* W8BH - MODE 1 (SCROLL FREQUENCY PRESETS) ROUTINES ;*************************************************************** INITMODE1: ldi temp1,1 ;start with first preset sts preset,temp1 rcall EELoadMem ;get it from EEPROM rcall ClearLine2 rcall ShowPreset ;and show it on LCD ret ENCODERMODE1: ldi temp2,1 ;set lower limit ldi temp3,NumPresets ;set upper limit lds temp1,preset ;get current preset# rcall EncoderValue ;change preset up or down sts preset,temp1 ;save current preset# rcall EELoadMem ;get the preset in LCD buffer rcall ShowPreset ;and display it ret TAPUP1: rcall LoadNewFreq ;DDS output new frequency ldi temp1,0 rcall ChangeMode ;go to mode 0 = normal op. ret HOLDDOWN1: ldi temp1,2 rcall ChangeMode ret ;go to next mode (2) HOLDUP1: rcall InitMode1 ret ;*************************************************************** ;* W8BH - MODE 2 (SAVE NEW PRESET) ROUTINES ;*************************************************************** ENCODERMODE2: ldi temp2,1 ;set lower limit ldi temp3,NumPresets ;set upper limit lds temp1,preset ;get current preset# rcall EncoderValue ;change preset up/down sts preset,temp1 ;save current preset# rcall ShowPresetNum ;display preset number ret TAPUP2: lds temp1,preset rcall EESaveMem ;save preset to EEPROM ldi temp1,9 rcall DisplayLine2 ;display 'SAVED' ldi temp1,2 rcall Blink_LED ;blink for user feedback ldi temp1,0 rcall ChangeMode ;return to tuning mode ret HOLDDOWN2: ;called when leaving this mode ldi temp1,3 rcall ChangeMode ret HOLDUP2: ;called when this entering this mode ldi temp1,1 sts preset,temp1 ;start with first preset rcall ClearLine2 ;erase line 2 rcall ShowMemFreq ;show frequency on line2 rcall ShowPresetNum ;show preset number on line2 ret ;*************************************************************** ;* W8BH - MODE 3 (KEYER MESSAGES) ROUTINES ;*************************************************************** .equ space = $20 ;ASCII space character .equ ASCIImin = $20 ;lowest displayed char .equ ASCIImax = $7A ;ASCII 'z' character ;this mode has two submodes, which determine how the button ;and encoder controls behave. Bit2 of the flag variable ;determines which of the two submodes is active. By default, ;the 'Norm' submode is active ;Norm submode = scrolling list of keyers messages ;Alt submode = editable display of the current message ;temp5 is used to keep track of alt-mode cursor position SETALTMODE: lds temp1,flags ;ALT MODE = sbr temp1,$04 ;set flag bit2 sts flags,temp1 ;save it ret SETNORMMODE: lds temp1,flags ;NORM MODE = cbr temp1,$04 ;clear flag bit 2 sts flags,temp1 ;save it ret ENCODERMODE3: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp NormEncoder3 rjmp AltEncoder3 TAPUP3: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp NormTU3 rjmp AltTU3 HOLDDOWN3: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp NormHD3 rjmp AltHD3 HOLDUP3: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp NormHU3 rjmp AltHU3 TAPDOWN3: ret NORMENCODER3: ldi temp2,1 ;set lower limit ldi temp3,NumMessages ;set upper limit lds temp1,msgnum ;get current message # rcall EncoderValue ;update message # by encoder sts msgnum,temp1 ;save message # rcall ShowMsgIndex ;and display it ret NORMTU3: rcall SetAltMode ;alternate control behavior rcall InitAlt3 ;init submode display ret NORMHU3: ; called when entering this mode rcall ClearLine2 ldi temp1,1 ;start with 1st msg sts msgnum,temp1 rcall ShowMsgIndex ;show beginning of 1st msg ret NORMHD3: ; called when leaving this mode ldi temp1,4 rcall ChangeMode ret ALTENCODER3: ldi temp2,AsciiMin ;set character limits ldi temp3,AsciiMax lds temp1,ch ;get current character rcall EncoderValue ;update character sts ch,temp1 ;save it rcall LCDCHR ;and display it mov temp1,temp5 rcall SetCursor ;place cursor under char ret ALTTU3: lds temp1,ch ;get current character st Z+,temp1 ;store in message buffer inc temp5 ;update cursor position cpi temp5,32 ;did we pass end of msg? brlo ta2 ;no, continue sbiw Z,32 ;yes, reset msg pointer ldi temp5,0 ;reset cursor pointer ta2: mov temp1,temp5 rcall SetCursor ;advance cursor ld temp1,Z ;get next char from buffer tst temp1 ;is it 0? brne ta3 ;no, continue ldi temp1,$20 ;yes, convert to a space ta3: sts ch,temp1 ;buffer current character rcall LCDCHR ;output new char mov temp1,temp5 rcall SetCursor ;put cursor under char ret ALTHD3: rcall SaveMessage ;save message to EEPROM rcall SetNormMode ;normal control behavior ldi temp1,3 rcall ChangeMode ;init normal mode3 display ret AltHU3: ret INITALT3: rcall ClearDisplay lds temp1,msgnum ;get message# rcall DisplayMsg ;show current message rcall HomeCursor ldi temp5,0 ;track cursor position ldi ZH,high(msgbuf) ;point to message buffer ldi ZL,low(msgbuf) ld temp1,Z ;get first char of msg sts ch,temp1 ;buffer it ret SAVEMESSAGE: ; save current message to EEPROM ldi temp1,9 ;display 'SAVED' rcall DisplayLine1 lds temp1,msgnum ;load message# rcall SaveEEMsg ;save msg to EEPROM ldi temp1,2 rcall Blink_LED ret MARKEND: ; puts a zero character at the end of the message ; if the msg length is <32 characters ldi ZH,high(msgbuf+32) ;point to end of msg ldi ZL,low(msgbuf+32) ld temp1,-Z ;get last character cpi temp1,space ;is it used? brne me2 ;no, msg is full me1: ld temp1,-Z ;get prior char cpi temp1,space+1 ;is it a space/null? brlo me1 ;yes, keep looping clr temp1 ;found non-space char adiw Z,1 ;point to next char st Z,temp1 ;add terminating zero me2: ret ;*************************************************************** ;* W8BH - MODE 4 (SET CODE SPEED) ROUTINES ;*************************************************************** ENCODERMODE4: ldi temp2,MinSpeed-1 ;set speed limits ldi temp3,MaxSpeed lds temp1,speed ;get speed tst temp1 ;is it zero (straight key)? brne em1 ;no, skip ldi temp1,MinSpeed-1 ;yes, so next up is Min em1: rcall EncoderValue ;update speed based on encoder cpi temp1,MinSpeed ;did speed go below Min? brge em2 ;no, skip clr temp1 ;yes, speed=0 (straight key) em2: sts speed,temp1 ;save new speed value rcall ShowSpeed ;display speed ret TAPUP4: rcall GetCWDelay ;convert speed into timing delay rcall EESaveParams ;save code speed to EEPROM ldi temp1,9 rcall DisplayLine2 ;display 'SAVED' ldi temp1,2 rcall Blink_LED ;blink for user feedback ldi temp1,0 rcall ChangeMode ;return to tuning mode ret HOLDDOWN4: ;called when leaving this mode ldi temp1,5 rcall ChangeMode ret HOLDUP4: ;called when entering this mode rcall ClearLine2 rcall ShowSpeed ret ;*************************************************************** ;* W8BH - MODE 5 (SET IF OFFSET) ROUTINES ;*************************************************************** ENCODERMODE5: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp Encoder5A rjmp Encoder5B TAPUP5: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp TapUp5A rjmp TapUp5B HOLDDOWN5: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp HoldDown5A rjmp HoldDown5B HOLDUP5: lds temp1,flags sbrs temp1,2 ;check for alternate submode rjmp HoldUp5A rjmp HoldUp5B ENCODER5A: ldi temp2,0 ;set lower limit ldi temp3,3 ;set upper limit lds temp1,IFmode ;get current IF mode rcall EncoderValue ;update value based on encoder sts IFmode,temp1 ;save new value rcall ShowIFmode ;and display new IFmode ret ENCODER5B: ;See EncoderMode0 for description tst encoder brpl e52 ;which way did encoder rotate? inc encoder ;remove 1 negative rotation rcall DecFreq0 ;reduce displayed frequency cpi temp1,55 ;55 = all OK brne e51 rcall IncFreq0 ;correct freq. underflow rjmp e55 e51: rcall DecFreq9 ;reduce magic number rjmp e54 e52: dec encoder ;remove 1 positive rotation rcall IncFreq0 ;increase displayed frequency cpi temp1,55 ;55 = all OK brne e53 rcall DecFreq0 ;correct freq. overflow rjmp e55 e53: rcall IncFreq9 ;increase magic number e54: rcall ShowFreq ;display new frequency e55: rcall QuickBlink ret TAPUP5A: lds temp1,IFmode ;which IF mode was selected? tst temp1 ;are we IFmode0 = no mixer? breq Exit5 ;no mixer, so save & exit rcall SetAltMode ;mixer, so get IF freq rcall Init5B ;set up display first ret TAPUP5B: ret TAPDOWN5: rcall TapDown0 ;cursor advance ret HOLDDOWN5A: ;called when leaving mode, nothing changed ldi temp1,6 rcall ChangeMode ret HOLDDOWN5B: rcall ZeroMagic rcall BuildMagic ;create the IF magic# in rcve0 rcall SaveIFmn ;buffer the IF magic# rcall LCD2IF ;buffer the IF value rcall RestoreRCV ;restore the VFO frequency &magic# rcall Exit5 ;save & return ret HOLDUP5A: ;called when entering this mode ldi temp1,11 rcall DisplayLine1 ;'Set mixer type' lds temp1,IFmode rcall ShowIFmode ;show current IF mode ret HOLDUP5B: ret SHOWIFMODE: ldi temp2,12 ;offset to IF mode messages add temp1,temp2 ;add in IF mode to get correct msg rcall DisplayLine2 ;display msg on line 2 ret INIT5B: ;called at the start of 5B ldi temp1,16 ;'Enter IF' rcall DisplayLine1 rcall SaveRCV ;save current VFO freq & magic# rcall IF2LCD ;load IF freq rcall ShowTuning ;display IF for editing ret EXIT5: ;called at the end of Mode 5 rcall TapUp4 ;save params to EEPROM & return rcall Freq_Out ;update DDS w/new mode & IF rcall SetNormMode ;normal control behavior ldi temp1,0 rcall ChangeMode ;back to tuning mode ret MOVEBYTES: ; copies up to 256 bytes from source to destination ; call with X=source, Y=destination, temp2=count ld temp1,X+ ;get byte from source st Y+,temp1 ;move byte to destination dec temp2 ;are we done yet? brne MoveBytes ;no, loop until done ret SAVERCV: ; temporarily save contents of LCDrcve0 & rcve0 ; while they are used during IF selection ldi XH,high(LCDrcve0) ;from LCDrcve0 ldi XL,low(LCDrcve0) ldi YH,high(msgbuf) ;to msgbuf ldi YL,low(msgbuf) ldi temp2,12 ;move 12 bytes rcall MoveBytes ret RESTORERCV: ; restores contents of LCDrcve0 & rcve0 ; after use by IF selection routines ldi XH,high(msgbuf) ;from msgbuf ldi XL,low(msgbuf) ldi YH,high(LCDrcve0) ;to LCDrcveo ldi YL,low(LCDrcve0) ldi temp2,12 ;move 12 bytes rcall MoveBytes ret LCD2IF: ; copies new IF from LCDrcve0 buffer ldi XH,high(LCDrcve0) ;from LCDrcve0 ldi XL,low(LCDrcve0) ldi YH,high(IFfreq) ;to IFfreq ldi YL,low(IFfreq) ldi temp2,8 ;move 8 bytes rcall MoveBytes ret IF2LCD: ; copies IF to LCD buffer ldi XH,high(IFfreq) ;from IFfreq ldi XL,low(IFfreq) ldi YH,high(LCDrcve0) ;to LCDrcve0 ldi YL,low(LCDrcve0) ldi temp2,8 ;move 8 bytes rcall MoveBytes ret SAVEIFMN: ; saves IF magic# (copies rcve0 to IFmn) ldi XH,high(rcve0) ;from rcve0 ldi XL,low(rcve0) ldi YH,high(IFmn) ;to IFmn ldi YL,low(IFmn) ldi temp2,4 ;move 4 bytes rcall MoveBytes ret ;*************************************************************** ;* W8BH - MODE 6 (SET STARTUP FREQUENCY) ROUTINES ;*************************************************************** ENCODERMODE6: rjmp Encoder5B ;change freq value TAPDOWN6: rjmp TapDown0 ;cursor advance HOLDUP6: ;called when entering mode rcall SaveRCV ;save current VFO freq & magic# rcall SF2LCD ;load SF freq rcall ShowTuning ;display SF for editing ldi temp1,6 ;mark mode entry mov R0,temp1 ret HOLDDOWN6: ;called when exiting mode mov temp1,R0 cpi temp1,6 ;was mode entered? brne ho6 ;if not, skip EEPROM save rcall LCD2SF ;buffer the IF value rcall RestoreRCV ;restore the VFO frequency &magic# rjmp TapUp4 ;save params to EEPROM & return ho6: clr R0 ldi temp1,7 rcall ChangeMode ;go to next mode ret LCD2SF: ; copies new start frequency from LCDrcve0 buffer ldi XH,high(LCDrcve0) ;from LCDrcve0 ldi XL,low(LCDrcve0) ldi YH,high(StartFreq) ;to IFfreq ldi YL,low(StartFreq) ldi temp2,8 ;move 8 bytes rcall MoveBytes ret SF2LCD: ; copies start frequency to LCD buffer ldi XH,high(StartFreq) ;from IFfreq ldi XL,low(StartFreq) ldi YH,high(LCDrcve0) ;to LCDrcve0 ldi YL,low(LCDrcve0) ldi temp2,8 ;move 8 bytes rcall MoveBytes ret ;*************************************************************** ;* W8BH - MODE 7 (TUNING RATE) ROUTINES ;*************************************************************** ENCODERMODE7: tst Encoder brmi e72 ;ignore CCW rotation sub StepRate,Encoder ;use encoder to advance cursor brcc e71 ;is new position <0? ldi StepRate,7 ;go back to 10MHz position e71: rcall ShowCursor rcall QuickBlink ;flash the LED e72: clr Encoder ret TAPDOWN7: rcall RestoreRCV ;restore the VFO frequency &magic# sts tunerate,steprate ;copy current tune rate to SRAM rjmp TapUp4 ;save tunerate to EEPROM & return HOLDUP7: ;called when entering mode rcall SaveRCV ;save current VFO freq & magic# rcall ShowCursor ;show current tuning rate ret HOLDDOWN7: ;called when exiting mode ldi temp1,0 rcall ChangeMode ;back to tuning mode ret ;*************************************************************** ;* W8BH - KEYPAD ROUTINES ;*************************************************************** ; ; KEYPAD CONNECTIONS (7 wires) ; Row1 to PB5, Row2 to BP4, Row3 to PB3, Row4 to PB2, ; Col1 to PD7, Col2 to PB0, Col3 to PB1 ; ; FUNCTIONS ; # = cursor right ; * = frequency presets. CHECKKEYPAD: tst encoder ;is encoder busy? brne kp0 ;wait for encoder to finish cbi PORTD,Col1 ;take column1 low ldi temp1,2 ;col1 value is 2 rcall ScanRows ;see if a row went low sbi PORTD,Col1 ;restore column1 high cbi PORTB,Col2 ;take column2 low ldi temp1,1 ;col2 value is 1 rcall ScanRows ;see if a row went low sbi PORTB,Col2 ;restore col2 high cbi PORTB,Col3 ;take column3 low ldi temp1,0 ;col3 value is 0 rcall ScanRows ;see if a row went low sbi PORTB,Col3 ;restore column3 high kp0: ret SCANROWS: clc ;clear carry sbis pinB,Row1 ;is row1 low? subi temp1,3 ;yes, subtract 3 sbis pinB,Row2 ;is row2 low? subi temp1,6 ;yes, subtract 6 sbis pinB,Row3 ;is row3 low? subi temp1,9 ;yes, subtract 9 sbis pinB,Row4 ;is row4 low? subi temp1,12 ;yes, subtract 12 brcc kp1 ;no carry = no keypress neg temp1 ;negate answer rcall PadCommand ;do something kp1: ret PADCOMMAND: cpi temp1,11 ;special case: is it 0? brne kp2 ;no, continue ldi temp1,0 ;yes, replace with real zero kp2: cpi temp1,12 ;special case: "#" command? brne kp3 ;no, try next command inc press ;emulate button press = cursor right ldi temp1,1 ;1 blink for switch debouncing rjmp kp6 ;done with '#' kp3: cpi temp1,10 ;special case:"*" command brne kp4 ;no, try next command rcall LoadNextPreset ;yes, get next preset rjmp kp6 ;done with '*' kp4: mov temp2,StepRate ;no, get current cursor position ldi ZH,high(rcve0) ;point to frequency value in memory ldi ZL,low(rcve0) ;16 bits, so need two instructions kp5: dec ZL ;advance through frequency digits dec temp2 ;and advance through cursor positions brpl kp5 ;until we get to current digit ld temp3,Z ;load value at cursor sub temp1,temp3 ;subtract from keypad digit mov encoder,temp1 ;set up difference for encoder routines. inc press ;advance cursor position kp6: ldi delay,150 ;simple key debouncer rcall wait ;give the LED a rest! ret ;*************************************************************** ;* W8BH - FREQUENCY PRESET ROUTINES ;*************************************************************** ZeroMagic: ldi ZH,high(rcve0) ;point to magic# ldi ZL,low(rcve0) ldi temp1,0 st Z+,temp1 ;zero first byte (MSB) st Z+,temp1 ;zero second byte st Z+,temp1 ;zero third byte st Z+,temp1 ;zero fourth byte (LSB) ret ShowMagic: ldi ZH,high(rcve0) ;point to magic number ldi ZL,low(rcve0) ;2 byte pointer ldi temp3,4 ;counter for 4 byte display ldi temp1,$80 ;display on line1 rcall LCDCMD sh1: ld temp1,Z+ ;point to byte to display rcall SHOWHEX ;display first nibble ldi temp1,' ' ;add a space rcall LCDCHR ;display the space dec temp3 ;all 4 bytes displayed yet? brne sh1 ;no, so do the rest ret AddMagic: ; adds one component to magic according to StepRate ; 0 = Hz rate, 3=Khz rate, 6=MHz rate, 7=10MHz rate rcall IncFreq9 ret BuildMagic: ; create the magic# for the operating (displayed) frequency ; input = 8 digit frequency stored at LCDrcve0 ; output = 32bit (4 byte) magic# for that frequency at rcve0 push StepRate ;save StepRate ldi XH,high(LCDrcve0) ;point to LCD digits ldi XL,low(LCDrcve0) ;16bit pointer ldi StepRate,7 ;Start with 10MHz position bm1: ld temp3,X+ ;get next LCD digit tst temp3 ;is it zero? breq bm3 ;yes, so go to next digit bm2: rcall AddMagic ;no, so add magic component dec temp3 ;all done with this component brne bm2 ;no, add some more bm3: dec StepRate ;all done with freq. positions? brne bm1 ;no, go to next (lowest) position pop StepRate ;restore StepRate ret LoadPMmem: ldi ZH,high(freqLCD*2) ;point to the preset table (-8 bytes) ldi ZL,low(freqLCD*2) ;16bit pointer lp1: adiw ZL,8 ;point to next frequency preset dec temp1 ;get to the right preset yet? brne lp1 ;no, keep looking ldi YH,high(LCDrcve0) ;yes, point to LCD digits ldi YL,low(LCDrcve0) ;16bit pointer ldi temp2,8 ;there are 8 frequency digits lp2: lpm temp1,Z+ ;get an LCD digit from FLASH mem st Y+,temp1 ;and put into LCD display buffer dec temp2 ;all digits done? brne lp2 ;not yet ret LoadNewFreq: rcall ZeroMagic ;clear out old magic number rcall BuildMagic ;build new one based on current freq rcall Freq_Out ;send new magic to DDS ; rcall ShowMagic ;show magic# on line 1 (debugging) ;nf1: tst encoder ;wait for encoder twist ; breq nf1 ret LoadNextPreset: lds temp1,preset ;check current preset cpi temp1,NumPresets ;at top of list? brne ln1 ;no, continue clr temp1 ;yes, start at beginning ln1: inc temp1 ;go to next preset# sts preset,temp1 ;save save it rcall EELoadMem ;get preset from EE rcall LoadNewFreq ;update DDS with new freq rcall ShowTuning ;display it ret ;*************************************************************** ;* W8BH - Timer 2 Overflow Interrupt Handler ;*************************************************************** ; This handler is called every 8 ms @ 20.48MHz clock ; Increments HOLD counter (max 128) when button held ; Resets HOLD counter if button released before hold met OVF2: push temp1 in temp1,SREG ;save status register push temp1 ldi temp1,90 ;256-90=160; 160*50us = 8ms sts TCNT2,temp1 ;reduce cycle time to 8 ms tst hold ;counter at max yet? brmi ov1 ;not yet sbic pinD,BUTTON clr hold ;if button is up, then clear sbis pinD,BUTTON inc hold ;if button is down, then count ov1: pop temp1 out SREG,temp1 ;restore status register pop temp1 reti ;*************************************************************** ;* W8BH - External Interrupt 1 Handler ;*************************************************************** ; This handler is replaces the original EXT_INT1 code ; It is called when a logic-level change on the ; external interrupt 1 (pushbutton) pin occurs. ; Press is incremented on button-down events. ; Release is incremented on button-up events. EINT1: push temp1 ;save temp1 register in temp1,SREG push temp1 ;save status register lds temp1,EICRa ;get interrupt control register sbrs temp1,2 ;bit2: rising edge =0, falling edge =1 rjmp ei1 ; -- here is the falling-edge code -- cbr temp1,$04 ;falling edge '11' -> rising edge '10' inc release ;count the button-up rjmp ei2 ; -- here is the rising-edge code -- ei1: sbr temp1,$04 ;rising edge '10' -> falling edge '11' inc press ;count the button-down ei2: sts EICRa,temp1 ;save interrupt control register pop temp1 out SREG,temp1 ;restore status register pop temp1 ;restore temp1 register reti ;*************************************************************** ;* W8BH - External Interrupt 0 Handler ;*************************************************************** ; This handler is replaces the original EXT_INT0 code ; It is called when a logic-level change on the ; external interrupt 0 (encoder state) pin occurs. ; Press is incremented on button-down events. ; Release is incremented on button-up events. EINT0: push temp1 ;save temp1 register in temp1,SREG ;save the status register push temp1 lds temp1,EICRA ;get current interrupt mode sbrs temp1,0 ;is mode rising-edge? rjmp i02 ;no, so go to falling edge (bit0=0) cbr temp1,$01 ;yes, clear bit 0 sts EICRA,temp1 ;change mode to falling-edge sbis PIND,PHASE ;is PHASE=1? rjmp i01 ;no, increase encoder (CW rotation) dec encoder ;yes, decrease encoder (CCW rotation) rjmp i04 i01: inc encoder rjmp i04 i02: ;current mode = falling-edge sbr temp1,$01 ;set bit 0 sts EICRA,temp1 ;change mode to rising-edge sbis PIND,PHASE ;is PHASE=1? rjmp i03 ;no, decrease encoder (CCW rotation) inc encoder ;yes, increase encoder (CW rotation) rjmp i04 i03: dec encoder i04: pop temp1 out SREG,temp1 ;restore the status register pop temp1 ;restore temp1 register reti ;*************************************************************** ;* W8BH - LCD Display routines ;*************************************************************** HOMECURSOR: ; puts the cursor at top-left push temp1 ;preserve register ldi temp1,$80 ;cursor at top-left rcall LCDCMD ;do it pop temp1 ;restore register ret HOMELINE2: ; puts the cursor at beginning of line2 push temp1 ;preserve register ldi temp1,$C0 ;cursor on line2 rcall LCDCMD ;do it pop temp1 ;restore register ret CLEARDISPLAY: ; clears the LCD display & puts cursor at top-left push temp1 ;save register ldi temp1,1 ;clear display command rcall LCDCMD ;do it rcall HomeCursor ;put cursor @ top-left pop temp1 ;restore register ret DISPLAYMSG: ; call with keyer msg# in temp1 ; will display full 32 byte msg on LCD push temp1 push temp2 ;preserve registers push ZH push ZL rcall LoadEEmsg ;get msg from EEPROM clr temp2 ;top-left cursor ldi ZH,high(msgbuf) ;point to message ldi ZL,low(msgbuf) dm1: mov temp1,temp2 rcall SetCursor ;set cursor position ld temp1,Z+ ;get next char in msg tst temp1 ;is it 0=done? breq dm2 ;yes rcall LCDCHR ;no, display it on LCD inc temp2 ;next cursor position cpi temp2,32 ;are we done? brne dm1 ;no, get next char dm2: pop ZL ;restore registers pop ZH pop temp2 pop temp1 ret DISPLAYLINE1: ; displays a 16-character msg on line 1 ; call with msg# in temp1 push temp1 mov temp2,temp1 ldi temp1,$80 ;use line 1 rcall LCDCMD rcall DISPLAY16 ;send 16 characters pop temp1 ret DISPLAYLINE2: ; displays a 16-character msg on line 2 ; call with msg# in temp1 push temp1 mov temp2,temp1 ldi temp1,$C0 ;use line 2 rcall LCDCMD rcall DISPLAY16 ;send 16 characters pop temp1 ret SETCURSOR: ; call with cursor position (0-31) in temp1 ; will place LCD cursor at desired position ; (0 = row 1, column 1; 31 = row 2, column 16) ; Assumes 2x16 LCD display push temp2 ;preserve registers push temp1 andi temp1,$1F ;allow only 0-31 input cpi temp1,16 ;is it >=16? brge sc1 ;yes: on 2nd line ldi temp2,$80 ;no, on first line rjmp sc2 sc1: subi temp1,16 ;get 2nd line offset ldi temp2,$C0 ;start of second line sc2: add temp1,temp2 ;add for cursor posn rcall LCDCMD ;set the cursor pop temp1 ;restore registers pop temp2 ret DISPLAY16: ; displays a 16-character msg ; call with msg# in temp2 push ZH push ZL push temp3 ldi ZH,high(messages*2-16) ldi ZL,low(messages*2-16) di1: adiw Z,16 ;add 16 for each message dec temp2 ;add enough? brne di1 ;no, add some more ldi temp3,16 ;16 characters di2: lpm temp1,Z+ ;get the next character rcall LCDCHR ;put character on LCD dec temp3 ;all 16 chars sent? brne di2 ;no, so repeat pop temp3 pop ZL pop ZH ret SHOWDECIMAL: ; displays a number 00-99 on the LCD push temp1 ;preserve registers push temp2 push temp3 clr temp2 ;10's counter sd1: cpi temp1,10 ;at least 10 remaining? brlo sd2 ;no, done counting 10's inc temp2 ;count the next 10 subi temp1,10 ;remove the next 10 brpl sd1 ;loop until all 10's gone sd2: mov temp3,temp1 ;save 10's counter mov temp1,temp2 rcall ShowDec ;display 10's digit mov temp1,temp3 ;get 1's digit rcall ShowDec ;and display it ldi temp1,' ' rcall LCDCHR ;put a space after number pop temp3 ;restore registers pop temp2 pop temp1 ret SHOWMEMFREQ: ; Displays the frequency in a more compact form: 'XX.XXXXXX' ldi temp1,$C5 ;second line, indented rcall LCDCMD ldi ZH,high(LCDrcve0) ;point to LCD freq buffer ldi ZL,low(LCDrcve0) ld temp1,Z+ ;get first digit (10 MHz posn) rcall ShowDec ;and show it ld temp1,Z+ ;get second digit (1 MHz posn) rcall ShowDec ;and show it ldi temp1,'.' ;decimal point rcall LCDCHR ;and show it ldi temp2,6 ;for the next 6 digits cf1: ld temp1,Z+ ;get them from buffer rcall SHOWDEC ;and show them on LCD dec temp2 ;done all of them? brne cf1 ;not yet ret SHOWINDEX: ; displays a two-digit index number at the beginning of line 2 ; call with number in temp1 push temp1 ;preserve register rcall HomeLine2 ;start beginning of line2 rcall ShowDecimal ;show the two-digit number ldi temp1,17 ;return cursor under number rcall SetCursor pop temp1 ;restore register ret SHOWPRESETNUM: ; displays current preset '#xx' on line2 lds temp1,preset ;get preset# rcall ShowIndex ret SHOWMSGNUM: ; displays current msg# on line2 lds temp1,msgnum rcall ShowIndex ret SHOWMSGPART: ; displays the first part of the current message on line2 ldi temp1,19 rcall SetCursor ldi temp2,13 ;do first 13 chars of msg ldi ZH,high(msgbuf) ;point to msg ldi ZL,low(msgbuf) sp0: ld temp1,Z+ ;get character in msg rcall LCDCHR ;put it on LCD dec temp2 ;all 13 done yet? brne sp0 ;no, not yet ret SHOWMSGINDEX: ; will display msg# & part of msg on line2 lds temp1,msgnum rcall LoadEEmsg ;get msg from EEPROM rcall ShowMsgPart rcall ShowMsgNum ret SHOWPRESET: rcall ShowMemFreq ;show preset frequency rcall ShowPresetNum ret SHOWSPEED: ; displays current code speed 'xx WPM' on line2 ldi temp1,$C4 ;second line, indented rcall LCDCMD lds temp1,speed rcall ShowDecimal ;display number ldi temp1,'W' ;display ' WPM' rcall LCDCHR ldi temp1,'P' rcall LCDCHR ldi temp1,'M' rcall LCDCHR ldi temp1,$C5 ;put cursor under number rcall LCDCMD ret SHOWTUNING: rcall ClearLine2 ;erase second line of LCD rcall ShowFreq ;display operating freq in Hz lds StepRate,TuneRate ;load preferred tuning rate rcall ShowCursor ;put cursor under correct digit ret CLEARLINE2: push temp1 push temp2 ldi temp1,$C0 rcall LCDCMD ldi temp2,16 cl1: ldi temp1,' ' rcall LCDCHR dec temp2 brne cl1 pop temp2 pop temp1 ret ;*************************************************************** ;* W8BH - EEPROM routines ;*************************************************************** ;Data is transferred to/from temp1 (single byte) or Z (multiple bytes) ;EE address must be put into Y prior to call ;See ATMEL application note "AVR100" .equ SigByte1 = 'B' ;first signature byte .equ SigByte2 = 'H' ;second signature byte READEE: sbic EECR,EEPE ;busy writing EEPROM? rjmp ReadEE ;yes, so wait out EEARH,YH ;set up address reg. out EEARL,YL sbi EECR,EERE ;strobe the read bit in temp1,EEDR ;get the data ret WRITEEE: sbic EECR,EEPE ;busy writing EEPROM? rjmp WriteEE ;yes, so wait out EEARH,YH ;set up address reg. out EEARL,YL out EEDR,temp1 ;put data in data reg. cli ;dont interrupt the write sbi EECR,EEMPE ;master write enable sbi EECR,EEPE ;strobe the write bit sei ;interrupts OK now ret READ8E: ;read 8 bytes from EE ldi temp2,8 ;counter=8 r81: rcall ReadEE ;get byte from EE st Z+,temp1 ;move byte to destination adiw Y,1 ;go to next EE addr dec temp2 brne r81 ret WRITE8E: ;write 8 bytes to EE ldi temp2,8 ;counter=8 r82: ld temp1,Z+ ;get byte from source rcall WriteEE ;store byte in EE adiw Y,1 ;go to next EE addr dec temp2 brne r82 ret EEFILL: ; repeatedly writes a byte to EE ; call with byte in temp1, count in temp2 ; starting address in YH:YL rcall WriteEE ;write temp1 value to EEPROM adiw Y,1 ;go to next address dec temp2 ;count finished? brne EEFill ;loop until done ret EEDEFFREQ: ;write default start freq ldi YL,17 ;point to destination ldi ZH,high(2*FreqLCD) ;point to source ldi ZL,low(2*FreqLCD) ldi temp2,8 ;8 bytes to copy es1: lpm temp1,Z+ ;get default frequency byte rcall WriteEE ;write to EEPROM adiw Y,1 dec temp2 ;all bytes written yet? brne es1 ret EEINITPAGE0: clr YH ;go to EEPROM page 0 clr YL ;at beginning of page ldi temp1,SigByte1 ;load first signature byte rcall WriteEE ;write it inc YL ;go to byte 1 ldi temp1,SigByte2 ;load second signature byte rcall WriteEE ;write it inc YL ldi temp1,DefaultSpeed rcall WriteEE ;write default CW speed inc YL ldi temp1,DefTuneRate rcall WriteEE ;write default tuning rate inc YL clr temp1 ldi temp2,28 rcall EEFill ;fill next 28 bytes with 0 rcall EEDefFreq ;write default start freq ldi YL,32 ldi temp2,NumPresets*8 ;load # of preset bytes ldi ZH,high(presets*2) ;point to preset bytes ldi ZL,low(presets*2) pe1: lpm temp1,Z+ ;get byte from program memory rcall WriteEE ;store byte in EE adiw Y,1 ;go to next EE addr dec temp2 ;all preset bytes written? brne pe1 ;loop until all written ret EEINITPAGE1: ldi YH,1 ;go to EEPROM page 1 clr YL ;at beginning of page ldi temp2,32 ;create space for 32 values clr temp1 rcall EEFill ;fill next 32 bytes with 0 ldi ZH,high(cwmsg*2) ;point to keyer messages ldi ZL,low(cwmsg*2) ldi temp2,224 ;7 messages * 32 bytes/msg pe2: lpm temp1,Z+ ;get next message char rcall WriteEE ;store byte in EE adiw Y,1 ;go to next EE addr dec temp2 ;all bytes written yet? brne pe2 ;loop until done ret EEPROGRAM: ; copy default memories from program FLASH to EE ldi temp1,17 ;'Factory Reset' rcall DisplayLine1 ;display it rcall EEInitPage0 ;initialize VFO presets rcall EEInitPage1 ;initialize keyer messages ret EECHECK: ; looks to see if EE has been loaded with default presets ; if not, defaults are programmed into the EE clr YH clr YL ;go to byte 00 rcall ReadEE ;look at first signature byte cpi temp1,SigByte1 ;is it correct? brne ee1 ;no, so store defaults inc YL ;go to byte 01 rcall ReadEE ;look at second signature byte cpi temp1,SigByte2 ;is it correct? brne ee1 ;no, so store defaults rjmp ee2 ;signature byte OK, so done ee1: rcall EEProgram ;write defaults to EE ee2: ret EESETUPMEM: ; called by LoadMem & SaveMem ; to set up source/destination EEPROM addresses ; call with preset# in temp1 clr YH ldi YL,24 ;point to EEPROM ldi ZH,high(LCDrcve0) ;point to LCD buffer ldi ZL,low(LCDrcve0) ge0: adiw Y,8 ;increment 8 bytes/preset dec temp1 ;correct preset yet? brne ge0 ;loop until done ge1: ret EELOADMEM: ; specify the preset# in temp1 ; will return the EE memory into LCDrcve0 rcall EESetupMem rcall Read8E ret EESAVEMEM: ; specify the preset# in temp1 ; will save frequency in LCDrcve0 to EE rcall EESetupMem rcall Write8E ret EELOADPARAMS: ; loads the 30-byte parameter block from EEPROM ; this block contains code speed, tune rate, IF offset, etc. clr YH ;page 0 ldi YL,2 ;point to block in EE ldi ZH,high(params) ldi ZL,low(params) ;point to destination in SRAM ldi temp2,30 ;count 30 bytes lo1: rcall ReadEE ;get byte from EE st Z+,temp1 ;move byte to destination adiw Y,1 ;go to next EE addr dec temp2 brne lo1 rcall GetCWDelay ;convert WPM into timing delay ret EESAVEPARAMS: ; saves the 30-byte parameter block to EEPROM ; this block contains code speed, tune rate, IF offset, etc. clr YH ;page 0 ldi YL,2 ;point to block in EE ldi ZH,high(params) ldi ZL,low(params) ;point to params in SRAM ldi temp2,30 ;count 30 bytes sp1: ld temp1,Z+ ;get the parameter from SRAM rcall WriteEE ;save byte to EEPROM adiw Y,1 ;go to next EE addr dec temp2 brne sp1 ret SETUPEEMSG: ; called by Load/Save EEMsg routines ; to set up source/destination addresses ldi YH,1 ;keyer memories are in page 1 ldi YL,0 ;start of page ldi ZH,high(msgbuf) ;point to message buffer ldi ZL,low(msgbuf) sa0: cpi temp1,0 breq sa1 adiw Y,32 ;add 32 bytes for each message dec temp1 ;loop until done brne sa0 sa1: ret SAVEEEMSG: ; saves the keyer message in EEPROM ; call with message# in temp1 rcall SetupEEMsg ;set source/destination pointers rcall Write8E ;write 32 bytes rcall Write8E rcall Write8E rcall Write8E ret LOADEEMSG: ; loads a keyer message from EEPROM ; call with message# in temp1 rcall SetupEEMsg ;set source/destination pointers rcall Read8E ;read 32 bytes rcall Read8E rcall Read8E rcall Read8E ret LOADFREQ: ; checks to see if parameter StartFreq has been loaded with a ; starting frequency. If so, use it to set the power-on start ; frequency. If not, use the hard-coded value stored at ; LCDfreq. ldi ZH,high(StartFreq) ldi ZL,low(StartFreq) clr temp1 ;accumulate result in temp1 ldi temp2,8 ;load 8 bytes gs0: ld temp3,Z+ ;get next byte of freq add temp1,temp3 ;add it to previous bytes dec temp2 ;all added together yet? brne gs0 tst temp1 ;do all bytes add to 0? breq gs3 ;yes, so quit ldi YH,high(LCDrcve0) ;destination = LCDrcve0 ldi YL,low(LCDrcve0) sbiw Z,8 ;source = StartFreq ldi temp2,8 gs2: ld temp1,Z+ st Y+,temp1 ;store in SRAM dec temp2 ;all 8 bytes transferred? brne gs2 ;not yet gs3: ret ;*************************************************************** ;* W8BH - Straight Key routine ;*************************************************************** .equ KeyState = 1 ;1=down, 0=up STRAIGHTKEY: ; Checks to see if either of the paddles have been pressed. ; Paddle inputs are active low lds temp5,flags ;get previous paddle state sbis PinC,LPaddle ;dit (left) paddle pressed? rjmp sk1 ;yes sbis PinC,RPaddle ;dah (right) paddle pressed? rjmp sk1 ;yes ;paddles are both up sbrs temp5,KeyState ;check previous state rjmp sk2 ;previous up = do nothing rcall KeyUp cbr temp5,1<= 5? brge gd1 ;yes ldi temp1,92 ;no, use default = 13 WPM rjmp gd2 gd1: subi temp1,MinSpeed ;index into speed table ldi ZH,high(2*ctable) ;point to table ldi ZL,low(2*ctable) add ZL,temp1 ;add in index clr temp1 adc ZH,temp1 lpm temp1,Z ;get the value gd2: sts cwdelay,temp1 ;and save it pop ZL ;restore registers pop ZH pop temp1 ret DITWAIT: lds delay,cwdelay ;get # of milliseconds for dit rcall wait ;and wait that long ret DAHWAIT: ;wait for 3 dits rcall DitWait rcall DitWait rcall DitWait ret WORDWAIT: ldi temp1,4 ;wait for 4 addl dits wd1: rcall DitWait ;(in addn to 3 post-char dits dec temp1 ;for a total of 7 dits) brne wd1 ret ;*************************************************************** ;* W8BH - Memory Keyer routines ;*************************************************************** .equ ContChar = '+' ;continuation character MORSEOUT: ; call this routine with the encoded morse byte in temp1 cpi temp1,$80 ;found stop bit yet: breq mo2 ;yes, so quit lsl temp1 ;no, get next bit into carry brcs mo1 ;is the bit a dit? (bit=1) rcall dah ;no, so send a dah rjmp MorseOut mo1: rcall dit ;yes, so send a dit rjmp MorseOut mo2: rcall DitWait ;end of char spacing rcall DitWait mo3: ret CQTEST: ; sends a CQ ldi temp1,$58 ;binary 0101.1000 = 'C' rcall MorseOut ldi temp1,$28 ;binary 0010.1000 = 'Q' rcall MorseOut ret ASCIITOMORSE: ; Call with an ASCII character in temp1 ; This routine will convert it into a coded morse character ; If input is control or graphic character, output = 0 push ZH ;preserve Z pointer push ZL ldi ZH,high(2*mtable) ;point to morse table ldi ZL,low(2*mtable) cpi temp1,$20 ;is it a space character? brne am1 ;no rcall WordWait ;yes, so wait appropriate time rjmp am3 am1: cpi temp1,$2A ;ignore control chars brmi am3 cpi temp1,$7A ;ignore graphic chars brpl am3 cpi temp1,$60 ;is it an lower-case char? brmi am2 ;no andi temp1,$DF ;yes, convert to upper-case am2: subi temp1,$2A ;start table at $2A='*' add ZL,temp1 ;add char offset to table pointer clr temp1 ;keep only the carry bit adc ZH,temp1 ;add carry, if any, to ZH lpm temp1,Z ;get character from table rjmp am4 ;done am3: ldi temp1,$80 ;output stop-bit for invalid chars am4: pop ZL ;restore Z pointer pop ZH ret CQTEST2: ldi temp1,'c' ;send a 'c' rcall AsciiToMorse rcall MorseOut ldi temp1,12 ;send invalid char (FORM FEED) rcall AsciiToMorse rcall MorseOut ldi temp1,'q' ;send a 'q' rcall AsciiToMorse rcall MorseOut ret RESETLINE2: ; used by MorseMsg to prep LCD line 2 rcall ClearLine2 ;erase line2 ldi temp1,$C0 ;set cursor to start of line rcall LCDCMD clr temp3 ;clear char counter ret MORSEMSG: ; call with Z pointing to message ; will output Morse and show it on LCD line 2 rcall ResetLine2 ;prep line2 for display mm1: ld temp1,Z+ ;get next ASCII character tst temp1 ;look for 0=stop byte breq mm2 ;done cpi temp1,ContChar ;is it a continuation character? breq mm2 ;yes, so quit this msg push temp1 ;save char rcall LCDCHR ;put char on LCD pop temp1 ;retrieve char rcall AsciiToMorse ;convert char to morse rcall MorseOut ;and send it inc temp3 ;incr character counter cpi temp3,16 ;is line2 full=16 chars? breq MorseMsg ;yes, clear it & continue rjmp mm1 ;no, keep going mm2: ret SENDMORSEMSG: ; sends the current message in morse code ldi temp1,10 ;Show 'Sending Message' rcall DisplayLine1 ;on LCD line 1 lds temp1,msgnum mr0: rcall LoadEEmsg ;load msg from EEPROM rcall MarkEnd ;remove trailing spaces ldi ZH,high(msgbuf) ;point to msg ldi ZL,low(msgbuf) rcall MorseMsg ;send out the morse code cpi temp1,ContChar ;was last char a continuation? brne mr1 ;no, so done lds temp1,msgnum ;grab msg# inc temp1 ;go to next msg# sts msgnum,temp1 ;and remember it rjmp mr0 ;send next message mr1: ldi temp1,0 ;restore LCD rcall ChangeMode ;to tuning mode ret QUEUEMSG1: ; puts the next available cw message into message buffer ; displays the pending message on line2 clr hold ;dont trigger hold while keying in # lds temp1,msgnum ;get message number inc temp1 ;go to next number cpi temp1,NumMessages ;have we passed the last msg? brlo ss1 ;no, continue clr temp1 ;yes, so allow escape rcall ShowTuning ;and erase queued msg display ss1: sts msgnum,temp1 ;get queued value tst temp1 ;retrieve message number breq ss2 ;zero = nothing was queued rcall ShowMsgIndex ;show queued message on LCD ss2: rcall DitWait ;keyer debouncer rcall DitWait ret QUEUEMSG2: ; skip the next available cw message, and go to following one ; display message the queued message on line2 lds temp1,msgnum inc temp1 ;advance message# by 2 sts msgnum,temp1 ;save it rcall QueueMsg1 rcall DahWait ;longer wait on right paddle ret ;*************************************************************** ;* W8BH - IF ROUTINES ;*************************************************************** SUBBYTE: ; subtracts byte at Y from byte at X, with carry ; result put in byte at Z ; used for 32-bit substraction routine ld temp1,X+ ld temp2,Y+ sbc temp1,temp2 ;subtract Y from X st Z+,temp1 ;store Z = X - Y ret ADDBYTE: ; adds byte at Y to byte at X, with carry ; result put in byte at Z ; used for 32-bit addition routine ld temp1,X+ ld temp2,Y+ adc temp1,temp2 ;add Y to X st Z+,temp1 ;store Z = X + Y ret COMPBYTE: ; compare bytes at X and Y ; return flags compatible with branch instructions ld temp1,X+ ld temp2,Y+ cpc temp1,temp2 ret COMP32: ; compares the 4-byte values at X and Y ; returns flags compatible with branch instructions push temp1 push temp2 push XH push XL push YH push YL clc rcall CompByte rcall CompByte rcall CompByte rcall CompByte pop YL pop YH pop XL pop XH pop temp2 pop temp1 ret SUB32: ; subtract the 4-byte value at Y from the 4-byte value at X ; stores the four byte result at Z push temp1 push temp2 clc ;clear the carry bit rcall SubByte ;subtract 1st bytes (LSB) rcall SubByte ;2nd bytes rcall SubByte ;3rd bytes rcall SubByte ;4th bytes (MSB) pop temp2 pop temp1 ret ADD32: ; adds the 4-byte value at Y to the 4-byte value at X ; stores the four byte result at Z push temp1 push temp2 clc ;clear the carry bit rcall AddByte ;add 1st bytes (LSB) rcall AddByte ;2nd bytes rcall AddByte ;3rd bytes rcall AddByte ;4th bytes (MSB) pop temp2 pop temp1 ret CLEAR32: ; clears a 4-byte value at VFOmn ldi ZH,high(VFOmn) ldi ZL,low(VFOmn) clr temp1 st Z+,temp1 st Z+,temp1 st Z+,temp1 st Z+,temp1 ret IFnone: ; no mixer (VFO = OF) ; copies rcve0 to VFOmn ldi YH, high(rcve0) ;point to OF magic# ldi YL, low(rcve0) ldi ZH, high(VFOmn) ;point to VFO magic# ldi ZL, low(VFOmn) ldi temp2,4 mn0: ld temp1,Y+ st Z+,temp1 dec temp2 brne mn0 ret IFModeA: ; additive mixer ; mixer equation: operating freq OF + VFO = IF ; In this mode, VFO goes down when OF goes up. ldi XH, high(IFmn) ;point to IF magic# ldi XL, low(IFmn) ldi YH, high(rcve0) ;point to OF magic# ldi YL, low(rcve0) ldi ZH, high(VFOmn) ;point to VFO magic# ldi ZL, low(VFOmn) rcall Comp32 ;is OF>IF? brlo if1 ;yes, so turn off rcall Sub32 ;no, set VFO = IF - OF rjmp if2 if1: rcall Clear32 if2: ret IFModeB: ; subtractive mixer, with low-side injection ; mixer equation: OF - VFO = IF ldi XH, high(rcve0) ;point to OF magic# ldi XL, low(rcve0) ldi YH, high(IFmn) ;point to IF magic# ldi YL, low(IFmn) ldi ZH, high(VFOmn) ;point to VFO magic# ldi ZL, low(VFOmn) rcall Comp32 ;is OF ? @ A .db 0b00000000, 0b11001110, 0b10010110, 0b10100000 ; B C D E .db 0b01111000, 0b01011000, 0b01110000, 0b11000000 ; F G H I .db 0b11011000, 0b00110000, 0b11111000, 0b11100000 ; J K L M .db 0b10001000, 0b01010000, 0b10111000, 0b00100000 ; N O P Q .db 0b01100000, 0b00010000, 0b10011000, 0b00101000 ; R S T U .db 0b10110000, 0b11110000, 0b01000000, 0b11010000 ; V W X Y .db 0b11101000, 0b10010000, 0b01101000, 0b01001000 ; Z [ \ ] (AR) .db 0b00110000, 0b00000000, 0b00000000, 0b10101100 ctable: ;This table converts code speed (in WPM) to the required length ;of each element (in milliseconds). The table goes from 5 to ;30 WPM. For example, the first table entry (for 5 WPM) is 240 ;milliseconds, 6 WPM = 200 milliseconds, and so on. .db 240, 200, 171, 150, 133, 120, 109, 100 .db 92, 86, 80, 75, 71, 67, 63, 60 .db 57, 55, 52, 50, 48, 46, 44, 43 .db 41, 40 cwmsg: ;Up to 7 user-defined keyer messages can be loaded into EEPROM ;The actual number of messages must equal your NumMessages value ;Enter your default 32-byte messages here: ; 12345678901234567890123456789012 .db "CQ CQ CQ de W8BH W8BH W8BH K " ;message 1 .db "TNX FER CALL - UR RST IS " ;message 2 .db "QTH DAYTON, OH ? DAYTON, OH - + " ;message 3 .db "NAME BRUCE ? BRUCE - SO HW CPY? " ;message 4 .db "RIG ELECRAFT K3 es ANT VERTICAL-" ;message 5 .db "RIG HOMEBREW es ANT VERTICAL - " ;message 6 .db "TNX FER QSO - 73 73 * de W8BH K " ;message 7 ;END OF "DDK.ASM" **********************************************