Ausgelassene Befehle in Animatronik Projekt

Hallo Kollegen,

ich arbeite an meinem zweiten Arduino Projekt, einem Animatronik-Totenkopf, der zu Halloween den Ghostbusters Song "singen" soll.

Ich bin Anfänger bei der Programmierung und Fortgeschrittener bei der elektronischen Umsetzung.

Ich habe eine elektronische Lösung und auch eine Programmlösung.
Unten ist ein Video verlinkt, das einen Test mit wenigen Kommandos zeigt.
Das läuft.
Das Programm für die endgültige Ansteuerung ist jedoch viel umfangreicher.
Und bei dem werden Befehle ausgelassen oder die Bewegung eines Servos fiert ein, bis der nächste Befehl für den Servo kommt.

Ich habe einen Arduino NANO als Steuerung gewählt, weil die recht günstig zu haben sind.
Ich habe darüber gelesen, dass der Nano empfindlich ist, wenn Servos betrieben werden.
Es werden 6 Servos und 2 Neopixel LEDs angesteuert.

Testvideo hier:

Die Elektronik sieht so aus:


Ich habe verschiedene Varianten der Spannungsversorgung probiert.
12V Netzteil direkt an den Nano & Stepdown 5 V für die Servos
12V Netzteil an Nano und 5 V Netzteil an die Servos
Stepdown 9V an Nano und Stepdown 5 V an Servos.
Alle Varianten zeigen dieselben Aussetzer. Die Aussetzer sind bei jedem Durchlauf anders, weswegen ich denke, es handelt sich um rückgekoppelte Störungen im Nano.

Der Programmcode îst sehr lang, ca 700 Zeilen.
Die Bewegungen der Servos müssen zum Takt der Musik passen. Deshalb ist das Programm mit einem Zeitcode programmiert.
Der Arduino zählt die millis() und vergleicht diese mit dem Zeitcode.
Wenn eine Zeitmarke erreicht ist, wird eine Bewegung ausgeführt.

Das Programm wartet auf das Startsignal eines zentralen Nanos, der das Adafruit SFX Board, die 3 Totenköpfe und Lichteffekte steuert.
Nach dem Startsignal läuft das ZeitCode Programm ab.

Hier ein Auszug aus dem Programm:

//  Servo Limits for Beta SKULL
// NOD  MAX UP 60   MID 90  MAX DOWN 120  
// SHAKE  MAX STBD 30   MID 100  MAX PORT 170
// TILT  MAX STBD 140   MID 95  MAX PORT 50
// JAW  MAX OPEN 95   MID 70  MAX CLOSED 48    
// Eye X  MAX STBD 40   MID 90  MAX PORT 140
// Eye Y  MAX UP 175   MID 140  MAX Down 80

#include <VarSpeedServo.h>
#include <Adafruit_NeoPixel.h>

// Servo 
VarSpeedServo servo1;   // NOD
VarSpeedServo servo2;   // SHAKE
VarSpeedServo servo3;   // TILT
VarSpeedServo servo4;   // Jaw
VarSpeedServo servo5;   // Eye X
VarSpeedServo servo6;   // Eye Y

const int servoPin1 = 3;
const int servoPin2 = 5;
const int servoPin3 = 6;
const int servoPin4 = 9;
const int servoPin5 = 10;
const int servoPin6 = 11;
const int resetPin = 7;
const int startpin = 12;
unsigned long runtime;
unsigned long offsetMillis;

// NeoPixel
#define PIN  2
#define NUMPIXELS 2
Adafruit_NeoPixel eyes = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() 
{
 // Pin Mode Selection
pinMode(startpin, INPUT_PULLUP);
pinMode (resetPin, INPUT_PULLUP);

// LED Initialising
eyes.begin(); 
// Preset eyes to off
eyes.setPixelColor(0, eyes.Color(0,0,0)); // Eyes off
eyes.setPixelColor(1, eyes.Color(0,0,0)); // Eyes off
eyes.show(); // This sends the updated pixel color to the hardware.

servo1.attach(servoPin1);
servo1.write(120,10,false); // NOD  MAX UP 60   MID 90  MAX DOWN 120  
servo2.attach(servoPin2);
servo2.write(100,40,false); // SHAKE  MAX STBD 30   MID 100  MAX PORT 170
servo3.attach(servoPin3);
servo3.write(95,40,false); // TILT  MAX STBD 140   MID 95  MAX PORT 50
servo4.attach(servoPin4);
servo4.write(40,40,false); // JAW  MAX OPEN 95   MID 70  MAX CLOSED 40   
servo5.attach(servoPin5);
servo5.write(90,40,false); // Eye X  MAX STBD 40   MID 90  MAX PORT 140
servo6.attach(servoPin6);
servo6.write(80,40,false); // Eye Y  MAX UP 175   MID 140  MAX Down 80
delay(2000);

// Wait for start signal
while (digitalRead(startpin) == HIGH) {};

// Define Offset millis
offsetMillis = millis();
}
 void(* resetFunc) (void) = 0;//declare reset function at address 0
void loop() 
{
 if (digitalRead(resetPin) == HIGH) {}  // 
 if (digitalRead(resetPin) == LOW) {resetFunc();}   // Reset triggered
 
runtime = millis();


// Eyes ZOOM IN BLUE
if ((runtime-offsetMillis) == 3500)   // Augen Zoom Blue                    //                                                      50
{ 
  for(int i = 0; (i < 256) && ((runtime-offsetMillis) >= 9); i++)
  {
   eyes.setPixelColor(0, eyes.Color(0,0,i)); // Fade in Magenta
   eyes.setPixelColor(1, eyes.Color(0,0,i)); // Fade in Magenta
   eyes.show(); // This sends the updated pixel color to the hardware.
   delay(25); // Delay for a period of time (in milliseconds).
       }
       }   
      
if ((runtime-offsetMillis) == 11000)  { 
   servo1.write(85,5,false); // NOD  MAX UP 60   MID 90  MAX DOWN 120 
   servo6.write(140,8,false);  // (Winkel, Geschwindigkeit)  // Eye Y  MAX UP 175   MID 140  MAX Down 80  
   }                                                                                             

       
//  ***********************************************************************************************************************************************

if ((runtime-offsetMillis) == 12521)  {}             // Beat 1
if ((runtime-offsetMillis) == 13041)  {}             // Beat 2
if ((runtime-offsetMillis) == 13562)  {}             // Beat 3
if ((runtime-offsetMillis) == 14082)  {}             // Beat 4
if ((runtime-offsetMillis) == 14603)  {}             // Beat 5
.........
if ((runtime-offsetMillis) == 134863)  {}             // Beat 236
if ((runtime-offsetMillis) == 135384)  {}             // Beat 237
if ((runtime-offsetMillis) == 135905)  {}             // Beat 238
if ((runtime-offsetMillis) == 136425)  {servo3.write(80,50,false);servo6.write(140,40,false);}    

if ((runtime-offsetMillis) == 136946) {servo4.write(90,120,false);}

if ((runtime-offsetMillis) == 137196) {servo4.write(47,120,false);servo3.write(110,50,false);}

if ((runtime-offsetMillis) == 137466) {servo4.write(90,120,false);}

if ((runtime-offsetMillis) == 137716) {servo4.write(47,120,false);servo3.write(80,50,false);}

if ((runtime-offsetMillis) == 137987) {servo4.write(90,120,false);}

if ((runtime-offsetMillis) == 138237) {servo4.write(47,120,false);servo3.write(110,50,false);}

if ((runtime-offsetMillis) == 138508) {servo4.write(90,120,false);}

if ((runtime-offsetMillis) == 138758) {servo4.write(47,120,false);servo3.write(95,60,false);}

if ((runtime-offsetMillis) == 139028)  {}             // Beat 244
if ((runtime-offsetMillis) == 139549)  {}             // Beat 245
if ((runtime-offsetMillis) == 140069)  {}             // Beat 246
if ((runtime-offsetMillis) == 140590)  {}             // Beat 247

}

Nun meine Bitte an die Community, habe ich ein elektronisches Problem, oder liegt es am Programmcode?

Bielen Dank für Eure Hilfe,

3D-Gurke

CODE TAGs müssen ohne Leerzeichen geschrieben werden damit sie funktionieren.
Vewende doch einfach den </> icon.

Grüße Uwe

Es wundert mich schon, daß dieses sehr kreative Timing überhaupt was macht. Eigentlich müßte es schon in der ersten Schleife hängen bleiben, weil (runtime-offsetMillis)==3500 auch (runtime-offsetMillis)>=9 impliziert. Zumindest sehe ich nicht, daß runtime da irgendwo aufdatiert wird.

Ein delay(25) o.ä. führt dazu, daß im nächsten Durchlauf von loop() runtime mindestens 25ms höher ist, nicht exakt so viel höher. Danach funktionieren alle == Vergleiche mit Zeiten höchstens noch zufällig, kein Wunder daß da manches unter den Tisch fällt. Andererseits kann bei kurzen Aktionen runtime unverändert sein, die Aktion also in den nächsten Durchläufen von loop() nochmal ausgeführt werden.

Hi

DrDiettrich hat's schon auf den Punkt gebracht - bei Vergleich mit == MUSS Biedes identisch sein - wenn loop() nur etwas länger als 1ms braucht (egal wann), fliegt Dir Das um die Ohren - oder es tut sich halt rein gar Nichts mehr, weil der nächste positive Vergleich erst in 49,batsch Tagen erreicht wird - könnte Dein 'Einfrieren' erklären (nach 49,batsch Tagen läuft millis() über, somit ist ein Vergleich, egal an welcher zeitlichen Stelle, spätestens nach 49,batsch Tagen gültig).

Im Moment hast DU die Zeiten hard codiert - Das finde ich für eine 1-malige Sache eventuell noch ok, wenn's aber mehrere Animationen sein sollen - Du sprachst von drei Köpfen, Die werden wohl nicht immer Alle exakt das Gleiche anstellen sollen - wäre mir eine Art Tabelle - Wann Was Wohin - in den Sinn gekommen.
Du merkst Dir 'nur' in welcher Zeile der Tabelle Du stehst und hast die Startzeit dieser Aktion, die Aktion selber (Auge rollen, LED links umfärben, Kiefer bewegen) und das Wohin (links/rechts/oben/unten, rot/grün, auf/mitte/zu).
Wenn Das ausgelöst wurde, springst Du in die nächste Tabellenzeile.
Abfragen mit millis()-startzeit>=wartezeit - das > schützt Dich davor, wenn loop() Mal länger braucht, die Aktion wird immer noch ausgeführt - vll. eine ms später, aber immerhin - so schnell guckt's kein vom Totenkopf erschreckter Besucher :wink:

Schönes Projekt!

MfG

PS: Die reset-Funktion hat den Nachteil, daß die Register des Arduino NICHT dabei in den Start-Status gesetzt werden - Du springst einfach nur an den Anfang des Programm ((void)0 -> jmp 0) - dort steht der 'reset vector', Der eben auch NACH einem Reset angesprungen wird.
Mache Das besser mit einem richtigem Reset - ggf. würde sich der WatchDog anbieten - WatchDog starten und per while(1); in der Endlosschleife auf den anstehenden Reset warten.
Beim Nano gab's, meines Wissen, mit dem oldBootloader das Problem, daß bei zu kurzen WatchDog-Zeiten der nächste Reset bereits wieder ausglöst wurde, bevor der Nano im setup() ankam um den WatchDog wieder abzuschalten- Das VORHER vll. Man anlesen.
Spätestens nach einem PowerOFF sollte das Steinchen aber wieder ansprechbar sein.
Oder den neueren Bootloader aufbrennen (noch nicht gemacht, aber auch noch keinen Wachhund benutzt).
... der Großteil wurde aus Dem wiedergegeben, was mir combie zu dieser Reset-Funktion sagte, als ich Diese entdeckte :slight_smile:
(und zugegeben: nocht hätte ich auch Diese nicht benötigt ... aber Haben ist besser als Brauchen ... toll, jetzt sammel ich auch Programm-Fetzen ... aka Sketch-Messi)

Hallo Junx,

vielen Dank für die Hilfe und die Blumen.

Ich werde den Code einmal umprogrammieren und " kucken, wat der Ball macht......."

Die Resetfunktion in der angewendeten Form funktioniert soweit.
Am Ende de Durchlaufs springt der Slave Arduino an den Anfang des Loops und wartet auf ein erneutes Startsignal des Master.
Das ganze Ensemble soll an Halloween von Dämmerung 17 Uhr bis 23 Uhr kontinuierlich laufen, sodass kein Überlauf entstehen wird. Ich werde mich mit watchdog Reset einmal befassen. Ich habe davon etwas gelesen, hab aber noch keine Ahnung davon.

Noch einmal vielen Dank! Ich bin einen Schritt weiter.

3D-Gurke

Die Resetfunktion in der angewendeten Form funktioniert soweit.

Bitte: Vergiss es.

Solange der µC noch ordnungsgemäß läuft, brauchst du keinen SoftReset.
Wenn das Programm nicht mehr ordnungsgemäß läuft, führt dich dein SoftReset auch nicht wieder in einen garantiert ordentlichen Zustand.

Auch einen WDT Reset brauchst du hier nicht.
Denn es hängen weder Leben noch großartige Werte daran.

Es gibt nur ganz wenige berechtigte Gründe für einen SoftReset.
Und hier sehe ich keinen davon.

Hier ist es (aus meiner Sicht) nicht mehr als ein "Dirty Hack"

Ich finde es allerdings ok, damit zu spielen, bis einem klar wird: "Das brauche ich nicht!"

Vergiß den Überlauf und vergiß den Reset. Beim Start der Sequenz wird offsetMillis auf die aktuelle (Start-) Zeit gesetzt, das reicht schon zur Berechnung der relativen Laufzeit. Man kann eventuell noch dafür sorgen, daß die Sequenz zuletzt abgeschaltet wird und nicht nach 49 Tagen von selbst wieder losläuft.

Hallo liebe Kollegen,

vielen vielen Dank für die Supertips!

Ich habe zunächst den Code von == auf >= geändert. Das hat aber nur bedingt etwas gebracht, da der Kopf Breakdance veranstaltete, denn der Arduino hatte dann immer mehrere Befehle, die der Anforderung >= Zeitmake genügten.

Dann habe ich für jeden Befehl anstatt einer festen Zeitmarke ein Zeitfenster von 3ms hergenommen.
Ungefähr so:

anstatt:
if (runtime-offsetMillis) == 28139 {servo4.write(70,80,false);}
nun:
if ((runtime-offsetMillis) >= 28139 && (runtime-offsetMillis)<=28141) {servo4.write(70,80,false);}
und siehe da: Das Ding läuft, genau wie programmiert.

Problem gelöst.

Herzlichen Dank an Euch für die tollen Tips!

Ich werde in Kürze ein neues Video mit dem erfolgreichen Test auf Thingiverse hochladen.

Danke! Danke! Danke!

3D-Gurke

Hi

Schön, daß Es jetzt erst Mal läuft.
Klar, Dein > Vergleich greift IMMER, also ab dem Zeitpunkt, wo's das erste Mal größer wird.
Deshalb nimmt man bei einer State-Maschine für Alles einen eigenen Status.
Wenn der Status fertig ist (ob eine Zeit die Endbedingung ist oder sonst was, spielt keine größere Rolle), wird in einen anderen State gewechselt.
Wenn es Dinge gibt, Die gleichzeitig und unabhängig voneinander ablaufen sollen, sind Das eben zwei eigenständige State-Maschinen, mit eigenen Merkern (wo sind wir überhaupt) und Zeiten (wann wurde zuletzt was gestartet) - und natürlich millis() als System-Uhr/die aktuelle Zeit.

Freue mich schon auf's nächste Video - denke, daß die Zielpersonen die 3ms 'Ungenauigkeit' nicht großartig störend wahrnehmen.

MfG

Hallo liebe Kollegen,

ich habe das aktuelle Testvideo hochgeladen. Ihr könnt es unter der Thingiverse Adresse finden:

Nochmal vielen Dank an alle!

Glück Auf!

3D-Gurke

Hi

Saubere Arbeit - Daumen Hoch auf YT :slight_smile:

MfG

Hallo liebe Kollegen,

anbei das neueste und letzte Update zu diesem Thread.

Ich weiss, es interessiert Euch nicht, ich poste es aber trotzdem :slight_smile:

Hier findet Ihr das neueste Video zu diesem Thread, die Programmierung dieses Projekts ist heirmit fast abgeschlossen.
[
Noch einmal vielen Dank für die Hilfe!

3D-Gurke](http://www.youtube.com/watch?v=_bPRN539xfE&feature=youtu.be /url)