; Ermittlung der Input-Pin-Schaltschwelle beim ATTiny13
; Erläuterungen: www.schramm-software.de/tipps
;----------------------------------------------------------------------
; Prozessor : ATTiny13
; Takt      : 1,2 MHz
; Sprache   : Assembler
; Version   : 1.0
; Autor     : Dr. Michael Schramm
; Datum     : 11.2009
;----------------------------------------------------------------------
; Portbelegung:
; PB0: O  Elko-Aufladung und Tonsignal (Lautspr. über R-C)
; PB1 - PB4: I  Hier wird die Schaltschwelle ermittelt
;----------------------------------------------------------------------
.include "tn13def.inc"
.include "../makros.inc"
.def r_adcl  = r2  ;ADC-Ergebnis zwischenspeichern
.def r_adch  = r3
.def r_tmpl  = r4  ;Zwischenergebnis-Speicher
.def r_tmph  = r5
.def timovl  = r12 ;Zähler für Timer-Overflow
.def intreg  = r13 ;reserviert für Interrupt-Routinen
.def einsreg = r14 ;permanent 1
.def nullreg = r15 ;permanent 0
.def portmask= r24 ;hier wird eine 1 durchgeshiftet
.def portbit = r25 ;durchläuft 1 .. 4
;----------------------------------------------------------------------
.DSEG ;SRAM-Belegung
dezzahl:  .byte 6
tmp_word: .byte 2
;----------------------------------------------------------------------
.CSEG
;Reset- und Interruptvektoren
  rjmp start ;0 RESET, Brown-out Reset, Watchdog Reset
  reti       ;1 INT0 External Interrupt Request 0
  reti       ;2 PCINT0 Pin Change Interrupt Request 0
  rjmp i_t0ov;3 TIM0_OVF Timer/Counter Overflow
  reti       ;4 EE_RDY EEPROM Ready
  reti       ;5 ANA_COMP Analog Comparator
  reti       ;6 TIM0_COMPA Timer/Counter Compare Match A
  reti       ;7 TIM0_COMPB Timer/Counter Compare Match B
  reti       ;8 WDT Watchdog Time-out
  rjmp adcint;9 ADC ADC Conversion Complete
;----------------------------------------------------------------------
zehnerpot: ;Zehnerpotenzen als 2-Byte-Zahlen für Funktion bin2dec
; Tabelle muss im Anfangsbereich des Speichers stehen, damit das
; High-Byte der Adresse 0 ist
.db 16,39  ;10.000
.db 232,3  ;1.000
.db 100,0  ;100
.db 10,0   ;10
.db 1,0    ;1

start:
; Stromsparmaßnahmen und Initialisierung
  sbi ACSR,ACD          ;Analog-Comparator ausschalten
  out_i SPL,low(RAMEND) ;Stackpointer setzen, 8bit-Pointer bei Tiny13
; Achtung: bei größeren Controllern ggf. auch SPH setzen!
  clr nullreg           ;bleibt dauerhaft null
  clr einsreg
  inc einsreg           ;bleibt dauerhaft eins
  ldi portmask,0b10     ;um Bit 2**portbit zu isolieren
  out_i ADMUX,3 ;zunächst ADC-Messung an ADC3 = PB3
  sei ;Interrupts erlauben

for portbit,1,portlp ;PB1 bis PB4 prüfen ("PBx")
  out DDRB,einsreg  ;Datenrichtung von Port B, nur PB0 als Output
; ADC einschalten mit Interrupt und Vorteiler 8
  out_i ADCSRA,(1<<ADEN)+(1<<ADIE)+(1<<ADPS1)+(1<<ADPS0)
  ldi r16,0b111111
  eor r16,portmask ;nur PBx-Bit auf 0
  out DIDR0,r16 ;alle bis auf einen Port-B-Input-Buffer ausschalten
  out PORTB,nullreg ;PB0 auf 0, Elko C2 wird entladen
  rcall wait1sek ;vor Messungsstart 1 Sek. Pause
  wait_bis0: ;sicherstellen, dass PBx tatsächlich auf 0 liegt
    in r0,PINB
    and r0,portmask ;dadurch wird PBx isoliert
  brne wait_bis0
  out PORTB,einsreg ;PB0 auf 1, Elko C2 wird geladen
  wait_bis1: ;nun den Umschaltmoment auf 1 an PBx abpassen
    in r0,PINB
    and r0,portmask ;dadurch wird PBx isoliert
  breq wait_bis1
  out DDRB,nullreg ;auch PB0 nun als Input, U an C2 nicht mehr ändern
  out_i MCUCR,(1<<SE)+(1<<SM0) ;ADC-Noise-Reduction-Modus
; in diesem Modus stößt sleep eine einzelne ADC-Umwandlung an
  sleep ;schlafen, bis ADC-Ergebnis vorliegt (in r_adch:r_adcl)
  out DDRB,einsreg  ;PB0 wieder als Output
  movw r_tmpl,r_adcl ;für die spätere Ausgabe merken
  ldi r17,2
  rcall waitr17 ;sicherheitshalber kurz warten und C2 weiter laden
  out PORTB,nullreg ;PB0 auf 0, Elko C2 wird wieder entladen
  wait_bisw0: ;nun den Umschaltmoment auf 0 an PBx abpassen
    in r0,PINB
    and r0,portmask ;dadurch wird PBx isoliert
  brne wait_bisw0
  out DDRB,nullreg ;auch PB0 nun als Input, U an C2 nicht mehr ändern
  out_i MCUCR,(1<<SE)+(1<<SM0) ;ADC-Noise-Reduction-Modus
; in diesem Modus stößt sleep eine einzelne ADC-Umwandlung an
  sleep ;schlafen, bis ADC-Ergebnis vorliegt (in r_adch:r_adcl)
  out_i ADCSRA,0 ;ADC ausschalten
  out DDRB,einsreg  ;PB0 wieder als Output
; nun haben wir die beiden Messwerte für den Port PBx, die anschließend
; als Tonsignale dem lauschenden Experimentator mitzuteilen sind
  for r30,2,ausglp ;Ausgabe der beiden Messwerte
    ldi_hl x,4 ;Adresse von r_tmpl
    ldi_hl y,dezzahl ;Speicherbereich für Dezimalzahl-Ergebnis
    rcall bin2dec
    tst r18 ;Sonderfall 0 berücksichtigen
    brne ziff_ausg
      st y,r18 ;Dezimalziffer 0 schreiben
      inc r18 ;1 Ziffer ausgeben, also 0
    ziff_ausg: ;R18 Ziffern ab (Y) ausgeben
      ld r0,y+
      rcall zifferton
    next_down r18,ziff_ausg
    ldi r17,9
    rcall waitr17 ;etwa eine halbe Sekunde warten
    movw r_tmpl,r_adcl ;zweiter Messwert für die zweite Ausgabe
  next_down r30,ausglp
  cpi portbit,2 ;nach Messung an PB2 den ADC-Port wechseln
  brne adcportok
  out ADMUX,einsreg ;weitere ADC-Messungen an ADC1 = PB2
adcportok:
  lsl portmask ;Maskierung für den nächsten Port
next_up portbit,5,portlp ;weiter mit nächstem Port-Bit / Eingangs-Pin

; Ende der Messungen: Energiesparmaßnahmen und Tiefschlaf
  out DDRB, nullreg  ;Port B komplett als Input
  out_i DIDR0,0b111111 ;alle Port-B-Input-Buffer ausschalten
  out_i MCUCR,(1<<SE)+(1<<SM1) ;Power-down-Modus
  sleep ;Tiefschlaf, warten auf Reset-Signal

;----------------------------------------------------------------------

; ******************* Unterprogramme / Funktionen *******************

tonsignal: ; *** Tonsignal an PB0 ausgeben
; (schlichte Realisierung über Zählschleife)
; R16: Periodenlänge als Vielfaches von 100 µs
; R17: 1/10 der Anzahl der zu erzeugenden Perioden
  push xl
  push xh
  push r20
  push r2
  tonsiglp:
    for r20,10,ton_10per ;10 Perioden erzeugen
      for xh,2,ton_period ;für die beiden halben Perioden
        mov r2,r16 ;R16 halbe Perioden erzeugen
        wt_halb_per: ;1/2 Periode erzeugen
          for xl,19,wt50lp ;50 µs warten (bei Taktfrequenz 1,2 MHz)
          next_down xl,wt50lp
        next_down r2,wt_halb_per
        out PINB,einsreg ;Port B0 umschalten
      next_down xh,ton_period
    next_down r20,ton_10per
  next_down r17,tonsiglp
  out PORTB,nullreg ;Port B0 auf 0
  pop r2
  pop r20
  pop xh
  pop xl
ret
;----------------------------------------------------------------------
zifferton: ; *** R0 als Tonsignalfolge ausgeben
  push r0
  push r16
  push r17
  tst r0
  brne ziff_pieps
  ldi r16,20 ;500 Hz für die Ziffer 0
  ldi r17,25 ;250 Perioden = 1/2 Sekunde
  inc r0 ; damit die folgende Schleife 1mal durchlaufen wird
  rjmp ausg_ziff_ton ;Sprung in die Schleife ...
  ziff_pieps: ;R0 Piepser für eine Dezimalziffer
    ldi r16,10 ;1 kHz
    ldi r17,25 ;250 Perioden = 1/4 Sekunde
    ausg_ziff_ton:
    rcall tonsignal
    ldi r17,5
    rcall waitr17 ;nach jedem Pieps 5/18 Sek. warten (knapp 300 ms)
  next_down r0,ziff_pieps
  ldi r17,11
  rcall waitr17 ;nach jeder Ziffer zusätzlich gut 600 ms Pause
  pop r17
  pop r16
  pop r0
ret
;----------------------------------------------------------------------
; Warte-Routinen sind energiesparend mit Timer + SLEEP-Modus realisiert
wait1sek: ; ***  gut 1 Sekunde warten
  ldi r17,19 ; R17 wird verändert
waitr17: ;  *** R17 * 1/18 Sekunde warten
  push r16
  clr timovl ;Überlauf-Reg. für den Timer, wird in Int.-Rout. inkrementiert
  out TCNT0,nullreg ;Counter-Wert nullsetzen
  out_i TIMSK0,(1<<TOIE0) ;Timer-Overflow-Interrupt aktivieren
  out_i TCCR0B,(1<<CS02) ;Timer starten mit Systemtakt / 256 = 4,6875 kHz
  wtr17lp:
    out_i MCUCR,(1<<SE) ;Idle-Modus
    sleep ;CPU schläft, wartet auf Interrupt (Timer)
	cp timovl,r17 ;r17 Timer-Überläufe erreicht?
  brcs wtr17lp ;noch nicht => weiter warten
  out TCCR0B,nullreg ;Timer stoppen
  pop r16
ret
;----------------------------------------------------------------------
bin2dec: ; *** Dezimaldarstellung einer 2-Byte-Binärzahl berechnen
; Input:  X zeigt auf Beginn der Binärzahl (LSB) im SRAM
;         Y zeigt auf Beginn des SRAM-Speicherbereichs der Dezimalzahl
; Output: Dezimalzahl ab (Y), eine Dez.ziffer pro Byte, Start mit MSD
;         R18 = Anzahl der Dezimalziffern (0 bei 0)
; Binärzahl (X) wird zu null abgebaut
  push r0
  push r1
  push r10
  push r11
  push r16
  push r17
  push zl
  push zh
  clr r18
  mov r11,xl ;R11 = Kopie von XL
  ldi_hl z,(zehnerpot*2) ;mal 2 wegen wortweiser Adressierung im Flash
  for r16,5,b2d_nxt_ziff ; R16 = Abwärtzzähler für Zehnerpotenzen
    clr r10 ;R10 = aktuelle Ziffer
    push yl
    ldi yl,low(tmp_word)
; hier wird ausgenutzt, dass Yh im kleinen SRAM immer gleich bleibt (0)
    for r17,2,b2d_pot_copy
      lpm r0,z+ ; Zehnerpotenz vom Flash ins SRAM kopieren
      st y+,r0
    next_down r17,b2d_pot_copy
    mov xl,r11
    adiw x,2
   b2d_nxt_test:
    for r17,2,b2d_compare
      ld r0,-y ;Zehnerpotenz
      ld r1,-x ;Rest der Ausgangszahl
      cp r1,r0
      brcs b2d_ziff_ok ;kein weiterer Abzug möglich
      brne b2d_subtract
    next_down r17,b2d_compare
   b2d_subtract:
    mov xl,r11
    ldi yl,low(tmp_word)
    rcall x_min_y
    inc r10
    rjmp b2d_nxt_test
   b2d_ziff_ok:
    pop yl
    tst r18
    brne b2d_st_ziff
    tst r10
    breq b2d_ziff_fertig
    b2d_st_ziff:
    st y+,r10
    inc r18
  b2d_ziff_fertig:
  next_down r16,b2d_nxt_ziff
  sub yl,r18 ; Y wieder auf Dezimalzahlbeginn
  pop zh
  pop zl
  pop r17
  pop r16
  pop r11
  pop r10
  pop r1
  pop r0
ret
;----------------------------------------------------------------------
x_min_y: ; *** Subtraktion, 2-Byte-Integer (X) <- (X) - (Y)
; r0, r1 werden verändert
; x und y zeigen anschließend hinter die Zahlen, sind also um 2 erhöht
  push r17
  clc
  for r17,2,xminy_lp
    ld r0,x
    ld r1,y+
    sbc r0,r1
    st x+,r0
  next_down r17,xminy_lp
; sbiw x,2
; sbiw y,2
  pop r17
ret
;----------------------------------------------------------------------

; ************************ Interrupt-Routinen *************************

adcint: ;ADC-Ergebnis liegt vor
  in r_adcl,ADCL
  in r_adch,ADCH
reti

i_t0ov: ; *** Timer/Counter Overflow
; Prescaler=256 => 18,3 Overflow-Interrupts pro Sekunde
  in intreg,sreg ;Statusregister merken
  inc timovl ;Zähler für (in etwa) 18tel Sekunden
  out sreg,intreg ;Statusregister wiederherstellen
reti

; ******************************* ENDE *******************************
