;----------------------------------------------------------------------
; Zufallszahlen-Erzeugung mit ATtiny13 per ADC
; Erläuterung: siehe www.schramm-software.de/tipps/
;----------------------------------------------------------------------
; Prozessor : ATtiny13
; Takt      : 1,2 MHz
; Sprache   : Assembler
; Version   : 1.0
; Autor     : Dr. Michael Schramm
; Datum     : Februar 2012
;----------------------------------------------------------------------
; Portbelegung:
; PB0,PB1: O  Tonsignal (Piezo-Piepser oder Lautspr. über R-C)
; PB2:     I  (ADC1) ADC-Eingang
; PB4      I  GND-Pegel aktiviert direkte ADC-Wert-Verwendung
; PB5:     I  (Reset)
;----------------------------------------------------------------------
.include "tn13def.inc"
.include "../makros.inc"
; Als globale Variablen genutzte Register:
.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 = r23 ;konstant 3
.def r_adcl  = r24 ;Übergabe des ADC-Messwertes (Low-Byte)
.def r_adch  = r25 ;Übergabe des ADC-Messwertes (High-Byte)
;----------------------------------------------------------------------
.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
  in intreg,SREG ;(3) Statusregister zwischenspeichern
  dec timovl     ;(4) Zeitmessung über Count-Down-Zähler
  brne it0ov_end ;(5) Zeit ist noch nicht um
  out TCCR0B,nullreg;(6) Timer stoppen
it0ov_end:
  out SREG,intreg;(7) Statusregister wiederherstellen
  reti           ;(8) WDT Watchdog Time-out
  in r_adcl,ADCL ; 9 ADC ADC Conversion Complete
  in r_adch,ADCH
reti
;Programmcode der beiden 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, 16 Halbtonschritte (rund 1 1/2 Oktaven).
.db 224,200,203,200,183,200,165,199,149,197,133,196,118,195,103,195
.db  89,195, 78,192, 58,202, 56,189,110,102,110, 88,100, 88, 99, 77

start:
; zunächst Stromsparmaßnahmen und Initialisierung
  out_i DIDR0,0b01111 ;nur PB4 benötigt digitalen Input-Buffer
  out_i DDRB, 0b00011 ;Datenrichtung von Port B
  out_i PORTB,0b11001 ;Pull-Up-Widerstände und PB0/1 unterschiedlich
  sbi ACSR,ACD        ;den Analog-Comparator ausschalten
; ADC konfigurieren mit Interrupt und Vorteiler 8 (-> 150 kHz)
  out_i ADMUX,1       ;Input = ADC1 = PB2
  out_i ADCSRA,(1<<ADEN)+(1<<ADIE)+(3<<ADPS0)
  clr nullreg         ;nullreg (R15) bleibt dauerhaft null
  out_i SPL,low(RAMEND);Stackpointer setzen, 8bit-Pointer bei Tiny13
  ldi konst_3,3       ;Umschalt-Wert für Port
  sei                 ;Interrupts erlauben
  sbic PINB,4         ;gewählte Funktion ermitteln
  rjmp ausw_16        ;bei offenem PB4

;10bit-ADC-Ergebnis direkt in eine Tonfrequenz umsetzen
dir_wied:
  out_i MCUCR,(1<<SE)+(1<<SM0) ;Noise-reduction-Modus
  sleep                        ;Warten auf ADC-Ergebnis
  adiw r_adcl,30     ;Mindestlänge einer halben Periode
  rcall init_timer   ;Timer starten, timovl herunterzählen
  ton_halbper:
    movw xl,r_adcl
    ton_waitlp:      ;Verzögerungsschleife für eine halbe Tonperiode
	  sbiw xl,1      ;pro Durchlauf 4 Taktzyklen
	brne 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 dir_wied        ;ja => neuer Ton

;4 Bits durch 4 ADC-Abfragen einsammeln, jeweils nur das unterste Bit
;des ADC-Ergebnisses verwenden ("am zufälligsten"). Die 4-Bit-Zahl wählt
;einen von 16 vorgegebenen Tönen im Halbtonabstand aus:
ausw_16:
  ldi r20,$10 ;Endemarke-Bit, sonst nur Nullen
  adc4bit:    
    out_i MCUCR,(1<<SE)+(1<<SM0) ;Noise-reduction-Modus
    sleep                        ;Warten auf ADC-Ergebnis
	ror r_adcl      ;nur Bit 0 des Ergebnisses interessiert, dieses
	rol r20         ;in R20 'einrotieren'
  brcc adc4bit      ;4 Durchläufe, bis Endemarke herausrotiert
  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
  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
    hp_waitlp1:      ;4 Taktzyklen pro Durchlauf
	  nop
	next_down r0,hp_waitlp1
    mov r0,r3
    hp_waitlp2:      ;3 Taktzyklen pro Durchlauf
	next_down r0,hp_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 ausw_16         ;nächster Ton

;----------------------------------------------------------------------
; ******************* Unterprogramme / Funktionen *******************

init_timer:            ;Timer initialisieren und starten
  ldi r16,3            ;Tonlänge = ca. 0,66 Sekunden
  mov timovl,r16
  out TCNT0,nullreg    ;Counter-Wert nullsetzen
  out_i TIMSK0,1<<TOIE0;Timer-Overflow-Interrupt aktivieren
  out_i TCCR0B,5<<CS00 ;für Systemtakt / 1024 = 1172 Hz (ca.)
ret
