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:


Kommentare

Beliebte Posts aus diesem Blog

Arduino Control (Teil 5) - PWM Signal einlesen

RC Fahrtenregler für Lego Kettenfahrzeug

Angular auf dem Raspberry Pi