//======================================================
//   (c)2008-9 Matthew Ford
//   Forward Computing and Control Pty. Ltd.
//   www.forward.com.au
//   All rights reserved
//
//   You may freely copy and distribute this program for 
//           commercial and non-commercial use provided:-
//   i) this copyright notice remains attached
//   ii) you clearly mark any changes you make to the code
//======================================================
// ThreeLevelDriver.asm
//
// Main:  ATtiny84
//      mode: ISP mode
//
// Program:  ATtiny84
//    Flash - Input HEX File: yourdir/ThreeLevelDriver.hex
//    EEPROM - not used
//
// Fuses:
//    SPIEN    ticked
//    BODLEVEL  Brown Out detection at VCC = 1.8V
//    CKDIV8   ticked
//    SUT_CKSEL  Int RC Osc. 8Mhz; .... 6CK/14CK +64mS
//    Auto Read  ticked
//    Smart Warnings  ticked
//    Verify After programming  ticked
//  then Progam the fuses
//
// LockBits:  
//    LB  no lock features enabled
// 
// Advanced:
//   Calibrate of frequency 8.0Mhz
//  Do Not write any value to the Cal byte.  startup handles this 
//
// HW Setting
//   VTarget  5.1 
//   VREF 5.1 (not used)
//   Clock Generator 3.686Mhz (max)
//
//  Auto:
//  Erase Device
//  Check Signature
//  Program FLASH
//  Verify FLASH
//---------------------------------------------------------

.include "tn84def.inc"

.equ CURRENT_SP_HIGH = 1000  // the current setpoint, 1000 = full scale current, approx 500mA
.equ CURRENT_SP_MED = 333  // the current setpoint, 333 = approx 167mA
.equ CURRENT_SP_LOW = 111  // the current setpoint, 111 = approx 55mA

.equ SW_B = PB1  // define name for switch input pin
.equ uC_OUTPUT_B = PB0  // define name for uC output pin

.def Temp = r16// Temporary register

// Debounce Counter Counts allows up to 50mS debounce = 250 count
.def DEBOUNCE_Counter = r17 //  count debounce timeout
.equ Debounce_Count_Low = 50 // about 0.01 sec  = 50x5kHz
.equ Debounce_Count_High = 50 // about 0.01 sec  = 50x5kHz
// increase the count if you switch needs a longer debouce in one direction

.def ADCLow = r18 // low byte of adc
.def ADCHigh = r19 // high byte of adc

//------------------------------------------------
//SW_Flags,  Holds the current debounced switch state and last state and change trigger
//------------------------------------------------
.def SW_Flags = r20  // 0b00000000 initially i.e. Switch up and last switch up and led off
.equ SW_SWDown = 0   // bit0, 1 if button pressed, 0 if button released.
.equ SW_SWLastInput = 1  // bit1,  1 if last read SW pressed (low) not debounced
.equ SW_LedOn = 2 // bit2, 1 if led is on, 0 if off
//------------------------------------------------

//------------------------------------------------
// TRIGGER_Flags  Flags set for triggers
//------------------------------------------------
.def TRIGGER_Flags = r21  // initially 0 no triggers set
.equ TRIGGER_SwitchChanged = 0 // bit0, 1 if switch just changed else 0

//------------------------------------------------
//New_Torch_State,  Holds the new state of the torch
// updated from current Torch_State by triggers
//------------------------------------------------
.def New_Torch_State = r22 

//------------------------------------------------
//Torch_State,  Holds the current state of the torch
//------------------------------------------------
.def Torch_State = r23   // initially 0 == off
.equ Torch_State_OFF = 0  // the torch state constants
.equ Torch_State_LOW = Torch_State_OFF+1 // 1
.equ Torch_State_MED = Torch_State_LOW+1 // 2
.equ Torch_State_HIGH = Torch_State_MED+1 // 3
.equ Torch_State_EXCEEDED = Torch_State_HIGH+1 // 4 first invalid state

// registers to hold current setpoint
.def SP_High = r24 // holds the high byte of the setpoint current
.def SP_Low = r25  // holds the low byte of the setpoint current



; ***** INTERRUPT VECTORS ************************************************
.org 0x0000
   rjmp RESET
.org ADCCaddr  // = 0x000d	; ADC Conversion Complete
   rjmp  ADC_INT

//---------------------------------------------
//  Main code
//  Program starts here
//---------------------------------------------
RESET:
    cli  // disable interrupts
    // set clock to 4Mhz so can run down to 1.8V
    ldi Temp, (1<<CLKPCE)
    out CLKPR, Temp  // enable clock change for next 4 cycles
    ldi Temp, (1<<CLKPS0) // divide by 2 for 4Mhz clock  
    out CLKPR,Temp  // set 4Mhz clock

    // uC_OUTPUT_B (PB0) is an output for driving the led  DDB0=1, PB0=0 low OFF to start
    // SW_B (PB1) is the switch input  DDB1=0, PB1=1 with pullup
    ldi Temp,(1<<SW_B)
    ldi r17, (1<<uC_OUTPUT_B)
    out PORTB,Temp
    out DDRB,r17

    // Enable ADC
    ldi Temp, (1<<ADEN) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS0) 
    // (1<<ADEN) enable ADC
    //  ADSC zero do not start conversion yet
    //  ADATE zero do not trigger conversion
    //  (1<<ADIE) enable ADC interrupt need I-bit in SREG set also
    //  (1<<ADPS2) (1<<ADPS0) pre-scale for 4Mhz clock and <200Khz ==> >20 pre-scale say 32
    //   i.e. 1,0,1  for 4Mhz/32 = 125Khz
    out ADCSRA, Temp       // set the  ADCSR

    // set Vref 1.1V (1,0) and the ADC mux inputs and gain (101101) PA2+ve, PA1-ve, x20
    ldi Temp, (1<<REFS1) | (1<<MUX5) | (1<<MUX3) | (1<<MUX2) | (1<<MUX0)
    out ADMUX, Temp

    sei  // enable interrupts
E_LOOP:
    // set up for ADC conversion and then sleep
    ldi Temp, (1<<SM0) | (1<<SE)
    out MCUCR, Temp   // ADC noise reduction rest zero, enable sleep
    sleep     // goto sleep  
    // wake up on ADC interrupt 
    // on completion of interrupt returns here
    rjmp E_LOOP  // loop for next ADC 
//*************************** END_RESTART_CODE **********************


//-------------------------------------
//  ADC interrupt, result available
//-------------------------------------
ADC_INT:
    // save ADC results
    in ADCLow,ADCL
    in ADCHigh,ADCH

    sbrs SW_Flags, SW_LedOn // skip rjmp if led should be on
    rjmp CURRENT_HIGH  // if led off set output low
    // else led should be on
    sub ADCLow, SP_low 
    sbc ADCHigh, SP_High
    brge CURRENT_HIGH      // branch if ADC >= setpoint  (signed comparison)
    // else ADC < setpoint

CURRENT_LOW:
    // ADC < setpoint  ==> make output high
    sbi  PORTB, uC_OUTPUT_B   // set output high
    rjmp END_ADC_INT

CURRENT_HIGH:
    // ADC > setpoint  ==> make output low
    cbi  PORTB, uC_OUTPUT_B   // set output low
    rjmp END_ADC_INT

// all paths jmp to here
END_ADC_INT:
   rcall SWITCH_DEBOUNCE  // every ADC cycle about 5kHz
   rcall PROCESS_STATE_TRIGGERS
reti  // interrupts enabled here
//----END ADC_INT -------------------------------------------------


// ------------------------
// SWITCH_DEBOUNCE
// call this each cycle about 5khz
//  checks for debounce and updates 
//  SW_SWDown and SW_SWLastInput and SW_SwitchChanged
//  SW_SwitchChanged set to 1 each time switch changes state
//  SW_SWDown set to 1 when switch is pressed (debouced) else 0 when up
//  SW_SWLastInput is set to 1 when last switch read was low/on else set 0 for high/off
// ------------------------
SWITCH_DEBOUNCE:
    andi TRIGGER_Flags, ~(1<<TRIGGER_SwitchChanged) // clear switch changed flag

    cpi DEBOUNCE_Counter, 0xfe    // inc counter but limit to 253
    brsh DEBOUNCE_Counter_LIMITED  // skip increment if at or above limit
    inc DEBOUNCE_Counter ; count up 5KHz counter for debouce

DEBOUNCE_Counter_LIMITED:
    sbic PINB, SW_B   // read SW if SW high jump to released
    rjmp SW_HIGH_RELEASED
    // else drop through to SW_LOW_PRESSED

SW_LOW_PRESSED: // read SW low/pressed
    sbrc SW_Flags, SW_SWLastInput //check if last flag high/on, rjmp if was high 
    rjmp SW_low_check_debounced   // if last input low and this one low check for debounce low
     
    // else this sw low and last high, set last input to high and reset debounce counter
    ori SW_Flags,(1<<SW_SWLastInput)  // 1 for last pressed
    clr DEBOUNCE_Counter;  load counter restart counter for next debounce
    rjmp SWITCH_DEBOUNCE_RETURN // no change yet

SW_low_check_debounced:
    cpi DEBOUNCE_Counter, Debounce_Count_Low 
    brne SWITCH_DEBOUNCE_RETURN  // only do switch change on DEBOUNCE count
    // else update output and return
    ori SW_Flags,(1<<SW_SWDown) // debounced
    ori TRIGGER_Flags, (1<<TRIGGER_SwitchChanged) // debouced switch changed
    rjmp SWITCH_DEBOUNCE_RETURN

SW_HIGH_RELEASED: // read SW high/released
    sbrs SW_Flags, SW_SWLastInput  // check if last flag low/off, rjmp if was low
    rjmp SW_high_check_debounced  // if last input high and this one high check for debounce high
     
    // else this sw high and last low, clear last input and reset debounce counter
    andi SW_Flags,~(1<<SW_SWLastInput)
    clr DEBOUNCE_Counter;  load counter restart counter for next debounce
    rjmp SWITCH_DEBOUNCE_RETURN  // no change yet

SW_high_check_debounced:
    cpi DEBOUNCE_Counter, Debounce_Count_High 
    brne SWITCH_DEBOUNCE_RETURN  // only do switch change on DEBOUNCE count
    // update output and return
    andi SW_Flags,~(1<<SW_SWDown) // debounced
    ori TRIGGER_Flags, (1<<TRIGGER_SwitchChanged) // debouced switch changed
    rjmp SWITCH_DEBOUNCE_RETURN

SWITCH_DEBOUNCE_RETURN:
ret
//-------------------------------------

//-------------------------------------
//PROCESS_STATE_TRIGGERS
//-------------------------------------
PROCESS_STATE_TRIGGERS:
   mov New_TORCH_State, TORCH_State // set initial state

   cpi TRIGGER_Flags, 0  // are any trigger flags set
   breq END_PROCESS_STATE_TRIGGERS   // all zero so just return

   sbrs TRIGGER_Flags, TRIGGER_SwitchChanged
   rjmp Finished_SwitchChanged_TRIGGER_PROCESSING
   // else switch changed state, check switch state
   sbrc SW_Flags, SW_SWDown // skip rcall if switch is up
   rcall PROCESS_SWDown_TRIGGER  // set level accordingly
Finished_SwitchChanged_TRIGGER_PROCESSING:
   // check other triggers here

END_PROCESS_STATE_TRIGGERS:
   rcall UPDATE_TORCH_STATE  // transfer new state to TORCH_State and update setpoint etc.
   clr TRIGGER_Flags // clear all triggers as should have been processed.
ret
//-------------------------------------


//-------------------------------------
//PROCESS_SWDown_TRIGGER
//  Press to cycle through levels
//-------------------------------------
PROCESS_SWDown_TRIGGER:
    // else switch pressed down, cycle through states.
   cpi TORCH_State, Torch_State_OFF
   breq PROCESS_SWDown_TRIGGER_TORCH_OFF
   // else
   cpi TORCH_State, TORCH_State_LOW
   breq PROCESS_SWDown_TRIGGER_TORCH_LOW
   // else
   cpi TORCH_State, TORCH_State_MED
   breq PROCESS_SWDown_TRIGGER_TORCH_MED
   // else
   cpi TORCH_State, TORCH_State_HIGH
   breq PROCESS_SWDown_TRIGGER_TORCH_HIGH
   // else invalid state drop through to set to off

PROCESS_SWDown_TRIGGER_TORCH_STATE_INVALID: // invalid -> off
PROCESS_SWDown_TRIGGER_TORCH_HIGH: // high -> off
   ldi New_TORCH_State, Torch_State_OFF
   rjmp END_PROCESS_SWDown_TRIGGER

PROCESS_SWDown_TRIGGER_TORCH_OFF: // off -> low
   ldi New_TORCH_State, Torch_State_LOW
   rjmp END_PROCESS_SWDown_TRIGGER
    
PROCESS_SWDown_TRIGGER_TORCH_LOW: // low -> med
   ldi New_TORCH_State, Torch_State_MED
   rjmp END_PROCESS_SWDown_TRIGGER

PROCESS_SWDown_TRIGGER_TORCH_MED:  // med -> high
   ldi New_TORCH_State, Torch_State_HIGH
   // rjmp END_PROCESS_SWDown_TRIGGER drop through

END_PROCESS_SWDown_TRIGGER:
ret
//-------------------------------------


//-------------------------------------
//UPDATE_TORCH_STATE
// Set the new torch state and load current setpoint
//-------------------------------------
UPDATE_TORCH_STATE:
   mov TORCH_State, New_TORCH_State // update state to new state
   rcall LOAD_CURRENT_SETPOINT // load new setpoint
ret
//-------------------------------------


//-------------------------------------
//LOAD_CURRENT_SETPOINT
//  Loads the current setpoint base on the torch state
//  Also set SW_LedOn depending on TORCH_State
//-------------------------------------
LOAD_CURRENT_SETPOINT:
   cpi TORCH_State, Torch_State_OFF
   breq SET_TORCH_OFF
   // else
   cpi TORCH_State, TORCH_State_LOW
   breq SET_TORCH_LOW
   // else
   cpi TORCH_State, TORCH_State_MED
   breq SET_TORCH_MED
   // else
   cpi TORCH_State, TORCH_State_HIGH
   breq SET_TORCH_HIGH
INVALID_STATE:  // else invalid turn off
   ldi TORCH_State, TORCH_State_OFF // set valid state
   // drop through to set off
SET_TORCH_OFF:
   clr SP_High
   clr SP_Low
   andi SW_Flags,~(1<<SW_LedOn) // turn off
   rjmp END_LOAD_CURRENT_SETPOINT
    
SET_TORCH_LOW:
   ldi SP_High, high(CURRENT_SP_LOW)
   ldi SP_Low, low(CURRENT_SP_LOW)
   ori SW_Flags,(1<<SW_LedOn) // turn on
   rjmp END_LOAD_CURRENT_SETPOINT

SET_TORCH_MED:
   ldi SP_High, high(CURRENT_SP_MED)
   ldi SP_Low, low(CURRENT_SP_MED)
   ori SW_Flags,(1<<SW_LedOn) // turn on
   rjmp END_LOAD_CURRENT_SETPOINT

SET_TORCH_HIGH:
   ldi SP_High, high(CURRENT_SP_HIGH)
   ldi SP_Low, low(CURRENT_SP_HIGH)
   ori SW_Flags,(1<<SW_LedOn) // turn on
   rjmp END_LOAD_CURRENT_SETPOINT

END_LOAD_CURRENT_SETPOINT:
ret
//-------------------------------------

