;throttle_momentum.asm

.NOLIST

;  ***************************************************************************************
;  * PWM MODEL RAILROAD THROTTLE                                                          *
;  *                                                                                      *
;  * WRITTEN BY:  PHILIP DEVRIES                                                          *
;  *                                                                                      *
;  *  Copyright (C) 2003 Philip DeVries                                                   *
;  *                                                                                      *
;  *  This program is free software; you can redistribute it and/or modify                *
;  *  it under the terms of the GNU General Public License as published by                *
;  *  the Free Software Foundation; either version 2 of the License, or                   *
;  *  (at your option) any later version.                                                 *
;  *                                                                                      *
;  *  This program is distributed in the hope that it will be useful,                     *
;  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
;  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                       *
;  *  GNU General Public License for more details.                                        *
;  *                                                                                      *
;  *  You should have received a copy of the GNU General Public License                   *
;  *  along with this program; if not, write to the Free Software                         *
;  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA           *
;  *                                                                                      *
;  ***************************************************************************************:
.LIST

.ifdef MOMENTUM_ENABLED
;********************************************************************************
;* MOMENTUM_ADJUST                                                               *
;* Top level routine                                                             *
;*                                                                               *
;* Momentum simulates the mass of the train.  Since model trains have little     *
;* mass, the locomotive speed can directly follow the throttle setting; in       *
;* other words, a model train can accelerate and decelerate instantly.           *
;* Real trains are very massive, and therefore they do not accelerate or         *
;* decelerate quickly.                                                           *
;*                                                                               *
;* According to Newtons law, the acceleration is proportional to the force,      *
;* and inversely proportional to the mass.  Therefore, the more massive the      *
;* train, the more slowly the train will accelerate or decelerate.  Also, the    *
;* more force the locomotive can provide, the faster the train will accelerate.  *
;* Deceler depends on the braking capability of the overall train.               *
;*                                                                               *
;* If force were constant, Newtons law states that acceleration would be         *
;* constant too.  This subroutine assumes that somewhat more force is            *
;* available at low speeds than at high speeds, so that acceleration will be     *
;* greater at low speeds.  The subroutine also assumes that opposing forces      *
;* (friction, wind resistance, etc) are stronger at higher speeds.  This         *
;* assumption also means that acceleration will be greater at low speeds.        *
;*                                                                               *
;* This subroutine calculates acceleration/deceleration by this simple           *
;* Method:                                                                       *
;*                                                                               *
;*    The rate of speed change (accleration and deceleration) depends on the     *
;*    current speed as                                                           * 
;*    {Speed_Max (0xFF) - Current_Speed} / {Tau*Rate}                            *
;*                                                                               *
;*       Where    T = index of sample time                                       *
;*                t = real time                                                  *
;*                Tau = time constant                                            *
;*                Rate = Update rate (nominally 100Hz)                           *
;*                                                                               *
;* That is, the acceleration/deceleration is maximum at zero speed, and          *
;* approaches zero at maximum speed.                                             *
;*                                                                               *
;* When accelerating, the speed at the next sample period is                     *
;*    Speed(T) = Speed(T-1) + {0xFF - Speed(T-1)} / {Tau*Rate}                   *
;*                                                                               *
;*    Giving an acceleration curve that looks like a normal exponential.         *
;*    Speed(t) = 0xFF { 1 - exp( - t / Tau) }                                    *
;*                                                                               *
;*                                        *                   *                  *
;*                       *                                                       *
;*             *                                                                 *
;*         *                                                                     *
;*       *                                                                       *
;*      *                                                                        *
;*     *                                                                         *
;*     *                                                                         *
;*                                                                               *
;*                                                                               *
;* When decelerating, the change rate equation is the same, but the change       *
;* is subtracted, as                                                             *
;*    Speed(T) = Speed(T) - {0xFF - Speed(T-1)} / {Tau*Rate}                     *
;*                                                                               *
;*    Giving a deceleration curve that looks like                                *
;*    Speed(t) = 0xFF { 1 - exp( -(T1 - t) / Tau) }                              *
;* which is a mirror image of the acceleration, NOT a normal exponential.        *
;*                                                                               *
;*     *              *                                                          *
;*                                        *                                      *
;*                                            *                                  *
;*                                              *                                *
;*                                               *                               *
;*                                                *                              *
;*                                                *                              *
;*                                                                               *
;*                                                                               *
;* In each case, the acceleration or deceleration is "clipped" at the current    *
;* throttle setting so that the speed doesn't overshoot or undershoot.           *
;*                                                                               *
;* Three different values of Tau are used, that is                               *
;*    Tau_accel      Corresponding to acceleration under power                   *
;*    Tau_coast      Corresponding to deceleration when coasting                 *
;*    Tau_brake      Corresponding to deceleration when braking                  *
;*                                                                               *
;* To permit finer control of momentum, the throttle setting is converted to a   *
;* 16 bit number, where the 8 msb's correspond to the throttle setting from      *
;* the throttle handle and sent forward.                                         *
;*                                                                               *
;* Inputs:  throttle_set      Throttle handle position ( 0x00 to 0xFF )          *
;*          speed_hi_prev     Hi byte of (T-1) throttle setting (stored)         *
;*          speed_lo_prev     Lo byte of (T-1) throttle setting (stored)         *
;* Returns: throttle_set      Adjusted throttle setting (T)                      *
;*          speed_hi_prev     Hi byte of (T) throttle setting  (stored)          *
;*          speed_lo_prev     Lo byte of (T) throttle setting  (stored)          *
;* Changed: B_Temp                                                               * 
;*          B_Temp1                                                              *
;*          B_Temp2                                                              *
;*          B_Temp3                                                              *
;* Calls:   NONE                                                                 *  
;* Goto:    MOMENTUM_ADJUST_RETURN                                               *
;********************************************************************************
   B_TEMPLOCAL2 _time_constant_adj


.ifdef TRADITIONAL_ENABLED
   .ifdef LEDS_ENABLED
      sbrc  Flags_1,BF_brake           ; If the brake flag is set,
      sbi   PORTB,dir_in_port          ; Port Output: Indicate deceleration
   .endif ;LEDS_ENABLED
.endif ;TRADITIONAL_ENABLED

   ;*******************************************************************
   ;* Adjust the value of "momentum_set".
   ;* This adjustment makes it easier to fine adjust low momentum settings
   ;* while still permitting large momentum settings.
   ;* 
   ;* The ammount of momentum to apply comes in in "momentum_set"
   ;* which is read in READ_THROTTLE.  The nominal range is
   ;* 0x00 to 0x40.  This value is multiplied by two and squared,
   ;* giving a new range from 0x00 to 0x4000.  The update rate is 100Hz,
   ;* and so the new range corresponds to a time constant from
   ;* 0(decimal) to 164(decimal) seconds.  Since the adjustment was
   ;* done by performing a square, the adjusted value is non-linear
   ;* with the input value.
   ;*******************************************************************
   lsl   momentum_set                        ; multiply by two

   HILOCAL1    _mset_multiplier              ; supply to mpy8u
   B_TEMPLOCAL _mset_multiplicand            ; supply to mpy8u

   mov   _mset_multiplier,momentum_set       ;  
   mov   _mset_multiplicand,momentum_set     ;
   rcall mpy8u                               ; square

   B_TEMPLOCAL1   _mset_hi_byte              ; return from mpy8u
   B_TEMPLOCAL    _mset_lo_byte              ; return from mpy8u

   ;*******************************************************************
   ;* Compute the difference between the maximum throttle and
   ;* the current throttle
   ;*******************************************************************
   HILOCAL2 _mset_diff_hi_byte
   HILOCAL1 _mset_diff_lo_byte

   ldi   _mset_diff_hi_byte,0xFF             ; Maximum possible speed
   ldi   _mset_diff_lo_byte,0xFF             ;                      

   sub   _mset_diff_lo_byte,speed_lo_prev    ; Difference between max speed 
   sbc   _mset_diff_hi_byte,speed_hi_prev    ; and current speed

   ;*******************************************************************
   ;* Determine whether to accelerate, decelerate, or remain unchanged.
   ;* Compare the throttle handle setting with the actual speed
   ;*******************************************************************
   cp    throttle_set,speed_hi_prev    ; Test if throttle position is larger
                                       ; or smaller than the speed.

   breq  EVEN_SPEED                    ; If the throttle position is the same
                                       ; as the speed.
                                       
   brlo  SETUP_DECELERATE              ; If the throttle position is smaller
                                       ; than the speed, then need to decelerate.

;  brsh  SETUP_ACCELERATE              ; If the throttle position is larger
                                       ; than the speed, then need to accelerate.

SETUP_ACCELERATE:
.ifdef TRADITIONAL_ENABLED
   .ifdef LEDS_ENABLED
      cpi   throttle_set,accel_led_threshold ; If the throttle is less than minimum
      brlo  END_SET_ACCEL_LED                ; don't light led

      mov   B_Temp2,throttle_set             ; If the throttle is closer than led_threshold
      subi  B_Temp2,accel_led_threshold      ; don't light led
      cp    B_Temp2,speed_hi_prev      
      brlo  END_SET_ACCEL_LED

      sbi   PORTB,momentum_port        ; Port Output: Indicate acceleration
      END_SET_ACCEL_LED:
   .endif LEDS_ENABLED
.endif TRADITIONAL_ENABLED

   sbr   Flags_1,F_accel               ; Set accelerating flag
                                       ; Indicate acceleration

   ldi   _time_constant_adj,accel_offset+1   ; Acceleration time constant adjust

   rjmp  CHECK_BRAKE

EVEN_SPEED:                            ; Arrive here if throttle_set=current speed

   sbrc  Flags_1,BF_brake              ; If the brake flag is set, decelerate
   rjmp  CHECK_BRAKE                   ;
 
   rjmp  DONE_WITH_MOMENTUM            ; Otherwise adjustment is necessary


SETUP_DECELERATE:
   cbr   Flags_1,F_accel               ; Clear accelerating flag 

.ifdef TRADITIONAL_ENABLED
   .ifdef LEDS_ENABLED

      cpi   throttle_set,0xff-decel_led_threshold  ; If the throttle is more than maximum
      brsh  END_SET_DECEL_LED                      ; don't light led

      mov   B_Temp2,throttle_set
      subi  B_Temp2,0x00-decel_led_threshold       ; If the throttle is closer than the led
      cp    B_Temp2,speed_hi_prev                  ; threshold, don't light the led
      brsh  END_SET_DECEL_LED

      sbi   PORTB,dir_in_port          ; Port Output: Indicate deceleration 
      END_SET_DECEL_LED:
   .endif LEDS_ENABLED
.endif TRADITIONAL_ENABLED

   ldi   _time_constant_adj,0+1       ; Coasting deceleration time const. adjust.
;  rjmp  CHECK_BRAKE

CHECK_BRAKE:                           ; Always check for the brake.

   sbrs  Flags_1,BF_brake              ; If brake flag is not set,
   rjmp  ADJUST_TAU                    ; proceed.  

                                       ; Brake overrides acceleration
                                       ; or coasting.
   cbr   Flags_1,F_accel               ; clear accelerating flag
                                       ; Indicate deceleration

   ldi   _time_constant_adj,brake_offset+1   ; Braking deceleration time const. adjust.

;  rjmp  ADJUST_TAU

ADJUST_TAU:
   ;B_TEMP2=B_TEMPLOCAL2
   dec   _time_constant_adj            ; Divide tau_base by 2^_time_constant_adj
   breq  DIVIDE_TAU                    ; to produce adjusted tau.

   lsr   _mset_hi_byte                              
   ror   _mset_lo_byte
   rjmp  ADJUST_TAU

DIVIDE_TAU:
   sbr   _mset_lo_byte,0b00000001      ; Force last bit 1.  Prevent divide by zero.

   rcall div16u                        ; Divide _mset_diff_hi_byte:_mset_diff_lo_byte
                                       ;  (difference) 
                                       ; by _mset_hi_byte:_mset_lo_byte (dividor)

   sbrs  Flags_1,BF_accel              ; add or subtract change depending
   rjmp  SUBTRACT_CHANGE               ; on F_accel flag
   ;rjmp ADD_CHANGE

ADD_CHANGE:                            ; Case accelerating

;  HILOCAL2 _mset_diff_hi_byte
;  HILOCAL1 _mset_diff_lo_byte

   add   speed_lo_prev,_mset_diff_lo_byte        ; Add in the change
   adc   speed_hi_prev,_mset_diff_hi_byte

   cp    throttle_set,speed_hi_prev    ; If larger than the throttle_set value 
   brlo  USE_SET_SPEED                 ; clamp at throttle_set value                 

   rjmp  DONE_WITH_MOMENTUM

SUBTRACT_CHANGE:                       ; Case decelerating

   sbrc  Flags_1,BF_brake              ; If the brake flag is set,
   clr   throttle_set                  ; decelerate all the way to zero

   sub   speed_lo_prev,_mset_diff_lo_byte        ; Subtract the change
   sbc   speed_hi_prev,_mset_diff_hi_byte        ;

   brlo  USE_SET_SPEED                 ; If less than zero
                                       ; clamp at throttle_set value

   cp    speed_hi_prev,throttle_set    ; If less than the throttle_set value
   brlo  USE_SET_SPEED                 ; clamp at throttle_set value

   rjmp  DONE_WITH_MOMENTUM

USE_SET_SPEED:                         ; Use the throttle_set value directly
   mov   speed_hi_prev,throttle_set
   clr   speed_lo_prev

DONE_WITH_MOMENTUM:
   mov   throttle_set,speed_hi_prev    ; Put the new value into throttle_set. 

.endif ;MOMENTUM_ENABLED