//======================================================
//   (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
//======================================================
// SwitchedLedDriver.asm
//
// Main:  ATtiny84
//      mode: ISP mode
//
// Program:  ATtiny84
//    Flash - Input HEX File: yourdir/SwitchedLedDriver.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 = 1000  // the current setpoint, 1000 = full scale current, approx 500mA

.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

.def ADCLow = r18 // low byte of adc
.def ADCHigh = r19 // high byte of adc



// 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

//------------------------------------------------
//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 not trigger 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_SwitchChanged = 2 // bit2, 1 if switch just changed else 0
.equ SW_LedOn = 3 // bit3, 1 if led is on, 0 if off
//------------------------------------------------


; ***** 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
    subi ADCLow, low(CURRENT_SP) 
    sbci ADCHigh, high(CURRENT_SP)
    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 SW_Flags, ~(1<<SW_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 SW_Flags, (1<<SW_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 SW_Flags, (1<<SW_SwitchChanged) // debouced switch changed
    rjmp SWITCH_DEBOUNCE_RETURN

SWITCH_DEBOUNCE_RETURN:
ret
//-------------------------------------

//-------------------------------------
//PROCESS_STATE_TRIGGERS
//  Press to switch on. Press to switch off
//-------------------------------------
PROCESS_STATE_TRIGGERS:

    sbrs SW_Flags, SW_SwitchChanged   // skip rjmp is there was a switch change
    rjmp END_PROCESS_STATE_TRIGGERS   // no switch change so just finish

    // else switch changed state, set SW_LedOn accordingly
    sbrs SW_Flags, SW_SWDown // skip rjmp if switch is down/on
    rjmp END_PROCESS_STATE_TRIGGERS  // do nothing on switch relase
    // else toggle led on state
    sbrc SW_Flags, SW_LedOn // skip rjmp if led already off
    rjmp TURN_LED_OFF   // led on, so turn off
    // else turn led on
    ori SW_Flags,(1<<SW_LedOn) // turn on
    rjmp END_PROCESS_STATE_TRIGGERS // finished

TURN_LED_OFF:
    andi SW_Flags,~(1<<SW_LedOn) // turn off
  // drop through to end

END_PROCESS_STATE_TRIGGERS:
ret
