Interrupt in Programm einbinden

Sans griasst

Kurz zu mir:
Ich bin immer mal wieder mit dem Arduino in Berührung gekommen, habe aber bisher die Projekte immer wieder aufgegeben. Der Grund dafür war eigentlich immer das ich nicht wirklich programmieren kann und irgendwann vor "unlösbaren" Problemen stand. Naja oder ich habe halt immer etwas hoch gestapelt :smiley: .Das soll diesmal aber nicht passieren, weswegen ich mich nun an euch wende.

Von Anfang an:
Schlussendlich soll eine Spulenwickelmaschine entstehen welche via Potentiometer die Geschwindigkeit regeln, via Toggle-Switch die Drehrichtung steuern, via Hall-Sensor die Umdrehungen messen und diese auf einem Oled anzeigen soll. Das macht es prinzipiell auch mit folgendem Code:

#include <Adafruit_SSD1306.h>
#include <splash.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SPITFT.h>
#include <Adafruit_SPITFT_Macros.h>
#include <gfxfont.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int in1 = 5; //Declaring where our module is wired
int in2 = 6;
int ConA = 9;// Don't forget this is a PWM DI/DO
int speed1;
int HallPinCounter = 0;   // counter for the number of button presses
int HallPinState = 0;         // current state of the button
int lastHallPinState = 0;     // previous state of the button
const int  HallPin = 7;    // the pin that the hall sensor is attached to
const int SwitchPin_L = 11;
const int SwitchPin_R = 12;

void setup() {
  // put your setup code here, to run once:
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(SwitchPin_L, INPUT);
  pinMode(SwitchPin_R, INPUT);
  pinMode(HallPin, INPUT);
  Serial.begin(9600);


  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.


}

void TurnMotorAR() { //We create a function which control the direction and speed
  //digitalWrite(SwitchPin_L, LOW);
  digitalWrite(in1, LOW); //Switch between this HIGH and LOW to change direction
  digitalWrite(in2, HIGH);
  speed1 = analogRead(A0);
  speed1 = speed1 * 0.2492668622; //We read thea analog value from the potentiometer calibrate it
  analogWrite(ConA, speed1); // Then inject it to our motor
}

void TurnMotorAL() { //We create a function which control the direction and speed
  //digitalWrite(SwitchPin_R, LOW);
  digitalWrite(in1, HIGH); //Switch between this HIGH and LOW to change direction
  digitalWrite(in2, LOW);
  speed1 = analogRead(A0);
  speed1 = speed1 * 0.2492668622; //We read thea analog value from the potentiometer calibrate it
  analogWrite(ConA, speed1); // Then inject it to our motor
}

void TurnOFFA() {
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
  analogWrite(ConA, 0);
}



void loop() {
  // put your main code here, to run repeatedly:
  HallPinState = digitalRead(HallPin);
  // compare the HallPinState to its previous state
  if (HallPinState != lastHallPinState) {
    // if the state has changed, increment the counter
    if (HallPinState == HIGH) {
      // if the current state is HIGH then the button went from off to on:
      HallPinCounter++;
      Serial.print("Umdrehungen: ");
      Serial.println(HallPinCounter);
    }
    // Delay a little bit to avoid bouncing
    // delay(0);
  }
  // save the current state as the last state, for next time through the loop
  lastHallPinState = HallPinState;

  Serial.print("R");
  Serial.println(digitalRead (SwitchPin_R));
  Serial.print("L");
  Serial.println(digitalRead (SwitchPin_L));
  Serial.println(speed1);

  if (digitalRead(SwitchPin_L) == HIGH) {
    TurnMotorAR(); //one function that keeps looping you can add another one with different direction or stop
  }

  if (digitalRead(SwitchPin_R) == HIGH) {
    TurnMotorAL(); //one function that keeps looping you can add another one with different direction or stop
  }

  display.clearDisplay();

  display.setTextSize(3);             // Draw 3X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 20);            // Start at top-left corner
  display.println(HallPinCounter);

  display.display();

}

Der Code ist wahrlich keine Augenweide, dies vor allem weil ich ähnliche Projekte gesucht habe und diese dann für meine Zwecke adaptiert. Der Sketch macht theoretisch was er soll. Alle Funktionen funktionieren, ABER ab einer gewissen Drehzahl traue ich dem Zähler nicht mehr. Er zählt trotz höherer Drehzahl langsamer. Also weiter recherchiert und auf das Gebiet "Interrupts" gestossen. Hört sich für mich so an als wäre das des Rätsels Lösung. Ich muss "nur" die Geschichte mit dem Hallsensor via Interrupt programmieren damit das zuverlässig unabhängig vom restlichen Code funktioniert.
Erfreulicher Weise habe ich den HallSensor zufällig an dem Interruptpin 7 meines Arduino Micro angelötet. In der Testphase auf dem Breadboard ist mir das Problem noch nicht aufgefallen.

Ich habe dann den Part den ich Interrupten will herausgeschält um etwas übersichtlicher Testen zu können. Ich habe allerlei ausprobiert, schaffe es aber nicht dass es funktioniert.

volatile int HallPinCounter = 0;   // counter for the number of button presses
volatile int HallPinState = 0;         // current state of the button
volatile int lastHallPinState = 0;     // previous state of the button
volatile int  HallPin = 7;    // the pin that the hall sensor is attached to


void setup() {
  // put your setup code here, to run once:
  pinMode(HallPin, INPUT);
  attachInterrupt(HallPin, Umdrehungen, CHANGE );
  Serial.begin(9600);
}

void loop() {

  Serial.println(HallPinCounter);
  //Serial.println("HallPin"), Serial.print(digitalRead (HallPin));
}


void Umdrehungen () {
  HallPinState = digitalRead(HallPin);
  // compare the HallPinState to its previous state
  if (HallPinState != lastHallPinState) { // if the state has changed, increment the counter
    if (HallPinState == HIGH) {
      HallPinCounter++;     // if the current state is HIGH then the button went from off to on:
    }
    //delay(20);
  }

  lastHallPinState = HallPinState; // save the current state as the last state, for next time through the loop
}

Ich meine zu verstehen was ein Interrupt bräuchte (bspw. keine Delays) und ich meine auch zu verstehen was mein Sketch an der Stelle macht. Aber diese zwei Dinge zu kombinieren bin ich ausserstande :roll_eyes: . Die Interrupt-Beispiele die ich gefunden habe sind immer "simpler" weswegen mir das adaptieren wahnsinnig schwer fällt.

Verwendete Hardware:

  • Arduino Micro
  • 12V Getriebemotor gesteuert mit L298n, Poti & Toggle
  • 128*64 Oled Display
  • DEBO SENS HALL A3141

Was muss ich tun um dieses Problem in den Griff zu bekommen?

Es ist etwas seltsam dass die einen Interrupt auf beide Flanken auslöst und dann die Art der Flanke abfrägst. Du kannst auch gleich Interrupts nur bei steigenden Flanken auslösen

Ansonsten:
1.) volatile ist für Variablen die Interrupts geändert werden. Für Pin-Definitionen brauchst das nicht. Da ist const schon richtig
2.) Multi-Byte Variablen die in Interrupts geändert werden musst du bei abgeschalten Interrupts auslesen
3.) delay() (und Serial) hat in Interrupts nichts verloren

Und generell vielleicht mal etwas über Variablentypen und Wertebereiche nachdenken anstatt für alles int zu verwenden

pinMode(HallPin, INPUT);

attachInterrupt(HallPin, Umdrehungen, CHANGE );

Das funktioniert wohl nur auf ARM Arduinos.
Mit AVR Arduinos nicht.

Das steht auch klipp und klar in der attachInterrupt() Doku.

Ja, das ist das Hauptproblem. Man übergibt da nicht die Pin-Nummer, sondern die Nummer des Interrupts. Pin 7 auf dem Micro wäre INT4. Es gibt digitalPinToInterrupt() um das umzurechnen!

Hi

PCINT funktioniert - meines Wissen - auf allen zugänglichen Pins (der AVR-Arduino).
Dort wäre wieder die Abfrage der Flanke nötig, da PinCangeINTerrupt halt auf sämtliche Änderungen - also beide Flanken - reagiert.

Ob der Arduino-Pin ein Interrupt_Pin ist -> die Funktion digitalPinToInterrupt() gibt -1 zurück, wenn der Pin keinen Interrupt unterstützt.
PCINT ist hiervon ausgeschlossen - man muß aber, wenn man mehrrere Pins so abfragt, selber testen, welcher Pin sich nun verändert hat, da (alle? müsste nachlesen - zumindest die Pins eines Port) Pins den gleichen Interrupt (eben PCINT) auslösen und somit in der gleichen ISR landen.
Auf Uno/Nano kommt Das raus:

  Serial.println(digitalPinToInterrupt(2));
  Serial.println(digitalPinToInterrupt(3));
  Serial.println(digitalPinToInterrupt(4));
-->
16:31:14.588 -> 0
16:31:14.588 -> 1
16:31:14.588 -> -1

D2 hat den Interrupt 0, Pin 3 den Interrupt 1, Pin 4 hat Keinen.

MfG

Vielen Dank erstmal für die Inputs. Habe unterdessen weiter recherchiert -dank eurer Hilfe in die richtige Richtung- und den Code angepasst. Sprich der INT-Pin funktioniert und Variabelnmässig habe ich auch optimiert. In der Doku habe ich noch eine Möglichkeit zum debouncen via "delayMicroseeconds" gefunden.

Das mit den multi-byte habe ich nicht verstanden wäre es möglich das etwas zu erläutern?

Der Code funktioniert grundsätzlich und wäre für meine Zwecke ausreichend genau. Problem ist nur noch, dass beim beginnen und tiefen Drehzahlen in 2er oder 3er Schritten gezählt wird. Erst wenn ich Tempo gebe wird es genauer. Die "delayMicroseeconds" haben darauf Einfluss wie ich herausgefunden habe aber klar ist es mir dennoch nicht. Ich würde es gerne für mein Verständnis noch perfektionieren obwohl es nun seinen technisch Zweck erfüllt.

Das implementieren in den "Gesamtcode" hat funktioniert aber der Teil war ja nicht der problematische. Deswegen hier wieder das Elementare:

volatile int HallPinCounter = 0;   // counter for the number of button presses
volatile boolean HallPinState = false;         // current state of the button
volatile boolean lastHallPinState = false;     // previous state of the button
const int  HallPin = 7;    // the pin that the hall sensor is attached to

void setup() {
  // put your setup code here, to run once:
  pinMode(HallPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(7), Umdrehungen, HIGH );
  Serial.begin(9600);
}

void loop() {

  Serial.println(HallPinCounter);
  //Serial.println("HallPin"), Serial.print(digitalRead (HallPin));
}

void Umdrehungen () {
  HallPinState = digitalRead(HallPin);
  // compare the HallPinState to its previous state
  if (HallPinState != lastHallPinState) { // if the state has changed, increment the counter
    if (HallPinState == HIGH) {
      HallPinCounter++;     // if the current state is HIGH then the button went from off to on:
    }
     delayMicroseconds(20000);
  }

  lastHallPinState = HallPinState; // save the current state as the last state, for next time through the loop
}

Das mit den multi-byte habe ich nicht verstanden wäre es möglich das etwas zu erläutern?

Falsch:

Serial.println(HallPinCounter);

Richtig:

noInterrupts();
auto tmp = HallPinCounter;
interrupts();
Serial.println(tmp);

Die "delayMicroseeconds" haben darauf Einfluss

Auch das hat in ISRs nichts verloren. Interrupt Routinen sollten so kurz wie möglich sein

Und wie gesagt:
Wieso implementierst du eine manuelle Flankenerkennung wenn man das in Hardware tun kann? Das braucht man nur bei PinChange Interrupts

Happy_Sam:
Ich meine zu verstehen was ein Interrupt bräuchte (bspw. keine Delays)

Und warum dann dieses in der ISR:

    delayMicroseconds(20000);

Das ist ein delay von 20ms! in der ISR - das ist ein absolutes nogo.
Ausserdem musst Du in der ISR nicht abfragen, ob sich der Input geändert hat. Wenn er sich nicht geändert hätte, wäre der Interrupt nicht ausgelöst worden.
Wenn es bei langsamen Drehzahlen zu Mehrfachauslösungen kommt, musst Du das gegebenenfalls durch Messen der Zeit zwischen 2 Interrupts erkennen.