; ATtiny.104.asm
; version 14 Oct 2021
; author: W8DIZ

; i2c tutorial via youtube: https://www.youtube.com/watch?v=7CgNF78pYQM
; i2c bus sec: https://kitsandparts.com/tutorials/learn.i2c/I2C_bus_spec.pdf

; cut and paste the desired AVRA or AVRDUDE commands to the command line on the LINUX TERMINAL PROGRAM.

; Set up the working directory for avra ==> cd ~/Desktop/freddieneil.com/kitsandparts.com/tutorials/ATtiny/104/

; Assemble the code ==> avra ATtiny.104.asm

; To enables PB5 and disable External Reset:
; for ATtiny13, clear bit 0 in Fuse High Byte.
; for ATtiny25/45/85, clear bit 7 in Fuse High Byte.
; Must use high voltage programmer like the dragon_hvsp if PB5 enabled

;  avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * avrispv2 * 

; avrdude -P usb -p t13 -c avrispv2 -U ATtiny.104.hex
; avrdude -P usb -p t13 -c avrispv2 -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m
; avrdude -P usb -p t13 -c avrispv2 -U ATtiny.104.hex -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m

; avrdude -P usb -p t45 -c avrispv2 -U ATtiny.104.hex
; avrdude -P usb -p t45 -c avrispv2 -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m
; avrdude -P usb -p t45 -c avrispv2 -U ATtiny.104.hex -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m

; avrdude -P usb -p t85 -c avrispv2 -U ATtiny.104.hex
; avrdude -P usb -p t85 -c avrispv2 -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m
; avrdude -P usb -p t85 -c avrispv2 -U ATtiny.104.hex -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m

; dragon_hvsp * dragon_hvsp * dragon_hvsp * dragon_hvsp * dragon_hvsp * dragon_hvsp * dragon_hvsp * dragon_hvsp * dragon_hvsp

; avrdude -P usb -p t13 -c dragon_hvsp -U ATtiny.104.hex
; avrdude -P usb -p t13 -c dragon_hvsp -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m
; avrdude -P usb -p t13 -c dragon_hvsp -U ATtiny.104.hex -U lfuse:w:0x6A:m

; avrdude -P usb -p t45 -c dragon_hvsp -U ATtiny.104.hex
; avrdude -P usb -p t45 -c dragon_hvsp -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m
; avrdude -P usb -p t45 -c dragon_hvsp -U ATtiny.104.hex -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m

; avrdude -P usb -p t85 -c dragon_hvsp -U ATtiny.104.hex
; avrdude -P usb -p t85 -c dragon_hvsp -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m
; avrdude -P usb -p t85 -c dragon_hvsp -U ATtiny.104.hex -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m


; define ATtiny version

.equ  ATtiny13  = 0   ; Declares the symbol ATtiny13
.equ  ATtiny25  = 0   ; Declares the symbol ATtiny25
.equ  ATtiny45  = 0   ; Declares the symbol ATtiny45
.equ  ATtiny85  = 1   ; Declares the symbol ATtiny85

; 1-Rst--------Vcc-8
; 2-PB3--------PB2-7
; 3-PB4--------PB1-6
; 4-Gnd--------PB0-5

.nolist

; conditional assembly: .define .undef .if .ifdef .ifndef .else .elif .endif .error .warning .message 
.if    ATtiny13  ==  1
  .include  "../../include/tn13def.inc"
  .message  "tn13def.inc selected"
.elif  ATtiny25  ==  1
  .include  "../../include/tn25def.inc"
  .message  "tn25def.inc selected"
.elif  ATtiny45  ==  1
  .include  "../../include/tn45def.inc"
  .message  "tn45def.inc selected"
.elif  ATtiny85  ==  1
  .include  "../../include/tn85def.inc"
  .message  "tn85def.inc selected"
.else
  .error  "include file not specified"
.endif

.list
 
.equ  PINx      = PINB
.equ  DDRx      = DDRB
.equ  PORTx     = PORTB
.equ  sda_bit   = portb0
.equ  scl_bit   = portb1
.equ  lcd_e     = portb2
.equ  tune_bit  = portb3
.equ  led_bit   = portb4
.equ  not_used  = portb5
.equ  si5351_i2cAddr  = 0xC0  ; si5351A address = 0x60 but needs to shift left 1 bit

; Define register names
; r0 is used by Z-registor
; NOTE: registers r0-r15 are not available for all instructions

.def  temp1   = r16
.def  temp2   = r17
.def  temp3   = r18
.def  temp4   = r19
.def  temp5   = r20
.def  delay   = r22
.def  i2cData = r23
.def	si5351_regNum = r24
.def	si5351_regVal = r25
; r26-r31 used as 16 bit register pointers X, Y ,Z
            
.dseg
.org SRAM_START

.cseg
.org 0

.if ATtiny13  ==  1
  rjmp  RESET         ; Reset Handler
  reti  ; EXT_INT0    ; IRQ0 Handler
  reti  ; PCINT0      ; PCINT0 Handler
  rjmp  OVF0_ISR      ; Timer0 Overflow
  reti  ; EE_RDY      ; EEPROM Ready Handler
  reti  ; ANA_COMP    ; Analog Comparator Handler
  reti  ; TIM0_COMPA  ; Timer0 CompareA Handler
  reti  ; TIM0_COMPB  ; Timer0 CompareB Handler
  reti  ; WATCHDOG    ; Watchdog Interrupt Handler
  reti  ;	ADC         ; ADC Conversion Handler
.else
  rjmp RESET
  reti  ; INT0        ; External Interrupt 0
  reti  ; PCI0        ; Pin change Interrupt Request 0
  reti  ; OC1A        ; Timer/Counter1 Compare Match 1A
  reti  ; OVF1        ; Timer/Counter1 Overflowscl
  rjmp  OVF0_ISR      ; Timer/Counter0 Overflow
  reti  ; ERDY        ; EEPROM Ready
  reti  ; ACI         ; Analog comparator
  reti  ; ADCC        ; ADC Conversion ready
  reti  ; OC1B        ; Timer/Counter1 Compare Match B
  reti  ; OC0A        ; Timer/Counter0 Compare Match A
  reti  ; OC0B        ; Timer/Counter0 Compare Match B
  reti  ; WDT         ; Watchdog Time-out
  reti  ; USI_START   ; USI START
  reti  ; USI_OVF     ; USI Overflow
.endif

RESET:  ; init stack pointer
  ldi   temp1,  low(RAMEND)
  out   SPL,    temp1 ; Set stack pointer to last internal RAM location
.if ATtiny13  ==  1
.elif  ATtiny25  ==  1
.else
  ldi   temp1,high(RAMEND)
  out   SPH,temp1
.endif

led_init:
  sbi   PORTx,  led_bit   ; set led_bit to turn off LED
  sbi   DDRx,   led_bit   ; set led_bit to enable output for LED control

i2c_init:
	cbi   DDRx,   sda_bit ; make sda_bit an input - release Si5351A sda_bit i2c sda line - float high via pull up resistor
	cbi   DDRx,   scl_bit ; make scl_bit an input - release Si5351A scl_bit i2c scl line - float high via pull up resistor
	cbi   PORTx,  sda_bit ; set sda_bit to logic "0" (when DDRx(sda_bit) changes to an output)
	cbi   PORTx,  scl_bit ; set scl_bit to logic "0" (when DDRx(scl_bit) changes to an output)

.if ATtiny13  ==  1
  ; set System Clock Prescaler - system default clock is 9.6 MHz - can be changed to 4.8 MHz by Fuse Low Byte
  ldi   temp1,  0b10000000  ; init for prescaler change
  out   CLKPR,  temp1       ; else out instruction to CLKPR will not take effect
  ; ldi   temp1,  0b00000000  ; prescaler / 1
  ; ldi   temp1,  0b00000001  ; prescaler / 2
  ; ldi   temp1,  0b00000010  ; prescaler / 4 - ( 9.6 MHz / 4) = 2.400 MHz = 417 nanoseconds
  ldi   temp1,  0b00000011  ; prescaler / 8 - default set by Fuse Low Byte ( 9.6 MHz / 8) = 1.200 MHz = 833 nanoseconds
  ; ldi   temp1,  0b00000100  ; prescaler / 16 - ( 9.6 MHz / 16) = 600 KHz = 1.667 microseconds
  ; the setting below disable reprogramming the uP - must use "-B 500" with AVRDUDE to fix
  ; ldi   temp1,  0b00000101  ; prescaler / 32 - ( 9.6 MHz / 32) = 300 KHz = 3.333 microseconds
  ; ldi   temp1,  0b00000110  ; prescaler / 64 - ( 9.6 MHz / 64) = 150 KHz = 6.667 microseconds
  ; ldi   temp1,  0b00000111  ; prescaler / 128 - ( 9.6 MHz / 128) = 75 KHz = 13.333 microseconds
  ; ldi   temp1,  0b00001000  ; prescaler / 256 - ( 9.6 MHz / 256) = 37500 Hz = 26.667 microseconds
  out   CLKPR,  temp1
.else
  ; set System Clock Prescaler - system default clock is 8.0 MHz - can be changed to 6.4 MHz by Fuse Low Byte
  ldi   temp1,  0b10000000  ; init for prescaler change
  out   CLKPR,  temp1       ; init for prescaler change
  ldi   temp1,  0b00000000  ; prescaler / 1 - ( 8.0 MHz / 1) = 8.000 MHz = 125 nanoseconds
  ; ldi   temp1,  0b00000001  ; prescaler / 2 - ( 8.0 MHz / 2) = 4.000 MHz = 250 nanoseconds
  ; ldi   temp1,  0b00000010  ; prescaler / 4 - ( 8.0 MHz / 4) = 2.000 MHz = 500 nanoseconds
  ; ldi   temp1,  0b00000011  ; prescaler / 8 - factory default; set by Fuse Low Byte ( 8.0 MHz / 8) = 1.000 MHz = 1.000 microsecond
  ; ldi   temp1,  0b00000100  ; prescaler / 16 - ( 8.0 MHz / 16) = 500 KHz = 2.000 microseconds
  ; the setting below disable reprogramming the uP - must use "-B 500" with AVRDUDE to fix
  ; ldi   temp1,  0b00000101  ; prescaler / 32 - ( 8.0 MHz / 32) = 250 KHz = 4.000 microseconds
  ; ldi   temp1,  0b00000110  ; prescaler / 64 - ( 8.0 MHz / 64) = 125 KHz = 8.000 microseconds
  ; ldi   temp1,  0b00000111  ; prescaler / 128 - ( 8.0 MHz / 128) = 62.5 KHz = 16.000 microseconds
  ; ldi   temp1,  0b00001000  ; prescaler / 256 - ( 8.0 MHz / 256) = 31250 Hz = 32.000 microseconds
  out   CLKPR,  temp1
.endif 

; with prescaler set to 8:
; ATtiny13 - set Timer/Counter Prescaler to System Clock Source
; ATtinyx5 - set Timer/Counter Prescaler to System Clock Source
; ldi   temp1,  0b00000001  ; no prescaling
  ldi   temp1,  0b00000010  ; CLK 1/8 from prescaler (6.667 uS fo ATtiny13, 8.000 uS for ATtinyx5)
; ldi   temp1,  0b00000011  ; CLK 1/64
; ldi   temp1,  0b00000100  ; CLK 1/256
; ldi   temp1,  0b00000101  ; CLK 1/1024
  out   TCCR0B, temp1	      ; set timer prescaler
  ldi   temp1,  0b00000010  ; overflow interrupt enable bit
.if ATtiny13  ==  1
  out   TIMSK0, temp1       ; enable timer/counter0 overflow interrupt
.else    
  out   TIMSK,  temp1       ; enable TIMER0 overflow interrupts
.endif
    
  sei   ; global all interrupt enable
  
menu:	  ; main program

.if ATtiny13  ==  1
  ldi  delay,  250   ; 250 * 1 mS = 250 mS for ATtiny13
.else
  ldi  delay,  250   ; 250 * 2 mS = 500 mS for ATtinyx5
.endif
wait1:
  tst   delay
  brne  wait1
  sbi   PINx,   led_bit ; toggle LED on/off
  
  ldi si5351_regNum, 3
  ldi si5351_regVal, 0xFF
  rcall i2cSend   ; disable all outputs

  ldi   si5351_regNum, 16
  ldi   si5351_regVal, 0x80
  rcall i2cSend   ; power down clock0

  ldi   si5351_regNum, 17
  ldi   si5351_regVal, 0x80
  rcall i2cSend   ; power down clock1

  ldi   si5351_regNum, 18
  ldi   si5351_regVal, 0x80
  rcall i2cSend   ; power down clock2

  ;ldi   si5351_regNum, 15
  ;ldi   si5351_regVal, 0x00   ; default is 0x00  
  ;rcall i2cSend   ; select PLLa,b source and use xtal input

  ;ldi   si5351_regNum, 24
  ;ldi   si5351_regVal, 0x00   ; default is 0x00  
  ;rcall i2cSend   ; clock disable state

  ldi si5351_regNum, 149
  ldi si5351_regVal, 0    ; disable spread spectrum on PLLa
  rcall i2cSend   ; reset both PLLs
  
; Output Multisynth0, e=0, f=1, MS0_P2 and MS0_P3
  ldi   si5351_regNum, 42
  ldi   si5351_regVal, 0
  rcall i2cSend   ; set MS0_P3[15:8]

  ldi   si5351_regNum, 43
  ldi   si5351_regVal, 1
  rcall i2cSend   ; set MS0_P3[7:0]
  
  ldi   si5351_regNum, 47
  ldi   si5351_regVal, 0
  rcall i2cSend   ; set MS0_P3[19:16] | MS0_P2[19:16]

  ldi   si5351_regNum, 48
  ldi   si5351_regVal, 0
  rcall i2cSend   ; set MS0_P2[15:8]

  ldi   si5351_regNum, 49
  ldi   si5351_regVal, 0
  rcall i2cSend   ; set MS0_P2:[7:0]
  
  ldi   si5351_regNum, 16
  ldi   si5351_regVal, 0x4F
  rcall i2cSend   ; power up clock0, MS0 in integer mode,  PLLa and 8 mA

  ;ldi si5351_regNum, 183
  ;ldi si5351_regVal, 0xD2 ;$D2=default 10pF, $92=8pF, $52=6pF
  ;rcall i2cSend   ; reset both PLLs

  ldi si5351_regNum, 177
  ldi si5351_regVal, 0xA0
  rcall i2cSend   ; reset both PLLs
  
  ldi si5351_regNum, 3
  ldi si5351_regVal, 0xFE
  rcall i2cSend   ; enable clock0 output only 
  
; Load PLLa Feedback Multisynth 26,27,28,29,30,31,32,33

  ldi   si5351_regNum,  26
  ldi   si5351_regVal,  0xff
  rcall i2cSend   ; set MSNA_P3[15:8]

  ldi   si5351_regNum,  27
  ldi   si5351_regVal,  0xff
  rcall i2cSend   ; set MSNA_P3[7:0]

  ldi   si5351_regNum,  28
  ldi   si5351_regVal,  0
  rcall i2cSend   ; set MSNA_P1[17:16]

  ldi   si5351_regNum,  29
  ldi   si5351_regVal,  0x0c
  rcall i2cSend   ; set MSNA_P1[15:8]
  
  ldi   si5351_regNum,  30
  ldi   si5351_regVal,  0x00
  rcall i2cSend   ; set MSNA_P1[7:0]
  
  ldi   si5351_regNum,  31
  ldi   si5351_regVal,  0xf0
  rcall i2cSend   ; set MSNA_P3[19:16] | MSNA_P2[19:16]
  
  ldi   si5351_regNum,  32
  ldi   si5351_regVal,  0xd6
  rcall i2cSend   ; set MSNA_P2[15:8]
  
  ldi   si5351_regNum,  33
  ldi   si5351_regVal,  0x00
  rcall i2cSend   ; set MSNA_P2[7:0]
   
; Load Output Multysynth with d (e and f already set during init and never changes) 44,45,46

  ldi   si5351_regNum,  44
  ldi   si5351_regVal,  0
  rcall i2cSend   ; set MS0_P1[17:16]

  ldi   si5351_regNum,  45
  ldi   si5351_regVal,  0x30
  rcall i2cSend   ; set MS0_P1[15:8]

  ldi   si5351_regNum,  46
  ldi   si5351_regVal,  0
  rcall i2cSend   ; set MS0_P1[7:0]

  ldi si5351_regNum, 177
  ldi si5351_regVal, 0x20
  rcall i2cSend   ; reset PLLa only

finis:
  rjmp  finis
 
i2cSend:  ; (uses si5351_regNum, si5351_regVal)
	rcall	i2c_start				  ; Send start condition and address
	MOV		i2cdata,  si5351_regNum	; Write word address (0x00)
	rcall	i2c_write_8bits 	  ; Execute transfer
	MOV		i2cdata,  si5351_regVal	; Set write data to 01010101b
	rcall	i2c_write_8bits 	  ; Execute transfer
	rcall	i2c_stop				  ; Send stop condition
	ret

i2c_start:
  ldi   i2cdata,  si5351_i2cAddr     ; load i2c device address to transmit register
  sbi   DDRx,     sda_bit   ; force data line low
  rcall i2cDelay_50
  sbi   DDRx,     scl_bit   ; force clock line low
  rcall i2cDelay_50
i2c_write_8bits:
  sec                       ; set carry flag
  rol   i2cdata             ; shift in carry bit and out bit into carry bit
i2c_write_bit:
  breq  i2c_get_ack
  brcc  i2c_bit_write_low
clock_it_high:
  cbi   DDRx,   sda_bit     ; release sda_bit - float sda high
  rjmp  clock_it
i2c_bit_write_low:
  sbi   DDRx,   sda_bit     ; force sda line low
clock_it:
  rcall i2cDelay_25
  cbi   DDRx,   scl_bit     ; release scl_bit - float scl high
  rcall i2cDelay_25
  sbi   DDRx,   scl_bit     ; force scl_bit low
  rcall i2cDelay_25
next_i2c_byte:
  lsl i2cdata
  rjmp  i2c_write_bit
  
i2c_get_ack:
  cbi   DDRx,   sda_bit     ; release sda_bit - float sda high
  rcall i2cDelay_25
  cbi   DDRx,   scl_bit     ; release scl_bit - float scl high
  rcall i2cDelay_25
i2c_get_ack_wait:
  sbis  PINx,   scl_bit     ; wait sda_bit high
  rjmp  i2c_get_ack_wait
  sbi   DDRx,   sda_bit     ; force sda_bit low
  rcall i2cDelay_25
  sbi   DDRx,   scl_bit     ; force scl_bit low
  rcall i2cDelay_25
  clc                       ; clear carry flag
  sbic  PINx,   sda_bit     ; if sda_bit is high
  sec                       ; set carry flag
  rcall i2cDelay_50
  ret  

i2c_stop:
  sbi   DDRx,   scl_bit     ; force scl_bit low
  sbi   DDRx,   sda_bit     ; force sda_bit low
  rcall i2cDelay_25
  cbi   DDRx,   scl_bit     ; release scl_bit
  rcall i2cDelay_25
  cbi   DDRx,   sda_bit     ; release sda_bit
  rcall i2cDelay_25
  ret
  
i2cDelay_50:
  ldi temp1, 17 ; total delay = 40(cycles) * 125 nS = 5.0 uS
  rjmp  i2cDelay_loop
i2cDelay_25:
  ldi temp1, 7  ; total delay = 20(cycles) * 125 nS = 2.5 uS
i2cDelay_loop:
  in    temp2,  SREG
  push  temp2
i2cDelay_loop2:
; with System Clock Prescaler set to /1
; 104 nS per clock for ATtiny13
;	125 nS per clock for ATtinyx5
; rcall instruction takes 2 clock cycles
; ret instruction takes 4 clock cycles
;	6 cycles * 0.104 uS per clock for ATtiny13 = 624 nS
;	6 cycles * 0.125 uS per clock for ATtinyx5 = 750 nS
; ldi & brne instructions take 1 clock cycle each
  dec temp1
  brne  i2cDelay_loop2
  pop   temp2
  out   SREG,   temp2
  ret
	
; Data transfer is initiated with a start condition (S) signaled by sda_bit being pulled low while SCL stays high.
  
OVF0_ISR:
; with System Clock Prescaler set to /8
; and Timer/Counter Prescaler set to /8
; and TCNT0 loaded with 150 or 250 (see below)
  push  temp1
  push  temp2
  in    temp1,  SREG
  push  temp1
.if ATtiny13  ==  1
  ldi   temp1,  256-150 ;	1 mS for ATtiny13 ; 6.667 uS per count
.else
  ldi   temp1,  256-250 ;	2 mS for ATtinyx5 ; 8.000 uS per count
.endif
  out   TCNT0,  temp1   ;	set for next overflow
  tst   delay
  breq  TIM0_exit
  dec   delay
TIM0_exit:
  pop   temp1
  out   SREG,   temp1
  pop   temp2
  pop   temp1
  reti
	
