Samstag, 13. Oktober 2018

PCA9685 PWM Driver Module mit Rasperry Pi & Win 10 Iot


Was mit dem Netduino geht, geht für gewöhnlich auch auf dem Raspberry Pi (wenn es nicht gerade um PWM Ausgänge geht). Grundsätzlich hatte ich das Modul tatsächlich für den Rasperry Pi gekauft, um die Fehlende Ausgabemöglichkeit eines PWM Signal auszugleichen. Zwar kann man einen Pin so programmieren, dass ein PWM Signal erzeugt wird, aber ich fand dieses Lösung zunächst nicht sehr ansprechend.

Benötigt:
  • Raspberry Pi 2 oder 3
  • 8GB SD Karte mit installierten Windows 10 IoT
  • Mindestens ein Servo zum Testen
  • Externe Spannungsquelle mit Maximal 6V


UWP Anwendung erstellen
Nam dem anlegen einer neuen Solution wird für den Zugriff auf die Schnittstelle I²C die Reference "Windows IoT Extensions for the UWP" hinzugefügt. Das Beispiel geht mit allen Version die für Windows 10 IoT und Raspberry Pi.


Fast identisch
Außer der Zugriff auf das Interface für I²C, ist der Aufruf gleich. Das Konfigurieren der Schnittstelle erfolgt z.B. nicht im Konstruktor, sondern in einer eigenen Initialisierungsmethode. Grund hierfür ist das abrufen der Instanz von I2cDevice das über die DeviceInformation abgeholt werden kann.

internal class Pca9685 {

private readonly byte _address = 0x40;
private readonly byte PCA9685_MODE1 = 0x00;
private readonly byte PCA9685_PRESCALE = 0xFE;
private readonly byte LED0_ON_L = 0x06;
private readonly byte LED0_ON_H = 0x07;
private readonly byte LED0_OFF_L = 0x08;
private readonly byte LED0_OFF_H = 0x09;

private I2cDevice _i2cDevice;

public Pca9685(int period) : base()
{
    this._period = period * 1000;
}

private async Task Init()
{
    var i2cSettting = new I2cConnectionSettings(this._address);
    i2cSettting.BusSpeed = I2cBusSpeed.StandardMode;

    var deviceSelector = I2cDevice.GetDeviceSelector();

    var deviceInfo = await DeviceInformation.FindAllAsync(deviceSelector);
    this._i2cDevice = await I2cDevice.FromIdAsync(deviceInfo[0].Id, i2cSettting);

    if (this._i2cDevice == null)
    {
        throw new Exception("i2cDevice is null");
    }
}
…

Zudem sind alle Methodenaufrufe Asynchron. Die Lösung ist auch in Synchron möglich, jedoch denke ich, dass die Verwendung von Async, Await und Task noch im Übersichtlichen Rahmen ist.
Allerdings muss man sich bewusst machen, obwohl die Methoden Asynchron ausgeführt werden können, müssen die Aufrufe nacheinander erfolgen. Überschneiden oder gleichzeitig ist Technisch für die Schnittstelle nicht möglich, weil die Bits nacheinander geschrieben werden können.

…
public async Task Reset()
{
    if(!this.Write(new byte[] { this.PCA9685_MODE1, 0x00 }))
    {
        throw new Exception("Can not send the reset command.");
    }

    await Task.Delay(10);
}

internal async Task Start()
{
    await this.Init();

    int hz = 1000000 / this._period;

    await this.Reset();
    await this.SetPwmFrequency(hz);
}

public async Task SetPwmFrequency(float frequency)
{
    byte prescale = this.GetPrescale(frequency);

    byte[] buffer = new byte[1] { this.PCA9685_MODE1 };

    if (!this.Read(buffer))
    {
        throw new Exception("can not read mode1");
    }

    byte oldMode = buffer[0];

    // sleep
    byte newMode = (byte)(((byte)oldMode & (byte)0x7F) | (byte)0x10);

    // go to sleep
    this.Write(new byte[] { this.PCA9685_MODE1, newMode });
    this.Write(new byte[] { this.PCA9685_PRESCALE, prescale });
    this.Write(new byte[] { this.PCA9685_MODE1, oldMode });
    await Task.Delay(5);
    this.Write(new byte[] { this.PCA9685_MODE1, (byte)(oldMode | 0x80) });

    byte[] bufferRead = new byte[] { this.PCA9685_MODE1 };
    this.Read(bufferRead);
}
        
internal void SetPwm(byte outputNumber, int on, int off)
{
    byte targetOutput_ON_L = (byte)(this.LED0_ON_L + 4 * outputNumber);
    byte targetOutput_ON_H = (byte)(this.LED0_ON_H + 4 * outputNumber);
    byte targetOutput_OFF_L = (byte)(this.LED0_OFF_L + 4 * outputNumber);
    byte targetOutput_OFF_H = (byte)(this.LED0_OFF_H + 4 * outputNumber);

    this.Write(new byte[] { targetOutput_ON_L, (byte)on });
    this.Write(new byte[] { targetOutput_ON_H, (byte)(on >> 8) });
    this.Write(new byte[] { targetOutput_OFF_L, (byte)(off) });
    this.Write(new byte[] { targetOutput_OFF_H, (byte)(off >> 8) });
}
…

Zur Vollständigkeit kommen noch die restlichen Methoden. Das Umrechnen (GetPrescale) ist hier direkt kopiert und unterscheidet sich auch nicht mal von der C++ Version die in den Sourcecode von Adafruit fand.
Das Versenden der Bytes braucht nicht mehr code als die Netduino Lösung.

…
private byte GetPrescale(float frequency)
{
    frequency *= 0.9f;
    // internal clock frequency
    float prescaleval = 25000000;
    prescaleval /= 4096;
    prescaleval /= frequency;
    prescaleval -= 1;

    return (byte)(prescaleval + 0.5);
}

private bool Write(byte[] buffer)
{
    var result = this._i2cDevice.WritePartial(buffer);

    if (result.Status != I2cTransferStatus.FullTransfer)
    {
        Debug.WriteLine(result.Status);
    }

    return result.Status == I2cTransferStatus.FullTransfer;
}

private bool Read(byte[] buffer)
{
    var result = this._i2cDevice.ReadPartial(buffer);

    if (result.Status != I2cTransferStatus.FullTransfer)
    {
        Debug.WriteLine(result.Status);
    }

    return result.Status == I2cTransferStatus.FullTransfer;
}
}

Kommt mir immer noch verdreht vor
Bereits im Blog Post für das Netduino Beispiel, hatte ich den Eindruck, dass die Ansteuerung verdreht ist. Das Verhalten ist auch hier Identisch.

Verkabeln
Vier Leitungen reichen, um das Modul Anzusteuern. Hier sind keine Besonderheiten. Damit der Angeschlossene Servo zuverlässig reagiert, sollte eine Externe Spannungsquelle angeschlossen werden.

Die Wesentlichen Technischen Infos:
PWM Signal kann in 4096 Stufen gesteuert werden (12Bit)
PWM Signal kann zwischen 24Hz bis maximal 1526Hz eingestellt werden.
Die 16 Anschlüsse für die Stromversorgung der Servos, können maximal betrieben werden in Abhängigkeit der Angeschlossenen Spannungsversorgung.
PWM Output selbst kann maximal bis 25mA verwendet werden für LEDs
Spannungsversorgung für die Steuerung über I²C darf von 2,3V bis 5,5V liegen.
16 PWM Ausgänge
Kann über einen Externen Clock betrieben werden, der maximal bis 50MHz sein darf.
Intern ist ein 25MHz Oszillator verbaut.
Kann für LEDs oder Servos verwendet werden.


Github Quellcode in C++
Datenblatt:


Sonntag, 23. September 2018

PCA9685 PWM Driver Modul mit dem Netduino


Nach etwas längerer Zeit ist eine Thema mit dem Netduino wieder dran. Diesmal ist das PWM Treiber Modul dran, den ich schon etwas länger hier liegen habe und es ist eigentlich zunächst für den Raspberry Pi gedacht, aber da fehlt mir noch eine neue Platine um einen eigenen Motor Treiber zu bauen.

Benötigt wird:
  • Netduino (sollte mit allen Versionen funktionieren)
  • PCA9685 16 Channel PWM Driver (Meins ist nicht von Adafruit)
  • Mindestens ein Servo zum Testen
  • Externe Spannunsquelle mit Maximal 6V


Neues Projekt
Für das Projekt werden die Referenzen Microsoft.SPOT.Hardware und Microsoft.SPOT.Native die für die I²C und Debug Ausgaben verwendet werden. Neben der bestehenden Program.cs Klasse wird eine eigene Klasse für den PWM Treiber hinzugefügt, die dann den Inhalt hat des hier folgenden gezeigten Code. Weitere Kommentare sind im Github Repository zu sehen.

public class Pca9685 : I2CDevice
{
    private readonly byte PCA9685_MODE1 = 0x00;
    private readonly byte PCA9685_PRESCALE = 0xFE;

    private readonly byte LED0_ON_L = 0x06;
    private readonly byte LED0_ON_H = 0x07;

    private readonly byte LED0_OFF_L = 0x08;
    private readonly byte LED0_OFF_H = 0x09;

    private readonly byte ALL_LED_ON_L = 0xFA;
    private readonly byte ALL_LED_ON_H = 0xFB;
       
    private int _period;
       
    public Pca9685(int period) : base(new Configuration(0x40, 100))
    {
        this._period = period * 1000;
    }

    public void Reset()
    {
        this.Write(new byte[]{ PCA9685_MODE1, 0x00 });
        Thread.Sleep(10);
    }

    internal void Start()
    {
        int hz = 1000000 / this._period;

        this.Reset();
        this.SetPwmFrequency(hz);
    }
        
    public void SetPwmFrequency(float frequency)
    {
        frequency *= 0.9f;
        float prescaleval = 25000000;
        prescaleval /= 4096;
        prescaleval /= frequency;
        prescaleval -= 1;

        byte prescale = (byte)(prescaleval + 0.5);

        byte[] buffer = new byte[1] { PCA9685_MODE1 };
           
        if (this.Read(buffer) != 1)
        {
            throw new Exception("can not read mode1");
        }

        byte oldMode = buffer[0];

        byte newMode = (byte)(((byte)oldMode & (byte)0x7F) | (byte)0x10);

        // go to sleep
        this.Write(new byte[] { PCA9685_MODE1, newMode });
        this.Write(new byte[] { PCA9685_PRESCALE, prescale });
        this.Write(new byte[] { PCA9685_MODE1, oldMode });
        Thread.Sleep(5);
        this.Write(new byte[] { PCA9685_MODE1, (byte)(oldMode | 0x80) });

        byte[] bufferRead = new byte[] { PCA9685_MODE1 };
        this.Read(bufferRead);
        Debug.Print("Mode now: " + bufferRead[0].ToString());
    }
       
    internal void SetPwm(byte outputNumber, int on, int off)
    {
        Debug.Print("Output: " + outputNumber.ToString() + ", ON: " + on.ToString() + ", OFF: " + off.ToString());

        byte targetOutput_ON_L = (byte)(LED0_ON_L + 4 * outputNumber);
        byte targetOutput_ON_H = (byte)(LED0_ON_H + 4 * outputNumber);
        byte targetOutput_OFF_L = (byte)(LED0_OFF_L + 4 * outputNumber);
        byte targetOutput_OFF_H = (byte)(LED0_OFF_H + 4 * outputNumber);

        this.Write(new byte[] { targetOutput_ON_L, (byte)on });
        this.Write(new byte[] { targetOutput_ON_H, (byte)(on >> 8) });
        this.Write(new byte[] { targetOutput_OFF_L, (byte)(off) });
        this.Write(new byte[] { targetOutput_OFF_H, (byte)(off >> 8) });
    }

    private int Write(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateWriteTransaction(buffer)
        };

        return Execute(trans, 1000);
    }
       
    private int Read(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateReadTransaction(buffer)
        };

        return Execute(trans, 1000);
    }
}

Inhalt zur Klasse
Mit der Fertigstellung der Klasse, bzw. ist diese jetzt soweit ausgeführt, dass die einzelnen PWM Ausgänge des Moduls über die I²C gesteuert werden können. Sieht man in das Datenblatt, dann ist zusehen, dass das Modul noch ein paar mehr Sachen kann. Zum einen bin ich hier nicht auf das Kaskadieren des Moduls mit mehreren nicht eingegangen. Dazu komme ich auf ein anderes mal, wenn ich mehrere Module davon besitze.
Der Programmcode selbst habe ich ursprünglich aus öffentlichen C++ Programmcode entnommen und habe deshalb auch die selben Namen für Methoden verwendet wie aus dem Adafruit Programmcode.

Vielleicht verdreht
An der Stelle für SetPwm bin ich mir noch nicht sicher ob die Ansteuerung so richtig ist. Zugegeben, ich bin noch nicht mit dem Datenblatt durch. Da ich selbst weis, wie ein PWM Signal auszusehen hat, sollte auch die Ansteuerung meiner Vorstellung entsprechen. Doch wenn ich den High Puls festlege, dann ist dieser länger als der Low Puls Länge Inherhalb der Periode. Das sollte eigentlich umgekehrt sein.

Verkabeln
Mit vier Leitungen ist das Modul vom Netduino steuerbar. Damit aber auch die Servos später versorgt sind, muss in der Mitte ebenfalls eine Spannungsquelle angeschlossen werden.


Probleme
Falls ihr die Stromversorgung in der Mitte vom Modul mit dem 5V Ausgang vom Netduino versorgt, kann es sein, dass es nicht funktioniert oder instabil reagiert. Bedeutet, kann funktionieren, muss aber nicht. Besser ist immer eine seperate Versorgung anzuschließen. Allein je nach größe des verwendeten Servos und dessen Belastung, könnte die Stromaufnahme zu groß sein (Leider konnte ich keine Information finden, wieviel Strom maximal am 5V Pin abgenommen werden kann vom Netduino).

Die Wesentlichen Technischen Infos:
PWM Signal kann in 4096 Stufen gesteuert werden (12Bit)
PWM Signal kann zwischen 24Hz bis maximal 1526Hz eingestellt werden.
Die 16 Anschlüsse für die Stromversorgung der Servos, können maximal betrieben werden in Abhängigkeit der Angeschlossenen Spannungsversorgung.
PWM Output selbst kann maximal bis 25mA verwendet werden für LEDs
Spannungsversorgung für die Steuerung über I²C darf von 2,3V bis 5,5V liegen.
16 PWM Ausgänge
Kann über einen Externen Clock betrieben werden, der maximal bis 50MHz sein darf.
Intern ist ein 25MHz Ozillator verbaut.
Kann für LEDs oder Servos verwendet werden.


Quellenangaben:
Github Quellcode in C++
Datenblatt:



Sonntag, 22. Juli 2018

Arduino Motor Driver Shield auf Raspberry Pi


Mit einem Expansion Board für Raspberry Pi können die Shields für Arduino verwendet werden. In diesen Beispiel wird ein Motor Shield verwendet, zu dem ich bereits zwei Blog Einträge geschrieben habe. In dem Beispiel mit dem Netduino ist nur die Programmiersprache gleich, aber das verwendete Framework unterscheidet sich stark von einander. Allein schon das Konfigurieren eines Pins für den Raspberry Pi geht vollständig einen anderen Weg. Offen gestanden bin ich wohl an der Stelle konservativ und würde eher im dot Net Umfeld das .NET Micro Framework bevorzugen.

Benötigte Hardware:

  • Raspberry Pi 2 oder 3
  • Waveshare ARPI600 IO Expansion Board
  • Arduino MotorShield mit L293D

Windows IoT Extensions
Mit dem Anlegen einer neuen Solution als Universal Application oder kurz gesagt UWP, muss zunächst die Reference Windows IoT Extensions hinzugefügt werden. Je nach Ziel Version von Windows IoT kann die aktuelle passende Bibliothek hinzugefügt werden. Mit dieser werden dann Klassen bereit gestellt, mit dem der Zugriff auf die GPIOs möglich ist.



Neue MotorShield Klasse anlegen
Den Aufbau der Klasse ist fast wie aus dem Beispiel mit dem Netduino. Die öffentlichen Methoden sind die gleichen. Nur intern musste das Initialisieren der Hardware Pins anders gelöst werden. (Motor Treiber für Rover (.NETMF))
Für mich verwunderlich war, dass es keine PWM Ansteuerung gab oder ich habe das einfach übersehen. Zumindest nach kurzer Suche kamen keine passenden Ergebnisse, wie man die GPIOs mit einem PWM Signal ansteuert. Daher nur Digitales Gas geben.

Anzusteuernde Pins
Im Netz sind Zahlreiche Bilder, die die Pins beschreiben. Auf Waveshare (https://www.waveshare.com/wiki/ARPI600) sind die Pins ebenfalls beschrieben, allerdings ist das auf der Erweiterung anders gekennzeichnet. Hier war es nicht immer schlüssig, ob der Hardware Pin oder GPIO Pin gemeint ist. Am Ende habe ich festgestellt, dass man die Pin Ansicht von Raspberry Pi benötigt und dann auf dem ARPI600 die Beschriftung ablesen und vergleichen musste, um auf das richtige Mapping zu kommen.


Die folgende Tabelle zeigt, mit welchen Bezeichnungen man zu tun hat und wie diese zu einander passen.

Raspberry Pin
Raspberry GPIO
ARPI600
Arduino
MotorShield
8
UARTTO TX
P_RX
D0

10
UARTTO RX
P_TX
D1

11
17
P0
D2

12
18
P1
D3
M2
13
27
P2
D4
Clock
15
22
P3
D5
M4
16
23
P4
D6
M3
18
24
P5
D7
Enable Shiftregister
22
25
P6
D8
Data
7
4
P7
D9

24
8
CE0
D10

19
10
MOSI
D11
M1
21
9
MISO
D12
Latch
23
11
SCK
D13


Verhältnismäßigkeit
Abgesehen der fehlenden Stufenlosen Regeln der Motoren, ist die Steuerung nicht anders als mit einem Arduino oder Netduino. Um nur den Motor Treiber zu verwenden, ist das verwenden mit einem Raspberry Pi gerade zu übertrieben. Selbst mit dem Raspberry Pi 3 und Windows 10 IoT dauerte der Bootvorgang ca. eine Minute, bis dann endlich der Motor Treiber ausgeführt wird. Das relativiert sich dann jedoch, wenn man weitere Funktionalitäten implementiert.


Samstag, 7. Juli 2018

Wetterstation und Daten Logger


Immer die Aktuellen Messdaten abzurufen ist schön, aber auf Dauer würde man doch gerne sich ansehen wie sich das Wetter über einen Zeitraum verändert hat. Man könnte natürlich immer zu jeder Stunde nach sehen und sich das in der Excel Tabelle Notieren. Besser wäre der Vorgang, ein Programm zu verwenden das im vordefinierten Intervall die Messdaten automatisch speichert.


Wemos Codebeispiele
Die kurz Beschreibung, wie die Daten auf die SD Karte geschrieben werden, ist auf dem Github zu Wemos zu finden.

Simple übernommen
Ich habe das Codebeispiel übernommen und entsprechende Anpassungen und Erweiterungen eingesetzt. Im wesentlich wird am Anfang das SD Shield initialisiert und geprüft, ob eine SD Karte eingesetzt ist. Über den Seriellen Monitor wird der Typ und die Kapazität der SD Karte abgezeigt. Wenn die Zieldatei noch nicht vorhanden ist beim Speichern, wird die Datei "weather.csv" angelegt und bekommt in die erste Zeile die Header Info zu den Spalten.

Abruf der gespeicherten Daten
Nach dem Initialisieren und der ersten Messungen, werden im Beispiel von mir, alle zehn Sekunden die aktuellen Daten auf die SD Karte gespeichert. Die Daten werden in einer CSV Datei angehängt. Hierbei wird die Datei geöffnet und der neue Datensatz in eine neue Zeile geschrieben. Um später die Datei zu entnehmen, muss die SD Karte aus dem Shield entnommen werden und am PC mit einem SD Karten Slot eingelesen werden.

Problem mit Excel
Öffnet man die Datei in Excel, so kann es sein das Werte als Datum Formatiert werden. Das liegt mitunter an der verwendeten Zeichen für Nachkommastelle. Technisch gesehen ist alles Richtig, nur Excel macht etwas zu viel des Guten.


Statt die Datei per Doppel klick zu öffnen, muss die Datei über Excel aufgerufen werden. Über den Reiter "Daten" und dann Daten abrufen -> Aus Datei -> Aus Text/CSV  kann die Datei importiert werden.


Nach dem Import öffnet sich ein neuer Dialog. In der dritten Combobox wählt ihr Dateitypen nicht ermitteln und anschließend kann dann auf den Button Laden geklickt werden.


Leider kann das hier nur zum Betrachten verwendet werden. Eine Darstellung in einem Diagramm geht hier ebenfalls nicht. Zugegeben empfand ich dies sehr nervig und schrieb hierzu ein einfachen Konverter, der mir die Werte in das Zielformat bringt. In dem vorigen Bild ist auch zu sehen, dass die Nachkommastelle komplett entfernt wurde.

Hat nur eine Aufgabe
Das Tool zum konvertieren der CSV Datei ist ebenfalls auf meinem Repository zu finden. Momentan kann das Tool nur eine Sache und zwar die Zahlen mit einem Punkt als Nachkommastelle umzustellen zu einem Komma. Wenn die Anforderungen steigt, dann werde ich sicherlich mehr daraus machen.


Im Diagramm betrachten
Kommen wir zum eigentlich Ziel. Die gemessenen Daten können in Excel im Einzelnen Betrachtet werden. Mit dem Anlegen einer Diagramm-kurve sieht man wie die Temperatur und Feuchtigkeit zu einander sich verändert haben. Der Luftdruck muss ein separates Diagramm haben, da die Werte im Hektopascal hinterlegt sind.


Immer noch nicht Rund
Das Automatische Aufzeichnen der Messdaten ist schon hilfreich. Aber an den gespeicherten Messungen ist keine Uhrzeit angegeben, so dass eine Zeitliche Orientierung nicht ermittelt werden kann. Und noch besser wäre, die Diagramme ebenfalls im Browser anzuzeigen. Aber dazu komme ich zu einem anderen Zeit.



Sonntag, 3. Juni 2018

Windows 10 Iot Core Dashboard Installation geblockt


Da will man wieder über das Dashboard seinen Raspberry Pi erreichen, erscheint eine Meldung, dass die Anwendung blockiert wurde. Für mich verwunderlich, dass etwas von Microsoft nicht mehr ausgeführt oder installiert werden konnte.

Ursache
Nach einem Größeren Update für Windows 10 im Mai, wurde die Sicherheitseinstellung für das Installieren über URL ausgeschaltet. Um das wieder frei zu geben, muss lediglich eine Einstellung in der Registrie geändert werden.

Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel\Internet = Enable


Die Änderung funktioniert akut, so dass die Installation des Windows 10 IoT Dashboard im Anschluss gestartet werden kann. Statt einer Meldung, dass die Installation geblockt wird, sollte nun die Option Installieren wieder verfügbar sein.


Zuletzt
Hier sollte man sich Bewusst machen, dass mit der Freigabe eine mögliche Sicherheitslücke für "Man in the middle attack" geöffnet wird. Unschön aber offensichtlich notwendig.

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....