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.

Montag, 18. April 2022

Crawler - LUA Skript für Lipo Sensor


Wie voll ein Lipo Akku ist, kann an der Spannung der einzelnen Zelle oder an der Gesamtspannung erkannt werden. Der Umstand ist immer, dass wir in unserem Kopf umrechnen, wie viel uns noch vom Akku an Kapazität bleibt.

Bei unseren Geräten wie dem Smartphone oder Tablet gibt es eine Prozentanzeige und dieses will ich hier nun umsetzen.

 

Benötigt

  • Frsky X-Lite oder Fernsteuerung mit OpenTX
  • Frsky Lipo Sensor

 

Ziel

Der Akkustand soll in Prozent ausgegeben und graphisch mit einem Ladebalken angezeigt werden. Wenn die verbrauchbare Akkuladung sich dem Ende nährt, soll außerdem eine Warnung ausgegeben werden.

 

Auslesen der Telemetrie

Anders als bei dem Arduino Projekt wird das Ergebnis nicht als einzelner Wert ausgegeben. Genau genommen ist der verwendete Typ eine Tabelle, das wie ein Dictionary verwendet wird. Mit einem Schleifendurchgang kann der Index und der Wert gelesen werden.

 local mCellId = nil  
 local mField = nil  
 -- read from telemetry pool by name  
 local function getTelemetryId(name)  
   mField = getFieldInfo(name)  
   if mField then  
    return mField.id  
   else  
    return -1  
   end  
 end  
 local function init()  
      mCellId = getTelemetryId("Cels")  
 end  

 

Spannungen umrechnen

Im Grunde ist es keine große Sache, die Spannung in Prozent umzurechnen. Wir wissen, dass eine Lipo-Zelle eine Lagerspannung von 3,7 Volt hat und bei ca. 4.2 Volt voll ist. Praktisch darf der Akku nur 0,5V an Spannung verlieren, um dann als verbraucht zu gelten. Einige haben 3,6V als Grenze, aber weiter runter sollte man hier nicht gehen, wenn der Akku keinen Schaden nehmen soll.

 

Gesamtspannung = 8,4 V

Lagerspannung = 7,4 V

Verbrauchbare Differenzspannung = Gesamtspannung - Lagerspannung

1,0 V = 8,4 V - 7,4 V

 

Messspannung = 8,1 V

Rest Differenzspannung = Messspannung - Lagerspannung

0,7 = 8,1 V - 7,4 V

 

Kapazitätswert in Prozent = Rest Differenzspannung / Verbrauchbare Differenzspannung * 100

70 % = 0,7 / 1,0 * 100

 

Genauer wird dies, wenn die Spannungen der einzelnen Zellen berechnet werden. Zudem bleibt die Möglichkeit die Zellen einzeln zu betrachten, um bei Bedarf den nicht-balancierten Zustand zu erkennen.

 -- Lipo total  
 local mLipoTotalFull = 8.4  
 local mLipoTolalLow = 7.4  
 -- lipo cell  
 local mLipoCellVoltage = 4.2  
 local mLipoCellVoltageLow = 3.7  
 -- results  
 local mTotalResult = 0  
 local mCellResult1 = 0  
 local mCellResult2 = 0  
 local mTotalCellsResult = 0  
 local mCells = 0  
 local mCell1 = 0  
 local mCell2 = 0  
 local function calculateCellsState()  
      if mCellId == -1 then  
           return 0  
      end       
      mCells = 0  
      local cells = getValue(mCellId)  
      if(type(cells) == "table") then  
           for index, voltage in ipairs(cells) do  
                mCells = mCells + voltage  
                if index == 1 then  
                     mCell1 = voltage  
                end  
                if index == 2 then  
                     mCell2 = voltage  
                end  
           end  
      end  
      -- total voltage   
      local totalFull = mLipoTotalFull - mLipoTolalLow  
      local totalState = mCells - mLipoTolalLow  
      mTotalResult = math.ceil(totalState / totalFull * 100)  
      -- cell  
      local cellFull = mLipoCellVoltage - mLipoCellVoltageLow  
      -- cell 1 voltage  
      local cellState1 = mCell1 - mLipoCellVoltageLow  
      mCellResult1 = cellState1 / cellFull * 100  
      -- cell 2 voltage  
      local cellState2 = mCell2 - mLipoCellVoltageLow  
      mCellResult2 = cellState2 / cellFull * 100  
      -- total result  
      mTotalCellsResult = math.ceil((mCellResult1 + mCellResult2) / 2.0)  
      return 1  
 end  

 

Werte und Balken als Zustandsanzeige

Anstatt nur Zahlen zu lesen, kann ein Balken gezeichnet werden. Damit man sehen kann, wo die 100 % liegen, wird zusätzlich ein Rahmen angelegt.
Anders gesagt wird es wie ein Ladebalken angezeigt, der als haptische Füllstandsanzeige verwendet werden soll.

 local function drawTextAndBars()  
      -- lipo accu state calculate to percentage  
      -- title  
      lcd.drawText(0, 0, "Accu State", MIDSIZE)  
      -- total lipo voltage  
      lcd.drawText(2, 12, "Lipo Accu:", SMLSIZE)  
      lcd.drawText(46, 12, mTotalResult.."%", SMLSIZE)  
      -- sum of cells lipo voltage  
      lcd.drawText(64, 12, "By Cells", SMLSIZE)  
      lcd.drawText(102, 12, mTotalCellsResult.."%", SMLSIZE)  
      -- Cell voltage  
      lcd.drawText(2, 20, string.format("%.2f", mCells).."V", SMLSIZE)  
      lcd.drawText(32, 20, string.format("%.2f", mCell1).."V", SMLSIZE)  
      lcd.drawText(62, 20, string.format("%.2f", mCell2).."V", SMLSIZE)  
      -- screensize  
      -- 128 x 64  
      -- cells  
      lcd.drawRectangle(5, 28, 118, 5)  
      lcd.drawLine(7, 30, math.ceil(mTotalResult * 1.18), 30, SOLID, FORCE)  
      -- cell 1  
      lcd.drawRectangle(5, 34, 118, 5, SOLID)  
      lcd.drawLine(7, 36, math.ceil(mCellResult1 * 1.18), 36, SOLID, FORCE)  
      -- cell 2  
      lcd.drawRectangle(5, 41, 118, 5, SOLID)  
      lcd.drawLine(7, 43, math.ceil(mCellResult2 * 1.18), 43, SOLID, FORCE)  
      return 0  
 end

 

Alternativ kann die Füllstandsanzeige mit lcd.drawGauge(x1, y1, w, h, maxfill) gezeichnet werden. Jedoch habe ich noch nicht die Option gefunden, eine vertikale Darstellung zu zeichnen. Ist deshalb in dem von mir hier erstellten Skript noch nicht der Fall.

 

Skript ausführen

Das Skript muss in den Ordner SCRIPTS/TELEMETRY und die Bezeichnung des Scripts darf hier nicht mehr als 5 Zeichen lang sein, da sonst das Skript nicht für das Display ausgewählt werden kann.

Nun habe ich da noch ein Problem mit der Darstellung auf dem Bildschirm. Obwohl das Skript im Menü eingebunden wurde, wird mir auf der Telemetrie Ansicht gemeldet, dass keine Telemetrie Screens verfügbar sei.


 

Am Ende offen

Den Fehler konnte ich noch nicht herausfinden und starte daher alternativ das Skript direkt aus dem Ordner heraus. Eine weitere Störung tritt auf, wenn man den Crawler ausschaltet, dann scheint die Fernsteuerung im Zustand des Betriebs zu springen und hier kann man nur noch ausschalten.

Aktuell ist die Darstellung nur sehr einfach und nicht sehr stylisch, weshalb ich ein Screen zusätzlich designe.

 

Handling Lipo Sensor Data - OpenTX 2.3 Lua Reference Guide (open-tx.org)

OpenTX 2.2 Lua Reference Guide (schorse-video.de)

Taranis-XLite-Q7-Lua-Dashboard/farl.lua at master · AndrewFarley/Taranis-XLite-Q7-Lua-Dashboard (github.com)

 

Gehäuseentwurf für Signalleuchten (ESP32)

RGB LEDs und LIPO Akkus sind bestellt. Weil ich bereits die Abmessungen habe, kann ich schon mal mit dem Gehäuseentwurf in Blender anfangen....