;*********************************************************************
;* Projekt: RC DC Motor Speed Control with ATtiny15                  *
;*********************************************************************
;* Owner: Christian Gornik, christian@gorniknet.de, www.gorniknet.de *
;*********************************************************************
  
.include "tn15def.inc" ; all Registernames can be used as in the manual

;*********************************************************************
;* Datasegment:                                                      *
;* As the ATtiny15 does not have any RAM, no Datasegment exists.     *
;* To get some variables the 32 working registers are used.          *
;* For "xxxx" you can choose a variablename                          *
;*********************************************************************

;.DSEG does not exsist!!!

; this registers can only operate with other registers !!!
.DEF imp_min             =r0
.DEF imp_max             =r1
.DEF debounce_mem        =r2
.DEF new_imp             =r3
.DEF new_tries           =r4
.DEF motor_brake_pos     =r5
.DEF accu_voltage        =r6
.DEF brake_pwm_counter   =r7
.DEF debounce_counter    =r8
.DEF overflow_counter    =r9
.DEF adc_loop_counter    =r10
.DEF switch_off_voltage  =r11
;.DEF xxxx=r12
;.DEF xxxx=r13
;.DEF xxxx=r14
;.DEF xxxx=r15
; this registers can operate with registers and immediates
.DEF temp_reg1            =r16
.DEF temp_reg2            =r17
.DEF temp_reg3            =r18
.DEF temp_reg1_int        =r19
.DEF temp_reg2_int        =r20
.DEF temp_reg3_int        =r21
.DEF imp_actual           =r22
.DEF status_1             =r23
.DEF status_2             =r24
.DEF counter              =r25
.DEF brake_sequence_state =r26
.DEF compare_int_counter  =r27
.DEF imp_min_counter      =r28
;.DEF xxxx=r29
;.DEF xxxx=r30 ZL-Register keep free for pointers
;.DEF xxxx=r31 ZH-Register keep free for pointers

;                           ATtiny 15L
;                             _____
;              RESET / PB5 -1|  o  |8- VCC
;        Motor brake / PB4 -2|     |7- PB2 / RC Signal
;                ADC / PB3 -3|     |6- PB1 / Motor PWM
;                      GND -4|_____|5- PB0 / LED
                       
;.equ switch   = PB5
.equ brake    = PB4
.equ adc      = PB3
.equ signal   = PB2
.equ motor    = PB1
.equ led      = PB0

.equ rising_edge_flag         = 0 ; bit mask for the status_1
.equ falling_edge_flag        = 1 ; bit mask for the status_1
.equ bad_signal_flag          = 2 ; bit mask for the status_1
.equ new_pwm_flag             = 3 ; bit mask for the status_1
.equ activate_pwm_flag        = 4 ; bit mask for the status_1
.equ activate_brake_flag      = 5 ; bit mask for the status_1
.equ two_second_flag          = 6 ; bit mask for the status_1
.equ activate_adc_flag        = 7 ; bit mask for the status_1

.equ low_voltage_flag         = 0 ; bit mask for the status_2
.equ full_throttle_flag       = 1 ; bit mask for the status_2

.equ stop_position            = 0x82 ; the stop position must be smaller than 1.3 ms
.equ min_imp_length           = 0x50 ; the min. impulse length is 0.8 ms
.equ max_imp_length           = 0xfa ; the max. impulse length is 2.5 ms
.equ full_throttle_offset     = 0x3c ; 0,60 ms = 0x3c
.equ time_to_1st_overflow     = 0x80 ; the 1st timer overflow occures after 128 ms
.equ stop_brake_hysteresis    = 0x01 ; 0.01 ms gap between stop position and motor brake
.equ stop_run_hysteresis      = 0x02 ; 0.02 ms gap between stop position and motor run
.equ pwm_100_hysteresis       = 0x01 ; 0.02 ms gap between PWM and motor run 100%
;*********** signal filter parameter ***************
.equ number_of_imp_mins       = 0x0a ; number of same imp_mins for learning the stop position
.equ number_of_same_signals   = 0x30 ; number of same signals to debounce the signal
.equ number_of_max_new_tries  = 0x05 ; number of max. debounce failures
.equ signal_deviation         = 0x20 ; signal deviation to the last signal
;***************************************************
.equ two_seconds              = 0xc8 ; 200 compare int. at a presc. of 64 are 2 seconds
.equ first_time               = 0x01
.equ zero                     = 0x00
;***************************************************
.equ first_brake_state        = 0x01
.equ last_brake_state         = 0x05
; Software version = sw_version_1.sw_version_2 = 0.13
.equ sw_version_1             = 0x00
.equ sw_version_2             = 0x13
;**************** EEPROM addresses *****************
.equ calibration_byte_address = 0x00 ; address in the eeprom
.equ sw_version_1_address     = 0x01 ; address in the eeprom
.equ sw_version_2_address     = 0x02 ; address in the eeprom
;***************************************************
.equ eeprom_read_enable       = 0x01

.equ _20_percent_brake_pwm    = 0x05
.equ _40_percent_brake_pwm    = 0x0a
.equ _60_percent_brake_pwm    = 0x0f
.equ _80_percent_brake_pwm    = 0x14
.equ _100_percent_brake_pwm   = 0x19
.equ brake_pwm_period         = 0x19
.equ min_accu                 = 0x37 ; minimal voltage = 5V
.equ max_accu                 = 0xb9 ; maximal voltage = 17V  @ 12 cells
.equ accu_measuring_loops     = 0x03
.equ voltage_of_one_cell      = 0x0c ; this value is in digits: 12 digits = 1.2V
                                     ; the value has to be changed if the voltage-divider is changed
.equ min_voltage_of_one_cell  = 0x08 ; 8 digits = 0.8V, this voltage should not fall below                                     

;*********************************************************************
;* Codesegment:                                                      *
;* Here everything begins!!!                                         *
;*********************************************************************

.CSEG

.ORG 0x0000  ; Startaddress of the Codesegment (Interrupt Vectors)

rjmp RESET ; Destination after a Reset
rjmp SIGNAL_PIN_CHANGE ; Destination after signal change at the Signalpin
rjmp SPUR_INT ; not used
rjmp TIMER1_COMPARE ; if the timer value has the same value as the OCR1A-Register
rjmp SPUR_INT ; not used
rjmp TIMER0_OVERFLOW ; Destination if the Timer0 overflows
rjmp SPUR_INT ; not used
rjmp SPUR_INT ; not used
rjmp SPUR_INT ; not used


.ORG 0x0009  ; Startadress of the program

;*********************************************************************
;* Undervoltage-cut-off-table :                                      *
;* This table defines the voltages for different numbers of cells,   *
;* when to stop the motor to save the cells for undervoltage         * 
;*********************************************************************

;CUT_OFF_VOLTAGE_TABLE:
; .DW 0xaa53
 
;*************************************************************************************************
;* interrupt routines                                                                            *
;*************************************************************************************************

RESET:
 rjmp MAIN_INIT ; after a reset we are jumping to MAIN_INIT

;*************************************************************************************************
;*************************************************************************************************
;*************************************************************************************************

SIGNAL_PIN_CHANGE:
 sei
 sbis PINB,PINB2 ;if PB2 gets a rising edge skip to RISING_EDGE, else go to FALLING_EDGE
 rjmp FALLING_EDGE
 rjmp RISING_EDGE
 
;*************************************************************************************************
;*************************************************************************************************
;*************************************************************************************************

TIMER1_COMPARE:
 sei
 inc compare_int_counter ; each time the timer reaches the value saved in OCR1A the interrupt
                         ; routine is called and the compare_int_counter is incremented
 cpi compare_int_counter,two_seconds ; if the interrupt appeared 200 times, 2 seconds are elapsed
 breq SET_TWO_SEC_FLAG
reti
 SET_TWO_SEC_FLAG:
 sbr status_1,(1 << two_second_flag) ; set two_second_flag
 clr compare_int_counter
reti 

;*************************************************************************************************
;*************************************************************************************************
;*************************************************************************************************

TIMER0_OVERFLOW:
 inc overflow_counter ; each overflow the overflow_counter is incremented
reti 

;*************************************************************************************************
;*************************************************************************************************
;*************************************************************************************************

SPUR_INT:
 rjmp SPUR_INT ; this endless loop will force a watchdog-reset!
 
;*************************************************************************************************
;end of interrupt routines ***********************************************************************
;*************************************************************************************************

;*********************************************************************
;* Funktion: FALLING_EDGE                                            *
;*********************************************************************
;* Description: If a falling edge is recognised at PB2, the Timer is *
;*              read and the PWM gets a new value                    *
;*********************************************************************
FALLING_EDGE:
 clr temp_reg1_int
 out TCCR0,temp_reg1_int ; the timer is stopped so it can not generate an overflow interrupt

 in temp_reg1_int,TIMSK
 andi temp_reg1_int,~(1<<TOIE0) ; timer0 overflow disabled
 out TIMSK,temp_reg1_int

 cbr status_1,(1 << rising_edge_flag) ; clear the rising_edge_flag
 sbr status_1,(1 << falling_edge_flag) ; set falling_edge_flag
 
 sbr temp_reg1_int,(1<<ISC00)+(1<<ISC01)+(1<<PUD)
 out MCUCR,temp_reg1_int ; After a falling edge a rising edge is following. To see the next rising
                         ; edge, we have to reconfigure the INT0 interrupt and the internal pull-ups
                         ; at all pins at PortB are switched off                      
 in temp_reg1_int,TCNT0 ; the timer is read (remember the resolution of 0.005ms, see in RISING EDGE)
 lsr temp_reg1_int ; the actual timer value is divided by 2
 clr temp_reg2_int
 cpse overflow_counter,temp_reg2_int ; skip if no overflow occured
 rjmp OVERFLOWS_OCCURED
 mov imp_actual,temp_reg1_int ;  and saved in imp_actual
 clr overflow_counter ; reset the overflow_counter                       
 clr temp_reg1_int
reti ; back to the main program

 OVERFLOWS_OCCURED:
  ldi temp_reg2_int,first_time
  cpse overflow_counter,temp_reg2_int ; skip if the first overflow occured
  rjmp TOO_MUCH_OVERFLOWS
  ldi temp_reg3_int,time_to_1st_overflow
  add temp_reg3_int,temp_reg1_int ;  and added to temp_reg3_int
  mov imp_actual,temp_reg3_int ; the new value is copied to imp_actual
  clr overflow_counter ; reset the overflow_counter                       
  clr temp_reg1_int
 reti ; back to the main program

 TOO_MUCH_OVERFLOWS:
  ldi temp_reg3_int,number_of_max_new_tries
  mov new_tries,temp_reg3_int ; we got a bad signal!!!
  clr imp_actual ; if more than one overflow occured the signal is not correct
  clr overflow_counter ; reset the overflow_counter                       
 reti ; back to the main program

;*********************************************************************
;* Funktion: RISING_EDGE                                             *
;*********************************************************************
;* Description: If a rising edge is recognised at PB2 the Timer is   *
;*              cleared                                              *
;*********************************************************************
RISING_EDGE:
 cbr status_1,(1<<falling_edge_flag) ; clear the falling_edge_flag
 sbr status_1,(1<<rising_edge_flag) + (1<<activate_adc_flag) ; set rising_edge_flag and every 20 ms the adc is started
  
 clr temp_reg1_int
 sbr temp_reg1_int,(1<<ISC01)+(1<<PUD)
 out MCUCR,temp_reg1_int ; After a rising edge a falling edge is following. To see the next falling
                         ; edge, we have to reconfigure the INT0 interrupt and the internal pull-ups
                         ; at all pins at PortB are switched off                      
 clr temp_reg1_int
 out TCNT0,temp_reg1_int ; in case of a rising edge we clear the timer register to measure the length
                         ; of the new signal.
 in temp_reg1_int,TIMSK
 ori temp_reg1_int,(1<<TOIE0) ; timer0 overflow enabled
 out TIMSK,temp_reg1_int

 clr temp_reg1_int
 sbr temp_reg1_int,(1<<CS01)
 out TCCR0,temp_reg1_int ; we have a CK of 1.6 MHz and take a prescaler of 8. So our timer will be
                     ; clocked with 200 kHz. This is a time resolution of 0.005 ms per digit.
                     ; TIMER0 will generate an overflow interrupt every 1.28 ms. This time is
                     ; saved in imp_actual (see the interrupt routine). If we see two times an
                     ; overflow the signal was irregularly long (2.55 ms). Normally the signal
                     ; is between 1.1 ms to 2.1 ms.
                    
;            0ms           1.1ms   1.28ms         2.1ms  2.55ms
;            |-------------|-------|--------------|------|           
;                               1st INT               2nd INT
;
; The first time we see an overflow interrupt, the time of 1.28ms is saved in imp_actual. The timer 
; is cleared and we are waiting for a falling edge at the signal pin. This falling edge has to 
; come before the second overflow of the TIMER0 appears.
                    
reti ; back to the main program


;*********************************************************************
;* Funktion: MAIN_INIT                                               *
;*********************************************************************
;* Description: Initialisation of the ports, timer, pwm etc.         *
;*********************************************************************

MAIN_INIT:

 rcall INIT_WORKING_REGISTER

 rcall INIT_OSCILLATOR
 
 rcall WRITE_SW_VERSION_INTO_EEPROM
 
 rcall INIT_PORTS

 rcall INIT_ACCU_CHECK
 
 rcall INIT_RC_SIGNAL
 
 rcall INIT_PWM

 rcall INIT_INT0_INTERRUPT
  
 rcall PEEP_PEEP_PEEP ; signals the user that the speed controler is ready
 
 rcall INIT_WATCHDOG
 
 rcall INIT_REGS
   
rjmp MAIN ; jumping to the MAIN loop

;*********************************************************************
;* Funktion: INIT_WORKING_REGISTER                                   *
;*********************************************************************
;* Description: all working registers are cleared                    *
;*********************************************************************
INIT_WORKING_REGISTER:
 clr r0
 clr r1
 clr r2
 clr r3
 clr r4
 clr r5
 clr r6
 clr r7
 clr r8
 clr r9
 clr r10
 clr r12
 clr r13
 clr r14
 clr r15
 clr r16
 clr r17
 clr r18
 clr r19
 clr r20
 clr r21
 clr r22
 clr r23
 clr r24
 clr r25
 clr r26
 clr r27
 clr r28
 clr r29
 clr r30
 clr r31
ret

;*********************************************************************
;* Funktion: INIT_OSCILLATOR                                         *
;*********************************************************************
;* Description: Initialisation of the ATtiny oscillator              *
;*********************************************************************
INIT_OSCILLATOR: 
 ldi temp_reg1,calibration_byte_address
 out EEAR,temp_reg1 ; write address of calibration-byte to EEAR
 ldi temp_reg1,eeprom_read_enable
 out EECR,temp_reg1 ; eeprom read enable
 nop ; the eeprom reading takes only one cycle
 in temp_reg1,EEDR ; get the calibration-byte
 out OSCCAL,temp_reg1 ; value to calibrate the RC-oscillator to 1.6 Mhz
ret

;*********************************************************************
;* Funktion: WRITE_SW_VERSION_INTO_EEPROM                            *
;*********************************************************************
;* Description: the software version will be written to the eeprom   *
;*********************************************************************
WRITE_SW_VERSION_INTO_EEPROM:
 sbic EECR,EEWE
 rjmp WRITE_SW_VERSION_INTO_EEPROM
 ldi temp_reg1,sw_version_1_address
 out EEAR,temp_reg1
 ldi temp_reg1,sw_version_1
 out EEDR,temp_reg1
 sbi EECR,EEMWE
 sbi EECR,EEWE
 SECOND_BYTE:
  sbic EECR,EEWE
  rjmp SECOND_BYTE
  ldi temp_reg1,sw_version_2_address
  out EEAR,temp_reg1
  ldi temp_reg1,sw_version_2
  out EEDR,temp_reg1
  sbi EECR,EEMWE
  sbi EECR,EEWE
ret

;*********************************************************************
;* Funktion: INIT_PORTS                                              *
;*********************************************************************
;* Description: Initialisation of the Ports                          *
;*********************************************************************
INIT_PORTS:
;
;            Reset
;            |    Brake
;            |    |    ADC
;            |    |    |    RC Signal
;            |    |    |    |    Motor PWM
;            |    |    |    |    |    LED
;            |    |    |    |    |    |
; | - | - |DDB5|DDB4|DDB3|DDB2|DDB1|DDB0|
; | 0 | 0 |  0 |  1 |  0 |  0 |  1 |  1 |  0=Input 1=Output

 clr temp_reg1
 sbr temp_reg1,(1<<DDB4)+(1<<DDB1)+(1<<DDB0)
 out DDRB,temp_reg1 ;DDB3,DDB1 and DDB0 are outputs
 clr temp_reg1
 out PORTB,temp_reg1 ;all Outputs are switched off

 sbi PORTB,PB0 ;LED is switched-off
ret


;*********************************************************************
;* Funktion: INIT_ACCU_CHECK                                         *
;*********************************************************************
;* Description: Initialisation of accu-check                         *
;*********************************************************************
;
;      ^ U_accu
;      |
;      -
;     | | R1 = 100K
;      -
;      |
;      o-----------o
;      |
;      -            U_adc
;     | | R2 = 5K6
;      -           o
;      |           | 
;     ---         ---
;
; The controller is configured for 10-Bit resolution, but we use only the 8 LSB as we do not 
; need to measure over the hole voltage range. I used a voltage divider with high resistance
; as we get high voltage peaks from the commutation of the motor.
; With this values it possible to measure up to 93 volts. But a well charged 12 cells accu
; has only a voltage of 15 volts sometimes more. With the 8 LSB of the ADC we can measure 23
; volts. The accuracy is 0.1 volts per digit. So we have enough protection for the ADC and we
; can measure up to 12 cells with enough accuracy.
;
; 
;                            R2         1024                      1
; ADC_digits = U_accu * ------------ * ------   =   U_accu * 10.9 -
;                        R1  +   R2      5V                       V
;
;                          1
; U_accu_max = 1024 / 10.9 - = 93V  the maximum measurable voltage
;                          V 
;
; 1 digit ~ 0.1V 
;
;                              1
; U_accu_max@8bit = 256 / 10.9 - = 23V the maximum measurable voltage at LSB 8 bit
;                              V
;
INIT_ACCU_CHECK:
 clr temp_reg1
 ; The ADC is enabled and the prescaler 1/128 is selected.
 ; In this case the ADC needs about 1 to 2 ms to measure the voltage.
 sbr temp_reg1,(1<<ADEN) + (1<<ADPS2) + (1<<ADPS1) + (1<<ADPS0)
 out ADCSR,temp_reg1
 clr temp_reg1
 sbr temp_reg1,(1<<MUX1) ; VCC is used as voltage reference, the adc result is right adjusted
                         ; and we use PB3 for measuring the voltage of the accu
 out ADMUX,temp_reg1
 
 CLEAR_ADC_LOOP_COUNTER:
  sbi ADCSR,ADSC ; start the conversion
  clr temp_reg3  ; temp_reg3 is the ADC_LOOP_COUNTER
 
 ADC_IS_STARTED_1:
  sbic ADCSR,ADSC
  rjmp ADC_IS_STARTED_1 ; we wait until the conversion has finished
  in temp_reg1,ADCL ; we read the ADC-value
  in temp_reg2,ADCH ; we have also to read ADCH to get the measured value
  cpi temp_reg1,min_accu ; Is the actual voltage higher than min_accu?
  brlo CLEAR_ADC_LOOP_COUNTER ; if yes then we can count it as a successful measuring
  inc temp_reg3  ; temp_reg3 counts every measuring loop
  sbi ADCSR,ADSC ; start the conversion again
  cpi temp_reg3,first_time ; if we measure the first time the accu-voltage..
  brne ADC_MEASURING_LOOP
  mov accu_voltage,temp_reg1 ; then we save it in the accu_voltage-variable
  rjmp ADC_IS_STARTED_1
    
  ADC_MEASURING_LOOP:
   cp accu_voltage,temp_reg1 ; we have to measure accu_measuring_loops-times the same accu-voltage
   brne CLEAR_ADC_LOOP_COUNTER
   cpi temp_reg3,accu_measuring_loops
   brne ADC_IS_STARTED_1 ; we have to restart the measuring
   clr adc_loop_counter
   cbi ADCSR,ADEN ; reset the prescaler
   clr temp_reg1
   sbr temp_reg1,(1<<ADEN) + (1<<ADPS1) + (1<<ADPS0) ; the prescaler 1/8 is selected
   out ADCSR,temp_reg1
   clr temp_reg1
   sbr temp_reg1,(1<<MUX1) ; same as before
   out ADMUX,temp_reg1
   clr temp_reg1
   mov temp_reg2,accu_voltage
  CALCULATE_THE_NUMBER_OF_ACCUS :
   inc temp_reg1
   subi temp_reg2,voltage_of_one_cell ; we substract the voltage of one cell from the measured voltage
                                      ; until the value gets negative, to calculate the number of cells
                                      ; in the plugged accu
   cpi temp_reg2,zero
   brge CALCULATE_THE_NUMBER_OF_ACCUS
   dec temp_reg1 ; the accu-counter temp_reg1 must be decremented once, as it counted one accu
                 ; too much, when the counter became smaller then zero
   clr temp_reg2
   ldi temp_reg3,min_voltage_of_one_cell
                                         
  CALCULATE_THE_MIN_VOLTAGE:
   add temp_reg2,temp_reg3 ; the min. voltage of a cell is added as often as we have cells
   dec temp_reg1
   cpi temp_reg1,zero
   brne CALCULATE_THE_MIN_VOLTAGE
   mov switch_off_voltage,temp_reg2 ; now we have calculated the min. voltage of our accu-pack
   ldi temp_reg2,min_accu
   cp switch_off_voltage,temp_reg2 ; if the calculated voltage is lower than min_accu, then we
                                   ; load min_accu into switch_off_voltage
   brsh END_OF_MEASURING
   mov switch_off_voltage,temp_reg2
  END_OF_MEASURING: 

ret

;*********************************************************************
;* Funktion: INIT_RC_SIGNAL                                          *
;*********************************************************************
;* Description: Initialisation of the RC imp_min and imp_max         *
;*********************************************************************
INIT_RC_SIGNAL:
 sei ; global interrupt enable

 clr temp_reg1
 
 RESTART_MEASURING_IMP_MIN:
  clr status_1
  clr temp_reg3

 ACTIVATE_INTERRUPT:
  rcall INIT_INT0_INTERRUPT

 MEASURING_IMP_MIN:
  sbrs status_1,falling_edge_flag
  rjmp MEASURING_IMP_MIN ; if we have seen a rising edge we go back to MEASURING_IMP_MIN
  clr status_1
  clr temp_reg1
  out GIMSK,temp_reg1 ; INT0 interrupt is deactivated so we can not be disturbed by the next int.
  cpi imp_actual,stop_position ; compare the actual impulse with the stop position
  brsh ACTIVATE_INTERRUPT ; if imp_actual is bigger than the stop_position 1.35 ms (0x87)
                          ; the throttle stick is not below the max. STOP-position                                  
  inc temp_reg3          ; temp_reg3 counts every measuring loop
  cpi temp_reg3,first_time     ; if it is not the first new measuring 
  brne TEN_TIMES_IMP_MIN ; we go to the label TEN_TIMES_IMP_MIN
  mov imp_min,imp_actual ; else we save the value in imp_min
  rjmp ACTIVATE_INTERRUPT ; and start the next measuring

 TEN_TIMES_IMP_MIN:
  cp imp_min,imp_actual ; if we do not measure the same value as saved in imp_min
  brne RESTART_MEASURING_IMP_MIN ; we have to restart the measuring of imp_min
  cpi temp_reg3,number_of_imp_mins ; until we have measured ten times the same imp_min
  brne ACTIVATE_INTERRUPT
; the full throttle position is calculated  
  ldi temp_reg1,full_throttle_offset ; temp_reg1 is loaded with full_throttle_offset
  mov imp_max,imp_min ; imp_min is copied to imp_max
  add imp_max,temp_reg1 ; and imp_max is now defined by adding the full_throttle_offset
  mov new_imp,imp_actual
; the brake position is calculated
  ldi temp_reg1,stop_brake_hysteresis
  mov motor_brake_pos,imp_min
  sub motor_brake_pos,temp_reg1 
; the stop position is calculated, as the motor should not run if the throttle stick is
; pushed a little bit
  ldi temp_reg1,stop_run_hysteresis
  add imp_min,temp_reg1
ret

;*********************************************************************
;* Funktion: INIT_PWM                                                *
;*********************************************************************
;* Description: Initialisation of the PWM                            *
;*********************************************************************
INIT_PWM:
 in temp_reg1,TIMSK
 andi temp_reg1,~(1<<OCIE1A) ; timer1 compare int. is deactivated
 out TIMSK,temp_reg1
 
 clr temp_reg1
 out OCR1A,temp_reg1 ; the OCR1A register defines the duty-cycle of the PWM (100% = OCR1B)
 sbr temp_reg1,(1<<PWM1)+(1<<COM1A1)+(1<<CS13)
 out TCCR1,temp_reg1 ; we configuer the timer1 as a PWM (PWM1 bit), we use a non inverted PWM (COM1A1
                     ; bit), and the timer1 is clocked by 200kHz (CS13 bit)
 ldi temp_reg1,full_throttle_offset
 out OCR1B,temp_reg1 ; the OCR1B register defines the length of a PWM periode. As the timer is
                     ; clocked by 200kHz and the output compare register B of timer B has a value 
                     ; of 60, we get a pwm frequence of 200kHz/60 = 3.3kHz. I took a value of 60 as
                     ; we can use the difference of imp_actual and imp_min directly for the PWM.
ret                    

;*********************************************************************
;* Funktion: INIT_INT0_INTERRUPT                                     *
;*********************************************************************
;* Description: Initialisation of INT0 Interrupt                     *
;*********************************************************************
INIT_INT0_INTERRUPT:
 sbr temp_reg1,(1<<ISC00)+(1<<ISC01)+(1<<PUD)
 out MCUCR,temp_reg1 ; the INT0 interrupt is configured to see a rising edge first
                     ; and the internal pull-ups at all pins at PortB are switched off                                            
 clr temp_reg1
 sbr temp_reg1,(1<<INTF0)+(1<<PCIF)
 out GIFR,temp_reg1
 clr temp_reg1
 sbr temp_reg1,1<<INT0
 out GIMSK,temp_reg1 ; INT0 interrupt is activated by setting the INT0 bit
ret

;*********************************************************************
;* Funktion: INIT_WATCHDOG                                           *
;*********************************************************************
;* Description: Initialisation of the watchdog                       *
;*********************************************************************
INIT_WATCHDOG:
 clr temp_reg1
 sbr temp_reg1,(1<<WDE)+(1<<WDP1)+(1<<WDP0)
 out WDTCR,temp_reg1 ; the watchdog will reset after 128 ms if the wdg-timer is not reset
ret  

;*********************************************************************
;* Funktion: INIT_COMPARE_INT                                        *
;*********************************************************************
;* Description: Initialisation of the Timer1 Compare Interrupt       *
;*********************************************************************
INIT_COMPARE_INT:
 clr temp_reg1
 out TCNT1,temp_reg1
 sbr temp_reg1,(1<<CTC1) + (1<<CS13) + (1<<CS12)
 out TCCR1,temp_reg1 ; the time restarts after every compare match and the timer runs with 12,5 kHz
 ldi temp_reg1,0x80
 out OCR1A,temp_reg1 ; compare the timer with temp_reg1, if the timer has the same value as
                     ; saved in OCR1A an interrupt is executed.
 clr temp_reg1
 
 in temp_reg1,TIMSK
 ori temp_reg1,(1<<OCIE1A) ; timer1 compare int. is activated
 out TIMSK,temp_reg1
ret 

;*********************************************************************
;* Funktion: INIT_REGS                                               *
;*********************************************************************
;* Description: Clears the temp_regs                                 *
;*********************************************************************
INIT_REGS:
; initialise the temporary registers to zero before entering MAIN
 clr temp_reg1
 clr temp_reg2
 clr temp_reg3
ret

;*********************************************************************
;* Funktion: MAIN                                                    *
;*********************************************************************
;* Description: MAIN loop                                            *
;*********************************************************************
MAIN: ; MAIN loop

 rcall WATCHDOGTIMER_RESET ; go to WATCHDOGTIMER_RESET

 rcall SIGNAL_FILTERING ; go to SIGNAL_FILTERING
 
 rcall MOTOR_CONTROL ; go to MOTOR_CONTROL

 rcall BRAKE_CONTROL ; go to BRAKE_CONTROL
 
 rcall ACCU_CHECK ; go to ACCU_CHECK
 
rjmp MAIN ; jumping back to MAIN


;*********************************************************************
;* Funktion: WATCHDOGTIMER_RESET                                     *
;*********************************************************************
;* Description: the watchdogtimer will be reset                      *
;*********************************************************************
WATCHDOGTIMER_RESET:
 wdr ; watchdogtimer reset
ret

;*********************************************************************
;* Funktion: SIGNAL_FILTERING                                        *
;*********************************************************************
;* Description: This funktion distinguishs between noise and good    *
;*              signal                                               *
;*********************************************************************
SIGNAL_FILTERING:
 sbrs status_1,falling_edge_flag
 ret
 
 cbr status_1,(1 << rising_edge_flag) + (1 << falling_edge_flag) 
 
 mov debounce_mem,imp_actual ; we save the value in debounce_mem

; The program checks if the new measured signal is inbetween the boarders of the 
; signal_deviation. The signal_deviation can be changed at the top of the program.
; As far as I have tested the program, the signal_deviation has a good value.
; If the signal_deviation is too small, the program detects a fast moved throttle-stick already
; as signal-noise. This can happen as in fact of the fast changed RC-signal, which is out-
; side the boarders of the signal_deviation. If the signal_deviation is too big, then 
; signal-noise is detected inertialy.
 mov temp_reg1,new_imp
 subi temp_reg1,-signal_deviation
 cp debounce_mem,temp_reg1
 brsh NEW_TRY ; if the signal is bigger then the frame then we go to NEW_TRY
 mov temp_reg1,new_imp
 subi temp_reg1,signal_deviation
 cp debounce_mem,temp_reg1
 brlo NEW_TRY ; if the signal is smaller then the frame then we go to NEW_TRY

; The program checks if the impulse length is too long or too short.
 ldi temp_reg1,max_imp_length
 cp debounce_mem,temp_reg1
 brsh BAD_SIGNAL
 ldi temp_reg1,min_imp_length
 cp debounce_mem,temp_reg1
 brlo BAD_SIGNAL
 
; if the bad_signal_flag is set, we have to go to WAIT_FOR_STOP_POSITION
 sbrc status_1,bad_signal_flag
 rjmp WAIT_FOR_STOP_POSITION

; if the low_voltage_flag is set, we have to go to BAD_SIGNAL
 sbrc status_2,low_voltage_flag
 rjmp BAD_SIGNAL
 
; If the new signal was inside of the time-frame then it is saved in new_imp 
 mov new_imp,debounce_mem
 clr new_tries ; and the new_tries-counter is cleared
 cp new_imp,motor_brake_pos ; if imp_new is smaller than motor_brake_pos, the brake is aktivated
 brlo INIT_BRAKE
 sbr status_1,(1 << activate_pwm_flag)
ret 

 INIT_BRAKE:
  sbrc status_1,activate_brake_flag ; take a look if the brake has been already activated
 ret 
  cbr status_1,(1 << activate_pwm_flag) ; clear the activate_pwm_flag
  clr temp_reg1
  out OCR1A,temp_reg1 ; motor is switched-off again
  sbr status_1,(1 << activate_brake_flag)
  clr compare_int_counter
  rcall INIT_COMPARE_INT ; the timer1 is initialised as a compare timer
  sbi PORTB,brake ;brake is activated
  ldi brake_sequence_state,_20_percent_brake_pwm
  mov brake_pwm_counter,brake_sequence_state
 ret
  
; If the new measured signal was not inside of the time-frame
 NEW_TRY:
; if the bad_signal_flag is set, we have to go to WAIT_FOR_STOP_POSITION
  sbrc status_1,bad_signal_flag
  rjmp CLEAR_COUNTER
  inc new_tries ; then the new_tries-counter is incremented
  ldi temp_reg2,number_of_max_new_tries
  cp temp_reg2,new_tries
  breq BAD_SIGNAL ; If we have already reached the number of max. new tries we go to BAD_SIGNAL.
                  ; The number of max. new tries can be changed at the top of the program.
                  ; The number_of_max_new_tries has an effect on the sensitivity of the filer.
  clr temp_reg2
 ret 
 
 BAD_SIGNAL:
; if the bad_signal_flag is set, we have to go to WAIT_FOR_STOP_POSITION
  sbrc status_1,bad_signal_flag
  rjmp CLEAR_COUNTER
  mov temp_reg3,imp_min
  subi temp_reg3,stop_brake_hysteresis
  mov new_imp,temp_reg3 ; new_imp is now smaller then imp_min.
  clr temp_reg3
 ; the detected bad signal is marked in the status_1 (see at the top of the filter)
  sbr status_1,(1 << bad_signal_flag)
  rcall INIT_BRAKE
 ret ; the new PWM will be adjusted

; If a signal-noise is detected, the motor stops running. Only if a save signal of the Stop-position
; is detected then the motor starts again!!  
 WAIT_FOR_STOP_POSITION:
  cbr status_1,(1 << falling_edge_flag) ; clear the falling_edge_flag
  cp imp_min,debounce_mem ; if we measure the same value as saved in imp_min
  brlo CLEAR_COUNTER
  mov temp_reg1,debounce_mem
  cpi temp_reg1,min_imp_length;we can continue
  brsh COUNT_THE_IMP_MIN
  CLEAR_COUNTER: 
   clr imp_min_counter ; else the imp_min_counter is set to zero
 ret
  
 COUNT_THE_IMP_MIN: 
  inc imp_min_counter ; imp_min_counter counts every measuring loop
  cpi imp_min_counter,first_time     ; if it is the first new measuring 
  breq RETURN_TO_MAIN ; we go back to MAIN
  cpi imp_min_counter,number_of_same_signals ; we have to see x times the stop-position to continue
  brne RETURN_TO_MAIN
  clr imp_min_counter
  cbr status_1,(1 << bad_signal_flag) + (1 << rising_edge_flag) + (1 << falling_edge_flag)
  cbr status_2,(1 << low_voltage_flag) 
 ret

;*********************************************************************
;* Funktion: RETURN_TO_MAIN                                          *
;*********************************************************************
;* Description: can be used by every routine, it just returns to main*
;*********************************************************************
RETURN_TO_MAIN:
ret
     
;*********************************************************************
;* Funktion: MOTOR_CONTROL                                           *
;*********************************************************************
;* Description: in this function the pwm is refreshed                *
;*********************************************************************

 MOTOR_CONTROL:
  sbrs status_1,activate_pwm_flag
 ret
  cbr status_1,(1 << activate_pwm_flag) ; clear the activate_pwm_flag
  sbrc status_1,activate_brake_flag
  rcall INIT_PWM  
  sbi PORTB,led ;LED is switched-off
  cbr status_1,(1 << activate_brake_flag) ; clear the activate_brake_flag
  cbi PORTB,brake ; the motor brake is switched-off
  cp imp_min,new_imp
  brlo PWM
  clr temp_reg1
  out OCR1A,temp_reg1 ; motor is switched off
 ret

 PWM:
  cp new_imp,imp_max
  brsh FULL_THROTTLE ; we have to adjust full throttle
  mov temp_reg1,new_imp
  sub temp_reg1,imp_min
  out OCR1A,temp_reg1 ; the right pwm is loaded
  sbi PORTB,led ;LED is switched-off  
  cbr status_2,(1 << full_throttle_flag) 
 ret

 FULL_THROTTLE:
  mov temp_reg1,imp_max
  sub temp_reg1,imp_min
  out OCR1A,temp_reg1 ; full throttle is loaded
  out OCR1B,temp_reg1 ; the pwm is adapted to the max. way of the throttle stick
                      ; one effect is, that the pwm frequency drops
  cbi PORTB,led ;LED is switched-on to show full throttle  
  cp imp_max,new_imp
  brsh END_OF_FULLTHROTTLE
  mov temp_reg1,new_imp ; if the new impulse is bigger then the max value, the max. value is adapted
  subi temp_reg1,pwm_100_hysteresis ; a hysteresis is substracted to compensate temperature
                                    ; drifts of the RC-potis
  mov imp_max,temp_reg1 ; the new value is copied to imp_max
  END_OF_FULLTHROTTLE:
 ret
 

;*********************************************************************
;* Funktion: BRAKE_CONTROL                                           *
;*********************************************************************
;* Description: in this function the brake is controlled             *
;*********************************************************************
    
  BRAKE_CONTROL:
   sbrs status_1,activate_brake_flag ; take a look if the brake has been already activated
  ret
   cpi brake_sequence_state,_100_percent_brake_pwm
   brsh RETURN_TO_MAIN
   clr temp_reg1
   dec brake_pwm_counter
   cpse brake_pwm_counter,temp_reg1
   rjmp CHECK_TWO_SECONDS
   sbic PORTB,brake
   rjmp SWITCH_BRAKE_OFF
   sbi PORTB,brake ;brake is switched on
   mov temp_reg1,brake_sequence_state   
   rjmp RELOAD_BRAKE_PWM_COUNTER
   SWITCH_BRAKE_OFF:
    cbi PORTB,brake ;brake is switched off
    ldi temp_reg1,_100_percent_brake_pwm
    sub temp_reg1,brake_sequence_state
   RELOAD_BRAKE_PWM_COUNTER:
    mov brake_pwm_counter,temp_reg1
   CHECK_TWO_SECONDS: 
    sbrs status_1,two_second_flag
   ret  
    cbr status_1,(1 << two_second_flag) ; clear two_second_flag
    ldi temp_reg1,_20_percent_brake_pwm
    add brake_sequence_state,temp_reg1
    mov brake_pwm_counter,brake_sequence_state
    
    MAX_BRAKE_FORCE:
    sbi PORTB,brake ;brake is switched on
   ret

;*********************************************************************
;* Funktion: ACCU_CHECK                                              *
;*********************************************************************
;* Description: accu-check                                           *
;*********************************************************************

ACCU_CHECK:
 sbrc status_2,low_voltage_flag ; as long as the low_voltage_flag is set we do not measure
ret 
 sbrs status_1,activate_adc_flag ; take a look if the adc has been already activated
ret
 sbi ADCSR,ADSC ; start the conversion

 ADC_IS_STARTED_2:
  sbic ADCSR,ADSC
  rjmp ADC_IS_STARTED_2 ; we wait until the conversion has finished
  in temp_reg1,ADCL ; we read the ADC-value
  in temp_reg2,ADCH ; we have also to read ADCH to get the measured value
  cp temp_reg1,switch_off_voltage ; Is the actual voltage higher than switch_off_voltage?
  brsh LEAVE_ADC_2 
  inc adc_loop_counter  ; adc_loop_counter counts every measuring loop
  ldi temp_reg2,accu_measuring_loops  
  cp adc_loop_counter,temp_reg2 
  brne LEAVE_ADC_1
  sbr status_2,(1 << low_voltage_flag)
  clr temp_reg1
  out OCR1A,temp_reg1 ; motor is switched off
  LEAVE_ADC_1:
   cbr status_1,(1 << activate_adc_flag)
 ret 

 LEAVE_ADC_2:
  cbr status_1,(1 << activate_adc_flag)
  clr adc_loop_counter
ret
 
;***********************************************************************
;*********************************************************************
;* Funktion: DELAY                                                   *
;*********************************************************************
;* Description: delay time                                           *
;*********************************************************************
DELAY:
 ldi	temp_reg1,0xff	;load counter 1 
 clr	temp_reg1	;clear counter 2 
 clr	temp_reg3	;clear counter 3 
 
 LOOP:
  inc	temp_reg1	;count up counter 1 
  brne	LOOP
  ldi	temp_reg1,0xff	;load  counter 1
  inc	temp_reg2       ;count up counter 2
  brne	LOOP		;Check if inner loop is finished 
  inc 	temp_reg3	;Count up counter 3 
  brne 	LOOP		;Check if delay is finished
ret

;*********************************************************************
;* Funktion: PEEP_PEEP_PEEP                                          *
;*********************************************************************
;* Description: the speed control peeps 3 times                      *
;*********************************************************************
PEEP_PEEP_PEEP:
 ldi counter,0xfd
 ONE_PEEP:
  ldi temp_reg1,0x01
  out OCR1A,temp_reg1
  cbi PORTB,PB0 ;LED is switched-on  
  rcall DELAY
  clr temp_reg1
  out OCR1A,temp_reg1
  sbi PORTB,PB0 ;LED is switched-off
  rcall DELAY
  inc counter
  brne ONE_PEEP
  clr counter  
ret  

 
