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)

 

Sonntag, 23. Januar 2022

Arduino als Messgerät mit Logging (Teil 2)

Im ersten Teil habe ich erläutert, welche Schritte nötig sind, um den Arduino als Messgerät einzusetzen. Nun gehe ich einen Schritt weiter. Statt einem, können jetzt vier Messungen erfasst und auf der WPF Anwendung angezeigt werden.

Die Anwendung ist eigentlich zum Probieren von verschiedenen Mechanismen gedacht. Praktisch ein Training, um für meinen Beruf immer fit zu bleiben.

 

Benötigte Materialien:

  • Arduino
  • mindestens 2 x 10k Ohm Widerstände für Spannungsteiler
  • Verschiedene Spannungsquellen für Messungen

 

Was geht?

Der Arduino kann mehr als nur eine Spannungsquelle messen. Mit der WPF Anwendung können vier Spannungsquellen gleichzeitig gemessen werden. Natürlich geht mehr, doch die weiteren sollen für die Schnittstelle I²C offen bleiben.

  

WPF Anwendung

Bei meinem letzten Blog Eintrag habe ich am Ende bereits geschrieben, dass ich auch schon die zukünftige WPF Anwendung im GitHub Repository mit hochgeladen habe. Zu dieser Zeit war jedoch die Messung nur an einem Messpunkt möglich.

Mit der Möglichkeit mehrere Spannungen zu messen, ändert sich der Programmcode für den Arduino. Ich habe das soweit ausgebaut, dass nun vier Messungen gleichzeitig möglich sind.

 

Arduino Code Änderung

Damit die vier Messungen auch übertragen werden, muss die Übertragung erweitert werden. Wie beim ersten, müssen die drei weiteren INPUT Eingangswerte ebenfalls aufbereitet und dann in das zu versendete Array geschrieben werden.

 #define PIN_Input_1 A0  
 #define PIN_Input_2 A1  
 #define PIN_Input_3 A2  
 #define PIN_Input_4 A3  
 void setup() {  
  Serial.begin(115200);  
  Serial.println("Volatage Measuerment Logger Started");  
  pinMode(PIN_Input_1, INPUT);  
  pinMode(PIN_Input_2, INPUT);  
  pinMode(PIN_Input_3, INPUT);  
  pinMode(PIN_Input_4, INPUT);  
 }  
 void loop() {  
  int16_t rawMeasurement1 = analogRead(PIN_Input_1);  
  int16_t rawMeasurement2 = analogRead(PIN_Input_2);  
  int16_t rawMeasurement3 = analogRead(PIN_Input_3);  
  int16_t rawMeasurement4 = analogRead(PIN_Input_4);  
  byte ba[9];  
  ba[0] = (byte)((rawMeasurement1 >> 8) & 0xff);  
  ba[1] = (byte)(rawMeasurement1 & 0xff);  
  ba[2] = (byte)((rawMeasurement2 >> 8) & 0xff);  
  ba[3] = (byte)(rawMeasurement2 & 0xff);  
  ba[4] = (byte)((rawMeasurement3 >> 8) & 0xff);  
  ba[5] = (byte)(rawMeasurement3 & 0xff); 
  ba[6] = (byte)((rawMeasurement4 >> 8) & 0xff);  
  ba[7] = (byte)(rawMeasurement4 & 0xff);   
  int8_t countedBits = CountBits(rawMeasurement1);  
  countedBits += CountBits(rawMeasurement2);  
  countedBits += CountBits(rawMeasurement3);  
  countedBits += CountBits(rawMeasurement4);  
  ba[8] = (byte)countedBits;  
  Serial.write(ba, 9);  
  delay(20);  
 }  
 int8_t CountBits(int16_t rawMeasurement) {  
  int8_t result = 0;  
  for(int i = 0; i < 16; i++) {  
   result += (int8_t)bitRead(rawMeasurement, i);  
  }  
  return result;  
 }  

Versuchsaufbau

Für den Test werden verschiedene Spannungsquellen eingesetzt:
Eine Schaltung mit einem Lichtsensor,
eine Solarplatte,
Batterie mit Step-Down und
eine weitere Schaltung mit einer Z-Diode. 

Diese vier werden wiederum selbst am Spannungsteiler angeschlossen.


 

Verbindung herstellen

Die Spannungsquellen können vor dem Einschalten/Anschließen des Arduinos angeschlossen werden. Ist der Arduino verbunden, kann die Verbindung zur Anwendung gestartet werden. Hier entsprechend den COM Port auswählen und den Button Connect klicken. Im Diagrammbar sollten nun vier Linien entsprechend der Spannung angezeigt werden.


Problem mit der Diagrammlinie

Aktuell besteht mit dem Diagramm das Problem, dass durch nicht genutzte Events die Diagrammlinie nicht aktualisiert wird, wenn sich z.B. der Mauszeiger über dem Steuerelement bewegt.


Loggen starten

Nicht immer schafft man es auf die Anzeige zu schauen und eventuell verpasst man die zu beobachtende Spannung. Deshalb habe ich den Logger mit eingebaut, um hier entgegen zu wirken.

Im Test konnten zehn minütige Aufzeichnungen vorgenommen werden, trotz des CSV Datenformates. Auch, wenn die Datei nur wenige Kilobyte groß ist, ist die Art der Speicherung in performant.

Um die Aufzeichnung anzusehen, muss auf den Tab „Log Data“ gewechselt werden. Dort wird dann der Log-Name ausgewählt, der zuvor zum Speichern verwendet wurde. Die Einträge sind hier nicht nur durchscrollbar, sondern man kann auch auf die nächst Spannungsveränderung springen/wechseln.


 

Unfertig!

Sofern ich wieder Zeit habe, entwickle ich das Programm weiter.
Das richtet sich auch etwas nach meinem eigenen Bedarf.

 
Ihr könnt mir auch in die Kommentare schreiben, welche Funktion man einbringen könnte.


Als nächstes kommt eine Erweiterung der Divisor Einstellung, welche aktuell noch für alle vier verwendet wird.
Die Anzeige im Diagramm mit über 10V ist noch ausbaufähig, da eine höhere Spannung angezeigt werden könnte. 

Neben dem Programm wäre eine vollständige Schutzschaltung schön. Das Thema bin ich bereits angegangen und ich musste feststellen, dass der Bereich größere Kreise zieht als ich gedacht habe.


Nun könnte ich alle meine Vorhaben hier unter hinschreiben, aber dann wäre das nur für heute aktuell. Deshalb packe ich meine offenen Punkte in den Issues.

Offene Aufgaben Liste - Issues


GitHub - VoltageMeasurementLogger

In der Solution befindet sich das Projekt SimpleVoltageMeasurement, das die Grundlage der Spannungsmessung am Arduino und C# Konsolenanwendung abbildet. Das Projekt VoltageMesurementLogger ist die Anwendung, die im Blogeintrag beschrieben wurde.

 

Dienstag, 7. Dezember 2021

Arduino als einfaches Messgerät


Eigentlich hatte ich einen anderen Text bereits fertig, aber der passte nicht mehr zu dem folgenden Inhalt.
Diesmal geht’s darum einen Arduino als Messgerät einzusetzen und den PC als Anzeigegerät. Natürlich mit dem Ziel eine größere Anwendung zu schreiben, mit welcher man die Messungen aufzeichnen kann.

 

Benötigt

  • Arduino
  • Breadboard
  • 2x 10kOhm Widerstände
  • 3x Jumper Kabel
  • Eine Anwendung, mit der man die Messungen loggen kann

 

Anforderung

Mit einem Arduino soll eine Spannung an einem Step-Down Modul gemessen werden, welches ich in meinem Crawler verwende. Da die Spannung auch höher als 5V sein kann, muss ein Spannungsteiler eingesetzt werden, der die zu erwartenden Spannung abdeckt. Um aktiv das Spannungsgeschehen zu beobachten, soll das Messergebnis auf dem PC wieder gegeben werden.

 

Arduino als Messgerät

Für ein einfaches Messgerät werden grundsätzlich zwei Widerstände benötigt. Zwar kann man auch direkt messen, aber das schränkt den Messbereich stark ein. In meinem Fall könnte die Spannung bei 7,4V hoch gehen und vielleicht auch drüber. Zudem soll mit den Widerständen auch der Strom begrenzt werden, dass zudem den Arduino vom Durchbrennen schützen soll.

 

Bevor eine Messung stattfindet, wird ein kleines Arduino Programm benötigt, mit dem wir auf dem Serial Monitor einen Wert ausgeben können.

 

 #define PIN_Input A0  
 void setup() {  
  Serial.begin(115200);  
  pinMode(PIN_Input, INPUT);  
 }  
 void loop() {  
  int16_t rawMeasurement = analogRead(PIN_Input);  
  byte ba[3];  
  ba[0] = (byte)((rawMeasurement >> 8) & 0xff);  
  ba[1] = (byte)(rawMeasurement & 0xff);  
  ba[2] = (byte)CountBits(rawMeasurement);  
  Serial.write(ba, 3);  
  delay(20);  
 }  
 int8_t CountBits(int16_t rawMeasurement) {  
  int8_t result = 0;  
  for(int i = 0; i < 16; i++) {  
   result += (int8_t)bitRead(rawMeasurement, i);  
  }  
  return result;  
 } 

 

Simple Consolen Anwendung

Der simple Aufbau ist zunächst ein Vorläufer zu meiner eigentlichen kleinen Anwendung. Die Auswahl des seriellen Port hätte man auch weg lassen können, aber auf den Komfort wollte ich dann doch nicht verzichten.

 

class Program  
 {  
   private static int _rawValue;  
   private static int _divisorValue = 511;  
   private static float _voltage;  
   static void Main(string[] args)  
   {  
     Console.WriteLine("Connect Serial with");  
     string[] ports = SerialPort.GetPortNames();  
     if(!ports.Any()) {  
       Console.WriteLine("No Arduino connected!");  
       return;  
     }  
     for (int index = 0; index < ports.Length; index++) {  
       Console.WriteLine($"Index:{index}, Port: {ports[index]}");  
     }  
     string selectindex = Console.ReadLine();  
     if (!int.TryParse(selectindex, out int indexResult)) {  
       Console.WriteLine("You can only typ the index number!");  
       return;  
     }  
     if(indexResult >= ports.Length) {  
       Console.WriteLine("You can only typ the index number!");  
       return;  
     }  
     var sp = new SerialPort(ports[indexResult], 115200);  
     sp.DataReceived += Sp_DataReceived;  
     sp.Open();  
     while (true) {  
       Console.Clear();  
       Console.WriteLine($"{_rawValue} / {_divisorValue} * 5.0 = {_voltage:N2} V");  
       Thread.Sleep(1000);  
     }  
   }  
   private static void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)  
   {  
     var sp = (SerialPort)sender;  
     byte[] buffer = new byte[3];  
     if (sp.Read(buffer, 0, 3) != 0) {  
       int result = (buffer[0] << 8) | buffer[1];  
       var b = new BitArray(new int[] { result });  
       bool[] bits = new bool[b.Count];  
       b.CopyTo(bits, 0);  
       if (bits.Sum(s => s ? 1 : 0) != buffer[2])  
       {  
         return;  
       }  
       _rawValue = result;  
       _voltage = result / (float)_divisorValue * 5.0f;  
     }  
   }  
 } 

 

Divisor festlegen

Man bekommt zunächst einen Rohwert, der zunächst nichts aussagt. Würde man direkt die 5V Pin vom Arduino abgreifen und am Eingangspin A0 anschließen, bekäme man einen Wert von 1024. Dieser Wert muss durch 1024 geteilt und dann mit 5,0 Volt multipliziert werden.

 

Spannung = Rohmesswert / Divisor * 5,0

5,0V = 1024 / 1024 * 5,0V

 

Durch den Spannungsteiler verschiebt sich der eigentliche Rohwert, der die 5 Volt darstellt. Statt durch 1024 zu teilen, fällt dieser Wert in meinem Beispiel auf 511.


2,5V = 511 / 1024 * 5,0V



Das bedeutet, der Divisor muss in meinem Fall auf den aktuellen Rohwert angepasst werden, denn die 5V sind jetzt bei einem Rohwert von 511.

 

5,0V = 511 / 511 * 5,0V

 

Zum testen kann nun vom 3,3V Pin die Spannung zum Messen verwendet werden. Hier sollte nun ein Rohwert bei ca. 345 ankommen. Folgendes Ergebnis ergab sich bei meinem Test:

 

3,39V = 346 / 511 * 5,0V


 

Kleiner Anfang

Natürlich sind die Ergebnisse nicht sehr genau, aber für eine einfache Lösung reicht die grobe Messung aus und ist besser als gar kein Messgerät.

Der Spannungsteiler, mit den zwei 10k Ohm Widerständen, kann eine Spannung von maximal 10V angelegt werden.

 

Warnung

Natürlich sollte man immer berücksichtigen, dass die Leistungsgrenzen nicht überschritten werden. Die zu verwendenden Widerständen sollten möglich hochohmig sein, so dass am Arduino Pin kein hoher Strom eingeht.

 

Zielmessung

Kommen wir zum Testaufbau, mit dem wir eine Messung an einem anderem Stromkreis vornehmen können. Wichtig ist, dass der Ground von der anderen Spannungsquelle mit am GND des Arduinos angeschlossen wird.


 

Was kommt als nächstes?

Eine Sache habe ich hier zunächst ausgelassen, und zwar das Aufzeichnen der Messungen. Eigentlich kann ein Mehrwert mit der Funktion gewonnen werden. Andernfalls hätte man auch ein kleines Display für den Arduino einsetzen können und auf das Spannungsergebnis ausgeben würde.

 

In der Solution befinden sich die Consolen Anwendung SimpleVoltageMeasurement und bereits mein neues Projekt VoltageMeasurementLogger, dass das Aufzeichnen der Daten ermöglicht.

GitHub - VoltageMeasurementLogger

Montag, 18. Oktober 2021

Günstiges Radar Modul für Bewegungen


Wieder mal was kleines feines und auch günstiges gefunden im Bereich Sensorik. Das Radar Modul RCWL-0516 kann Bewegungen von Personen wahrnehmen. Genau genommen von jedem Lebewesen und nicht durch bewegte Gegenständen wie Staubsaugerroboter.

Benötigt

  • Arduino
  • RCWL-0516 (Preislich 6 Stück ab 4€)
  • Eine Person die sich  bewegt

 

Anforderung

Herausfinden in welchem Winkel und Entfernungen der Sensor funktioniert.

 

Technische Daten

Im Datenblatt steht mehr, da mir die anderen Details für das Grundlegende zunächst keinen Nutzen bringen.

 

Betriebsspannung: 4V bis 28V und mindestens 3mA

Erkennungsreichweite: ca. 5m bis 9m

Frequenz: 3.2GHz

Ausgang (OUT): geschaltet ca. 2 Sekunden lang

 

PINs:

3V3 => 3,3V Versorgungsspannung (Output)

GND => Ground

OUT => Signal Ausgang Hi-Pegel wenn eine Bewegung wahrgenommen wurde

VIN => Eingang Spannung von 4V bis 28V

CDS => Externer  Lichtabhängiger Widerstand (Ausschalten der Radar Funktion)

 

Optional:

C-  Verlängert den Geschalteten Zustand am Ausgang (OUT)Hierfür wird ein Kondensator benötigt.

R-GN Erkennungsreichweite. Mit einem 1M Ohm Widerstand kann die Reichweite von 7 Meter auf 5 Meter reduziert werden.

CDS Platz für die Installation eines Lichtabhängigen Lichtwiderstandes, um bei Licht die Radar Wahrnehmung auszuschalten.

R-CDS Einstellung für die Empfindlichkeit mit dem Lichtabhängigen Widerstandes. Dient als Spannungsteiler und je kleiner der Widerstand gewählt ist, um so heller muß die Umgebung sein für das Ausschalten der Radar Funktion




Test Programm

Der Code, um den Sensor auszulesen ist sehr simpel. Das Signal, welches über den OUT Ausgegeben wird, ist entweder 0V oder 3,3V. Damit reicht einen PIN für digitales Signal Lesen aus. Analog Signal einlesen bringt uns da keinen Mehrwert, denn hier kann nur ein LOW- oder HIGH-Pegel ausgegeben werden.

 int mRadarInput = 2;  
 void setup() {  
  Serial.begin(115200);  
  pinMode(mRadarInput, INPUT);  
 }  
 void loop() {  
  bool movmentDetect = digitalRead(mRadarInput);  
  Serial.print("Movement: ");  
  Serial.println(movmentDetect, BIN);  
  delay(100);  
 }  

Anschließen

Für den Beispielaufbau werden nur drei Leitungen zum Verbinden des Sensor verwendet. Der Anschluß auf dem 3.3V steht, ist ein OUTPUT und kann verwendet werden, andere Controller zu versorgen. Der CDS Anschluß kann wiederum für ein Lichtsensor verwendet werden, um das Feststellen von Bewegungen abzustellen.

 

 

Entfernung und Winkel

Die Reichweite ist gut für das Feststellen von Bewegungen innerhalb eines Zimmers. Der Sensor kann Bewegungen rund um dem Modul war nehmen, aber Ideal ist wiederum die Seite mit dem Aufgelöteten Chip. Die Wahrnehmung auf Bewegungen wirkt unterschiedlich, aber gut brauchbar.

Typische Gegenstände wie Kunststoff oder Holz schränkt die Funktion nicht ein. Metall hingegen wirkt Abschirmend auf das Radar.

 

Timing

Wenn ich mich bei der Übersetzung vertan habe, dann kann das Timing von 2 Sekunden nur verlängert werden, aber nicht verkürzt.

 

Anwendungsbeispiele

In den meisten Fällen würde man dieses Radar Modul zum Einschalten von Lampen verwenden. Natürlich könnte man da auch Alarmanlagen dafür einsetzen, setzt jedoch einen Raum voraus, in dem keiner sein sollte.

 


Sourcen

Datasheet PDF

 

Dienstag, 28. September 2021

Error Text ausgeben bei einer Stored Procedure in C# Anwendung


Vielleicht ist das, worauf ich diesmal eingehe selbstverständlich, aber ich habe zu dem Thema ein wenig gesucht und mal wieder nichts gefunden. Wenn ich in meiner Anwendung eine Stored Procedure ausführe, dann würde bei einem Fehler keine Rückmeldung von der Ausgeführten Prozedur kommen und der Eindruck entsteht, dass die gespeicherte Prozedur normal durchgelaufen ist. Nun kann man auch einen Output Wert einrichten und erwarten, das ein entsprechender Wert herauskomm. Käme nun ein Standard Wert heraus, könnte daraus geschlossen werden, dass etwas in der Prozedur nicht gemacht macht wurde. Ohne Detail Information, kann man alles vermute. Fehler im Script, keine Ausreichende Berechtigung vergeben oder Datensatz hat einen Fehler. Was auch immer. Schöner wäre einen Fehlertext zu erhalten.

 

Benötigt

  • MS SQL Datenbank
  • Visual Studio 2019

 

Ziel

Das Beispiel soll ein Wert zurück geben, wenn die Stored Procedure ohne Fehler ausgeführt wurde. Bei einem Ausnahmefehler soll wiederum der Fehler Text Ausgeben werden in der Anwendung.

 

Beispiel Prozedur anlegen

Die Folgende Prozedur kann in ihrer Ausführung normal durchlaufen und den erwarteten Ausgangs Wert in 'myGetStatusMessage' ausgeben. Wird der Parameter 'showMeErrorMessage' auf 1 gesetzt, dann wird eine Ausnahme geworfen.

 USE [example-data]   
 GO  
 SET Ansi_Nulls ON  
 GO  
 CREATE PROCEDURE [dbo].[myStoredProcedure]    
   @showMeErrorMessage AS BIT,   
   @myGetStatusMessage AS NVARCHAR(20) OUTPUT,     
   @myStoredProcedureErrorMessage NVARCHAR(100) OUTPUT   
 AS  
 Begin Try  
   IF @showMeErrorMessage = 1  
   Begin  
     THROW 123, 'Eine Ausnahme wurde geworfen', 1;   
   end  
   SELECT @myGetStatusMessage = 'Alles in Ordnung!'   
 end try  
 begin catch  
   SELECT @myStoredProcedureErrorMessage = ERROR_MESSAGE();  
 end catch  

 

Beispiel C# Anwendung

Nun zu einer Consolen Anwendung, die den Aufruf einer gespeicherten Prozedure ausführen kann und auch unter Ziel Bedingungen den Fehlertext ausgeben kann, dass durch den Catch Block der Prozedur ausgefüllt wurde.

 

 using System;  
 using System.Data.SqlClient;  
 Console.WriteLine("Ein Beispiel wie man eine Error Meldung aus einer Store Procedure ausgegeben werden kann");  
 Console.WriteLine("Store Procedure wird ausgeführt ohne Ausnahmefehler!");  
 RunStoreProcedure(false);  
 Console.WriteLine("\r\nStore Procedure wird ausgeführt mit Ausnahmefehler!");  
 RunStoreProcedure(true);  
 Console.WriteLine("\r\nPress enter to close application");  
 Console.ReadLine();  
 static void RunStoreProcedure(bool throwError)  
 {  
   var connection = new SqlConnection(  
     @"server=127.0.0.1,1433;User Id=CustomUser;Password=passwort;Database=example-data;");  
   connection.Open();  
   var query = "DECLARE @StatusOutput AS NVARCHAR(20), @ErrorOutput AS NVARCHAR(100);" +  
         $"EXEC [dbo].[myStoreProcedure] {(throwError ? 1 : 0)}, " +  
         "@StatusOutput OUTPUT, " +  
         "@ErrorOutput OUTPUT; " +  
         "SELECT @StatusOutput AS Status, @ErrorOutput AS Error";  
   var command = new SqlCommand(  
     query,   
     connection);  
   var reader = command.ExecuteReader();  
   while (reader.Read())  
   {  
     Console.Write("Ergebnis: ");  
     for (var iColumn = 0; iColumn < reader.GetColumnSchema().Count; iColumn++)  
     {  
       Console.Write($"{reader.GetValue(iColumn)}, ");  
     }  
     Console.WriteLine("");  
   }  
   connection.Close();  
 } 

 

That's it

Der Umfang ist übersichtlich und kann in den meisten Fällen für eine Anwendung in der Form eine Store Procedure ausführen. Was nicht so einfach ist, wenn eine Tabelle zurück gegeben wird und dort die Möglichkeit statt der Ergebnis Tabelle, wiederum einen Fehlertext auszugeben.

 

GitHub - ExampleStoreProcedureErrorOutput

Sonntag, 12. September 2021

Sensor Daten über Smart Port versenden mit dem Arduino


Der Empfänger, den ich für meinen FPV RC Crawler verwende, hat einen Smartport an dem z.B. Sensoren angeschlossen werden können. Die Anzahl der verfügbaren Sensoren von Frsky ist übersichtlich, aber speziell für meinen Bedarf war keiner dabei. Wie gut, dass hier bereits eine Lösung mit dem Arduino vorhanden ist und ich dazu auf den Arduino Webseite den Eintrag "S.Port sensor library for FrSky" gefunden habe.

Verwendet

  • Arduino Mini
  • Temperatur Sensor
  • Diode
  • FrSky Empfänger mit Smartport
  • Fernsteuerung mit OpenTX

 

Ziel

Die erfasste Temperatur des Sensors soll auf der Fernsteuerung umgerechnet und auf dem Display angezeigt werden.

 

Funktioniert das wirklich?

Wie bei jeder neuen Sache, prüfe ich mit wenig Aufwand, ob das Zielvorhaben erreichbar ist. Praktisch ein PoC. Im Grunde formuliere ich meine Blogeinträge anfangs als "Proof of Concept", bevor daraus ein Beispiel wird und das gilt auch wieder für diesen Eintrag. Nur diesmal beschreibe ich das Testen mit.

In den Sourcen zur Bibliothek für den SmartPort, sind, wie auch bei den anderen Beispiele Codes vorhanden. Für mich ist der ein feststehender Wert zu wenig und deshalb ändere ich den zu sendenden Wert in der Loop Methode.

 #include <SPort.h>  
 SPortHub hub(0x12, 3);  
 CustomSPortSensor sensor(getSensorData);   
 int mSendingValue = 0;  
 int mCurrentMillis;  
 void setup() {  
  hub.registerSensor(sensor);  
  hub.begin();                
 }  
 void loop() {  
  hub.handle();   
  int current = millis();  
  if(current - mCurrentMillis < 100) {  
    return;  
  }  
  mCurrentMillis = current;  
  mSendingValue++;    
 }  
 sportData getSensorData(CustomSPortSensor* sensor) {  
  sportData data;   
  data.applicationId = 0x5900;        
  data.value = mSendingValue;  
  return data;  
 }  

Ist der Code auf dem Arduino geschrieben, muss eine Verkabelung zum Smartport hergestellt werden. Hierfür soll die Diode verwendet werden, aber vielleicht sollte ich die Beschreibung nochmal durchgehen, wofür die Diode gut ist. (Später habe ich die Diode doch nicht verwendet)

 

 

Sensor einbinden

Die Fernsteuerung mit dem OpenTX Betriebssystem kann nun eingeschaltet werden. Im Menü TELEMETRY (Abschnitt 11 auf der Xlite pro) für Sensoren, kann nun die Suche gestartet werden. Wenn der Arduino mit dem Code als Sensor funktioniert, sollte schon nach wenigen Sekunden der Sensor in OpenTX  gefunden werden.

 


 

Display Wiedergabe

Für den Bildschirm können mehrere Seiten eingerichtet werden, auf denen die Sensor Ergebnisse ausgegeben werden. Dazu muss über das Menü DISPLAY (Abschnitt 12 auf der Xlite pro) der Screen auf "Nums" eingestellt werden und dann muss darunter ein Feld mit "---" auf den neuen Sensor eingestellt werden.

 


Ist die Fernsteuerung auf den Hauptbildschirm geschaltet, muss man in meinem Fall den Stick für eine Sekunde nach unten ziehen, damit das eingerichtete DISPLAY gezeigt wird.

 

 

Echten Sensor anschließen

Das war schon alles, um sich an den SmartPort ran zuhängen. Natürlich macht der Anschluss nur Sinn mit einem echten Sensor.

Für dieses Beispiel verwende ich einen Temperatur Sensor, der über die I²C Schnittstelle angesprochen wird. An der Stelle hätte ich auch einen interessanteren Sensor nehmen können, dennoch soll das Beispiel schlicht bleiben. Und der Code für den Temperatur Sensor lässt sich in wenigen Zeilen eingeben.

 #include <SPort.h>  
 #include <Wire.h>  
 SPortHub hub(0x12, 3);  
 CustomSPortSensor sensor(getSensorData);  
 #define TMP102_ADD 0x48  
 void setup() {  
  Wire.begin();  
  hub.registerSensor(sensor);  
  hub.begin();                
 }  
 void loop() {  
  hub.handle();  
 }  
 sportData getSensorData(CustomSPortSensor* sensor) {  
  sportData data;   
  data.applicationId = 0x5900;  
  int raw = getTemperatureValue();  
  float result = raw * 0.0625;  
  data.value = raw;  
  return data;  
 }  
 int getTemperatureValue() {  
  Wire.beginTransmission(TMP102_ADD);  
  Wire.write(0x00);  
  delay(100);  
  Wire.endTransmission();  
  Wire.requestFrom(TMP102_ADD, 2);  
  while(Wire.available() < 2) { }  
  return ((Wire.read() << 8) | Wire.read()) >> 4;  
 } 


Wert-Ausgabe nur Ganzzahl

Nun haben wir einen Rohwert, der einem so nichts sagt. Eigentlich könnte der Wert am Arduino umgerechnet werden, aber ich hatte aus den Source Code gelesen, dass nur Ganzzahlen übermittelt werden.

 

LUA Script

Die Antwort für das Umrechnen der Ganzzahlen in einen Wert mit Nachkommastellen ist die Unterstützung von Lua Script auf OpenTX. Statt den Rohwert auf dem Display auszugeben, kann dieser durch einen Script umgerechnet werden und dann auf dem Display als lesbaren Wert ausgegeben werden.

Aber kommen wir zunächst zu dem Teil, was eigentlich ein LUA Script haben muss. Im Wesentlichen hilft hier die Dokumentation Telemetry Scripts.

 

Vorraussetzung

  • Das Lua Script muss auf der SD Karte in das Unterverzeichnis "SCRIPTS\TELEMETRY\".
  • Das Script muss die Namenserweiterung ".lua" haben.
  • Der Name des Lua Script darf nicht länger als sechs Zeichen lang sein, ansonsten wird das Script in der DISPLAY Einrichtung nicht angezeigt. Die Namenserweiterung ".lua" wird an der Stelle nicht mit gezählt.

Im Grunde sollte das Script mindestens die 'local function run' enthalten. Die "Init" Funktion ist optional und eigentlich sollte das auch ohne eine Background-Funktion funktionieren. Wichtig ist, dass die Methode am Ende einen Return-Wert ausgibt.

 local function run_func(event)  
      lcd.clear()  
      local receivedValue = getValue('TEMP')  
      local toDecValue = receivedValue * 0.0625;  
      lcd.drawText(10, 10, "Temperature", MIDSIZE)  
      lcd.drawText(10, 25, string.format("%.2f", toDecValue), MIDSIZE)  
      return 0  
 end  
 return { run=run_func }   


Einbinden des Scripts

Nun kann das Script auf die SD Karte und in die Fernsteuerung mit OpenTX gesteckt werden. Nach dem Einschalten kann über das Menü DISPLAY (auf der Xlite Pro befindet sich dieses in Abschnitt 12), auf Screen 1 von Nums gewechselt werden. Rechts von der Script Einstellung, kann man nun das Script myVal.lua auswählen.


 

Anschließen geht’s zurück auf den Hauptscreen. Mit dem 1 bis 2 Sek nach unten gezogenen Stick kann man vom Hauptdisplay auf den gescripteten Bildschirm (Display) wechseln. Fertig!

 

 

Schlusswort

Irgendwie habe ich im Internet keine Beschreibung gefunden, die in wenigen Schritten aufzeigt, was zu tun ist, um einen Wert auf dem Display anzuzeigen. Am Ende ist der Aufbau sehr übersichtlich, aber die kleinen Details, die man wissen muss, halten einen auf.

Natürlich werde ich an den Sensor mehr als nur ein Temperatur Sensor anschließen, schließlich kann man die Lage noch ermitteln und Abstände messen.

Was ich vermisse, ist ein Designer für die Bildschirm Ausgabe. Entweder habe ich den nicht gefunden oder für diesen Zweck gibt’s einfach keinen. Dabei wäre ein Designer nicht schwierig, aber es wäre zeitaufwendig diesen zu entwickeln (womit ich mir praktisch die Frage selbst beantworte).

 

Eintrag zu meinem Repository:

GitHub - ExampleFrskySensor

 

Sourcen

Elektromotor36GP-555

Der Motor ist heute mit der Post gekommen. Anschließend hatte ich nochmal in die Technischen Daten gesehen. Zugegeben, ich habe den Motorbes...