; Beispiel für Tonsignalausgabe einer Dezimalzahl mit ATMega8
; geeignet bspw. für myAVR-Entwicklungsboards
; Erläuterungen: www.schramm-software.de/tipps
;----------------------------------------------------------------------
; Prozessor : ATMega8
; Takt      : 3,6 MHz (ca.)
; Sprache   : Assembler
; Autor     : Dr. Michael Schramm
; Datum     : 2.2010
;----------------------------------------------------------------------
; Portbelegung:
; Port C: mehrere Ausgänge zum Testen der Funktion
; 5: Piezo-Pieper für Zahlenausgabe
;----------------------------------------------------------------------
.include  "m8def.inc"
.include  "../makros.inc"
.def tc0ov   = r3 ;Zähler für Timer-0-Interrupt
.def intreg  = r4 ;für Interrupt-Routinen reserviert
.def nullreg = r8 ;bleibt fest null
;----------------------------------------------------------------------
.DSEG ;SRAM-Nutzung
; die 8 Bytes in dezzahl und tmp_word müssen in derselben "Speicherseite"
; stehen, soll heißen, das Adress-Highbyte muss gleich bleiben
dezzahl:  .byte 6
tmp_word: .byte 2
;----------------------------------------------------------------------
.CSEG ;Flash-Inhalt
;Reset and Interruptvectoren  ;VNr.  Beschreibung
begin:
  rjmp init     ; 1  POWER ON RESET
  reti          ; 2  Int0-Interrupt
  reti          ; 3  Int1-Interrupt
  reti          ; 4  TC2 Compare Match
  reti          ; 5  C2 Overflow
  reti          ; 6  TC1 Capture
  reti          ; 7  TC1 Compare Match A
  reti          ; 8  TC1 Compare Match B
  reti          ; 9  TC1 Overflow
  rjmp i_ovlTC0 ;10  TC0 Overflow
  reti          ;11  SPI, STC Serial Transfer Complete
  reti          ;12  UART Rx Complete
  reti          ;13  UART Data Register Empty
  reti          ;14  UART Tx Complete
  reti          ;15  ADC Conversion Complete
  reti          ;16  EEPROM Ready
  reti          ;17  Analog Comperator
  reti          ;18  TWI (I²C) Serial Interface
  reti          ;19  Store Program Memory Ready
;------------------------------------------------------------------------
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
.dw 10000,1000,100,10,1

init: ;Start, Power ON, Reset
  out_i spl,low(RAMEND)
  out_i sph,high(RAMEND);Init Stackpointer
  clr nullreg
  out_i DDRC,0b111111 ;Port C soll Programmzustände signalisieren
; Timer 0 für innere Uhr aktivieren, Takt / 1024, Overflow-Interrupt
; der Overflow tritt etwa 14mal pro Sekunde auf
  ldi r16,(1<<CS02) + (1<<CS00)
  out TCCR0,r16
  ldi r16,(1<<TOIE0)
  out TIMSK,r16
; falls der Timer 0 nicht in dieser Weise verwendet werden soll, muss
; die Warte-Routine wait_r16 angepasst werden (evtl. nur schlichte
; Zählschleife)

; ... weitere Initialisierung ...

; ... hier steht das Hauptprogramm ...

; Code-Schnipsel für die Ausgabe einer Bytefolge. Hier wird angenommen,
; dass die Bytefolge ab Adresse Z im SRAM vorliegt und eine Längeninfor-
; mation enthält. Dieses Beispiel passt für Datenpakete, wie sie ein
; bestimmtes Display mit Touch-Funktion zurückliefert (EA eDIPTFT43-ATP).

  ldd r19,z+1 ;Byte mit Längeninformation
  subi r19,-3 ;plus 3 für DC1 bzw. DC2, Länge, Prüfsumme
  clr r25
  morselp:
    rcall wait1sek ;vor jeder Zahlenausgabe 1 Sek. Pause
    ld r24,z+
    ldi_hl x,24 ;Adresse von R24
    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, nämlich die Null
    ziff_ausg: ;R18 Ziffern ab (Y) ausgeben
      ld r0,y+
      rcall zifferton
    next_down r18,ziff_ausg
  next_down r19,morselp

; ...

power_down:
  out_i MCUCR,(1<<SE) + (1<<SM1)
  sleep
  rjmp power_down

;----------------------------------------------------------------------
; ******************** Unterprogramme / Funktionen ********************
;----------------------------------------------------------------------

wait1sek: ;1 Sekunde warten
  ldi r16,14
wait_r16: ; *** warten, Zeitdauer = R16 14tel Sekunden ***
; R16 darf nicht null sein, sonst ewiges Warten ...
  push r19
  out TCNT0,nullreg
  clr tc0ov
  ldi r19,(1<<SE)
waitl:
  out MCUCR,r19
  sleep
  cp tc0ov,r16
  brlo waitl
  pop r19
ret
;----------------------------------------------------------------------
tonsignal: ; *** Tonsignal an PC5 ausgeben
; (schlichte Realisierung über Zählschleife)
; R16: Periodenlänge als Vielfaches von 100 µs
; R17: 1/10 der Anzahl der zu erzeugenden Perioden
  push r2
  push r18
  push r19
  push r20
  push r21
  push xl
  push xh
; Zur ungestörten Tonausgabe werden alle Interrupts gesperrt. Sollte das
; nicht aktzeptabel sein, muss es notfalls auch mit Interrupts gehen.
  in r21,sreg
  cli
  ldi r18,0b100000
  in r19,PORTC
  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,57,wt50lp ;50 µs warten (bei Taktfrequenz 3,6 MHz)
          next_down xl,wt50lp
        next_down r2,wt_halb_per
        eor r19,r18
        out PORTC,r19
      next_down xh,ton_period
    next_down r20,ton_10per
  next_down r17,tonsiglp
  out sreg,r21 ;SREG inkl. Interrupt-Flag wiederherstellen
  pop xh
  pop xl
  pop r21
  pop r20
  pop r19
  pop r18
  pop r2
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 r16,2
    rcall wait_r16 ;nach jedem Pieps 2/14 Sek. warten (ca. 140 ms)
  next_down r0,ziff_pieps
  ldi r16,7
  rcall wait_r16 ;nach jeder Ziffer zusätzlich ca. 1/2 Sek. Pause
  pop r17
  pop r16
  pop r0
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 gleich bleibt (alles in 1 Speicherseite)
    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

;----------------------------------------------------------------------
; ************************* Interruptroutinen *************************
;----------------------------------------------------------------------

i_ovlTC0: ;wird rund 14mal pro Sekunde aufgerufen
  in intreg,sreg
  inc tc0ov ;Interrupt-Zähler, kann im Hauptprogramm abgefragt werden
  out sreg,intreg
reti
