[gelöst] random () ohne Zufall

Von einer endlichen Zahl Musikstücke möchte ich eines zufällig auswählen und wiedergeben. Dazu verwende ich random(). Da die Funktion nach einer zufälligen Zeit durch Drücken einer Fernbedienung aufgerufen wird, hatte ich eine mehr oder minder zufällige Zufallszahl erwartet. Leider werden immer die selben Zahlen erzeugt:

LiedNummer = 28
LiedNummer = 79
LiedNummer = 77
LiedNummer = 2
LiedNummer = 103
LiedNummer = 101
LiedNummer = 57

Mangels freiem analogen Eingang habe ich randomSeed(millis()); versucht. Leider ohne das gewünschte Ergebnis.

const int eingangPin = 7;
int LiedNummer;
int anzahlLieder = 140;
boolean zustandEingang;

void setup() {
  Serial.begin(9600);
  pinMode(eingangPin, INPUT_PULLUP);
  zustandEingang = digitalRead(eingangPin);
  randomSeed(millis());
}

void loop() {
  if (zustandEingang == HIGH && digitalRead(eingangPin) == LOW) {
    LiedNummer = random(anzahlLieder + 1); // Zufallsnummer erzeugen zwischen 1 bis anzahlLieder
    Serial.print("LiedNummer = "); Serial.println(LiedNummer); // Debug-Ausgabe
  }
  zustandEingang = digitalRead(eingangPin);
  delay(10);
}

Mache ich was falsch?

agmue:
Mache ich was falsch?

Ja. Pseudo-Zufallszahlengeneratoren erzeugen immer dieselbe Folge von Zufallszahlen, wenn sie mit demselben Startwert initialisiert sind.

Wenn Du den Startwert nicht selbst initialisierst, ist es nach jedem Neustart des Programms immer derselbe.

Abhilfe wäre: Den Startwert selbst initialisieren, und zwar zeitabhängig wenn eine Taste gedrückt wird:
http://www.arduino.cc/en/pmwiki.php?n=Reference/RandomSeed

Beispielsweise beim Tastendruck:

randomSeed(micros());

oder

randomSeed(millis());

Das im setup() zu machen ist relativ witzlos, weil es da immer in derselben Millisekunde nach dem Programstart abläuft, und dann wird ja auch immer derselbe Startwert initialisiert. Richtig wäre: Beim Drücken einer Taste den Startwert initialisieren.

Oder wenn die Schaltung eine RTC hat, könntest Du die RTC-Uhrzeit zum Initialisieren verwenden.

Schau dir mal wie Pseudo-Zufallsgeneratoren funktionieren. Das ist ganz normal. Wenn der gleiche Seed verwendet wird, wird die gleiche Reihe erzeugt. Das gibt es z.B. auch bei Computerspielen, vor allem bei rundenbasierenden Taktik-Spielen wie die XCOM Serie oder ähnliches. Wenn man immer genau das gleiche macht, machen die Gegner auch das gleiche, weil die Zahlen gleich sind. Da gibt es dann manchmal eine Option namens "save scumming" um beim Neuladen einen neuen Seed für den PRNG zu erzeugen.

Bessere Zufallsgeneratoren verwenden den Jitter zwischen einem Hardware-Timer und dem Watchdog-Timer (der einen eigenen RC-Oszillator hat) um richtige Zufallszahlen zu erzeugen. Nachteil ist dass es sehr langsam ist, dann der Watchdog nur alle paar ms ein Bit liefern kann. Man kann das aber auch nur einmal machen, um dann diese Zufallszahl als Seed für die Arduino random() Funktion zu liefern. Das wird hier gezeigt:

Da gibt es aber auch eine vollständige Library dafür (mit der oben genannten Einschränkung):

Ein Analog-Eingang streut übrigens bei weitem nicht genug um da wirklich vernünftige Zahlen zu erzeugen. Da gibt es erschreckende Diagramme über die statische Verteilung.

Das im setup() zu machen ist relativ witzlos

Wenn ich millis() nehme, muß ich das nach dem Tastendruck machen!

Die anderen Links schaue ich mir auch mal an, aber für eine Auswahl an Musikstücken sollte es so reichen, werde ich aber nochmal testen.

Danke Euch! :slight_smile:

Für jene, die mitlesen:

const int eingangPin = 7;
int LiedNummer;
int anzahlLieder = 140;
boolean zustandEingang;

void setup() {
  Serial.begin(9600);
  pinMode(eingangPin, INPUT_PULLUP);
  zustandEingang = digitalRead(eingangPin);
}

void loop() {
  if (zustandEingang == HIGH && digitalRead(eingangPin) == LOW) {
    randomSeed(millis());
    LiedNummer = random(anzahlLieder + 1); // Zufallsnummer erzeugen zwischen 1 bis anzahlLieder
    Serial.print("LiedNummer = "); Serial.println(LiedNummer); // Debug-Ausgabe
  }
  zustandEingang = digitalRead(eingangPin);
  delay(10);
}

Wenn ich millis() nehme, muß ich das nach dem Tastendruck machen!

Ja.
Die Zeit seit dem einschalten des Arduinos und dem Tastendruck ist hineichend nicht wiederholbar daß eine gute Basis für randomSeed() ergibt.

Tastendruck
randomSeed(millis);
random(anzahlLieder + 1);

Grüße Uwe

Hallo Uwe,
danke für Deine Erklärung, aber ich hatte das Thema schon als gelöst gekennzeichnet, da die Musiktitel jetzt hinreichend zufällig ausgewählt werden. Bitte spare Deine Kraft, denn ich habe noch einen defekten UNO zu reparieren! :roll_eyes:

Zitat von Serenifly

Ein Analog-Eingang streut übrigens bei weitem nicht genug um da wirklich vernünftige Zahlen zu erzeugen. Da gibt es erschreckende Diagramme über die statische Verteilung.

Halbleiter rauschen gerne, was man normalerweise nicht haben will.
Wenn man hier eine Diode als Rauschgenerator nutzt und am Analog-Eingang anlegt könnte man das Optimieren ?

Keine Ahnung. Lässt sich vielleicht machen, aber ich habe das noch nicht getestet und auch keine Erfahrung damit.

Und dass ein offener CMOS Eingang einen willkürlichen Wert annimmt stimmt zwar und man ließt immer wieder mal dass Leute Unsinn erhalten wenn sie versuchen offene Eingänge zu messen, aber die Variation ist auf dem AVR wohl nicht so hoch wie man denkt. Das wird auch schaltungstechnische Gründe haben. Auf anderen Mikrocontrollern geht das wohl eher.

Wie gesagt, kann man sich durch den Watchdog Timer einen zufälligen Seed holen. Und das ganz ohne externe Hardware. Wenn man aber sowieso was mit Tastern macht, dann wird die Zeit des ersten Tastendrucks einfacher sein.

    LiedNummer = random(anzahlLieder + 1); // Zufallsnummer erzeugen zwischen 1 bis anzahlLieder

ist falsch weil dabei auch 0 herauskommen kann.

Ich würde einfach ausnutzen, daß die Aufrufzeit zufällig ist. Die Kombination aus Random Seed und Random tut das letztendlich auch genau so. D.h. entweder so

void loop() {
  
  if (zustandEingang == HIGH && digitalRead(eingangPin) == LOW) {
    LiedNummer =  (millis() % anzahlLieder) + 1;  // +1 weglassen falls von 0 anfangend gezählt werden soll
    Serial.print("LiedNummer = "); Serial.println(LiedNummer); // Debug-Ausgabe
  }
  zustandEingang = digitalRead(eingangPin);
  delay(10);
}

oder so

void loop() {
  random(anzahlLieder);  // Zufallszahlengenerator "frei" laufen lassen
  if (zustandEingang == HIGH && digitalRead(eingangPin) == LOW) {
    LiedNummer = random(anzahlLieder) + 1;  // +1 weglassen falls von 0 anfangend gezählt werden  
    Serial.print("LiedNummer = "); Serial.println(LiedNummer); // Debug-Ausgabe
  }
  zustandEingang = digitalRead(eingangPin);
  delay(10);
}

Stimmt :-[

    LiedNummer = random(anzahlLieder) + 1; // Zufallsnummer erzeugen zwischen 1 bis anzahlLieder

Da der Max-Wert "exclusive" ist, sollte es so gehen.

Danke für diesen und alle anderen Hinweise! Während ich das schreibe, höre ich zufällig ausgewählte Musik. :slight_smile:

Ich hoffe, der Beschenkte freut sich dann auch! Wenn man lange an einer Sache programmiert und gebastelt hat, mag man es nicht mehr gerne aus der Hand geben. Geht sicher nicht nur mir so.

agmue:
Bitte spare Deine Kraft, denn ich habe noch einen defekten UNO zu reparieren! :roll_eyes:

Lieber Uwe, neben der Kristallkugel scheinst Du auch telepathische Kräfte zu haben: Mein UNO hat sich selbst repariert! Danke auch dafür! 8)