;---------------------------;
;  Ultrasonic Sensor Trial  ;
;---------------------------;
;  Mike Shegedin
;  20.November.2011
;
;  Ver1.2
;     Cleanup of code and documentation
;
;  AVR:  ATTiny13A
;     AVR PIN  FUNCTION
;     =======  =======================
;      Pin2    PB3 SENSELED (sense LED) (optional)
;      Pin3    PB4 BUTTON (optional)
;      Pin5    PB0 TRIG pin on ultrasonic sensor
;      Pin6    PB1 ECHO pin on ultrasonic sensor
;      Pin7    PB2 RANGELED (range LED) (optional)
;
;  Ultrasonic Sensor:
;        HC-SR04 Ultrasonic ranging module
;
;  Operation:
;     Apply a 10µs signal on TRIG to start the sensing cycle. The HC-SR04 sensor will
;     send out a 40kHz pulse for 200µs. If something is in the path of the ultrasonic
;     pulse, this pulse will bounch off that and return to the sensor. The ECHO line on
;     the sensor will go high for 150µs~25ms depending on distance to detected object.
;     If the ECHO line is high for 38ms it indicates that no return pulse was detected.
;     There should be an interval of at least 50ms between subsequent TRIG pulses
;     otherwise a lingering ultrasonic pulse may be detected during the next ping cycle.
;
;  =====================================================================================
;
;  Distance Calculation for ultrasonic sensing:
;     Speed of sound is 340.29 m/s. Pulse must be sent and then return.
;     Therefore time for a sound pulse (ping) to hit the target and return to the sensor
;     is 170.15 m/s.
;
;     Convert to µs/s:
;
;           s        m     1x10^6(1E6)µs   58.772µs
;        ------- X ----- X ------------- = --------
;        170.15m   100cm        s            cm
;
;     This means it takes about 59µs for a sound pulse to travel 1cm to an object and
;     then return to the sensor.
;
;  Compute distance based on time:
;     Example 1: A ping takes 150µs to hit an object and return. How far away is the
;                object?
;
;                   cm
;        150µs X -------- = approx. 2.55cm
;                58.772µs
;
;     Example 2: How long for a ping to hit and return from an object 30cm away?
;
;               58.772µs
;        30cm X -------- = approx. 1,763µs or 1.763ms
;                  cm
;
;  =====================================================================================
;
;  AVR TIMINGS
;     Use default ATTINY13 timings (9.6MHz with /8 prescaler) for a 1.2MHz clock.
;     Fuse setting: -U lfuse:w:0x6a:m -U hfuse:w:0xff:m
;
;     =============  1.2MHz clock timings  =============
;
;     1,200,000 clk  = 1s        1,000,000 clk = 833.3ms
;       120,000 clk  = 100ms       100,000 clk =  83.3ms
;        12,000 clk  = 10ms         10,000 clk =   8.3ms
;         1,200 clk  = 1ms           1,000 clk = 833.3µs
;           120 clk  = 100µs           100 clk =  83.3µs
;            12 clk  = 10µs             10 clk =   8.3µs
;           1.2 clk  = 1µs               1 clk = 833.3ns
;
;  NOTE: Timing accuracy of the AVR clock without a crystal is only about ±10%. Much
;        higher accuracy can be achieved by using an external crystal if desired. Of
;        course, if an external crystal is used, clock timings will be based on the
;        value of the crystal.
;
;  =====================================================================================
;
;     DELAY: Double Byte Timing Loop Routine
;        By counting the number of clock cycles for the routine, including loading n
;        into YH and YL, calling the delay routine, and returning back, the number of
;        clock cycles (clks) was found to be as follows:
;
;        Delay in clock pulses (clks) based on n: Time(clks) = 4n + 8
;
;        Time delay in µs based on n: 
;                                    10µs                          
;           Time(µs) = (4n+8)clks X ------
;                                   12clks
;
;           Therefore, simplified:
;
;                      10n + 20
;           Time(µs) = --------
;                         3
;
;        Compute n for the desired time T(µs) (solve for n):
;
;               3T(µs) - 20
;           n = ===========
;                    10
;
;     Example: What should n be for a 100µs delay?
;
;               (3 x 100) - 20    
;           n = --------------
;                    10
;
;           n = 28
;
;     Example 2: What should n be for a 50ms delay?
;
;                                           1000µs
;        First, convert ms to µs --> 50ms x ------ = 50,000µs
;                                             1ms
;
;               (3 x 50,000) - 20              (3 x 50,000)
;           n = -----------------      or  n = ------------
;                      10                          10
;
;           n = 14,998                 or  n = 15,000
;
;           Note that the "- 20" term can probably be dropped for millisecond
;           calculations since doing so results in a less than 1% error in these cases.
;
;  =====================================================================================
;
;  Conversion Summary
;
;  Time/Distance Ratio from start of sending ping to the time it is received back:
;
;     58.772µs
;     --------
;        cm
;
;  AVR Clock/Time Ratio (for 1.2MHz AVR clock)
;
;     1.2clks
;     -------
;       µs
;
;  AVR Clock clks/Distance Formulae
;
;     70.526clks      0.014179cm
;     ----------  or  ----------
;         cm             clks
;
;  Timing of pulse indicating no object sensed (according to sensor datasheet):
;
;     38ms  which is the equivilant of 31,667clks.
;
;  =====================================================================================
;

.INCLUDE "TN13DEF.INC"     ;  ATTINY13 DEFINITIONS


   .EQU  SENSELED=PORTB3   ;  SENSELED pin (Output on AVR)
   .EQU  RANGELED=PORTB2   ;  RANGELED pin (Output on AVR)
   .EQU  BUTT=PORTB4       ;  Button pin (Input on AVR)
   .EQU  TRIG=PORTB0       ;  Sensor TRIG pin (Output on AVR, input on sensor)
   .EQU  ECHO=PORTB1       ;  Sensor ECHO pin (Input on AVR, output on sensor)

   .EQU  BDV10usCNT=1      ; Number of iterations for (approx.) 10µs delay
   .EQU  WDV50msCNT=10415  ; WDELAY count for 50ms delay
   .EQU  BDV1cmCNT=19      ; Number of iterations for BDELAY loop to get 1cm
                           ; resolution.
   .EQU  BDV2mmCNT=6      ; Number of iterations for BDELAY loop to get 2mm
                           ; resolution.


   .DEF A = R16            ;  GENERAL PURPOSE ACCUMULATOR
   .DEF BDV = R17          ;  Counter used in BDELAY routine.
   .DEF CNT = R18          ;  CNT will be the no. of 150µs intervals of the ECHO
                           ;  ECHO pulse, each of which indicates 2.55cm. CNT=0
                           ;  indicates object not detected.
   .DEF DTEST=R19          ;  DTEST is used to test for particular distances.
                           ;  Will be 1 if distance test passes or 0 if failed
   .DEF DRNG1=R20          ;  Distance in increments of CNT for DNEARER and the shorter
                           ;  distance for DBETWEEN.
   .DEF DRNG2=R21          ;  Longer distance (in increments of CNT) for DBETWEEN.

	.ORG 0000
      RJMP  ON_RESET       ;  GO HERE WHEN CHIP IS TURNED ON OR RESET


	ON_RESET:
   	ldi	A, low(RAMEND)	; 1   Set Stack Pointer to end of RAM (1 BYTE ON TINY13)
   	out	SPL,A

      ldi   A, (1<<SENSELED) | (1<<RANGELED) | (1<<TRIG)
      out   DDRB, A        ; Set output pins
      sbi   PORTB, BUTT    ; Enable pullup on button
      cbi   PORTB, ECHO    ; Disable pullup on ECHO line


;   rcall WAIT4BUTTON
   
	MAIN_LOOP:
      rcall ONTRIG            ; Start 10µs trigger pulse by setting TRIG high.
      ldi   BDV, BDV10usCNT   ; Set DELAY count for delay of approx. 10µs.
      rcall BDELAY            ; Run the delay.
      rcall OFFTRIG           ; Turn off the trigger.

      rcall WAIT4ONECHO    ; Wait for start of ECHO pulse
      rcall GETECHOPULSE   ; Get length of echo pulse in CNT.
                           ; At this point, CNT will have either the number of
                           ; 2.55cm intervals to the object or will be zero,
                           ; indicating the object was not detected.

      tst   CNT            ; Check CNT for zero, indicating object not detected.
      breq  NOOBJECT
      rcall ONSENSELED     ; Turn on SENSELED indicating object was detected.

      ldi   DRNG1,30
      ldi   DRNG2,31
      rcall DBETWEEN       ; Test for object within certain distance.
      tst   DTEST          ; DTEST will be 1 if test passed (object is near).
      breq  NOTNEARER
      rcall ONRANGELED
      rjmp  MAINWAIT
NOTNEARER:
      rcall OFFRANGELED
      rjmp  MAINWAIT


   NOOBJECT:
      rcall OFFSENSELED          ; Turn OFF SENSELED indicating object not detected.
      rcall OFFRANGELED          ; Turn OFF RANGELED since distnace test must be false.

   MAINWAIT:                     ; The total sensing cycle from start to finish should
      ldi YH, HIGH(WDV50msCNT)   ; take at least 50ms in order to allow existing
      ldi YL, LOW(WDV50msCNT)    ; ultrasonic pulses in the room to completely fade and
      rcall WDELAY               ; not interfere with the subsequent sensing cycle.
                                 ; Since the time from triggering the pulse to minimum
                                 ; ultrasonic pulses in the room to completely fade and
                                 ; not interfere with the subsequent sensing cycle.
                                 ; Since the time from triggering the pulse to minimum
                                 ; pulse detection cycle is about 50µs, a delay of 50ms
                                 ; here is probably appropriate. 
  END:
 	   RJMP  MAIN_LOOP         ; MAIN LOOP DOES NOTHING (YET).


; -------------------------------------
;  DNEARER
;  Checks CNT to determine if object was detected closer than a particular distance.
;  The distance will be determined by the delay in GETECHOPULSE.
;  DRNG1 must be loaded with the desired range value before this routine is called.

DNEARER:
   ldi   DTEST,1        ; Assume test passed
   cp    CNT,DRNG1
   brlo  DNret          ; If CNT lower (closer) then exit with 1.
   ldi   DTEST,0        ; Otherwise, farther out, so exit with 0
DNret:
   ret

; -------------------------------------
;  DBETWEEN
;  Checks CNT to determine if object was within a range of distances.
;  The distance will be determined by the delay in GETECHOPULSE.
;  DRNG1 and DRNG2 must be loaded with range values before this routine is called.

DBETWEEN:
   ldi   DTEST,0        ; Assume test does not pass
   cp    CNT,DRNG1      ; Check lower distance
   brlo  DBret          ; If CNT lower (closer) then exit with 0.
   cp    CNT,DRNG2      ; Check upper distance
   brpl  DBret          ; If CNT higher (farther) then exit with 0.
   ldi   DTEST,1        ; Otherwise, farther out, so exit with 1
DBret:
   ret

; -------------------------------------
;  WAIT4BUTTON
;  Stops until button is pressed (grounded) and released, then returns.

WAIT4BUTTON:
   sbic     PINB, BUTT         ; Check button and skip if high (no press)
   rjmp     WAIT4BUTTON
W4B1:
   sbis     PINB, BUTT
   rjmp     W4B1
   ret

; -------------------------------------
;  WAIT4ONECHO
;  Waits for echo to start from ultrasonic sensor.

WAIT4ONECHO:
   sbis     PINB, ECHO         ; Check ECHO line of sensor and skip if high
   rjmp     WAIT4ONECHO
   ret

; -------------------------------------
;  GETECHOPULSE
;  Loop timing is set up to generate one iteration/count per a fixed length (ex. 1cm.)
;  According to the timings listed above, we'll want 70 or 71 clks for 1cm increments. 
;  The maximum pulse length for when an object is not detected is 38ms. However a
;  shorter maximum distance can be regarded as an object not detected.
;
;  Everything between GEP1 and GEPret will be executed CNT times. The rest of the
;  routine has a 1-time 4clks overhead including the time to call the routine. These
;  clock cycles should be considered negligiable. A delay routine can be inserted or
;  called between the breq and sbic instructions such that CNT can be associated with
;  a set distance. Use NOP instructions to fine tune this delay section.
;
;  If CNT overflows to 0 on the 256th count, the routine will exit and this should be
;  considered an object-not-detected condition.

GETECHOPULSE:
   clr      CNT            ; 1   Clear CNT
GEP1:
   inc      CNT            ; 1   
   tst      CNT            ; 1   Increment CNT (assume first pulse)
   breq     GEPret         ; 1/2 If CNT overflows to zero, exit routine.
                           ;     Timing: false:1clk, true:2clk
   ldi      BDV,BDV2mmCNT  ; *   Load delay count here
   rcall    BDELAY         ; *   *time is 7+3(BDVxxxCNT) incl. setting BDV and this
                           ;      rcall.
   sbic     PINB, ECHO     ; 1/2 Check ECHO pin. If no ECHO, skip over next instruction.
                           ;     Timing: false:1clk, true:2clk
   rjmp     GEP1           ; 2   

GEPret:
   ret                     ; 4


; -------------------------------------
; BDELAY
; A simple 1-byte loop delay
; See notes above for timing specifications
; BDV must be loaded with timing count before this routine is called!
                                    
BDELAY:                    ; 3   # of clks to call here
   dec      BDV            ; 1   Decrement counter
   brne     BDELAY         ; 1/2 Loop back up if not zero
                           ;     Timing: false:1clks, true:2clks
   ret                     ; 4   Else return


; -------------------------------------
; WDELAY
; A simple 2-byte (Word) loop delay
; See notes above for timing specifications
; YH and YL must be pre-loaded with desired n *before* this routine is called as follows:
;     ldi      YH, HIGH(n)
;     ldi      YL, LOW(n)
;     rcall    WDELAY

WDELAY:
   sbiw     YH:YL,1        ; 2   Word-level decrement
   brne     WDELAY         ; 1/2 Loop back up if not zero
                           ;     Timing: false:1clks, true:2clks
   ret                     ; 4   Else return

; -------------------------------------
; TOGGLE_LEDx
; Toggles either SENSELED or RANGELED

TOGGLESENSELED:
   sbi      PINB,SENSELED
   ret

TOGGLERANGELED:
   sbi      PINB,RANGELED
   ret

; -------------------------------------
; ON/OFF outputports
; Switches output port ON or OFF

ONSENSELED:
   SBI   PORTB,SENSELED
   ret
ONRANGELED:
   SBI   PORTB,RANGELED
   ret
ONTRIG:
   SBI   PORTB,TRIG
   ret
OFFSENSELED:
   CBI   PORTB,SENSELED
   ret
OFFRANGELED:
   CBI   PORTB,RANGELED
   ret
OFFTRIG:
   CBI   PORTB,TRIG
   ret
;------------------;
;  END OF PROGRAM  ;
;------------------;
