Posts mit dem Label LED werden angezeigt. Alle Posts anzeigen
Posts mit dem Label LED werden angezeigt. Alle Posts anzeigen

Montag, 9. Mai 2022

Karosseriebeleuchtung mit PCA9685 (Zusatz)

Eine Sache ist noch offen: Bei meinem vorigen Blogpost konnte man in den Videos sehen, dass die LEDs beim Fade-OUT nie ganz aus waren.

Benötigt werden:

  • Arduino
  • PWM Modul PCA9685
  • LEDs und Widerstände

Ziel

Die LEDs sollen im Fade-OUT komplett ausgehen. Asynchrone Animationsabläufe.


Ursache

Es lag daran, dass der Wert nie den Endwert von 4095 erreicht hat. Auch, wenn das High Level nur sehr kurz anliegt, reicht die Spannungsversorgung aus, um einen LED noch glimmen zu lassen.

Daher liegt die Lösung nahe, dass am Ende eines Schleifendurchgang nochmal ein Signal-Zustand festgelegt wird, mit dem genannten Endwert. Jedoch soll das für die asynchronen Vorgänge auch funktionieren. Hier nochmal das Beispiel, in denen die LEDs nicht ganz aus sind.

 #include <Wire.h>  
 #include <Adafruit_PWMServoDriver.h>  
 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();  
 void setup() {  
  pwm.begin();  
  pwm.setPWMFreq(1600);  
  Wire.setClock(400000);  
 }  
 void loop() {  
  int stepAdd = 10;  
  int leds = 4;  

  for (uint16_t i=0; i<4096; i += stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, 4096 - i);  
   }  
  }  
  delay(1000);  
  for (uint16_t i=0; i<4096; i += stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, i);  
   }  
  }  
  delay(2000);  
 }  

Einfache Lösung

Mit der bekannten Schrittweite kann nach dem Schleifendurchlauf bedingt die PWM Ports auf den Max Wert eingestellt werden.

 #include <Wire.h>  
 #include <Adafruit_PWMServoDriver.h>  
 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();  
 void setup() {  
  pwm.begin();  
  pwm.setPWMFreq(1600);  
  Wire.setClock(400000);  
 }  
 void loop() {  
  int stepAdd = 10;  
  int leds = 4;  

  for (uint16_t i=0; i<4096; i += stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, 4096 - i);  
   }  
  }  
  delay(1000);  
  uint16_t iSetValue = 0;  
  for (uint16_t i=0; i < 4096; i += stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, i);  
   }  
   iSetValue = i;  
  }  
  if(iSetValue > 4095 - stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, 4095);  
   }  
  }  
  delay(2000);  
 }  

Lösung für asynchrone Vorgänge

Das gleiche kann auch für den asynchronen Vorgang verwendet werden. Jedoch hat die bisherige Lösung einen Nachteil: Dass sie schnell unübersichtlich wird. 

Die Beispiele habe ich oft nur mit zwei LEDs abgebildet, aber für 16 LEDs wird's schlecht leserlich.

Deshalb gehe ich hier mit der Beispiellösung einen Schritt weiter und lege ein Objekt an, in dem ich für die einzelne LED den Zustand hinterlegen kann.

 #include <Wire.h>  
 #include <Adafruit_PWMServoDriver.h>  
 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();  
 unsigned long mCurrentMillis = 0;  
 void setup() {  
  pwm.begin();  
  pwm.setPWMFreq(1600);  
  Wire.setClock(400000);  
  // schaltet alle aus  
  for (uint8_t pwmnum=0; pwmnum < 16; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, 4095);  
  }  
 }  
 // status objekt für eine LED  
 typedef struct {  
   unsigned long LastMillis;      // letzte Zeitstand  
   unsigned long DistanceFromNowToLast;// Intervall länge  
   uint16_t Value;           // aktuell verwendeter Wert  
   bool IsFlashing;          // soll nur an und aus  
   bool IsGoingOn;           // Schritt soll heller   
                     // oder an sein  
 } ledStateType;  
 // Konfiguration einzelner LEDs  
 ledStateType mLEDs[] = {  
  { 0, 5, 0, false, true },       // fade animation  
  { 0, 1000, 0, true, true }      // flashing  
 };  
 void PrepareLED(int ledIndex) {  
  int stepAdd = 10;  
  unsigned long rest = mCurrentMillis - mLEDs[ledIndex].LastMillis;  
  if(rest < mLEDs[ledIndex].DistanceFromNowToLast) {  
   return;  
  }  
  if(mLEDs[ledIndex].IsFlashing) {  
   if(mLEDs[ledIndex].IsGoingOn) {  
    mLEDs[ledIndex].Value = 4095;  
    mLEDs[ledIndex].IsGoingOn = false;  
   }  
   else {  
    mLEDs[ledIndex].Value = 0;  
    mLEDs[ledIndex].IsGoingOn = true;  
   }  
  }  
  else {  
   if(mLEDs[ledIndex].IsGoingOn) {  
    if(mLEDs[ledIndex].Value > stepAdd) {  
     mLEDs[ledIndex].Value -= stepAdd;  
    }  
    else {  
     mLEDs[ledIndex].Value = 0;  
     mLEDs[ledIndex].IsGoingOn = false;  
    }  
   }  
   else {  
    if(mLEDs[ledIndex].Value < 4096) {  
     mLEDs[ledIndex].Value += stepAdd;  
    }  
    if(mLEDs[ledIndex].Value > 4095 - stepAdd) {  
     mLEDs[ledIndex].Value = 4095;  
     mLEDs[ledIndex].IsGoingOn = true;  
    }  
   }  
  }  
  mLEDs[ledIndex].LastMillis = mCurrentMillis;  
 }  
 void loop() {  
  mCurrentMillis = millis();  
  for(int i = 0; i < 2; i++) {  
   PrepareLED(i);  
   pwm.setPWM(i, 0, mLEDs[i].Value);  
  }  
 }  



Natürlich ist das nur eine Möglichkeit, wie der Programmablauf aussehen könnte. Der geübte C++ Programmierer würde das sicherlich anders lösen können.



Vorheriger Blog Post:

Sonntag, 24. April 2022

Karosseriebeleuchtung mit PCA9685


Ein Teil des Ausbaus meines FPV Crawler bestand darin, die LEDs zu montieren und entsprechend mit Energie zu versorgen. Natürlich reichte nicht ein einfaches Anschließen an die Versorgungsspannung aus. Denn die LEDs sollten je nach ihrer Aufgabe eingeschaltet werden, was zunächst einfach klingt.
Vom Gedanken bis zur Umsetzung zeigte sich ein deutliche höherer Aufwand beim Verkabeln mehrere Leitungen und beim Programmieren des PWM Moduls, als gedacht.


Benötigt

  • Arduino
  • PWM Modul PCA9685
  • LEDs und Widerstände

 

Ziel

Standlicht, Fahrlicht, Blinker und Flutlichter sollten alle individull an-steuerbar sein. Hier sollen die Lichter immer durch ein Fade-Effekt abgebildet werden.

 

Initialisieren

Für die folgenden Beispiele verwendete ich immer die gleiche Initialisierung.
 #include <Wire.h>  
 #include <Adafruit_PWMServoDriver.h>  
 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();  
 void setup() {  
  pwm.begin();  
  pwm.setPWMFreq(1600);  
  Wire.setClock(400000);  
  // schaltet alle aus  
  for (uint8_t pwmnum=0; pwmnum < 16; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, 4095);  
  }  
 }  


Fade-In und Fade-Out

Leider kann man nicht einfach einen Wert von 0 bis 256 in den Controller eingeben, um Animationsverläufe darin abzubilden. Wenn man versucht auf diese Weise die LEDs anzusteuern, dann funktioniert das schlecht oder gar nicht. Zum Glück ist bereits ein Beispiel Code in den Sourcen für das PCA9685 Modul integriert, dass genau diese Funktion darstellt.


Mit dem Adafruit Beispiel  'pwmtest' wird gezeigt, wie das Zuweisen um-gesetzt wird. Dort sieht man, dass nicht immer plus eins, sondern immer plus acht addiert wird. Das ist sicherlich auf die 12Bit pro Port zurück zu führen.

 
void loop() {  
  int stepAdd = 10;  
  int leds = 4;  
  for (uint16_t i=0; i<4096; i += stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, 4096 - i);  
   }  
  }  
  delay(1000);  
  for (uint16_t i=0; i<4096; i += stepAdd) {  
   for (uint8_t pwmnum=0; pwmnum < leds; pwmnum++) {  
    pwm.setPWM(pwmnum, 0, i);  
   }  
  }  
  delay(1000);  
 }


Asynchroner Ablauf

Für mein Ziel wird mehr als eine LED und neben dem Modul noch WS2812b RGB LEDs eingesetzt. Damit ich nicht für jede LED einen eigenen Controller benötige, muss ich sicherstellen, wie schnell etwas aus- oder eingeblendet werden soll.

 unsigned long mCurrentMillis;  
 unsigned long mLastMillis_LED_1;   
 unsigned long mLastMillis_LED_2;  
 uint16_t mValueLED_1 = 0;  
 uint16_t mValueLED_2 = 0;  
 void loop() {  
  uint16_t endValue = 4096;  
  int stepAdd = 10;  
  int leds = 4;  
  mCurrentMillis = millis();  
  if(mCurrentMillis - mLastMillis_LED_1 > 5) {  
   if(mValueLED_1 < endValue) {   
    mValueLED_1 += stepAdd;  
    pwm.setPWM(0, 0, mValueLED_1);  
   }  
   else {   
    mValueLED_1 = 0;  
   }  
   mLastMillis_LED_1 = mCurrentMillis;  
  }  
  if(mCurrentMillis - mLastMillis_LED_2 > 10) {  
   if(mValueLED_2 < endValue) {   
    mValueLED_2 += stepAdd;  
    pwm.setPWM(1, 0, mValueLED_2);  
   }  
   else {   
    mValueLED_2 = 0;   
   }   
   mLastMillis_LED_2 = mCurrentMillis;  
  }  
 }   


Blinker

Einfaches Ein- und Ausschalten ist nicht schwer zu programmieren. Mit einem schnelle Fade-In, Fade-Out war nur etwas ausprobieren nötig. Zuerst hatte ich das Problem. dass die LEDs nie ganz ausgingen, womit das Blinken selbst weniger deutlich zu sehen waren, wenn der Blinker betätigt wurde.
Im Grunde war das ein Denkfehler, weil ich die Schrittweite zum Fade-Out nicht berücksichtigt habe und, um eine Iteration zu früh den Vorgang verlassen habe.

 unsigned long mCurrentMillis;  
 unsigned long mLastMillis_LED_1;   
 unsigned long mLastMillis_LED_2;  
 uint16_t mValueLED_1 = 0;  
 bool mFlashingLED_2 = false;  
 void loop() {  
  uint16_t endValue = 4096;  
  int stepAdd = 10;  
  int leds = 4;  
  mCurrentMillis = millis();  
  if(mCurrentMillis - mLastMillis_LED_1 > 5) {  
   if(mValueLED_1 < endValue) {   
    mValueLED_1 += stepAdd;  
    pwm.setPWM(0, 0, mValueLED_1);  
   }  
   else {   
    mValueLED_1 = 0;  
   }  
   mLastMillis_LED_1 = mCurrentMillis;  
  }  
  if(mCurrentMillis - mLastMillis_LED_2 > 200) {  
   mFlashingLED_2 = !mFlashingLED_2;  
   if(mFlashingLED_2) {   
    pwm.setPWM(1, 0, 4095);  
   }  
   else {   
    pwm.setPWM(1, 0, 0);  
   }   
   mLastMillis_LED_2 = mCurrentMillis;  
  }  
 } 

 

Viel aus-probieren

Erst am Ende, wenn alles im Programmcode eingegeben ist, muss die Geschwindigkeit der Animationen eingestellt werden. Damit dies geht, sollte möglichst am Anfang diese Einstellungen zentral eingehalten werden, um das Nachjustieren zu beschleunigen.

Samstag, 31. Juli 2021

Mini Solar Projekt mit ATtiny25/45/85


Gerade baue ich einen RC Crawler und hin und wieder kommen mir Ideen, was man da noch umsetzen könnte. Die meisten Ideen verwerfe ich, nur wenige verbleiben bei mir als Notiz in OneNote.
Zum Schluss gibt es dann doch meistens zu viele Gründe, warum ich eine Idee nicht mehr weiter verfolge. Das fängt häufig damit an, das ich nicht die passenden Teile habe oder ich will die fehlenden Komponenten nicht bestellen.

Heute konzentrierte ich mich auf die Teile, die ich da habe. In meinem Sortierschrank befinden sich zahlreiche Komponenten mit denen sich etwas anfangen lässt. Allerdings meine ich, wenn ich von Basteln spreche, eher etwas auszuprobieren oder sich damit auseinandersetzen und dann ein Beispiel darüber zu schreiben. Diesmal soll es ein fertiges Teil sein, dass einfach zu modden ist.


Benötigt wird:

  • Solar Platte 40mm x 40mm, 2V, 40mA
  • 5 LEDs (Idealerweise Rot, wegen der Erforderlichen Spannung)
  • ATtiny 45 oder 85
  • 5 x 10 Ohm Widerstände

Zielbeschreibung

Mit der Solar Zelle soll ein ATtiny 45/85 betrieben werden, dass mit 1MHz läuft. Die 5 LEDs sollen einen Dezimal Wert in Binär abbilden, der hier einfach hochgezählt wird bis 32. Wenn die 32 erreicht wurde, sollen alle LEDs nacheinander ausgeschaltet werden, anschließen hinter einander wieder an und dann wieder hintereinander aus.


Schaltung

Kommen wir zur Schaltung, die eigentlich für keine stabile Spannung ausgelegt sein kann. Die Solarplatte, die ich hier verwendet, erzeugt bei Sonnenlicht etwas mehr als 2V und 40mA. Genug um den ATtiny mit 1,8V Mindestspannung und mit 1MHz zu betreiben. Die Stromaufnahme von 300µA ist so klein, dass sie zu anderen betreibbaren Komponenten kaum auffällt. Da können glatt noch zwei LEDs mehr mit betrieben werden (naja, nicht glatt 20mA) oder 19 x Low Current LEDs. Die Low Current LEDs wollte ich als erstes verwenden, jedoch ist das Licht zu schwach bei Sonnenlicht zu sehen. Die normalen LEDs leuchten nicht mit ihrer Nennleistung, aber sie sind dennoch deutlich heller.

Nun hatte ich zu den benötigten Teilen 5 LEDs genannt und in der Anforderung sollten sogar alle 5 zum Leuchten gebracht werden. Nun direkt werden nicht alle an sein. Durch die Programmierung werden die LEDs im Schnell-Wechsel durchgeschaltet, so das für uns alle LEDs scheinbar an sind. Vielleicht kann man das leichte Flimmern (Ähnlich wie bei PWM Betrieb) sehen, aber dafür leuchten mehr, als eigentlich die Solar Platte an Strom her geben kann.



Alle Lampen mit geringen Stromverbrauch

Der Wechsel wird durch eine Methode verwaltet, die die einzuschaltenden Ausgänge steuert.

 void LightControllerShow() {  
  if(mCurrentLED < 4)   
   mCurrentLED++;   
  else  
   mCurrentLED = 0;  
  for(int i = 0; i < 5; i++) {  
   if(mCurrentLED == i && mLightsOn[i] == true)  
    digitalWrite(i, HIGH);  
   else  
    digitalWrite(i, LOW);  
  }  
 }  

Das Beispiel weist mehr Aufwand auf, als bei einem üblichen Programmablauf, bei dem die Ausgänge alle wirklich durchgeschaltet sind. Dafür wird nur noch ein Fünftel des Stroms benötigt.

 

Testlauf

Auch wenn später der ATtiny mit nur 1 MHz läuft, können wir dies testweise mit einem Arduino prüfen. Die Kamera ist mit einer schnellen Verschlusszeit eingestellt, deshalb wird der Schaltvorgang als Flimmern war genommen im folgendem Clip.

 

 

Zielgerade

Kommen wir zu dem eigentliche Programm. Wie bereits in der Anforderungen beschrieben, soll bis 32 gezählt werden. Der Wert wird wiederum Binär auf die 5 LEDs ausgegeben. Kommen wir zum nächsten Codeabschnitt.

 bool mLightsOn[5];  
 int mLastTime = 0, mCurrentLED = 0, mVal = 0;  
 void setup() { for(int i = 0; i < 5; i++) { pinMode(i, OUTPUT); mLightsOn[i] = false; } }  
 void loop() {  
  LightControllerShow();  
  int currentTime = millis();  
  if(currentTime - mLastTime > 1000) { mLastTime = currentTime; Next(); }  
 }  
 void Next() {  
  if(mVal > 32) { mVal = 0; RunLight(); } else { mVal++; }  
  DecimalToBinar(mVal);  
 }  
 void DecimalToBinar(int val) {  
  int bin = 16, index = 0;  
  SetOnTo(-1);  
  while(true) {  
   if(val >= bin) { mLightsOn[index] = true; val -= bin; }  
   else { mLightsOn[index] = false; }  
   index++; bin /= 2;  
   if(val <= 0 || index > 4 || bin == 0) { break; }  
  }  
 }  
 void RunLight() {  
  int iteration = 0,lastTime = 0, lightOn = 0;  
  bool up = true;  
  SetOnTo(lightOn);  
  while(true) {   
   if(iteration > 10) { break; }  
   LightControllerShow();  
   int currentTime = millis();  
   if(currentTime - lastTime > 200) {  
    lastTime = currentTime;  
    if(up) { lightOn++; } else { lightOn--; }  
    if(lightOn >= 5) { up = false; lightOn = 4; }  
    if(lightOn < 0) { up = true; lightOn = 0; iteration++; }  
    SetOnTo(lightOn);  
   }  
  }  
 }  
 void SetOnTo(int indexLight){  
  for(int i = 0; i < 5; i++) { mLightsOn[i] = false; }  
  if(indexLight < 0) { return; }  
  mLightsOn[indexLight] = true;  
 }  
 void LightControllerShow() {  
  if(mCurrentLED < 4) { mCurrentLED++; }  
  else { mCurrentLED = 0; }  
  for(int i = 0; i < 5; i++) {  
   if(mCurrentLED == i && mLightsOn[i] == true) { digitalWrite(i, HIGH); }  
   else { digitalWrite(i, LOW); }  
  }  
 }  


Ab auf den ATtiny

Der ATtiny 45 erfüllt die Anforderung. Ob nun ein ATTiny25 geht, konnte ich wegen fehlendem Bestand nicht prüfen.

Nun sollte der Arduino IDE im Board Manager die Unterstützung für ATtiny haben. Falls nicht, dann solltet ihr in: File -> Preference und dann in 'Additional Boards Manager URLs:' diesen Link eintragen:

https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json

 

Für mehr Detail zu ATtiny und Arduino kann ich diese Seite empfehlen: 

Programming an ATtiny with Arduino

 

Arduino UNO als ISP verwenden

Der Arduino kann einen ATtiny mit dem Ziel Code beschreiben. Dazu muss der Arduino mit dem Code für ISP beschrieben werden. Die sollte unter den Beispiel Programmcode hinterlegt sein.

Auswählen und dann auf den Arduino.

 

Ziel Einstellung

Board: ATtiny25/45/85

Processor: ATtiny45

Clock: Internal 1 MHz


Bevor der Code geladen wird, muss der Bootloader auf den ATtiny. Wie bereits im letzten Bild zu sehen, kann über den letzten Eintrag 'Burn Bootloader' der ATtiny gebracht werden.

Nun kann der Sketch auf den ATtiny geschrieben werden und anschließend in die Schaltung eingebracht werden.

 

Fertig

Nun brauchen wir etwas Sonnenlicht, um das Ergebnis zu prüfen. Je nach Sonnenschein kann auch indirekt die Solar Platte Energie erzeugen. Auf dem Hinterhof konnte selbst im Schatten genügend Energie erzeugt werden, dass die Schaltung anlief.


 

Was nun?

Statt auf dem Breadboard die Schaltung weiter laufen zu lassen, kann man daraus etwas geschlossenes bauen. In meinem Fall habe ich daraus ein Gepäckstück für mein RC Modell gebaut, so dass halt nicht nur das Aussehen einer So
larplatte erfüllt, sondern
auch etwas Funktion zeigt.

 

 

Source

Alles über die Sourcen zum Thema ATtiny25/45/85:  

Highlowtech - Programming an ATtiny with Arduino


Und Natürlich:

Github - MiniSolarProjectWithATtiny

Gehäuseentwurf für Signalleuchten (Part 2)

Heute morgen sah ich mir mein Druckergebnis an und dachte: Nein, das sieht nicht nach einem Kristall aus. Also im Internet gesucht und ein T...