PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Arduino Nano Programmierung


Filp
2016-06-18, 17:13:31
Ich habe mich in der letzten Zeit für ein Bastelprojekt (Blastergewehr) mit der Programmierung eines Arduino Nano auseinandersetzen müssen. Am Ende hab ich jetzt auch alles soweit laufen wie ich es mir vorgestellt habe, da ich allerdings mit Programmierung absolut gar nichts zu tun habe, ist es garantiert recht unsauber und könnte besser sein. Da besser immer gut ist und ich auch in Bereichen die nicht meins sind gerne dazu lerne, frag ich hier einfach mal, ob sich hier irgendwer das Ganze mal anschauen möchte und mir Tipps zur Optimierung geben kann.

Weiter hab ich noch ein Problem beim Starten des ganzen. Wenn der Strom abgeklemmt war, läuft der erste Start nicht synchron. Bei einem Reset gibt es dagegen keine Probleme, da läuft alles wie es soll...

Zum vereinfachten Aufbau:
Ich steuere mit dem Arduino Nano einmal ein MP3Modul (WT5001M02-28P) für den Sound und dann einige LEDs und 2 LED Bargraphs die durch 2 gekoppelte Treiber (TLC5940NT) gesteuert werden und für die Energieanzeige des Magazins bzw die Energieladung des Schusses zuständig sind.

Der Programmcode:
const int buttonPinFire = 2; // Feuertaster
const int buttonPinReload = 4; // Nachladentaster
const int buttonPinSingle = 5; // Einzelschuss Schalter
const int buttonPinBurst = 6; // Salve Schalter
const int motor = 7; // Pin zur Motorsteuerung
const int clockPin = 8;
const int fire = 15; // Pin zur Feuer LED
const int clockstart = 14; // Pin zum aktivieren Zähler 4017
const int magalert = 16; // Pin zur "kein Magazin" LED
const int magok = 17; // Pin zur "Magazin" LED

int buttonPushCounter = 0; // Zähler für Feuerschalter
int buttonStateFire = 0; // Status Feuertaster
int buttonStateSingle = 0; // Status Einzelschuss Schalter
int buttonStateBurst = 0; // Status Salve Schalter
int buttonStateAmmo = 0; // Status Nachladetaster

#include "Tlc5940.h" // TLC5940; belegt Pin 3, 9, 10, 11 und 13

void setup() {

pinMode(buttonPinFire, INPUT);
pinMode(buttonPinReload, INPUT);
pinMode(buttonPinSingle, INPUT);
pinMode(buttonPinBurst, INPUT);
pinMode(motor, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(fire, OUTPUT);
pinMode(clockstart, OUTPUT);
pinMode(magalert, OUTPUT);
pinMode(magok, OUTPUT);

Tlc.init(0); // Initialisiert den TLC5940 und setzt alle Kanäle auf aus

//Steuerung MP3 Modul
Serial.begin(9600);

Serial.write(0x7E); // Kein loop (ist eigentlich Standard, aber ich hatte anfangs Probleme mit Schleifen...)
Serial.write(0x03);
Serial.write(0xA9);
Serial.write(0x00);
Serial.write(0x7E);

Serial.write(0x7E); // Lautstärke max
Serial.write(0x03);
Serial.write(0xA7);
Serial.write(0x1F);
Serial.write(0x7E);

buttonStateAmmo = digitalRead(buttonPinReload);
if (buttonStateAmmo == HIGH) { // Abfrage ob Nachladetaster gedrückt ist
digitalWrite(magok, LOW); // Wenn Magazin entfernt dann aus
while (digitalRead(buttonPinReload) == HIGH) { // Während das Magazin entfernt ist warten und blinken
delay(250);
digitalWrite(magalert, HIGH);
delay(250);
digitalWrite(magalert, LOW);
}
}

ledBarStart (); // Startet Startfunktion für Munitionsanzeige (20er LED-Bargraph)
ledBar2Start (); // Startet Startfunktion für Energieanzeige (10er LED-Bargraph)
digitalWrite(magok, HIGH); // Kontrolllampe an
delay (1000);
}

void loop() {

// -------------------------------------------------- Feuern --------------------------------------------------

buttonStateFire = digitalRead(buttonPinFire); // Status Feuertaster lesen
buttonStateSingle = digitalRead(buttonPinSingle); // Status Schalter Einzelfeuer lesen
buttonStateBurst = digitalRead(buttonPinBurst); // Status Schalter Salve lesen

// -------------------------------------------------- Einzelfeuer --------------------------------------------------

if (buttonStateFire == HIGH && buttonStateSingle == HIGH) { // Abfrage ob Feuertaster gedrückt ist bei Schalter Einzelfeuer
buttonPushCounter++; // Zähler +1

if (buttonPushCounter <= 20) { // Abfrage ob Magazin noch geladen ist; Zähler <= 20

Serial.write(0x7E); // Starte Sound
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(0x02); // Track Nummer 2
Serial.write(0x7E);
delay(150);

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainSingle (); // Start entladen Funktion Energieanzeige
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

Tlc.set(20 - buttonPushCounter, 0); // Munitionsanzeige -1
Tlc.update(); // Änderung durchführen

digitalWrite(clockstart, HIGH); // Startet die "Kühlung" (Zähler 4017)
delay(150);
clock();
delay(150);
clock();
delay(150);
clock();
delay(150);
digitalWrite(clockstart, LOW);

delay(629);

if (buttonPushCounter < 20) { // Wenn noch Munition vorhanden ist...
ledBar2Start (); // ...dann Startfunktion Energieanzeige
}
}

if (buttonPushCounter > 20) { // Abfrage ob Magazin leer ist; Zähler > 20

Serial.write(0x7E); // Starte Sound
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(0x04); // Track Nummer 4
Serial.write(0x7E);

empty_ammo_led (); // Start Funktion Munition leer
delay(150);
}
}

// -------------------------------------------------- Salve --------------------------------------------------

if (buttonStateFire == HIGH && buttonStateBurst == HIGH) { // Abfrage ob Feuertaster gedrückt ist bei Schalter Salve
buttonPushCounter++; // Zähler +1

if (buttonPushCounter <= 20) { // Abfrage ob Magazin noch geladen ist; Zähler <= 20

Serial.write(0x7E); // Starte Sound
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(0x03); // Track Nummer 3
Serial.write(0x7E);
// delay(250);

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst1 (); // Start entladen Funktion 1 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus
delay(20);
digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst2 (); // Start entladen Funktion 2 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus
delay(20);
digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst3 (); // Start entladen Funktion 3 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

Tlc.set(20 - buttonPushCounter, 0); // Munitionsanzeige -1
Tlc.update(); // Änderung durchführen

digitalWrite(clockstart, HIGH); // Startet die "Kühlung" (Zähler 4017)
delay(150);
clock();
delay(150);
clock();
delay(150);
clock();
delay(150);
digitalWrite(clockstart, LOW);

if (buttonPushCounter < 20) { // Wenn noch Munition vorhanden ist...
ledBar2Start (); // ...dann Startfunktion Energieanzeige
}
}

if (buttonPushCounter > 20) { // Abfrage ob Magazin leer ist; Zähler > 20

Serial.write(0x7E); // Starte Sound
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(0x04); // Track Nummer 4
Serial.write(0x7E);

empty_ammo_led (); // Start Funktion Munition leer
delay(150);
}
}

// -------------------------------------------------- Nachladen --------------------------------------------------

buttonStateAmmo = digitalRead(buttonPinReload);
if (buttonStateAmmo == HIGH) { // Abfrage ob Nachladetaster gedrückt ist
buttonPushCounter = 0; // Zähler wieder auf 0 setzen
Tlc.setAll(0); // Munitionsanzeige und Energieanzeige aus, da Magazin entfernt
Tlc.update();
digitalWrite(magok, LOW); // Kontrolllampe aus
while (digitalRead(buttonPinReload) == HIGH) { // Während das Magazin entfernt ist warten und blinken
delay(250);
digitalWrite(magalert, HIGH);
delay(250);
digitalWrite(magalert, LOW);
}
ledBarStart (); // Magazin wieder drin Startfunktion für Munitionsanzeige
ledBar2Start (); // Startfunktion für Energieanzeige
digitalWrite(magok, HIGH); // Kontrolllampe an
}

}

// -------------------------------------------------- Funktionen --------------------------------------------------

void ledBarStart () { // Start- und Nachladsequenz

Serial.write(0x7E);
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(0x01); // Track Nummer 1
Serial.write(0x7E);

delay(250);

for (int i = 0; i < 20; i++) { // LEDs der Munitionsanzeige (1-20) nacheinander an
Tlc.set(i, 4095);
Tlc.update();
delay(120);
}
}

void ledBar2Start () { // Start- und Nachladsequenz

for (int i = 0; i < 10; i++) { // LEDs der Energieanzeige (21-30) schnell nacheinander an
Tlc.set(20 + i, 4095);
Tlc.update();
delay(15);
}
}

void drainSingle () { // Energieentladung bei Einzelschuss

for (int i = 0; i < 10; i++) { // LEDs der Energieanzeige (21-30) schnell nacheinander aus
Tlc.set(29 - i, 0);
Tlc.update();
delay(15);
}
}

void drainBurst1 () { // Energieentladung bei Salve

for (int i = 0; i < 4; i++) { // LEDs der Energieanzeige (28-30) aus
Tlc.set(29 - i, 0);
}
Tlc.update();
}

void drainBurst2 () { // Energieentladung bei Salve
for (int i = 0; i < 3; i++) { // LEDs der Energieanzeige (25-27) aus
Tlc.set(25 - i, 0);
}
Tlc.update();
}

void drainBurst3 () { // Energieentladung bei Salve
for (int i = 0; i < 3; i++) { // LEDs der Energieanzeige (21-24) aus
Tlc.set(22 - i, 0);
}
Tlc.update();
}

void empty_ammo_led () { // Blinklicht, wenn Magazin leer und gefeuert wird

for (int s = 0; s < 2; s++) { // 4 mal

delay(300);

for (int i = 0; i < 20; i++) { // LEDs der Munitionsanzeige (1-20) an
Tlc.set(i, 100);
}
Tlc.update();
delay(300);

for (int i = 0; i < 20; i++) { // LEDs der Munitionsanzeige (1-20) aus
Tlc.set(i, 0);
}
Tlc.update();
}
}

void clock() {
digitalWrite(clockPin, HIGH);
delay(1);
digitalWrite(clockPin, LOW);
}

#44
2016-06-18, 17:25:17
Ich habe nur gaaaanz kurz drüber geschaut.

Du könntest aus der Sound-Ausgabe eine eigene Methode machen - diese Befehlsfolge verwendest du schließlich mehrfach. Tracknummer ist da natürlich ein Parameter.

Die drei DrainBurst-Methoden könnte man auch zu einer zusammenführen und durch Parameter bestimmen, welcher Bereich nun geschalten werden soll.
Die Kommentare von DB1 u. 3 stimmen außerdem nicht so recht mit dem Code überein...

Filp
2016-06-18, 22:17:44
Ich habe nur gaaaanz kurz drüber geschaut.

Du könntest aus der Sound-Ausgabe eine eigene Methode machen - diese Befehlsfolge verwendest du schließlich mehrfach. Tracknummer ist da natürlich ein Parameter.

Die drei DrainBurst-Methoden könnte man auch zu einer zusammenführen und durch Parameter bestimmen, welcher Bereich nun geschalten werden soll.
Die Kommentare von DB1 u. 3 stimmen außerdem nicht so recht mit dem Code überein...
Da bei den Sounds jedes mal ein anderer Track kommt, wüßte ich nicht wie ich das machen muss.
Beim DrainBurst sind es erst einmal 4 LEDs und dann 2 mal 3 die ausgehen, auch hier wäre ich überfordert.
Die Kommentare hatte ich wohl nach er Änderung vergessen anzupassen, war mir nicht aufgefallen. Ansonsten hab ich halt auch 1-30 gezählt, während die Technik von 0-29 zählt :)

#44
2016-06-19, 08:20:01
Mit Variablen.
Die nutzt du ja schon an vielen Stellen und einige Methoden hast du auch schon deklariert (aufgerufen werden sie ja auch ständig).

In empty_ammo_leds kannst du mit den an/aus Blöcken ebenfalls vorgehen. Was identisch ist bleibt gleich, was sich ändert wird zu einer Variablen bzw. einem Parameter.

void playSound (byte trackNumber) {
Serial.write(0x7E);
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(trackNumber);
Serial.write(0x7E);
}

Die vier Stellen, an denen du mit dieser Befehlsfolge Sound ausgibst, kannst du dann durch einen einzeiligen Aufruf ersetzen.

Filp
2016-06-19, 15:07:33
Mit Variablen.
Die nutzt du ja schon an vielen Stellen und einige Methoden hast du auch schon deklariert (aufgerufen werden sie ja auch ständig).

In empty_ammo_leds kannst du mit den an/aus Blöcken ebenfalls vorgehen. Was identisch ist bleibt gleich, was sich ändert wird zu einer Variablen bzw. einem Parameter.

void playSound (byte trackNumber) {
Serial.write(0x7E);
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(trackNumber);
Serial.write(0x7E);
}

Die vier Stellen, an denen du mit dieser Befehlsfolge Sound ausgibst, kannst du dann durch einen einzeiligen Aufruf ersetzen.

Soweit ich weiß, kann ich aber keine Variablen in die Methoden übergeben.

Ectoplasma
2016-06-19, 15:15:59
Soweit ich weiß, kann ich aber keine Variablen in die Methoden übergeben.

Türlich kannst du das.

Monger
2016-06-19, 16:15:02
Was mir an dem Code nicht klar ist: wo genau ist eigentlich der Anfang? Ich tippe mal dass der Treiber irgendwelche Methoden aufruft (loop? Setup?). Das wäre fürs Gesamtverständnis schon wichtig, welche Methoden quasi fix definiert sein müssen, und welche von dir frei definiert sind.

Ansonsten gebe ich erstmal nur einen Tipp: wenn nichts bereits geschehen, hol dir eine kleine lokale kostenlose Quellverwaltung. Git z.B. mit TortoiseGit unter Windows hat zwar eine enorm steile Lernkurve, aber keiner der programmiert (oder sonst irgendjemand der Klartext Dateien hat) sollte ohne Quellverwaltung arbeiten müssen. Das nimmt einem die Angst davor irgendwas anzufassen, weil es kaputt gehen könnte. Du lernst so viel mehr und so viel schneller, sobald du gefahrlos was kaputt machen kannst.

#44
2016-06-19, 16:25:29
Diese beiden Methoden sind Teil des Arduino-Frameworks. Zuerst wird einmalig setup() aufgerufen, dann loop() bis in alle Ewigkeit.

Ectoplasma
2016-06-20, 07:18:54
Wer übrigens mal mit einem Elektronik - Emulator, in dem auch ein Arduino emuliert werden kann, rumspielen möchte, dem empfehle ich die Seite 123d.circuits.io. (https://123d.circuits.io/circuits/2258116-star-trek-clock)

Monger
2016-06-20, 09:07:23
Mal ein Tipp zum Anfangen:
Jedes mal wenn du das Bedürfnis hast etwas zu kommentieren, frage dich warum. Quellcodekommentare sitzen oft genug genau an den Stellen die man verbessern sollte.

In deinem Fall: du hast in der Loop mehrere Zwischenüberschriften reingezogen, um die Lesbarkeit in der sehr langen Methode zu erhöhen. Jede Zwischenüberschrift beschreibt einen recht gut abgrenzbaren Schritt im Ablauf, ergo kannst du jeden davon als Funktion rausziehen.

Die Loop sähe dann anschließend ungefähr so aus:


void loop() {

// -------------------------------------------------- Feuern --------------------------------------------------

buttonStateFire = digitalRead(buttonPinFire); // Status Feuertaster lesen
buttonStateSingle = digitalRead(buttonPinSingle); // Status Schalter Einzelfeuer lesen
buttonStateBurst = digitalRead(buttonPinBurst); // Status Schalter Salve lesen


if (buttonStateFire == HIGH && buttonStateSingle == HIGH) {
einzelfeuer();
}

if (buttonStateFire == HIGH && buttonStateBurst == HIGH) {
salve();
}
buttonStateAmmo = digitalRead(buttonPinReload);
if (buttonStateAmmo == HIGH) {
nachladen();
}
}

Ähnliches gilt für Variablennamen. Wenn ich sowas LEDBarStart und LEDBarStart2 sehe... was unterscheidet die beiden? Nimm doch das was im Kommentar steht:

startAmmoLED();
startEnergyLED();

Genauso bei den Bursts:

drainBurstLow();
drainBurstMedium();
drainBurstHigh();

Das sind Kleinigkeiten, aber die vielen Kleinigkeiten summieren sich auf zu schwer lesbarem Code.

#44
2016-06-20, 09:29:10
Und mit der gewonnenen Lesbarkeit erkennt man dann, dass eine einzige Betätigung des Abzugs sowohl einen Einzelschuss als auch einen Burst nacheinander auslösen kann. Es können beide if-Blöcke direkt hintereinander ausgeführt werden, sofern die Schalter für Burst/Single sich nicht mechanisch oder elektrisch gegenseitig sperren.

Statt zwei it-Blöcken if/elseif zu benutzen würde das verhindern. Falls gewünscht.

Die Arduino-Playground Seite sagt ausserdem:
Usage: Tlc.init(); Tlc.clear(); sets all the grayscale values to zero, but does not send them to the TLCs. To actually send the data, call Tlc.update() (http://playground.arduino.cc/Learning/TLC5940)
Vielleicht ist das ja dein Kaltstart-Problem. (So recht habe ich aber noch nicht verstanden, was da passiert bzw. passieren sollte.)

Filp
2016-06-20, 16:03:42
Zu der Übergabe von Variablen hab ich mal geschaut wie das geht. Im Falle der MP3 Tracks müsste ich das dann mit nem String machen?

Ansonsten gebe ich erstmal nur einen Tipp: wenn nichts bereits geschehen, hol dir eine kleine lokale kostenlose Quellverwaltung. Git z.B. mit TortoiseGit unter Windows hat zwar eine enorm steile Lernkurve, aber keiner der programmiert (oder sonst irgendjemand der Klartext Dateien hat) sollte ohne Quellverwaltung arbeiten müssen. Das nimmt einem die Angst davor irgendwas anzufassen, weil es kaputt gehen könnte. Du lernst so viel mehr und so viel schneller, sobald du gefahrlos was kaputt machen kannst.
Kaputt machen geht da nicht viel, entweder ist ein Pin auf HIGH oder LOW, und da ich im Grunde nur LEDs mit entsprechenen Vorwiderständen dran hab, sind die entweder an oder aus.

Mal ein Tipp zum Anfangen:
Jedes mal wenn du das Bedürfnis hast etwas zu kommentieren, frage dich warum. Quellcodekommentare sitzen oft genug genau an den Stellen die man verbessern sollte.
Die Kommentare hatte ich so viele drin, um Bekannten die auch absolut gar nichts mit dem zu tun haben etwas zu erklären, was ich da gemacht habe.

In deinem Fall: du hast in der Loop mehrere Zwischenüberschriften reingezogen, um die Lesbarkeit in der sehr langen Methode zu erhöhen. Jede Zwischenüberschrift beschreibt einen recht gut abgrenzbaren Schritt im Ablauf, ergo kannst du jeden davon als Funktion rausziehen.

Die Loop sähe dann anschließend ungefähr so aus:


void loop() {

// -------------------------------------------------- Feuern --------------------------------------------------

buttonStateFire = digitalRead(buttonPinFire); // Status Feuertaster lesen
buttonStateSingle = digitalRead(buttonPinSingle); // Status Schalter Einzelfeuer lesen
buttonStateBurst = digitalRead(buttonPinBurst); // Status Schalter Salve lesen


if (buttonStateFire == HIGH && buttonStateSingle == HIGH) {
einzelfeuer();
}

if (buttonStateFire == HIGH && buttonStateBurst == HIGH) {
salve();
}
buttonStateAmmo = digitalRead(buttonPinReload);
if (buttonStateAmmo == HIGH) {
nachladen();
}
}

Ähnliches gilt für Variablennamen. Wenn ich sowas LEDBarStart und LEDBarStart2 sehe... was unterscheidet die beiden? Nimm doch das was im Kommentar steht:

startAmmoLED();
startEnergyLED();

Genauso bei den Bursts:

drainBurstLow();
drainBurstMedium();
drainBurstHigh();

Das sind Kleinigkeiten, aber die vielen Kleinigkeiten summieren sich auf zu schwer lesbarem Code.
Ich hatte ganz am Anfang mal alles nur in der Loop und hab dann mal gesehen, dass es möglich ist, sowas auszugliedern. Da hab ich dann halt angefangen es zu zerstückeln so gut ich das konnte. So radikal geht es natürlich auch und sorgt für deutlich mehr Übersicht im eigentlichen Ablauf. Danke.

Und mit der gewonnenen Lesbarkeit erkennt man dann, dass eine einzige Betätigung des Abzugs sowohl einen Einzelschuss als auch einen Burst nacheinander auslösen kann. Es können beide if-Blöcke direkt hintereinander ausgeführt werden, sofern die Schalter für Burst/Single sich nicht mechanisch oder elektrisch gegenseitig sperren.

Statt zwei it-Blöcken if/elseif zu benutzen würde das verhindern. Falls gewünscht.
Da hab ich mir nie Gedanken drum geamacht, weil durch einen EIN/AUS/EIN Kippschalter gewechselt wird, Mechanisch kann es also nicht passieren. Ich hatte es anfangs mal so, dass erst einmal abgefragt wurde ob es Single oder Burst ist, da hatte ich dann aber glaub ich Probleme mit dem Zähler oder so, genau weiß ich es nicht mehr aber irgendwas lief damals noch nicht. Ich hab halt auch ne ganze Menge immer wieder verändert und probiert. Anfangs hatte ich 4 verschiedene Programme um einzelne Abläufe zu testen und die hatte ich dann nach und nach miteinander kombiniert bis alles lief.


Die Arduino-Playground Seite sagt ausserdem:

Vielleicht ist das ja dein Kaltstart-Problem. (So recht habe ich aber noch nicht verstanden, was da passiert bzw. passieren sollte.)
Das Startproblem ist komisch und das erste wo wirklich was passiert, ist ja wenn im Setup die Methode ledBarStart aufgerufen wird und da hängt der Sound dann hinterher. Hatte da auch schon gedacht, dass das MP3Modul einfach länger braucht am Anfang, aber auch durch ein delay bevor Sound und LEDs gestartet werden ändert sich da nichts. Die Verzögerung ist auch nicht immer gleich und ganz selten hab ich auch einfach nur ne Soundschleife die den Startsound durchgehend wiederholt. Durch den Druck auf Reset ist das wieder weg und alles startet ganz normal. Hab auch 2 verschiedene Nanos und 3 MP3Module zum probieren gehabt und es ist immer gleich...

Ich hab auch nochmal ne frage zu einem Stück aus nem Code an dem ich viel gelernt habe. Der setzt nicht einfach nur den Zähler (buttonPushCounter2) einen hoch, sondern fragt vorher nochmal den letzten Status des Feuerknopfes ab, wobei mir der Nutzen nicht schlüssig ist:
if (buttonState2 == HIGH) { // control that input is HIGH (button pressed)

if (buttonState2 != lastButtonState2) { // compare the buttonState to its previous state

if (buttonState2 == HIGH) { // if the state has changed, increment the counter
// if the current state is HIGH then the button
// wend from off to on:
buttonPushCounter2++;
}

// if the current state is LOW then the button
// wend from on to off:
}
Am Ende des Durchlaufs setzt er immer den lastButtonState2 = buttonState2

#44
2016-06-20, 17:35:16
Du kannst direkt den Code aus meinem Spoiler oben übernehmen, der nutzt Bytes als Parameter:
void playSound (byte trackNumber) {
Serial.write(0x7E);
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(trackNumber);
Serial.write(0x7E);
}

Ein Aufruf sähe dann so aus:

playSound(0x02);
Damit würde dann Track2 abgespielt werden. So ersetzt du dann diese komplette Aufruffolge durch einen einzeiligen Methodenaufruf. Das vermeidet den 4x gleichen Code, den du im Moment hast.


Das Code-Schnipsel sorgt dafür, dass ein Halten des Abzugs nicht zur Inkrementierung des Zählers führt.
War HIGH, ist wieder HIGH -> Nix passiert.
War LOW, ist jetzt HIGH -> hochzählen.

Damit gibt es dann (vermutlich) kein Dauerfeuer.

---

Zum Kaputt machen: Da meint Monger den Quellcode an sich. Radikale umbauten lassen sich natürlich immer besser angehen, wenn man ein Backup in der Hinterhand hat. Eine Quellcodeverwaltung macht genau das.
Für ein einzelnes, einfaches Arduino-Projekt würde ich aber kein GIT empfehlen, das ist schon ein massiver Overhead mit straffer Lernkurve.

Da sowas idr. kaum über 2-3 Dateien hinauswächst, tun es Dateikopien auch noch.

Filp
2016-06-20, 18:01:02
Du kannst direkt den Code aus meinem Spoiler oben übernehmen, der nutzt Bytes als Parameter:
void playSound (byte trackNumber) {
Serial.write(0x7E);
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(trackNumber);
Serial.write(0x7E);
}

Ein Aufruf sähe dann so aus:

playSound(0x02);
Damit würde dann Track2 abgespielt werden. So ersetzt du dann diese komplette Aufruffolge durch einen einzeiligen Methodenaufruf. Das vermeidet den 4x gleichen Code, den du im Moment hast.
Ah ok, hatte nicht gesehen, dass oben schon die Übergabe mit drin ist. Danke.


Das Code-Schnipsel sorgt dafür, dass ein Halten des Abzugs nicht zur Inkrementierung des Zählers führt.
War HIGH, ist wieder HIGH -> Nix passiert.
War LOW, ist jetzt HIGH -> hochzählen.

Damit gibt es dann (vermutlich) kein Dauerfeuer.
Ok, ich hab vermutet, dass es irgendwas zur Sicherheit ist, konnte es mir nur nicht erklären. Bei mir hab ich schon viel rumprobiert und auch wenn ich halte läuft der Zähler ohne Fehler sauber runter und es läuft alles korrekt nacheinander durch.

Zum Kaputt machen: Da meint Monger den Quellcode an sich. Radikale umbauten lassen sich natürlich immer besser angehen, wenn man ein Backup in der Hinterhand hat. Eine Quellcodeverwaltung macht genau das.
Für ein einzelnes, einfaches Arduino-Projekt würde ich aber kein GIT empfehlen, das ist schon ein massiver Overhead mit straffer Lernkurve.

Da sowas idr. kaum über 2-3 Dateien hinauswächst, tun es Dateikopien auch noch.
Bisher geht es noch, aber ich mache mir auch bei jeder Veränderung eine Sicherung, damit nichts passiert. Viel größer wird es auch nicht werden. Auch wenn ich für später schon was neues an anderer Stelle plane, bleibt es doch meist bei einfachen Dingen, ist mir auch so schon komplex genug ;)

Monger
2016-06-20, 18:13:25
Wenn du ein paar Vorschläge aus dem Thread hier umgesetzt hast, poste doch den kompletten Code nochmal hier rein. Dann können wir gucken was wir noch alles besser machen können.

Filp
2016-06-20, 18:26:51
Wenn du ein paar Vorschläge aus dem Thread hier umgesetzt hast, poste doch den kompletten Code nochmal hier rein. Dann können wir gucken was wir noch alles besser machen können.
Werde ich wohl erst am Mittwoch die Zeit für haben, aber dann mach ich das.

/edit: Ist es eigentlich Sinnvoll wenn man in Methoden weitere Methoden aufruft? das macht es doch eigentlich auch wieder unübersichtlicher.

Filp
2016-06-21, 19:50:53
So hatte etwas Zeit, weil meine Kids auswärts das Spiel sehen ;)
Hab auch noch die "Kühlung", die ja auch 2 mal vorkommt, als eigene Methode angehängt und soweit erstmal alles umgesetzt (Bis auf die Kommentare). Funktioniert auch alles weiter wie gehabt :)


const int buttonPinFire = 2; // Feuertaster
const int buttonPinReload = 4; // Nachladentaster
const int buttonPinSingle = 5; // Einzelschuss Schalter
const int buttonPinBurst = 6; // Salve Schalter
const int motor = 7; // Pin zur Motorsteuerung
const int clockPin = 8;
const int fire = 15; // Pin zur Feuer LED
const int clockstart = 14; // Pin zum aktivieren Zähler 4017
const int magalert = 16; // Pin zur "kein Magazin" LED
const int magok = 17; // Pin zur "Magazin" LED

int buttonPushCounter = 0; // Zähler für Feuerschalter
int buttonStateFire = 0; // Status Feuertaster
int buttonStateSingle = 0; // Status Einzelschuss Schalter
int buttonStateBurst = 0; // Status Salve Schalter
int buttonStateAmmo = 0; // Status Nachladetaster

#include "Tlc5940.h" // TLC5940; belegt Pin 3, 9, 10, 11 und 13

void setup() {

pinMode(buttonPinFire, INPUT);
pinMode(buttonPinReload, INPUT);
pinMode(buttonPinSingle, INPUT);
pinMode(buttonPinBurst, INPUT);
pinMode(motor, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(fire, OUTPUT);
pinMode(clockstart, OUTPUT);
pinMode(magalert, OUTPUT);
pinMode(magok, OUTPUT);

Tlc.init(0); // Initialisiert den TLC5940 und setzt alle Kanäle auf aus

//Steuerung MP3 Modul
Serial.begin(9600);

Serial.write(0x7E); // Kein loop
Serial.write(0x03);
Serial.write(0xA9);
Serial.write(0x00);
Serial.write(0x7E);

Serial.write(0x7E); // Lautstärke max
Serial.write(0x03);
Serial.write(0xA7);
Serial.write(0x1F);
Serial.write(0x7E);

buttonStateAmmo = digitalRead(buttonPinReload);
if (buttonStateAmmo == HIGH) { // Abfrage ob Nachladetaster gedrückt ist
digitalWrite(magok, LOW); // Wenn Magazin entfernt dann aus
while (digitalRead(buttonPinReload) == HIGH) { // Während das Magazin entfernt ist warten und blinken
delay(250);
digitalWrite(magalert, HIGH);
delay(250);
digitalWrite(magalert, LOW);
}
}

startAmmoLED (); // Startet Startfunktion für Munitionsanzeige (20er LED-Bargraph)
startEnergyLED (); // Startet Startfunktion für Energieanzeige (10er LED-Bargraph)
digitalWrite(magok, HIGH); // Kontrolllampe an
delay (1000);
}

void loop() {

// -------------------------------------------------- Feuern --------------------------------------------------

buttonStateFire = digitalRead(buttonPinFire); // Status Feuertaster lesen
buttonStateSingle = digitalRead(buttonPinSingle); // Status Schalter Einzelfeuer lesen
buttonStateBurst = digitalRead(buttonPinBurst); // Status Schalter Salve lesen

// -------------------------------------------------- Einzelfeuer --------------------------------------------------

if (buttonStateFire == HIGH && buttonStateSingle == HIGH) { // Abfrage ob Feuertaster gedrückt ist bei Schalter Einzelfeuer
einzelfeuer();
}

// -------------------------------------------------- Salve --------------------------------------------------

if (buttonStateFire == HIGH && buttonStateBurst == HIGH) { // Abfrage ob Feuertaster gedrückt ist bei Schalter Salve
salve();
}

// -------------------------------------------------- Nachladen --------------------------------------------------

buttonStateAmmo = digitalRead(buttonPinReload);
if (buttonStateAmmo == HIGH) { // Abfrage ob Nachladetaster gedrückt ist
nachladen();
}
}

// -------------------------------------------------- Funktionen --------------------------------------------------

void einzelfeuer() {
buttonPushCounter++; // Zähler +1

if (buttonPushCounter <= 20) { // Abfrage ob Magazin noch geladen ist; Zähler <= 20

playSound(0x02);
delay(150);

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainSingle (); // Start entladen Funktion Energieanzeige
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

Tlc.set(20 - buttonPushCounter, 0); // Munitionsanzeige -1
Tlc.update(); // Änderung durchführen

cooldown();

delay(629);

if (buttonPushCounter < 20) { // Wenn noch Munition vorhanden ist...
startEnergyLED (); // ...dann Startfunktion Energieanzeige
}
}

if (buttonPushCounter > 20) { // Abfrage ob Magazin leer ist; Zähler > 20

playSound(0x04);

empty_ammo_led (); // Start Funktion Munition leer
delay(150);
}
}

void salve() {
buttonPushCounter++; // Zähler +1

if (buttonPushCounter <= 20) { // Abfrage ob Magazin noch geladen ist; Zähler <= 20

playSound(0x03);

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst (29,4); // Start entladen Funktion 1 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus
delay(20);
digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst (25,3); // Start entladen Funktion 2 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus
delay(20);
digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst (22,3); // Start entladen Funktion 3 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

Tlc.set(20 - buttonPushCounter, 0); // Munitionsanzeige -1
Tlc.update(); // Änderung durchführen

cooldown();

if (buttonPushCounter < 20) { // Wenn noch Munition vorhanden ist...
startEnergyLED (); // ...dann Startfunktion Energieanzeige
}
}

if (buttonPushCounter > 20) { // Abfrage ob Magazin leer ist; Zähler > 20

playSound(0x04);
empty_ammo_led (); // Start Funktion Munition leer
delay(150);
}
}

void nachladen() {
buttonPushCounter = 0; // Zähler wieder auf 0 setzen
Tlc.setAll(0); // Munitionsanzeige und Energieanzeige aus, da Magazin entfernt
Tlc.update();
digitalWrite(magok, LOW); // Kontrolllampe aus
while (digitalRead(buttonPinReload) == HIGH) { // Während das Magazin entfernt ist warten und blinken
delay(250);
digitalWrite(magalert, HIGH);
delay(250);
digitalWrite(magalert, LOW);
}
startAmmoLED (); // Magazin wieder drin Startfunktion für Munitionsanzeige
startEnergyLED (); // Startfunktion für Energieanzeige
digitalWrite(magok, HIGH); // Kontrolllampe an
}

void startAmmoLED () { // Start- und Nachladsequenz
playSound(0x01);
delay(250);
for (int i = 0; i < 20; i++) { // LEDs der Munitionsanzeige (1-20) nacheinander an
Tlc.set(i, 4095);
Tlc.update();
delay(120);
}
}

void startEnergyLED () { // Start- und Nachladsequenz
for (int i = 0; i < 10; i++) { // LEDs der Energieanzeige (21-30) schnell nacheinander an
Tlc.set(20 + i, 4095);
Tlc.update();
delay(15);
}
}

void drainSingle () { // Energieentladung bei Einzelschuss
for (int i = 0; i < 10; i++) { // LEDs der Energieanzeige (21-30) schnell nacheinander aus
Tlc.set(29 - i, 0);
Tlc.update();
delay(15);
}
}

void drainBurst (int firstLED, int LEDCount) { // Energieentladung bei Salve
for (int i = 0; i < LEDCount; i++) { // LEDs der Energieanzeige aus
Tlc.set(firstLED - i, 0);
}
Tlc.update();
}


void empty_ammo_led () { // Blinklicht, wenn Magazin leer und gefeuert wird
for (int s = 0; s < 2; s++) { // 2 mal
delay(300);
for (int i = 0; i < 20; i++) { // LEDs der Munitionsanzeige (1-20) an
Tlc.set(i, 100);
}
Tlc.update();
delay(300);
for (int i = 0; i < 20; i++) { // LEDs der Munitionsanzeige (1-20) aus
Tlc.set(i, 0);
}
Tlc.update();
}
}

void clock() {
digitalWrite(clockPin, HIGH);
delay(1);
digitalWrite(clockPin, LOW);
}

void playSound (byte trackNumber) {
Serial.write(0x7E);
Serial.write(0x04);
Serial.write(0xA0);
Serial.write((byte)0x00);
Serial.write(trackNumber);
Serial.write(0x7E);
}

void cooldown () {
digitalWrite(clockstart, HIGH); // Startet die "Kühlung" (Zähler 4017)
delay(150);
clock();
delay(150);
clock();
delay(150);
clock();
delay(150);
digitalWrite(clockstart, LOW);
}

Mosher
2016-06-21, 20:36:27
Für Arduino-Programmierung kann ich Codebender.cc (https://codebender.cc/home) empfehlen.

Hat bei mir aus 33kB kompilierten Code 29kB gemacht und damit dafür gesorgt, dass ich ihn auf den Nano bringe :D

Du kannst dort auch eigene Bibliotheken erstellen und außerdem deinen Nano direkt über die Weboberfläche flashen.

Monger
2016-06-22, 08:05:12
/edit: Ist es eigentlich Sinnvoll wenn man in Methoden weitere Methoden aufruft? das macht es doch eigentlich auch wieder unübersichtlicher.
Jede Software die du startest, beginnt irgendwo mit einem einzigen Methodenaufruf, und fächert sich dann in weitere auf. Auch wenn man Windows hochfährt, steht irgendwo am Anfang sinngemäß "WindowsStart()".

Das ist auch so lange kein Problem, so lange nicht jede Methode an jede andere gekoppelt ist. Sowas regelt man, indem man die Sichtbarkeit von Methoden reduziert... das ist in C ein bisschen hakeliger, aber auch da kann man n bissl Struktur reinbringen.

#44
2016-06-22, 08:42:49
Im allgemeinen sollten deshalb auf einer Ebene stehende Methoden auch die selbe Granularität haben.

Für den Arduino könnte das so aussehen:
Auf der abstraktesten Ebene ruft man dann eben keine Methoden auf, die mit der Hardware interagieren, sondern welche, die das auf einer Zwischenebene strukturieren. Diese nutzen dann wiederum lowlevel Helfer-Methoden, die letztlich die Hardware ansprechen.

loop() wäre hier highlevel, einzelfeuer()/salve() das midlevel und cooldown()/playSound() lowlevel.

Solche Regeln sind dabei aber nur eine mentale Stütze. Wenn es gute Gründe gibt, kann und sollte man sie brechen. Nachdem man abgewägt hat, ob es vlt. nicht doch auf andere Art machbar ist.
Diese Regel heißt also nicht, das Aufrufhirarchien immer gleich tief sein müssen.

Filp
2016-06-22, 09:47:50
Im allgemeinen sollten deshalb auf einer Ebene stehende Methoden auch die selbe Granularität haben.

Für den Arduino könnte das so aussehen:
Auf der abstraktesten Ebene ruft man dann eben keine Methoden auf, die mit der Hardware interagieren, sondern welche, die das auf einer Zwischenebene strukturieren. Diese nutzen dann wiederum lowlevel Helfer-Methoden, die letztlich die Hardware ansprechen.

loop() wäre hier highlevel, einzelfeuer()/salve() das midlevel und cooldown()/playSound() lowlevel.

Solche Regeln sind dabei aber nur eine mentale Stütze. Wenn es gute Gründe gibt, kann und sollte man sie brechen. Nachdem man abgewägt hat, ob es vlt. nicht doch auf andere Art machbar ist.
Diese Regel heißt also nicht, das Aufrufhirarchien immer gleich tief sein müssen.

Also theoretisch würde ich auch die LED an/aus Zeilen in einer weiteren Methode ausgliedern und bei einzelfeuer() und salve() auch nur abrufen.

#44
2016-06-22, 10:13:50
Natürlich könnte man eine Methode zum An-/Abschalten benutzen. Aber der Nutzen, zwei Aufrufe durch einen zu ersetzen ist jetzt auch nicht soo groß. Der Gewinn in der Verständlichkeit ist quasi nicht vorhanden. Wirklich wartbarer wird es dadurch auch nicht - wenn sich Einzelfeuer und Salve an der Stelle in Zukunft doch einmal unterschiedlich verhalten sollen, ist die Kopplung, die daraus entsteht hinderlich. Interessanter wäre es, die kompletten Teilblöcke auszulagern.

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainSingle (); // Start entladen Funktion Energieanzeige
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

---

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst (29,4); // Start entladen Funktion 1 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

Leider unterscheidet es sich durch zwei Aufrufe, so dass das nicht einfach geht.
Man könnten aber den Teil für die Salve auslagern, da der 3x genutzt wird.

€: Mit etwas Anpassung könntest du nachladen() noch am Ende von setup() benutzen.

Filp
2016-06-22, 10:27:15
Natürlich könnte man eine Methode zum An-/Abschalten benutzen. Aber der Nutzen, zwei Aufrufe durch einen zu ersetzen ist jetzt auch nicht soo groß. Der Gewinn in der Verständlichkeit ist quasi nicht vorhanden. Wirklich wartbarer wird es dadurch auch nicht - wenn sich Einzelfeuer und Salve an der Stelle in Zukunft doch einmal unterschiedlich verhalten sollen, ist die Kopplung, die daraus entsteht hinderlich. Interessanter wäre es, die kompletten Teilblöcke auszulagern.

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainSingle (); // Start entladen Funktion Energieanzeige
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

---

digitalWrite(fire, HIGH); // Feuer LED an
digitalWrite(motor, HIGH); // Motor an
drainBurst (29,4); // Start entladen Funktion 1 Energieanzeige
delay(125);
digitalWrite(fire, LOW); // Feuer LED aus
digitalWrite(motor, LOW); // Motor aus

Leider unterscheidet es sich durch zwei Aufrufe, so dass das nicht einfach geht.
Man könnten aber den Teil für die Salve auslagern, da der 3x genutzt wird.
Deswegen ja auch theoretisch, der Nutzen wäre halt nicht groß.
Bei der Salve müsste ich ja dann die Variablen über 2 Methoden weiter reichen, wie mache ich das korrekt?


void salve() {
...
feuer(29,4);
feuer(25,3);
feuer(22,3);
...
}
void feuer (int firstLED, int LEDCount) {

digitalWrite(fire, HIGH);
digitalWrite(motor, HIGH);
drainBurst (firstLED,LEDCount);
delay(125);
digitalWrite(fire, LOW);
digitalWrite(motor, LOW);
}

void drainBurst (int firstLED, int LEDCount) {
for (int i = 0; i < LEDCount; i++) {
Tlc.set(firstLED - i, 0);
}
Tlc.update();
}
void

Würde das so funktionieren? Dürften die Variablen gleich benannt werden?

#44
2016-06-22, 10:31:39
Variablen können im selben Kontext (Fachbegriff: scope) nicht gleich benannt sein - wie soll man die dann auch unterscheiden?
Zwei Mal firstLED in feuer() geht also nicht. firstLED und LEDCount in feuer() und in drainBurst() ist hingegen keine Problem - das ist jeweils ein eigener Kontext.

Wobei feuer() ein zu allgemeiner Name für eine relativ spezielle (Unter-)Funktion ist.

Filp
2016-06-22, 10:36:40
Variablen können im selben Kontext (Fachbegriff: scope) nicht gleich benannt sein - wie soll man die dann auch unterscheiden?
Zwei Mal firstLED in feuer() geht also nicht. firstLED und LEDCount in feuer() und in drainBurst() ist hingegen keine Problem - das ist jeweils ein eigener Kontext.

Wobei feuer() ein zu allgemeiner Name für eine relativ spezielle (Unter-)Funktion ist.
War nen Tippfehler, sollte natürlich auch jeweils LEDCount sein, war etwas schnell beim tippen... Sollte auch nur schnell mal ein Beispiel sein, um zu klären ob ich da richtig liege mit der Umsetzung.