;----------------------------------------------------------------------
; Zufallszahlen-Anwendungsbeispiele mit ATtiny13
; Erläuterung: siehe www.schramm-software.de/tipps/
;----------------------------------------------------------------------
; Prozessor : ATtiny13
; Takt      : 1,2 MHz
; Sprache   : Assembler
; Version   : 1.1
; Autor     : Dr. Michael Schramm
; Datum     : Februar 2012
;----------------------------------------------------------------------
; Portbelegung:
; PB0,PB1: O  Tonsignal (Piezo-Piepser oder Lautspr. über R-C)
; PB2:     I  GND-Pegel aktiviert LFSR 2
; PB3,PB4  I  Auswahl der Funktionen
; PB5:     I  (Reset)
;----------------------------------------------------------------------
.include "tn13def.inc"
.include "../makros.inc"
; Als globale Variablen genutzte Register:
.DEF lfsr0   = r6  ;3 Register bilden ein 23bit-LFSR
.DEF lfsr1   = r7  ; (linear rückgekoppeltes Schieberegister)
.DEF lfsr2   = r8  ; für den Pseudo-Zufallszahlengenerator
.DEF lfsr3   = r9  ; für den Pseudo-Zufallszahlengenerator
.DEF intreg  = r10 ;für Interrupt-Routinen reserviertes Register
.DEF timovl  = r11 ;Count-Down-Zähler für Timer-Interrupts
.DEF nullreg = r12 ;konstant 0
.DEF konst_3 = r25 ;konstant 3
;----------------------------------------------------------------------
.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
i_t0ov:
  in intreg,SREG ;Statusregister zwischenspeichern
  dec timovl ;Zeitmessung über Count-Down-Zähler
  brne it0ov_end ;Zeit ist noch nicht um
  out TCCR0B,nullreg ;Timer stoppen
it0ov_end:
  out SREG,intreg ;Statusregister wiederherstellen
reti
;Programmcode zum letzten genutzten Vektor einfach direkt eingetragen,
;spart vor allem die Zeitverzögerung für RJMP.
;----------------------------------------------------------------------
tontab: ;Tabelle für Verzögerungszähler, um die Noten-Frequenzen zu
;treffen. 2 Bytes pro Note, 13 Halbtonschritte für eine Oktave.
.db 224,200,203,200,183,200,165,199,149,197,133,196,118,195
.db 103,195, 89,195, 78,192, 58,202, 56,189,110,102

start:
; zunächst Stromsparmaßnahmen und Initialisierung
  out_i DDRB,0b11 ;Datenrichtung von Port B
  out_i PORTB,0b11101 ;Pull-Up-Widerstände und PB0/1 unterschiedlich
  sbi ACSR,ACD  ;den Analog-Comparator ausschalten
  clr nullreg ;nullreg (R15) bleibt dauerhaft null
  mov lfsr0,r16 ;beliebiger LFSR-Startwert ungleich null
  out_i SPL,low(RAMEND);Stackpointer setzen, 8bit-Pointer bei Tiny13
  mov lfsr1,r16
  ldi konst_3,3 ;Umschalt-Wert für Port
  sei ;Interrupts erlauben
  in r16,PINB ;gewählte Funktion ermitteln
  mov lfsr2,r16
  andi r16,0b11000 ;nur PB4 und PB3 interessieren
  breq func_00
  cpi r16,0b10000
  brcs func_01
  breq func_10

  func_11: ;Zufallstöne, völlig 'wilde' Frequenzen und Tonlängen
    ldi r16,3 ;3-Bit-Zufallszahl für Tonlänge
    rcall zufallszahl
	andi xl,7 ;nur 3 Bits verwenden
    subi xl,-2 ;Mindestlänge = 2/17 Sekunden
    rcall init_timer ;Timer starten, timovl herunterzählen
    ldi r16,7 ;7-Bit-Zufallszahl für Ton-Periodenlänge
    rcall zufallszahl
	andi xl,$7F ;nur 7 Bits verwenden
    subi xl,-100 ;Mindestlänge = 120*5 Taktzyklen
    ton_halbper:
      mov r0,xl
      ton_waitlp:
	    adiw zl,0 ;2 Taktzyklen Verzögerung
	  next_down r0,ton_waitlp
      out PINB,konst_3 ;PB0 und PB1 jeweils umschalten
      tst timovl ;gewählte Zeit verstrichen?
    brne ton_halbper ;nein => weiter mit demselben Ton
  rjmp func_11 ;ja => neuer Ton

  func_00: ;Rauschen
    ldi r16,1 ;nur 1 Bit wird benötigt
    rcall zufallszahl
    sbrc xl,0 ;keine Änderung, falls Zufallsbit = 0
    out PINB,konst_3 ;anderenfalls PB0 und PB1 jeweils umschalten
  rjmp func_00

  func_10: ;wechselnde LED-Helligkeit per PWM (Pulsweitenmodulation)
    out_i PORTB,0b11100 ; Grundzustand mit PB0 und PB1 auf GND
    ;PB0 und PB1 mit dem Timer-Schwellwertvergleich verknüpfen
	;und 'fast PWM' einstellen
	out_i TCCR0A,(1<<COM0A1)+(1<<COM0B1)+(1<<COM0B0)+(3<<WGM00)
	f10_loop:
  	  ldi r16,8 ;8 Bit werden benötigt
      rcall zufallszahl
	  out OCR0A,xl ;Vergleichswerte zum Umschalten der Ports
	  out OCR0B,xl
      ldi r17,1<<CS01 ;für Systemtakt / 8 = 150 kHz
      rcall init_timer2
	  f10_wtlp: ;warten, bis 256 Timer-Durchläufe erledigt
	    out_i MCUCR,1<<SE ;Idle-Modus
	    sleep ;CPU schläft, wartet auf Interrupt (Timer)
        tst timovl ;256 Timer-Durchläufe erledigt (ca. 0,4 Sekunden)?
      brne f10_wtlp
	rjmp f10_loop ;nächste Helligkeitsstufe

  func_01: ;Zufallstöne aus der Tonleiter (1 Oktave, Halbtonschritte)
    ldi r20,5 ;Startton
	f01_loop:
	  ldi r16,3 ;3-Bit-Zufallszahl für den Ton
      rcall zufallszahl
	  andi xl,7 ;nur 3 Bits verwenden
	  breq f01_loop ;nur 1 bis 7 verwenden, bei 0 neue Ziehung
	  add r20,xl ;den nächsten Ton zufällig ermitteln
	  subi r20,4 ;gleiche Wahrscheinlichkeit für aufwärts/abwärts
	  brcc pc+2
	  clr r20 ;kleiner als 0 ist nicht erlaubt
	  cpi r20,13
	  brcs pc+2
	  ldi r20,12 ;und größer als 12 auch nicht
	  ldi_hl z,tontab*2 ;die Verzögerungswerte für den nächsten Ton
	  add zl,r20 ;ermitteln
	  add zl,r20 ;(8-Bit-Überlauf ist ausgeschlossen)
	  lpm r2,z+ ;Tabellenwerte in R2 und R3 merken
	  lpm r3,z
      ldi xl,5 ;Tonlänge = 5/17 Sekunden
      rcall init_timer ;Timer starten, timovl herunterzählen
; Durchlaufzeit der folgenden Schleife für eine halbe Periode:
; R2*4 + R3*3 + 4 Taktzyklen
      ton_halbperio:
        mov r0,r2
        f01_waitlp1: ;4 Taktzyklen pro Durchlauf
	      nop
	    next_down r0,f01_waitlp1
        mov r0,r3
        f01_waitlp2: ;3 Taktzyklen pro Durchlauf
	    next_down r0,f01_waitlp2
        out PINB,konst_3 ;PB0 und PB1 jeweils umschalten
        tst timovl ;gewählte Zeit verstrichen?
      brne ton_halbperio ;nein => weiter mit demselben Ton
    rjmp f01_loop ;nächster Ton

;----------------------------------------------------------------------
; ******************* Unterprogramme / Funktionen *******************

zufallszahl:
  sbis PINB,2 ;abhängig von PB2 den Zufallszahlengenerator wählen
  rjmp zufallsz2

zufallsz1: ; *** Pseudo-Zufallszahl erzeugen (per LFSR mit 23 Bits)
; Input:  R16 = Anzahl n der zu erzeugenden Bits (1 ... 16)
; Output: XH:XL = Zufallszahl (in den unteren n Bits)
  zuf_lp1: ;n Shift-Operationen mit Rückkopplung
    mov xh,lfsr1
    lsl xh ;bit 13 des LFSR steht nun in bit 6 von xh
	eor xh,lfsr2 ;bit 22 und 13 des LFSR sollen verknüpft werden
	lsl xh ;jetzt in bit 7
	lsl xh ;jetzt im Carry-Flag
	rol lfsr0 ;gesamtes LFSR links-shiften und neues Bit einschieben
	rol lfsr1
	rol lfsr2
  next_down r16,zuf_lp1 ;n "neue bits" stehen bereit in LFSR
  movw x,lfsr0
ret
;----------------------------------------------------------------------
zufallsz2: ; *** ein weiterer Zufallszahlengenerator (LFSR mit 32 Bits)
  zuf_lp2: ;n Shift-Operationen mit Rückkopplung
    mov xh,lfsr0
    ror xh ;bit 0 des LFSR steht nun im Carry-Flag
	ror xl ;... und nun in bit 7 von xl
	ror xh ;bit 1 des LFSR steht nun im Carry-Flag
	ror xh ;... und nun in bit 7 von xh
	eor xh,xl ;bit 0, 1 und 31 des LFSR werden exkl.-oder-verknüpft
	eor xh,lfsr3
	lsl xh ;jetzt im Carry-Flag
	rol lfsr0 ;gesamtes LFSR links-shiften und neues bit einschieben
	rol lfsr1
	rol lfsr2
	rol lfsr3
  next_down r16,zuf_lp2 ;n "neue bits" stehen bereit in LFSR
  movw x,lfsr0
  eor xh,lfsr2
  eor xl,lfsr3
ret
;----------------------------------------------------------------------
init_timer: ;Timer initialisieren und starten, Zeitdauer gemäß XL
  mov timovl,xl
  out TCNT0,nullreg ;Counter-Wert nullsetzen
  ldi r17,1<<CS02 ;für Systemtakt / 256 = 4,6875 kHz
init_timer2:
  out_i TIMSK0,1<<TOIE0;Timer-Overflow-Interrupt aktivieren
  out TCCR0B,r17 ;Timer starten mit gewähltem Systemtakt
ret

