2x Ultraschall mit Step-Motor und Interrupts

Hi,

ich bin neu hier im Forum und hätte da direkt mal eine Frage an euch! :wink:
Ich möchte an meinem Arduino Mega 2560 gerne 2 Ultraschallmodule (HC-SR04) zusammen mit einem Step-Motor benutzen. Beide Sensoren sollen später jeweils in entgegengesetzte Richtungen gerichtet werden. Sobald einer der beiden Sensoren auf ein Objekt in ca. 5cm Entfernung trifft soll mein laufender Step-Motor gestoppt werden.
Wenn ich das bisher richtig verstanden habe benötige ich dafür interrupts, da das Arduino ja (z.B. bei Stepper.steps(200)) während der 200 steps nichts anderes machen kann, als den Motor zu steuern.
Ich habe beide Module bereits mit folgendem Code erfolgreich getestet, nur schaffe ich es selbst nicht in den Code Interrupts einzufügen. Könnte mir da evtl. jemand von euch helfen?

//Sonar 1
int echoPin1 =19;
int initPin1 =6;
int distance1 =0;

//Sonar 2
int echoPin2 =18;
int initPin2 =8;
int distance2 =0;

void setup() {
  
  pinMode(initPin1, OUTPUT);
  pinMode(echoPin1, INPUT);
  pinMode(initPin2, OUTPUT);
  pinMode(echoPin2, INPUT);
  Serial.begin(9600);
  
}

void loop() {
  
  distance1 = getDistance(initPin1, echoPin1);
  printDistance(1, distance1);
  delay(150);
  
  distance2 = getDistance(initPin2, echoPin2);
  printDistance(2, distance2);
  delay(150);

  delay(500);
  
}

int getDistance (int initPin, int echoPin){

 digitalWrite(initPin, HIGH);
 delayMicroseconds(10); 
 digitalWrite(initPin, LOW); 
 unsigned long pulseTime = pulseIn(echoPin, HIGH); 
 int distance = pulseTime/58;
 return distance;
 
}
 
 void printDistance(int id, int dist){
  
     Serial.print(id);
    if (dist >= 120 || dist <= 0 ){
      Serial.println(" Out of range");
    }else
    for (int i = 0; i <= dist; i++) { 
         Serial.print("-");
    }
    Serial.print(dist, DEC);
    Serial.println(" cm");
    
 }

Schonmal vielen Dank!
LG :slight_smile:

Hallo und willkommen im Forum!

Mit Interrupts im Zusammenhang mit Arduino kenne ich mich nicht aus. Mein erster Blick, als ich mit Arduino angefangen habe, galt auch den Inerrupts. Allerdings habe ich festgestellt, bisher bin ich auch ohne ausgekommen. Daher mein alternativer Vorschlag, den abzufahrenden Weg in kleinere Portionen aufzuteilen, um zwischenzeitlich die Sensoren abfragen zu können. Ein Beispiel wäre stepper_speedControl.ino, wo myStepper.step(stepsPerRevolution / 100); genau dies macht.

Bei Interesse helfe ich gerne weiter, sonst gibt es hier im Forum natürlich auch Spezialisten für Interrupts.

Wie lange dauert denn ein Step ?

Vielen Dank für die schnellen Antworten! :slight_smile:

@agmue: genau das war auch meine erste Idee :slight_smile: nur bin ich die Schritte mit einer for-Schleife durch gegangen. Nur war der Motor dadurch deutlich langsamer als ohne for-Schleife.
Werde ich mir aber auf jeden Fall gleich nochmal anschauen. Danke dafür! :slight_smile:

@EIEspanol: das kann ich so nicht sagen, da die Geschwindigkeit des Steppers einstellbar und nicht bei einem fixen Wert bleiben soll.

LG

Marw42:
Nur war der Motor dadurch deutlich langsamer als ohne for-Schleife.

Wenn loop einschließlich der Sensormessung zu langsam für die Bewegung ist, geht es wohl nur mit Interrupt. Würde mich freuen, denn dann lerne ich etwas! :slight_smile:

Deine “Gleichung” hat mehrere Unbekannte für mich: wie schnell ist die Bewegung, wie viel Bewegung pro Schritt, Genauigkeit der Reproduzierbarkeit der Entfernungsmesser, wie präzise muß angehalten werden usw. Daher kann ich keinen Rat geben. Wenn Du die Entfernung ständig mißt, kennst Du ja auch die Differenz zu den 5 cm. Große Differenz bedeutet mehr Schritte bis zur nächsten Messung, kleine Differenz kleine Schritte mit zwangsweise geringerer Geschwindigkeit. Da könnte der Nachteil zum Feature werden. Da näherst Du Dich einer Regelung.

Also das Ganze wird später so ablaufen:
Ein Schrittmotor wird über einen Riemen ein Objekt über eine 2-3m lange Schiene bewegen.
Sobald der Schrittmotor zum Ende der Schiene gelangt, soll einer der beiden Sensoren melden, dass die Schiene bald zu Ende ist und der Schrittmotor keine weiteren Schritte mehr machen darf.
Das selbe dann mit dem anderen Ultraschallsensor zur anderen Richtung hin.
Und wie gesagt, die Geschwindigkeit des Schrittmotors und seine Laufzeit ist nicht festgelegt sondern kann später über ein kleines Interface verstellt werden.

Bin schon seit ein paar Tagen dabei das Problem zu lösen, habe bisher aber nur herausgefunden dass ich Interrupts brauche, aber ich kriege es einfach nicht hin :smiley:

Ich sehe zwei Probleme:

  1. Der Ultraschallsensor liefert nur ein Echo, wenn vorher ein Triggersignal ausgegeben wird. Dann muß der Abstand bestimmt werden, dann müßte ein Interrupt ausgelöst werden. Das geht aber nicht, während myStepper.step() blockiert. Natürlich könnte man zeitgesteuert einen Interrupt, der die Messung durchführt, auslösen. Ich bezweifele aber, daß dies gegenüber der Lösung ohne Interrupt einen merklichen Vorteil bringt.

  2. Anstelle eines Ultraschallsensors könnte man sich auch einen Endschalter, der einen Interrupt auslöst, vorstellen. Dann müßte die Interruptroutine die Bewegung anhalten. Dies würde einen Eingriff in den Ablauf von myStepper.step() bedeuten. Wäre zu prüfen, ob dies mit setSpeed=0 geht.

Von der Interrupt Technik mit dem Timer habe ich gehört. Das hieße ja dann, dass in einem regelmäßigen Abstand eine bestimmte Aktion ausgeführt wird, egal was gerade sonst bearbeitet wird (wenn ich das richtig verstanden habe). Jedoch würde der Stepper dann wahrscheinlich immer wieder für sagen wir mal 50ms stehen bleiben, oder?
Für mich ist es halt sehr wichtig dass der Motor schön gleichmäßig ohne Unterbrechungen läuft. Wobei man eine Unterbrechung von 50ms wahrscheinlich nicht mal merken würde.

Bei deiner zweiten Idee habe ich es noch nicht mit dem Schalter verstanden. Wie genau stellst du dir das vor? Beim Aufprall wird ein Button betätigt der einen Interrupt auslöst? :smiley:
Auf jeden Fall kann man den Motor mit stepper.step(0); ohne Probleme anhalten lassen.

LG

Interrupt sehe ich hier nicht, da:
Ultraschallmessung ist nicht im IRQ möglich, das dauert zulange
Steps für den Motor denke ich mal, auch, da es ja etwas mechanisches bewirkt und da sind wir nicht mehr im Microsekundenbereich

Also wofür Interrupt?

Einen schnellen Loop, der alle x ms einen Distanzwert anfordert und einen RunningMedian oder Running Average bildet.
Und den Motor weiterdreht. Je nach Geschwindigkeit und Trägheit bei Annäherung die Geschwindigkeit des Steppers verringern.
Und KEINE Schleifen in den Code.

Marw42:
Bei deiner zweiten Idee habe ich es noch nicht mit dem Schalter verstanden. Wie genau stellst du dir das vor? Beim Aufprall wird ein Button betätigt der einen Interrupt auslöst? :smiley:

Der Schalter sollte vom Objekt im Vorbeifahren betätigt werden, damit die bewegte Masse abgebremst werden kann. Endschalter im Sinne von “Ende der gewollten Bewegung”. Dahinter gehört dann noch ein NotAus-Schalter, der unabhängig von der Elektronik Motoren stromlos schaltet. Die Sicherheitsbestimmungen kennst Du sicher auswendig, wenn Du sowas baust!

Marw42:
Auf jeden Fall kann man den Motor mit stepper.step(0); ohne Probleme anhalten lassen.

Stimmt, habe ich ausprobiert:

#include <Stepper.h>
const int stepsPerRevolution = 96;
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);
volatile int state = LOW;
int zaehler = 0;
int geschwindigkeit = 60;

void setup() {
  myStepper.setSpeed(geschwindigkeit);
  Serial.begin(9600);
  pinMode (13, OUTPUT);
  attachInterrupt(0, blink, FALLING);
  Serial.println("Programmstart");
}

void loop() {
  zaehler++;
  digitalWrite(13, state);
  myStepper.step(stepsPerRevolution);
  Serial.println(zaehler);
}

void blink() {
  myStepper.setSpeed(0);
  state = !state;
}

Leider hängt sich das Programm erwartungsgemäß auf, verläßt myStepper.step(); nicht. :smiling_imp:

Ich stimme ElEspanol zu.

Da ich gerade mit Schrittmotortreiber (L298N) und bipolarem Schrittmotor (NEOCENE 2T354211) probiere, hier ein ausbaubarer Testsketch:

#include <Stepper.h>
const int stepsPerRevolution = 96;
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);
int geschwindigkeit = 60;
const byte led = 13;
const byte trig_1 = 7;
const byte echo_1 = 6;
const long schwellwert_1 = 10;

void setup() {
  //  Serial.begin (9600);
  myStepper.setSpeed(geschwindigkeit);
  pinMode(trig_1, OUTPUT);
  pinMode(echo_1, INPUT);
  pinMode(led, OUTPUT);
  digitalWrite(trig_1, HIGH);
  delay(10);
}

void loop() {
  if (messung(trig_1, echo_1, schwellwert_1)) {
    digitalWrite(led, HIGH);
    myStepper.step(stepsPerRevolution);
  } else {
    digitalWrite(led, LOW);
  }
}

bool messung (byte trig, byte echo, long schwellwert) {
  digitalWrite(trig, LOW);
  long entfernung = (pulseIn(echo, HIGH) / 2) / 29.1;  // Entfernung in cm
  digitalWrite(trig, HIGH);
  return (entfernung > schwellwert);
}

Ich kann keine Unterbrechung der Drehung des Schrittmotors durch die Messung wahrnehmen. Das war der Punkt, der mich interessierte. Hängt aber von verschiedenen Faktoren ab, mußt Du mit Deinem Aufbau testen. Wenn es Ultraschall sein soll, würde ich auf diesem Weg weitergehen.

ElEspanol:
Interrupt sehe ich hier nicht, da:
Ultraschallmessung ist nicht im IRQ möglich, das dauert zulange

Halte ich für unrichtig. Das ist absolut möglich und auch sinnvoll.

Nur die Kantenzeitpunkte des Ausgangssignal aufzunehmen ist kein Problem.
Warten auf die erfolgte Messung in der Hauptschleife 'dauert zu lange'.

Da die Kanten so selten auftreten, könnte eine ISR locker viele Sensordaten aufnehmen.

Man könnte natürlich auch via Timer etwas basteln das die Sensoren bedient,
aber das wäre auch wieder Polling.

Whandall:
Nur die Kantenzeitpunkte des Ausgangssignal aufzunehmen ist kein Problem.

Kante = Flanke und Ausgangssignal = Echo?

Das Echo könnte man als Interruptsignal verwenden, aber wann soll der Trigger gesetzt werden? Dein Ansatz hört sich interessant an, nur verstehe ich den Ablauf nicht so richtig. Würde mich über eine Erläuterung freuen :slight_smile:

Genau.

Loop (oder Timer) startet die Messung(en),
ISR notiert microseconds bei steigender und dann später bei fallender Flanke,
und setzt ein Flag, wenn ein neue(r) Messwert(e) da ist/sind.

Loop holt sich neue Messungen wenn vorhanden und berechnet den Abstand.

Jetzt habe ich es verstanden, glaube ich zumindest. Die grauen Zellen arbeiten ...

Die von dir verwendeten Pins (18 + 19) sind nicht ganz so glücklich,
du könntest doch wahrscheinlich auf zwei Pins ausweichen, die eigene Interrupt Vektoren besitzen.
Dein 2560 hat acht davon, wenn ich mich recht entsinne.

D18 entspricht INT3 (attachInterrupt(5, ...))
D19 enspricht INT2 (attachInterrupt(4, ...))

(Das Prinzip ist natürlich auch mit allen anderen Pins möglich,
nur etwas lästiger, bei den externen Interrupts weisst du halt welcher Pin aktiv wurde,
bei den anderen musst du das selbst heraus-bit-friemeln. ;))

Es gibt da auch neuerdings auch ein sehr schönes Arduino Makro namens digitalPinToInterrupt(). Da kann man die Pin-Nummer übergeben und erhält die Interrupt Nummer.

Sicher. So wie

digitalPinToPCICR
digitalPinToPCICRbit
digitalPinToPCMSK
digitalPinToPCMSKbit
uint16_t PROGMEM port_to_mode_PGM[]
uint16_t PROGMEM port_to_output_PGM[]
uint16_t PROGMEM port_to_input_PGM[]
uint8_t PROGMEM digital_pin_to_port_PGM[]
uint8_t PROGMEM digital_pin_to_bit_mask_PGM[]
uint8_t PROGMEM digital_pin_to_timer_PGM[]

Whandall:
Loop (oder Timer) startet die Messung(en),
ISR notiert microseconds bei steigender und dann später bei fallender Flanke,
und setzt ein Flag, wenn ein neue(r) Messwert(e) da ist/sind.

Meinen Versuch, diese Idee in einen Sketch zu gießen, stelle ich zur Diskussion:

#include <Stepper.h>
const int stepsPerRevolution = 96;
const int steps = 10;
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);
int geschwindigkeit = 60;
const byte led = 13;
const byte trig_1 = 7;
const byte echo_1 = 2;
const unsigned long schwellwert_1 = 10 * 2 * 29.1;
volatile bool neuerMesswert = LOW;
volatile unsigned long startMicros, stopMicros;

void setup() {
  //  Serial.begin (9600);
  myStepper.setSpeed(geschwindigkeit);
  pinMode(trig_1, OUTPUT);
  pinMode(echo_1, INPUT_PULLUP);
  pinMode(led, OUTPUT);
  attachInterrupt(0, zeitStartStop, CHANGE);
  digitalWrite(trig_1, HIGH);
  delay(10);
  digitalWrite(trig_1, LOW);
}

void loop() {
  if (neuerMesswert) {
    neuerMesswert = LOW;
    long temp = stopMicros - startMicros;
    //    Serial.print("startMicros: "); Serial.print(startMicros); Serial.print("   stopMicros: "); Serial.print(stopMicros); Serial.print("   Diff: "); Serial.println(temp);
    if (stopMicros - startMicros > schwellwert_1) {
      digitalWrite(led, HIGH);
      myStepper.step(steps);
    } else {
      digitalWrite(led, LOW);
      myStepper.step(0);
    }
    digitalWrite(trig_1, LOW);
  }
}

void zeitStartStop() {
  if (digitalRead(echo_1)) {
    startMicros = micros();
    digitalWrite(trig_1, HIGH);
  } else {
    stopMicros = micros();
    neuerMesswert = HIGH;
  }
}

Das Re-Triggern ist nicht ganz vollständig, sollte wohl so lauten:

  digitalWrite(trig_1, HIGH);
  delay(10);
  digitalWrite(trig_1, LOW);

gibt auch eine nette kleine Routine ab....

Solange du das so synchron auslöst, wird es funktionieren.
Wenn der Trigger asynchron erfolgt, solltest du die Interrupts sperren,
die Messerte kopieren, Interrupts wieder freigeben, dann Messwerte verarbeiten.
Sonst könnte dir die ISR ins Gehege kommen. :wink: