Jump to content
Intermodellbau 2024: Unser Stand in Halle 3 ist fertig für Besucher - schaut mal 'rein. ×
Schiffsmodell.net

Betriebssystem für Tauchboot


lab

Recommended Posts

Ich hatte ja schon einmal ein Einzelproblem angesprochen ("Endlagenschalter"), hier nun das dazu gehörige Projekt:

 

Ich möchte für ein Miniatur-Tauchboot die Bordelektronik auf Arduino-Basis aufbauen (mit dem Fernziel eines allgemeinen Betriebssystem für Modellschiffe) und plane dabei die folgenden Funktionen:

 

  • Geschwindigkeitsregelung
  • Steuerung
  • Tauchtank (inkl. Endlagenabschaltung)
  • Sonderfunktion Scheinwerfer
  • sowie Sicherheitsfunktionen wie Batterieüberwachung, fail-safe und Wassereinbruchsdetektion

 

Im Moment funktionieren die meisten Funktionen bis auf fail-safe und Wassereinbruchserkennung.

 

Da es nicht möglich ist, hier im Forum kompletten Code zu posten habe ich ein Projekt auf GitHub eingerichtet: https://github.com/labomat/rcduino

 

Auf Hinweise zu Fehlern, Verbesserungsvorschläge und Ergänzungen freue ich mich!

 

Gruß, Kai

Link to comment

Die 2 die beim überfliegen aufgetaucht sind:

 

#include <PinChangeInt.h>
void setup(){
 PCintPort::attachInterrupt(ENDSWITCH,checkEnd,CHANGE);}

Das sind Softwareinterrupts und als solche bestenfalls eine Notlösung, keine gute Idee. Du verwendest nur einen Interrupt und selbst der Arduino UNO bietet zwei Hardware Interrupts, der Leonardo hat sogar vier. Du musst nur den ENDSWITCH von Pin 9 auf Pin 2 oder 3 legen (0-3 beim Leonardo) und statt der Bibliothek verwendest du attachInterrupt(). In Hardware sind die Interrupts (gelegentlich deutlich) schneller.

 

Mal unabhängig davon das dieser Interrupt in Hardware schöner wäre ist dieser Interrupt total unsinnig. Ein Interrupt ist etwas wichtiges, ein ACHTUNG mit 7 !!!!!!! hintendran. In den Interrupt gehört reingeschrieben: Pumpe aus!!! und nicht Könnte die nächste Schicht bitte die Pumpe abschalten wenn sie mal Zeit hat. Will sagen: Im Interrupt wird nicht die Variable PumpeAmEnde = 1; gesetzt, sondern da wird die Pumpe ausgeschaltet. Andernfalls macht der Interrupt überhaupt keinen Sinn und verzögert nur die Zeit bis die Pumpe tatsächlich ausgeschaltet wird.

 

Außerdem finde ich deine überlange loop() ziemlich hässlich, ich persönlich würde das in Funktionen auslagern die sinnvoll zusammengehören.

Link to comment

Hallo Kessl,

 

Danke für die Anmerkungen. Ja, der Software-Interrupt stammt noch von der vorherigen Version, bei der ich noch alle 4 Kanäle einzeln aus den Empfängerausgängen eingelesen hatte.

 

Das Auslagern in Funktionen habe ich auch noch vor, bin aber im ersten Versuch daran gescheitert, dass ich noch nicht ausreichend durchblicke welche wann deklarierten Variablen wo überall gültig sind. Das einfache Verlagern einzelner Funktionalitäten aus der Loop in separate Funktionen klappte nicht auf Anhieb.

 

Die Abfrage des Endschalters würdest Du dann einfach als Funktion umsetzen?

 

Gruß, Kai

Link to comment

Hi

 

Im Moment setzt der Interrupt ja nur eine Variable von 0 auf 1, ausgeschaltet wird die Pumpe dann aber im nächsten Durchgang der loop(). Aber einen Interrupt auszulösen um im nächsten Durchgang der loop() dann die Pumpe auszuschalten ist Unsinn. Entweder schaltest du die Pumpe gleich im Interrupt aus, oder du setzt die Variable in der loop(). Ich würde ganz klar das erste machen, wenn ein Endlageschalter schließt, dann die Pumpe abschalten.

 

Die Idee hinter dem Interrupt ist ja grob gesagt, dass dank ihm egal ist ob deine loop() 20 ms benötigt um 1x durchlaufen zu werden, oder 2000. Nach 2000 ms könnte deine Pumpe aber bereits dein Boot zerstört haben, daher sollte die Rettung, Pumpe aus, direkt im Interrupt ausgeführt werden.

Link to comment

Jein,

es kommt darauf an, wie lange das Ausschalten der Pumpe dauert. Denn gerade im RC Umfeld, wenn man auch noch Servos steuern und Empfänger auslesen möchte, muss eine Interruptroutine (ISR) möglichst kurz sein.

Beispiel:

Ich baue für mein Buch gerade an einem RC Ruderer. Der wird mit 4 Servos gesteuert. Weiterhin muss ich natürlich 2 Empfängerkanäle auswerten.

Mess ich dabei z.B. in der ISR die Zeit des Empfängerimpulses per micros() hab ich ein Problem. Denn das dauert viel zu lange. Noch dazu muss ich mit unsigned long arbeiten, was Berechnungen nicht gerade beschleunigt. Ergebniss beim ersten Test war, die Servos zittern wie Aale in der Sonne. Stell ich das ganze auf einen eigenen Timer um (in dem Fall Timer 1) und les die Werte als uint16_t aus, ist das ganze deutlich schneller und die Servos zittern nicht mehr. Die ISR sieht sehr ähnlich aus, ist aber deutlich schneller abgearbeitet.

 

Vorher

void RCReceive::handleInterrupt() {
 if (digitalRead(myPin) ) { 
   // Positive Flanke
   RcTemp = micros(); 
 } 
 else {
   // negative Flanke
   unsigned long RcValue = micros() - RcTemp; 
   this->pushRcValue(RcValue);
 }
 if ((state & 0xF0) == 0) {
   calcNP();
 }
}

Nachher:

void RCReceive::handleInterrupt() {
 if (digitalRead(myPin) ) { 
   // Positive Flanke
   RcTemp = TCNT1; 
//    RcTemp = micros(); 
 } 
 else {
   // negative Flanke
   uint16_t RcValue = (TCNT1 - RcTemp) >> 1; 
//  unsigned long RcValue = micros() - RcTemp; 
   this->pushRcValue(RcValue);
 }
 if ((state & 0xF0) == 0) {
   calcNP();
 }
}

 

Meiner Meinung nach ist die höchste Prio bei ISR ist und bleibt die Ausführungszeit. Die muss so kurz wie möglich sein. UNd wenn man dann eben nur Übergabevariablen setzt, um Stati zu ändern ist das völlig i.O.

Ich denke, ob die Pumpe 10ms länger oder kürzer läuft ist unerheblich.

 

Achja und der PinChange Intterupt ist nur unwesentlich länger als der reine Hardwareinterrupt. Um genau zu sein, wenn man es geschickt macht, ca. 2-4 Zyklen.

Link to comment

Ein DigitalWrite(pin,low); ist alles was nötig wäre die Pumpe auszuschalten. Was könnte bitte schneller sein? Noch dazu kann man bei einem Hardwareinterrupt statt CHANGE auch einfach LOW verwenden und spart sich so 50% der Interrupts.

 

Außerdem funktioniert so ein Interrupt auch dann wenn die loop(), oder main(), mal ge-deadlocked ist oder auf laaange timeouts wartet weil der Empfänger keine Verbindung mehr hat.

 

Und was deine Funktion angeht: AUS EINEM INTERRUPTHANDLER HERAUS RUFT MAN KEINE FUNKTIONEN AUF!!!

 

1000 mal abschreiben!!

 

Gleich danach schreibst du bitte: Variablen die von Interrupts verändert werden immer volatile machen!!!!

 

So geht das mit dem auslesen problemlos, aber ich verwende einen Summensignalempfänger und den Hardwareinterrupt:

 

const int rc_kanaele = 7;
void setup(){
 attachInterrupt(0, irq_rc, CHANGE);
}
void loop(){
 rc_read();
 /*in rc_wert[i] stehen jetzt werte zwischen 1000 und 2000*/
}
volatile unsigned long rc_hoch[rc_kanaele];
volatile unsigned long rc_tief[rc_kanaele]; //Zeit merken wann das Signal auf low gegangen ist
volatile unsigned long last = 0; //Zeit merken wann da Signal das letzte mal auf high gegangen ist
void irq_rc(){
 if(digitalRead(2)){ //signal hoch
   if(rc_n >= rc_kanaele);
   else rc_hoch[rc_n] = last = micros();
 }
 else{ //signal tief
   if(micros() > (last + 5000)) rc_n = 0;
   else if(rc_n >= rc_kanaele);
   else{
     rc_tief[rc_n] = micros();
     rc_n++;
}}}
unsigned int rc_wert[rc_kanaele];
void rc_read(){
 for(int i = 0; i < rc_kanaele; i++){
   if(rc_tief[i] > rc_hoch[i]){
     rc_wert[i] = (unsigned int)(rc_tief[i] - rc_hoch[i]);
}}}

Link to comment
Ein DigitalWrite(pin,low); ist alles was nötig wäre die Pumpe auszuschalten.

Was könnte bitte schneller sein?

Ein direkter Ausgabe Befehl auf den Port. Ohne dieses Pingemappe des Arduinos. :D

Noch dazu kann man bei einem Hardwareinterrupt statt CHANGE auch einfach LOW verwenden und spart sich so 50% der Interrupts.

Wie willst du mit einem nur LOW Interrupt bitte Pulse messen? Willst in der ISR auf den nächsten HIGH warten?

Außerdem funktioniert so ein Interrupt auch dann wenn die loop(), oder main(), mal ge-deadlocked ist oder auf laaange timeouts wartet weil der Empfänger keine Verbindung mehr hat.

Richtig, ist aber immer so.

Und was deine Funktion angeht: AUS EINEM INTERRUPTHANDLER HERAUS RUFT MAN KEINE FUNKTIONEN AUF!!!

 

1000 mal abschreiben!!

Jein, die beiden Funktionen, calcNp() und pushRCValue, die du meinst, sind mittlerweile nur noch macros... Also gar keine echten Funktionen.

Gleich danach schreibst du bitte: Variablen die von Interrupts verändert werden immer volatile machen!!!!

Ne, mußt du nur machen, wenn die Variablen auch ausserhalb der ISR verwendung finden. Wenn du Variablen nur innerhalb der ISR verwendest, solltest du das nicht machen. Es wird dadurch zus. Code für den Zugriff auf die Variable generiert. Bzw. es wird die Codeoptimierung ausgeschaltet. Muss ja nicht sein, wenn unnötig.

So geht das mit dem auslesen problemlos, aber ich verwende einen Summensignalempfänger und den Hardwareinterrupt:

 

const int rc_kanaele = 7;
void setup(){
 attachInterrupt(0, irq_rc, CHANGE);
}
void loop(){
 rc_read();
 /*in rc_wert[i] stehen jetzt werte zwischen 1000 und 2000*/
}
volatile unsigned long rc_hoch[rc_kanaele];
volatile unsigned long rc_tief[rc_kanaele]; //Zeit merken wann das Signal auf low gegangen ist
volatile unsigned long last = 0; //Zeit merken wann da Signal das letzte mal auf high gegangen ist
void irq_rc(){
 if(digitalRead(2)){ //signal hoch
   if(rc_n >= rc_kanaele);
   else rc_hoch[rc_n] = last = micros();
 }
 else{ //signal tief
   if(micros() > (last + 5000)) rc_n = 0;
   else if(rc_n >= rc_kanaele);
   else{
     rc_tief[rc_n] = micros();
     rc_n++;
}}}
unsigned int rc_wert[rc_kanaele];
void rc_read(){
 for(int i = 0; i < rc_kanaele; i++){
   if(rc_tief[i] > rc_hoch[i]){
     rc_wert[i] = (unsigned int)(rc_tief[i] - rc_hoch[i]);
}}}

Im Prinzip unterscheidet sich das nicht von meiner Routine, nur das ich mittlerweile die micros() auch noch raus habe. Denn das ist eine echte Funktion. Und die kostet richtig Zeit. Und gibt zusammen mit der Servobibliothek ordentlich Jitter auf die Servos.

Link to comment

Hab's endlich gefunden:

 

A variable should be declared volatile whenever its value can be changed by something beyond the control of the code section in which it appears, such as a concurrently executing thread. In the Arduino, the only place that this is likely to occur is in sections of code associated with interrupts, called an interrupt service routine.

 

Will heißen, wenn Variablen von 2 Seiten benutzt werden. Hintergrund.

Der Compiler versucht sich an der Codeoptimierung. Manche Variabeln werden dann einmal im RAM und in einem Register gespeichert. (Registerzugriffe sind schneller) Wenn man nun in der ISR die Variable ändert, kann es sein, das man zwar die Variable im RAM ändert, aber der unterbrochene Code (also das Hauptprogram) weiter auf der Kopie im Register arbeitet. Gleiches umgekehrt. D.h. man arbeitet u.U. mit falschen Werten. Das kann nicht passieren solange man sich im gleichen Codesegment befindet. Also nur ISR oder nur Programm.

Link to comment
Ein DigitalWrite(pin,low); ist alles was nötig wäre die Pumpe auszuschalten.

 

Einfach aussschalten wäre aber doch nicht ausreichend - schließlich möchte ich die Pumpe in Gegenrichtung auch wieder anfahren können - auch wenn der Schalter noch geschlossen ist. Und ich habe nur einen Schalter(eingang), der beide Zustände ("oben" und "unten") anzeigt.

 

Gruß, Kai

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.