[Arduino Mega] Blinkende LED mit variabler Frequenz

Hallo Zusammen :slight_smile:

Ich bin auf der Suche nach etwas Hilfe, da ich momentan mit meinen Programmierarbeiten am Arduino nicht weiter komme.

Folgender Plan: Ich habe eine 1 Watt LED √ľber einen kleinen Transistor am Arduino angeschlossen (PIN 12). Soweit funktioniert das.
Des Weiteren habe ich einen Drehgeber (ALPS STEC11B03) angeschlossen. Funktioniert soweit auch.

Mit dem Drehgeber möchte ich der Frequenz, mit welcher die LED blinkt, im Betrieb vorgeben. Das ganze von 0… 100 Hz.
Um die LED blinken zu lassen, nutze ich eine Timer Bibliothek, funktioniert soweit super.
Mein Problem ist jetzt folgendes: Der Drehgeber spuckt zur Zeit nur Mist aus. Inkrementieren klappt soweit ganz gut, es gibt aber auch hier kleine Spr√ľnge. Dekrementieren leider nicht so ganz. Der springt sporadisch, beispielsweise von 30 auf 5.
Des Weiteren scheint mein Ansatz in der main nicht zu funktionieren. Im Prinzip m√∂chte ich die aktuelle Frequenz auslesen ( = Position des Drehgebers). Wenn sich diese ge√§ndert hat, wird der Timer f√ľr die LED mit der neuen Frequenz beschrieben. Andernfalls passiert nichts.

Habt ihr vlt. einen Ansatz, wie man hier am besten rangehen k√∂nnte? ich bin f√ľr jeden Tipp dankbar :slight_smile:

Den programmcode habe ich im Anhang beigef√ľgt. Gibt es eine M√∂glichkeit, diesen hier direkt sichtbar zu posten?

MFG

#include <TimerOne.h>

#define ledPin          13    // Nummer des LED Pins
#define encoderPinA      2    // Drehgeber Pin A -> Pin A = Weiß
#define encoderPinB      3    // Drehgeber Pin B -> Pin B = Braun
#define startFrequency   0    // Startfrequenz, bevor diese durch den Encoder geändert wird
#define maxFrequency   100    // Maximale Frequenz
#define minFrequency     0    // Minimale Frequenz

// Enth√§lt die Encoder Position, Startposition = 0 (f√ľr 0 Hz)
volatile unsigned int encoderPos = startFrequency;

// frequency setzt die gewählte Frequenz, oldFrequency dient der Abfrage in der main-Schleife
// ob sich etwas an der Frequenz geändert hat.    
unsigned long frequency, oldFrequency;         

int ledState = LOW;                           // Bestimmt den LED Status (AN / AUS)

void setup() {
  pinMode(ledPin, OUTPUT);                    // Pin der LED als Ausgang definieren
  pinMode(encoderPinA, INPUT);                // Pin A als Eingang definieren
  digitalWrite(encoderPinA, HIGH);            // Pin A via internen Pull-up anschließen
  pinMode(encoderPinB, INPUT);                // " "
  digitalWrite(encoderPinB, HIGH);            // " " 

¬† attachInterrupt(0, doEncoder, CHANGE);¬† ¬† ¬† // Interrupt bei √Ąnderung an Pin 2
  
  Timer1.initialize(frequency);               // Interrupt Timer 1 init
¬† Timer1.attachInterrupt(blinkLED);¬† ¬† ¬† ¬† ¬†  // Auszuf√ľhrende Funktion beim Interrupt

  Serial.begin (9600);
}

// Interrupt Handler f√ľr √Ąnderung am encoder Pin A
void doEncoder(void) {
  if (digitalRead(encoderPinA) == digitalRead(encoderPinB)) {
    if (encoderPos < maxFrequency) encoderPos++;
  }
  else {
    if (encoderPos > minFrequency) encoderPos--;  
  }
}

// Interrupt Handler f√ľr Timer 1
void blinkLED(void) {
  ledState = !ledState;
  digitalWrite(ledPin, ledState);
}

void loop() {
  // static = Variable wird einmal in der Funktion initiiert
  // timeVal ist nur innerhalb des loop() aufrufbar
  static unsigned long timeVal;             
  noInterrupts();
  frequency = encoderPos;
  interrupts();
  if (frequency != oldFrequency) {
    if (frequency > minFrequency){
      timeVal = (1/frequency)*1000000;      // t = 1/f * 10^6 (da Timer in us)  
      Timer1.setPeriod(timeVal);  
      Serial.println(timeVal,DEC);
      Serial.println(frequency,DEC);
      Serial.println("----------");
    }
    else Timer1.setPeriod(minFrequency);
    oldFrequency = frequency;    
  }
  delay(1000);
}

Strobelight_v2.ino (2.39 KB)

ArduinoUser2k16: Den programmcode habe ich im Anhang beigef√ľgt. Gibt es eine M√∂glichkeit, diesen hier direkt sichtbar zu posten?

In Postings bis zu 9000 Zeichen Gesamtlänge kannst Du den Code 'inline' innerhalb von Code -Tags posten.

Schau mal in das sticky Posting "How to use this Forum". Da ist ein Link auf ein gleichnamiges Forumsposting drin und dort Punkt 7.

Ganzzahlige Rechteck-Frequenzen ab 32 Hz aufwärts kannst Du am bequemsten mit der Arduino "tone()" Funktion ausgeben, wenn es Dir nicht auf das allerletzte Quentchen Frequenzgenauigkeit ankommt.

F√ľr Frequenzen unterhalb 32 Hz (oder mit Hz-Bruchteilen) m√ľ√ütest Du dann wohl selbst ein paar Zeilen Code schreiben.

Und wenn Dein Drehgeber beim Z√§hlen "springt", dann steuerst Du ihn wahrscheinlich generell falsch an, z.B. mit "Hardware-Interrupts" oder "PinChange-Interrupts", statt das Teil vern√ľnftig auszulesen, z.B. ganz einfach per Polling oder mit h√∂herem Programmieraufwand durch zeitgesteuertes Sampling in Timer-Interrupts.

timeVal = (1/frequency)*1000000;

Das funktioniert nicht, da 1/frequency ein Bruch ist, der in unsigned long nicht dargestellt werden kann. Das geht:

timeVal = (1000000UL / frequency);

Wichtig ist UL, da der Standard int ist (beim Mega).

Problematisch ist auch

else Timer1.setPeriod(minFrequency);

setPeriod möchte eine Zeit, keine Frequenz. Das geht:

Timer1.setPeriod(1000000UL);

Hier kann UL stehen.

Bei der Initialisierung

 Timer1.initialize(1000000UL);               // Interrupt Timer 1 init

gilt das ebenso.

timeVal = (1000000UL / frequency);

Wichtig ist UL, da der Standard int ist (beim Mega).

Na ja, immerhin ist es zum Gl√ľck so definiert:

     unsigned long frequency;

aber (1/frequency) ist 0, und das ist nicht das gew√ľnschte, da hast du schon recht.

in deinem setup() sehe ich

  Timer1.initialize(frequency);               // Interrupt Timer 1 init
  • frequency ist noch undefiniert, als globale Variable also 0 :confused:

  • die Methode ist so definiert: initialize (unsigned long microseconds=1000000); ( braucht also eher einen Wert wie 1000000UL/minFrequency oder gar keinen Parameter )

jurs:
Ganzzahlige Rechteck-Frequenzen ab 32 Hz aufw√§rts kannst Du am bequemsten mit der Arduino ‚Äútone()‚ÄĚ Funktion ausgeben, wenn es Dir nicht auf das allerletzte Quentchen Frequenzgenauigkeit ankommt.

Danke erstmal f√ľr den Tipp :slight_smile: Die Tone-Funktion ist daf√ľr nat√ľrlich super. Funktioniert soweit auch ab den 31 Hz. Um das ganze f√ľr <31 Hz lauff√§hig zu bekommen, habe ich gelesen, man sollte einen 16 Bit Timer verwenden. Davon hat das Arduino Mega ja mehrere. Also dachte ich mir, ich passe einfach mal die ‚ÄúTone.cpp‚ÄĚ im Ordner cores an. In der cpp kann man direkt den Timer definieren, der verwendet werden soll. Nur leider funktioniert die Tone-Funktion jetzt nicht mehr in meinem Programm‚Ķ Gibt es einen weg, wie ich diese wieder zum laufen bekomme? Ich vermute es liegt an meiner Anpassung, das Backup der ‚ÄúTone.cpp‚ÄĚ akzeptiert die Aduino IDE irgendwie auch nicht mehr :confused:

jurs:
Und wenn Dein Drehgeber beim Z√§hlen ‚Äúspringt‚ÄĚ, dann steuerst Du ihn wahrscheinlich generell falsch an, z.B. mit ‚ÄúHardware-Interrupts‚ÄĚ oder ‚ÄúPinChange-Interrupts‚ÄĚ, statt das Teil vern√ľnftig auszulesen, z.B. ganz einfach per Polling oder mit h√∂herem Programmieraufwand durch zeitgesteuertes Sampling in Timer-Interrupts.

Super Tipp :smiley: Danke dir daf√ľr :slight_smile: Ich habe einige Varianten mit Interrupts probiert, einige funktionierten, andere gar nicht. Am zuverl√§ssigsten ist es aber wirklich, sobald ich die ‚ÄúPolling‚ÄĚ-Variante verwende. Klappt super :wink:

agmue:

timeVal = (1/frequency)*1000000;

Das funktioniert nicht, da 1/frequency ein Bruch ist, der in unsigned long nicht dargestellt werden kann. Das geht:

timeVal = (1000000UL / frequency);

Wichtig ist UL, da der Standard int ist (beim Mega).

Ich dachte mir schon, das es Probleme mit den Datentypen geben wird. Meine Hoffnung war, dass im Endeffekt nur der Teil vor dem Komma √ľbernommen wird.

agmue:
Problematisch ist auch

else Timer1.setPeriod(minFrequency);

setPeriod möchte eine Zeit, keine Frequenz. Das geht:

Timer1.setPeriod(1000000UL);

Ja, das habe ich wohl etwas blöd definiert. MinFrequency ist ja 0, und das hat er bei mir so akzeptiert :slight_smile: Aber sauberer wäre wohl eine andere Bezeichnung.

michael_x:
in deinem setup() sehe ich

  Timer1.initialize(frequency);               // Interrupt Timer 1 init
  • frequency ist noch undefiniert, als globale Variable also 0 :confused:

Das habe ich so als Startbedingung vorgesehen. Sprich, zu Beginn soll der Timer mit 0 Hz initialisiert werden, was im Endeffekt als Dauerleuchten der LED resultiert :slight_smile:


Das ist der aktuelle Stand vom Programm (etwas geändert):

#define ledPin          12    // Led Pin 
#define encoderPinA      2    // Drehgeber Pin A -> Pin A = Weiß
#define encoderPinB      3    // Drehgeber Pin B -> Pin B = Braun      

int valA;¬† ¬† ¬† ¬† ¬† ¬† ¬† ¬† ¬†  // Variable f√ľr Pin A Encoder
int valB;¬† ¬† ¬† ¬† ¬† ¬† ¬† ¬† ¬†  // Variable f√ľr Pin B Encoder
int oldVal;¬† ¬† ¬† ¬† ¬† ¬† ¬† ¬†  // Puffer f√ľr alten Wert von Pin A
int encoderVal;¬† ¬† ¬† ¬† ¬† ¬†  // Variable f√ľr aktuellen Encoderwert
int frequency = 31;         // Enthält die aktuelle Frequenz 
//int ledState = LOW;         // LED-Status, zu Beginn AUS

void setup() {
  pinMode(ledPin, OUTPUT);                    // Pin der LED als Ausgang definieren
  pinMode(encoderPinA, INPUT);                // Pin A als Eingang definieren
  digitalWrite(encoderPinA, HIGH);            // Pin A via internen Pull-up anschließen
  pinMode(encoderPinB, INPUT);                // " "
  digitalWrite(encoderPinB, HIGH);            // " " 
  Serial.begin (9600);
}

int encoder_read() {
  int rc = 0;                                 // Hilfsvariable
  valA = digitalRead(encoderPinA);
  valB = digitalRead(encoderPinB);
  if (valA != oldVal) {
    if (valA == valB) rc = 1;
    else rc = -1;
    oldVal = valA;
  }
  return rc;
}

void loop() {
  encoderVal = encoder_read();
  if (encoderVal != 0) {
    if (encoderVal == 1 && frequency < 100) 
      frequency += encoderVal;
    if (encoderVal == -1 && frequency > 31)
      frequency += encoderVal;
    tone(12,frequency);
    Serial.println (frequency);
  }
}

Soweit funktioniert das super, aber halt nur bis 31 Hz. Kleiner gehts momentan nicht wegen dem verwendeten Timer2 der Tone(). Und momentan gehts halt gar nicht mehr, da ich an der Tone() rumgebastelt hab und die Arduino IDE diese jetzt nicht mehr akzeptiert. Wie kann ich diese Problem beheben?

Danke nochmals f√ľr die Antworten :slight_smile:

MfG

In Tone.cpp steht das:

#else

#define AVAILABLE_TONE_PINS 1
#define USE_TIMER2

// Leave timer 0 to last.
const uint8_t PROGMEM tone_pin_to_timer_PGM[] = { 2 /*, 1, 0 */ };
static uint8_t tone_pins[AVAILABLE_TONE_PINS] = { 255 /*, 255, 255 */ };

#endif

Da musst du wahrscheinlich sowohl USE_TIMER2 ändern als auch die erste Zahl im oberen Array

Das gilt f√ľr den UNO. F√ľr andere Prozessoren musst du andere Abschnitte √§ndern

F√ľr eine funktionierende Version der Datei kannst du dir die .zip Version der IDE runterladen

Serenifly: Da musst du wahrscheinlich sowohl USE_TIMER2 ändern als auch die erste Zahl im oberen Array

Das gilt f√ľr den UNO. F√ľr andere Prozessoren musst du andere Abschnitte √§ndern

Super :D Das hat geklappt :) Danke dir f√ľr den Tipp ;) Kann jetzt runtergehen bis 1 Hz :)

Mahlzeit :)

Nach einem kurzem Test mit einem Tischl√ľfter, ist mir ein Flackern aufgefallen, welches ich nun durch variieren der Pulsbreite beseitigen m√∂chte. Sprich, der L√ľfter soll, bei entsprechender Frequenz des Stroboskops, "stillstehen" und nicht flackern. Momentan sieht es so aus, als wenn der L√ľfter minimal hin- und her "t√§nzelt" bzw. flackert.

Mein erster Ansatz wäre das ganze per PWM umzusetzen. Eine Realisierung via delay() fällt aus, da ich zusätzlich noch ein Display anschließen werde. Ich denke mit dem Drehgeber + Display könnte ich unter Verwendung der Delay-Funktion Probleme mit der Genauigkeit bekommen. Eine Möglichkeit bei der momentan verwendeten tone-Funktion die Pulsbreite anzupassen, habe ich auf die schnelle nicht gefunden.

W√ľrdet ihr den Ansatz via PWM empfehlen? Es muss halt definitiv die M√∂glichkeit bestehen, Pulsbreite und Frequenz im laufenden Betrieb via Drehgeber anzupassen.

MfG & ein angenehmes Wochenende :)

Guten Abend zusammen :slight_smile:

Ich habe etwas weiter gebastelt an meinem Projekt. Mittlerweile ist ein kleines 16x2 LCD hinzugekommen, um die aktuelle Frequenz und Pulsbreite anzuzeigen. Leider habe ich immer noch ein kleines Problem: Sobald ich das ganze an einem L√ľfter teste, steht dieser nicht 100% still, obwohl er das ja bei der richtigen Frequenz tun sollte.

Dazu mal ein kleines Youtube-Video von mir:
Klick mich

Am Anfang √§ndere ich die Frequenz auf 49 Hz und passe anschlie√üend die Pulsbreite an, damit die Rotorbl√§tter nicht so ‚Äúverschwommen‚ÄĚ wirken. Der L√ľfter sollte dann eigentlich stillstehen, tut er aber nicht so richtig‚Ķ

Habt ihr eine Idee, woran das liegen k√∂nnte? Meine Vermutung ist, dass es entweder am L√ľfter liegt, oder dass ich quasi irgendeinen Wert zwischen 49 Hz und 50 Hz brauche. Leider habe ich noch keine M√∂glichkeit gefunden, Frequenzen mit Nachkomma-Stellen zu √ľbergeben‚Ķ Falls ihr eine Idee habt, freue ich mich √ľber jedes Feedback :slight_smile:

Momentan steuere ich das ganze √ľber eine PWM Bibliothek:

#include <Wire.h>                 
#include <LCD.h>                  
#include <LiquidCrystal_I2C.h> 
   
#include <ClickButton.h>
#include <Encoder_Polling.h>
#include <PWM.h>

// Definitions for 16x2 LCD
#define I2C_ADDR          0x27  
#define BACKLIGHT_PIN        3
#define En_pin               2
#define Rw_pin               1
#define Rs_pin               0
#define D4_pin               4
#define D5_pin               5
#define D6_pin               6
#define D7_pin               7

// More Definitions...
#define ledPin          11    // External Led Pin
#define ledPin2         13    // Internal LED 
#define buttonPin        2    // Pin Pushbutton
#define encoderPinA      3    // Encoder Pin A -> Pin A = White
#define encoderPinB      4    // Encoder Pin B -> Pin B = Brown      

unsigned long previousMillis = 0;   // Used for display
const long interval = 500;          // Refresh rate of display (500 ms)


//Pulse width in 1% steps. 100% = 255
int pulses[101] = {0, 3, 5, 8, 10, 13, 15, 18, 20, 23, 26, 28, 31, 33, 36, 38, 41, 43, 46, 48, 51,
                   54, 56, 59, 61, 64, 66, 69, 71, 74, 77, 79, 82, 84, 87, 89, 92, 94, 97, 99, 102,
                   105, 107, 110, 112, 115, 117, 120, 122, 125, 128, 130, 133, 135, 138, 140, 143, 
                   145, 148, 150, 153, 156, 158, 161, 163, 166, 168, 171, 173, 176, 179, 181, 184, 
                   186, 189, 191, 194, 196, 199, 201, 204, 207, 209, 212, 214, 217, 219, 222, 224,
                   227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, 255};   

int frequency = 0;            // [Hz]
int oldfrequency = 0;         
int pulseWidth = 50;          // Defines pulse width. Initial value = 50%.
int oldpulseWidth = 50;       
int clicked = LOW;            

int ledState2 = LOW;          // Internal LED

// Init 16x2 LCD
LiquidCrystal_I2C  lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

// Init Pushbutton; LOW = State, when button is pushed; Use internal pull up
ClickButton button1(buttonPin, LOW, CLICKBTN_PULLUP);     

void setup() {
  lcd.begin (16,2);                           // Define display size (16 x 2)
  
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);  // Init Backlight of display
  lcd.setBacklight(HIGH);                     // Switch on display backlight
  lcd.home (); 

  // Initial message on display:
  lcd.print("Frequenz: ");
  lcd.print(frequency, DEC);
  lcd.print(" Hz");
  lcd.setCursor (0,1);
  lcd.print("Pulsbreite: ");
  lcd.print(pulseWidth, DEC);
  lcd.print("%");
  
  InitTimersSafe();                           // Used for PWM library
  encoder_begin(encoderPinA, encoderPinB);    // Init encoder
  digitalWrite(encoderPinA, HIGH);            // Use internal pull up for encoder
  digitalWrite(encoderPinB, HIGH);      
 
  button1.debounceTime = 20;                  // Button debounce time in [ms]
  pinMode(ledPin, OUTPUT);                   
  pinMode(ledPin2, OUTPUT);
}

// Print new values on display
void display_write(int frequency, int pulseWidth){
  // Only print values, if values changed...
  if (oldfrequency != frequency || oldpulseWidth != pulseWidth){
    oldfrequency = frequency;
    oldpulseWidth = pulseWidth;
    lcd.clear();                  

    // Print new values:
    lcd.print("Frequenz: ");
    lcd.print(frequency, DEC);
    lcd.print(" Hz");
    lcd.setCursor (0,1);
    lcd.print("Pulsbreite: ");
    lcd.print(pulseWidth, DEC);
    lcd.print("%");
  }
  
}
  
void loop() {
  unsigned long currentMillis = millis(); // Used for timing (display)
  int dir = encoder_data();               // Check encoder. Gives back 1 or -1.
  button1.Update();                       // Update button state.

  // Button is used, to differ between changing frequency or changing pulse width
  if (button1.clicks == 1) {              
    clicked = !clicked;
    ledState2 = !ledState2;
    digitalWrite(ledPin2, ledState2);     // Internal Led lights up, when pulse width is changed.
  }

  if (!clicked){                          // Frequency
    if (dir == 1 && frequency < 200) {    // Increment [maximum 200 Hz]
      frequency++;
      SetPinFrequencySafe(ledPin, frequency); // Set new frequency
      pwmWrite(ledPin, pulses[pulseWidth]);   // Required to activate new frequency on LED pin    
    }  
    if (dir == -1 && frequency > 0) {     // Decrement [minimum 0 Hz]
      frequency--;
      SetPinFrequencySafe(ledPin, frequency);
      pwmWrite(ledPin, pulses[pulseWidth]); 
    }
  }

  if (clicked) {                          // Pulse width
    if (dir == 1 && pulseWidth < 100) {   // increment [max. 100%]
      pulseWidth+= 1;
      pwmWrite(ledPin, pulses[pulseWidth]); // Set new pulse width on led pin
    }  
    if (dir == -1 && pulseWidth > 0) {    // decrement [min. 0%]
      pulseWidth-= 1;
      pwmWrite(ledPin, pulses[pulseWidth]);
    }     
  }

  // Update display only every 500 ms
  if (currentMillis - previousMillis >= interval) {           
    previousMillis = currentMillis;
    display_write(frequency, pulseWidth);
  }
}

Danke schon mal f√ľr euer Feedback :slight_smile:
Angenehmes Wochenende :wink:

Sobald ich das ganze an einem L√ľfter teste, steht dieser nicht 100% still, obwohl er das ja bei der richtigen Frequenz tun sollte.

Du setzt voraus, dass der L√ľfter sich mit absolut konstanter Geschwindigkeit dreht. Da w√§re ich mir nicht so sicher.

Gruß,

Helmuth

Hallo Helmut :)

Erstmal danke f√ľr dein Feedback :) Ich gebe dir Recht, der L√ľfter hat keine konstante Drehzahl. Das Ganze habe ich nochmal beim Schleudergang meiner Waschmaschine getestet, leider kann ich nicht genau die Frequenz einstellen, bei der die Trommel zum Stillstand kommt... Man hat bei der Waschmaschine aber wenigstens eine konstante Drehzahl gesehen. (lasst euch nicht verwirren, am Anfang habe ich nochmal mit der Pulsbreite rumgespielt. Danach habe ich nochmal die Frequenz variiert, entweder dreht die Trommel minimal vorw√§rts oder minimal r√ľckw√§rts...).

Seht selbst: Klick

Gibt es eine elegante Möglichkeit via PWM die Frequenzen mit einer höheren Auflösung einzustellen? Sprich vlt. in 0,1 - 0,5 Hz-Schritten? Damit könnte ich die Frequenz bestimmt treffen, bei der die Trommel zum Stillstand kommt.

Gr√ľ√üe,

Niko