; W8DIZ Keyer used with the PigRig.
; April 19, 2013
; using ATmel ATtiny13A or ATtiny25/45/85
;
; cd ~/Desktop/Dropbox/w8diz.com/pigrig/
;
; ./avra13 keyer_i.asm

; avrdude -P usb -p t13 -c avrispv2 -U keyer_i.hex
; avrdude -P usb -p t13 -c avrispv2 -U lfuse:w:0x7A:m ; 0x6A is default
; avrdude -P usb -p t13 -c avrispv2 -U hfuse:w:0xF9:m  Brownout; 0xFF is default

; avrdude -P usb -p t45 -c avrispv2 -U keyer_i.hex
; avrdude -P usb -p t45 -c avrispv2 -U lfuse:w:0xE2:m ; Clock divided by 8 turned off; 0x62 is default
; avrdude -P usb -p t45 -c avrispv2 -U hfuse:w:0xDC:m ; Brownout; 0xDF is default

; avrdude -P usb -p t85 -c avrispv2 -U keyer_i.hex
; avrdude -P usb -p t85 -c avrispv2 -U lfuse:w:0xE2:m ; Clock divided by 8 turned off; 0x62 is default
; avrdude -P usb -p t85 -c avrispv2 -U hfuse:w:0xDC:m ; Brownout; 0xDF is default

.nolist
;.include "tn45def.inc"
;.include "tn85def.inc"
.include "tn13Adef.inc"
.list

.equ DIT_MASK  = 0
.equ DAH_MASK  = 1
.equ CMD = 2
.equ XMIT = 4 ; .equ XMIT = 4 on production rig
.equ TONE = 3 ; .equ TONE = 3 on production rig
.equ DIT = 2 ; 1 on bit and 1 off bit
.equ DAH = 4 ; 3 on bits and 1 off bit

;--Rst--------Vcc--
;--PB3--------PB2--
;--PB4--------PB1--
;--Gnd--------PB0--

.equ NUM_0 = 0b00111111
.equ NUM_1 = 0b00101111
.equ NUM_2 = 0b00100111
.equ NUM_3 = 0b00100011
.equ NUM_4 = 0b00100001
.equ NUM_5 = 0b00100000
.equ NUM_6 = 0b00110000
.equ NUM_7 = 0b00111000
.equ NUM_8 = 0b00111100
.equ NUM_9 = 0b00111110
.equ LET_A = 0b00000101
.equ LET_B = 0b00011000
.equ LET_C = 0b00011010
.equ LET_D = 0b00001100
.equ LET_E = 0b00000010
.equ LET_F = 0b00010010
.equ LET_G = 0b00001110
.equ LET_H = 0b00010000
.equ LET_I = 0b00000100
.equ LET_J = 0b00010111
.equ LET_K = 0b00001101
.equ LET_L = 0b00010100
.equ LET_M = 0b00000111
.equ LET_N = 0b00000110
.equ LET_O = 0b00001111
.equ LET_P = 0b00010110
.equ LET_Q = 0b00011101
.equ LET_R = 0b00001010
.equ LET_S = 0b00001000
.equ LET_T = 0b00000011
.equ LET_U = 0b00001001
.equ LET_V = 0b00010001
.equ LET_W = 0b00001011
.equ LET_X = 0b00011001
.equ LET_Y = 0b00011011
.equ LET_Z = 0b00011100
.equ CHR_ERROR  = 0b01001100
.equ CHR_COMMA  = 0b01110011
.equ CHR_PERIOD = 0b01010101
.equ CHR_SLASH  = 0b00110010

;R0 and R1 reserved for hardware multiply
.DEF rd1l = R3 ; LSB 16-bit-number to be divided
.DEF rd1h = R4 ; MSB 16-bit-number to be divided
.DEF rd1u = R5 ; interim register
.DEF rd2 = R6 ; 8-bit-number to divide with
.def sidetone = r7
.def tone_delay = r8 ; load and count down to zero for action

.def straight_key = r12 ; straight key flag
.def paddles = r13 ; reverse paddles flag
.def wpm_lo = r14 ; load and count down to zero for action
.def wpm_hi = r15 ; load and count down to zero for action
.def temp1 = r16
.def temp2 = r17
.def temp3 = r18
.def key_down = r19
.def transmit_enabled = r20
.def character = r21
.def dit_period = r22 ; 4 for dah & 2 for dit 
.def delay = r23 ; 100 mS per bit general delay
.def delay_100_lo = r24 ; load and count down to zero for action
.def delay_100_hi = r25 ; load and count down to zero for action
.def delay_wpm_lo = r26 ; load and count down to zero for action
.def delay_wpm_hi = r27 ; load and count down to zero for action
.def capture_dit = r28
.def capture_dah = r29
;R30/ZL and R31/ZH used for data pointer

.dseg

.org SRAM_START

.cseg

.org 0
 rjmp RESET

#ifdef _TN13ADEF_INC_
.org INT0addr
 reti ; External Interrupt 0
.org PCI0addr
 reti ; External Interrupt Request 0
.org OVF0addr
 rjmp TIM0_OVF_ISR ; Timer/Counter0 Overflow
.org ERDYaddr
 reti ; EEPROM Ready
.org ACIaddr
 reti ; Analog Comparator
.org OC0Aaddr
 reti ; Timer/Counter Compare Match A
.org OC0Baddr
 reti ; Timer/Counter Compare Match B
.org WDTaddr
 reti ; Watchdog Time-out
.org ADCCaddr
 reti ; ADC Conversion Complete
#else 
.org INT0addr
 reti ; External Interrupt 0
.org PCI0addr
 reti ; ; Pin change Interrupt Request 0
.org OC1Aaddr
 reti ; Timer/Counter1 Compare Match 1A
.org OVF1addr
 reti ; Timer/Counter1 Overflow
.org OVF0addr
 rjmp TIM0_OVF_ISR ; Timer/Counter0 Overflow
.org ERDYaddr
 reti ; EEPROM Ready
.org ACIaddr
 reti ; Analog comparator
.org ADCCaddr
 reti ; ADC Conversion ready
.org OC1Baddr
 reti ; Timer/Counter1 Compare Match B
.org OC0Aaddr
 reti ; Timer/Counter0 Compare Match A
.org OC0Baddr
 reti ; Timer/Counter0 Compare Match B
.org WDTaddr
 reti ; Watchdog Time-out
.org USI_STARTaddr
 reti ; USI START
.org USI_OVFaddr
 reti ; USI Overflow
#endif

RESET:
#ifdef _TN13ADEF_INC_
#else
 ldi temp1,low(RAMEND)
 out SPL,temp1 ; Set stack pointer to last internal RAM location
 ldi temp1,high(RAMEND)
 out SPH,temp1
#endif

 ldi temp1,$07
 out PORTB,temp1 ; pull-ups enabled for DIT, DAH, CMD
 ldi temp1,$18
 out DDRB,temp1 ; set outputs for XMIT and TONE
 cbi PORTB,XMIT ;turn XMIT high
 
;ATtiny13A clock set at 9.6 MHz with the CKDIV8 fuse turned off = 0.104 uS clock 
;ATtiny45 clock set at 8 MHz with the CKDIV8 fuse turned off = 0.125 uS clock
 ldi temp1,0b00000010 ; set timer0 prescale divisor to 8
 out TCCR0B,temp1 ; using 8 MHz clock = 1 uS timer pulse
;ATtiny13A using 9.6 MHz clock = 0.832 uS timer pulse
;ATtiny45 using 8 MHz clock = 1 uS timer pulse

 ldi temp1,0b00000010
#ifdef _TN13ADEF_INC_
 out TIMSK0,temp1 ;enable TIMER0 overflow interrupts
#else
 out TIMSK,temp1 ;enable TIMER0 overflow interrupts
#endif

 ldi delay_100_lo,232 ; default 100mS
 ldi delay_100_hi,3 ; default 100mS
 
 sbic PINB,CMD ; Skip if CMD button pressed
 rjmp RESET2
 ldi temp1,0 ; set address for WPM value
 ldi temp2,0
 ldi temp3,15 ; set default WPM value
 rcall EEPROM_WRITE
 ldi temp2,1 ; set address for sidetone value
 ldi temp3,8 ; set default sidetone value
 rcall EEPROM_WRITE
 ldi temp2,2 ; set address for paddles value
 ldi temp3,0 ; set default paddles value
 rcall EEPROM_WRITE
  
RESET2:
 clr temp1 ; set address for WPM value
 clr temp2
 rcall EEPROM_READ
 cpi temp3,$FF
 brne SET_WPM
 ldi temp3,15 ; default WPM
SET_WPM:
 rcall WPM_SET

 clr temp1 ; set address for sidetone value
 ldi temp2,1
 rcall EEPROM_READ
 cpi temp3,$FF
 brne SET_SIDETONE
 ldi temp3,8 ; default sidetone 625 Hz
SET_SIDETONE:
 mov sidetone,temp3
 mov tone_delay,sidetone ; toggle every 800 uS = 625 Hz

 clr temp1 ; set address for paddles value
 ldi temp2,2
 rcall EEPROM_READ
 cpi temp3,$FF
 brne SET_PADDLES
 ldi temp3,0 ; default paddles value
SET_PADDLES:
 mov paddles,temp3
   
 clr key_down
 clr capture_dit
 clr capture_dah
 clr transmit_enabled
 clr straight_key
  
 sei ; global all interrupt enable
 
 ldi temp1,LET_O
 rcall SEND_CHAR ; send an "O"
 
 rcall CHAR_DELAY
 
 ldi temp1,LET_O
 rcall SEND_CHAR ; send an "O"

 sbic PINB,CMD ; Skip if CMD button pressed
 rjmp LOOP
 ldi delay,15
 rcall WAIT
 rjmp LOOP

WAIT:
 tst delay
 brne WAIT
 ret

CHAR_DELAY:
 ldi delay,2
 rcall WAIT
 ret
 
MENU:
 cpi character,LET_S ; SET_SPEED ******************************
 brne MENU20
SET_SPEED:
 ldi temp1,LET_E 
 rcall SEND_CHAR ; send an ACK
 rcall GET_CHAR
 cpi character,1
 brne SET_SPEED10
 rjmp ERROR
SET_SPEED10:
 rcall MTON ; convert morse code from character to ascii in temp1
 cpi temp1,$FF
 brne SET_SPEED20
 rjmp ERROR
SET_SPEED20:
 cbr temp1,$30
 mov temp2,temp1 ; multiply by 10
 add temp2,temp1
 lsl temp1
 lsl temp1
 lsl temp1
 add temp2,temp1 ; temp2 holds upper digit for wpm
 ldi temp1,LET_E 
 rcall SEND_CHAR ; send an ACK
 rcall GET_CHAR
 cpi character,1
 brne SET_SPEED30
 rjmp ERROR
SET_SPEED30:
 rcall MTON ; convert morse code from character to ascii in temp1
 cpi temp1,$FF
 brne SET_SPEED40
 rjmp ERROR
SET_SPEED40:
 cbr temp1,$30
 add temp1,temp2
 tst temp1 ; error if zero
 brne SET_SPEED50
 rjmp ERROR
SET_SPEED50:
 cpi temp1,46 ; error if same or higher
 brlo SET_SPEED60
 rjmp ERROR
SET_SPEED60:
 mov temp3,temp1
 clr temp1 ; set up address for WPM
 clr temp2
 rcall EEPROM_WRITE ; save WPM to EEPROM
 rcall WPM_SET
 rjmp OK

MENU20:
 cpi character,LET_F ; SET_TONE Frequency ******************************
 brne MENU30
SET_TONE:
 ldi temp1,LET_E 
 rcall SEND_CHAR ; send an ACK
 rcall GET_CHAR
 cpi character,1
 brne SET_TONE10
 rjmp ERROR
SET_TONE10:
 rcall MTON ; convert morse code from character to binary number in temp1
 cpi temp1,0x3A
 brlo SET_TONE20
 rjmp ERROR
SET_TONE20:
 ldi temp2,43
 sub temp1,temp2
 mov sidetone,temp1
 mov temp3,temp1
 clr temp1 ; set up address for sidetone
 ldi temp2,1
 rcall EEPROM_WRITE ; save sidetone to EEPROM
 rjmp OK
;9/14 = 357 hz
;8/13 = 385 Hz
;7/12 = 417 Hz
;6/11 = 455 Hz
;5/10 = 500 Hz
;4/9 = 555 Hz
;3/8 = 625 Hz
;2/7 = 714 Hz
;1/6 = 833 Hz
;0/5 = 1000 Hz

MENU30:
 cpi character,LET_R ; REVERSE_KEY ******************************
 brne MENU40
REVERSE_KEY:
 tst paddles
 breq REVERSE2
 clr paddles
 ldi temp1,LET_P
 rcall SEND_CHAR
 rjmp REVERSE3
REVERSE2:
 inc paddles
 ldi temp1,LET_X
 rcall SEND_CHAR
REVERSE3:
 clr temp1 ; set up address for REVERSE_PADDLES
 ldi temp2,2
 mov temp3,paddles
 rcall EEPROM_WRITE ; save PADDLES value to EEPROM
 rjmp LOOP

MENU40:
 cpi character,LET_E ; STRAIGHT_KEY ******************************
 breq KEY5
 cpi character,LET_T
 brne MENU50
KEY5:
 inc straight_key
 ldi temp1,LET_S ; send "S" for straight key active
 rcall SEND_CHAR
KEY10:
 sbic PINB,CMD ; Skip if CMD button pressed
 rjmp KEY15
 clr straight_key
 rjmp OK
KEY15:
 sbis PINB,PB0 ; Skip if DIT paddle open
 rjmp KEY20
 sbis PINB,PB1 ; Skip if DAH paddle open
 rjmp KEY20
 clr key_down
 clr transmit_enabled
 rjmp KEY10
KEY20: ; key down
 ser transmit_enabled
 ser key_down
 rjmp KEY10
MENU50:

#ifdef _MESSAGE_
 cpi character,LET_M ; MESSAGE ******************************
 brne MENU60
 ldi ZH,high(2*msg1)
 ldi ZL,low(2*msg1)
MESSAGE1:
 lpm temp1,Z+
 push ZH
 push ZL
 tst temp1
 breq	MSG_COMPLETE
 ldi ZH,high(2*xref1)
 ldi ZL,low(2*xref1)
MESSAGE3:
 lpm temp2,Z+
 cp temp1,temp2
 breq MESSAGE7
 lpm temp2,Z+ ; dummy to increment pointer only
 rjmp MESSAGE3
MESSAGE7:
 lpm temp1,Z+
 ser transmit_enabled
 rcall SEND_CHAR
 rcall CHAR_DELAY
 pop ZL
 pop ZH
 rjmp MESSAGE1
MSG_COMPLETE:
 pop ZL
 pop ZH
 rjmp LOOP
MENU60:
#endif

ERROR:
 ldi temp1,CHR_ERROR
 rcall SEND_CHAR
 rjmp LOOP
OK:
 ldi temp1,LET_R ; ROGER
 rcall SEND_CHAR
 clr capture_dit
 clr capture_dah
LOOP:
 ser transmit_enabled
 rcall READ_PADDLES
 sbic PINB,CMD ; Skip if CMD button pressed
 rjmp LOOP
 clr key_down
 clr transmit_enabled
 ldi temp1,LET_R ; READY
 rcall SEND_CHAR
 rcall GET_CHAR
 cpi character,1
 breq LOOP20
 rjmp MENU ;
LOOP20:
 sbic PINB,CMD ; Skip if CMD button pressed
 rjmp ERROR
 rjmp RESET

READ_PADDLES:
 tst capture_dit
 breq DAH10
 lsl character
 tst paddles
 brne DIT15
 ldi dit_period,DIT
 rjmp DIT20
DIT15:
 inc character
 ldi dit_period,DAH
DIT20:
 tst dit_period
 brne DIT20
 clr capture_dit
DAH10:
 tst capture_dah
 breq NEXT
 lsl character
 tst paddles
 brne DAH15
 ldi dit_period,DAH
 inc character
 rjmp DAH20
DAH15:
 ldi dit_period,DIT
DAH20:
 tst dit_period
 brne DAH20
 clr capture_dah
NEXT:
 ret

WPM_SET:
 rcall DIVIDE16BY8
 mov delay_wpm_lo,temp1
 mov delay_wpm_hi,temp2
 mov wpm_lo,delay_wpm_lo
 mov wpm_hi,delay_wpm_hi
 ret
 
GET_CHAR:
 push temp1
 push temp2
 ldi delay,30 ; 3 sec get char period
 ldi character,1
GET_CHAR10:
 rcall READ_PADDLES
 mov temp2,character
 cpi character,1
 brne GET_CHAR20
 tst delay
 brne GET_CHAR10
 rjmp GET_CHAR_EXIT
GET_CHAR20:
 mov temp1,wpm_hi
 lsl temp1
 ;add temp1,wpm_hi
 mov delay,temp1 ;  set delay to 5 x wpm_hi
GET_CHAR30:
 tst delay
 brne GET_CHAR40
GET_CHAR_EXIT:
 pop temp2
 pop temp1
 ret
GET_CHAR40:
 rcall READ_PADDLES
 cp temp2,character
 breq GET_CHAR30
 mov temp2,character
 rjmp GET_CHAR20
 
SEND_CHAR: ; character is in temp1
 push temp2
 ldi temp2,9
SEND10:
 dec temp2
 lsl temp1
 brcc SEND10
SEND20:
 dec temp2
 breq SEND_CHAR_EXIT
 lsl temp1
 brcc SEND_DIT
SEND_DAH:
 ldi dit_period,DAH
SEND_DAH1:
 tst dit_period
 brne SEND_DAH1
 rjmp SEND20
SEND_DIT:
 ldi dit_period,DIT
SEND_DIT1:
 tst dit_period
 brne SEND_DIT1
 rjmp SEND20
SEND_CHAR_EXIT:
 pop temp2
 ret

TIM0_OVF_ISR:
 push temp1
 in temp1,SREG
 push temp1
 
#ifdef _TN13ADEF_INC_
 ;ldi temp1,256-120 ; counts up to 120
 ldi temp1,256-110 ; counts up to 110
#else
 ldi temp1,256-100 ; counts up to 100
#endif
 out TCNT0,temp1 ; each overflow = 100 uS
 
 sbis PINB,PB0 ; Skip if Bit in I/O Register is set (dit not active)
 ldi capture_dit,1
 sbis PINB,PB1 ; Skip if Bit in I/O Register is set (dah not active)
 ldi capture_dah,1
 
 tst delay ; used for command timing
 breq TIM0_OVF_10

 sbiw delay_100_lo,1 ; decrement delay_100 timer
 brne TIM0_OVF_10
 
 ldi delay_100_lo,232 ; default 100mS
 ldi delay_100_hi,3 ; default 100mS
 dec delay

TIM0_OVF_10:
 tst key_down
 brne TIM0_OVF_30
 tst straight_key
 breq TIM0_OVF_12
 cbi PORTB,XMIT ;turn XMIT low
TIM0_OVF_12: 
 tst dit_period
 breq TIM0_OVF_EXIT
 sbiw delay_wpm_lo,1 ; decrement wpm timer 
 brne TIM0_OVF_20
 mov delay_wpm_lo,wpm_lo
 mov delay_wpm_hi,wpm_hi
 dec dit_period
 breq TIM0_OVF_EXIT
TIM0_OVF_20:
 cpi dit_period,1
 breq TIM0_OVF_50

TIM0_OVF_30:
 dec tone_delay
 brne TIM0_OVF_EXIT
 
;TONE producing routine
 tst transmit_enabled
 breq TONE2
 sbi PORTB,XMIT ;turn XMIT high
TONE2:
 mov tone_delay,sidetone
 sbi PINB,TONE ; toggle every 800 uS = 625 Hz
 rjmp TIM0_OVF_EXIT
 
TIM0_OVF_50:
 sbi PORTB,TONE ;turn output high
 cbi PORTB,XMIT ;turn XMIT low
 
TIM0_OVF_EXIT:
 pop temp1
 out SREG,temp1
 pop  temp1
 reti

DIVIDE16BY8: ; divide 12000 by temp1, result temp1=LSB temp2=MSB
 	mov rd2,temp3
 	ldi temp1,$E0
 	mov rd1l,temp1
 	ldi temp2,$2E
	mov rd1h,temp2
div8:
	clr rd1u ; clear interim register
	clr temp2 ; clear result (the result registers
	clr temp1 ; are also used to count to 16 for the
	inc temp1 ; division steps, is set to 1 at start)
div8a:
	clc ; clear carry-bit
	rol rd1l ; rotate the next-upper bit of the number
	rol rd1h ; to the interim register (multiply by 2)
	rol rd1u
	brcs div8b ; a one has rolled left, so subtract
	cp rd1u,rd2 ; Division result 1 or 0?
	brcs div8c ; jump over subtraction, if smaller
div8b:
	sub rd1u,rd2; subtract number to divide with
	sec ; set carry-bit, result is a 1
	rjmp div8d ; jump to shift of the result bit
div8c:
	clc ; clear carry-bit, resulting bit is a 0
div8d:
	rol temp1 ; rotate carry-bit into result registers
	rol temp2
	brcc div8a ; as long as zero rotate out of the result
stop:
	ret

MTON: ; convert morse code from character to binary number in temp1
 push temp2
 ldi ZH,high(2*xref2)
 ldi ZL,low(2*xref2)
MTON1:
 lpm temp1,Z+
 lpm temp2,Z+
 tst temp1
 breq MTON8
 cp character,temp2
 brne MTON1
 rjmp MTON9
MTON8:
 ldi temp1,$FF
MTON9:
 pop temp2
 ret

EEPROM_READ:
 sbic EECR,EEPE
 rjmp EEPROM_READ ; Wait for completion of previous write
 out EEARH,temp1 ; Set up address register
 out EEARL,temp2
 sbi EECR,EERE ; Start eeprom read by writing EERE
 in temp3,EEDR ; Read data from data register
 ret

EEPROM_WRITE:
 sbic EECR,EEPE
 rjmp EEPROM_write ; Wait for completion of previous write
 push temp1
 ldi temp1,0 ; Set Programming mode
 out EECR,temp1
 pop temp1
 out EEARH,temp1 ; Set up address register
 out EEARL,temp2
 out EEDR,temp3 ; Write data temp3 to data register
 sbi EECR,EEMPE ; Write logical one to EEMPE
 sbi EECR,EEPE ; Start eeprom write by setting EEPE
 ret

msg1:
;    1234567890123456789012345678901234567890
.db "CQ CQ CQ DE W8DIZ W8DIZ @",0

xref1:
.db " ",0b00000001
xref2:
.db "0",0b00111111
.db "1",0b00101111
.db "2",0b00100111
.db "3",0b00100011
.db "4",0b00100001
.db "5",0b00100000
.db "6",0b00110000
.db "7",0b00111000
.db "8",0b00111100
.db "9",0b00111110
.db "A",0b00000101
.db "B",0b00011000
.db "C",0b00011010
.db "D",0b00001100
.db "E",0b00000010
.db "F",0b00010010
.db "G",0b00001110
.db "H",0b00010000
.db "I",0b00000100
.db "J",0b00010111
.db "K",0b00001101
.db "L",0b00010100
.db "M",0b00000111
.db "N",0b00000110
.db "O",0b00001111
.db "P",0b00010110
.db "Q",0b00011101
.db "R",0b00001010
.db "S",0b00001000
.db "T",0b00000011
.db "U",0b00001001
.db "V",0b00010001
.db "W",0b00001011
.db "X",0b00011001
.db "Y",0b00011011
.db "Z",0b00011100
.db "?",0b01001100
.db ",",0b01110011
.db ".",0b01010101
.db "/",0b00110010
.db "@",0b00101010 ; "AR"
.db "$",0b01000101 ; "SK"
.db 0,0