Um die Möglichkeiten, auf dem Raspberry Pi in C# zu programmieren, auszutesten, habe ich ein einfaches Python-Projekt genommen und versucht, es mit C# umzusetzen.
Das Projekt kommt aus dem Freenove Starter Kit, Kapitel 9: Potentiometer und RGB-LED.
Das sah eigentlich nach einem guten Startprojekt aus. Nicht zu simpel (wie eine LED zum Blinken zu bringen) und auch nicht zu schwer, da nicht allzu viel Hardware und Code zum Einsatz kommt.
Zunächst wurde die Hardware vorbereitet und der Python-Code ausprobiert. Das funktionierte alles auf Anhieb und machte Mut für die C#-Schritte.

Mit der .net-Vorbereitung sollte schon mal ein guter Grundstein gelegt sein. Da die Hardware schon vorbereitet ist, kann es direkt mit der Projekterstellung in Visual Studio losgehen:

Ganz mutig .net9:

Damit die GPIO-Pins und die daran angeschlossene Hardware unter C# zur Verfügung steht, müssen dem Projekt zwei Pakete hinzugefügt werden:

Und dort in der Suchmaske im Reiter „Durchsuchen“ iot.device.bingings

und system.device.GPIO

hinzufügen. Als letzter Vorbereitungsschritte muss jetzt noch im Konfigurationsmanager die ARM64-Umgebung hinzugefügt werden.

Nun kann mit der Programmierung gestartet werden:
Der erste Block ist recht einfach und wenig überraschend:
Mit den using-Zeilen wird der Zugriff auf die benötigten Elemente aus den Paketen erleichtert, die Konstanten helfen bei der Programmierung und das erzeugte I2CDevice wird für den Analog->Digitalkonverter benötigt:
using System.Device.I2c;
using System.Device.Pwm.Drivers;
// Definitionen der Adressen und Pin-Zuordnungen für den PCF8591
const int BUS_ID = 1;
const int PCF8591_ADRESS = 0x48;
const int ANALOG_IN_MASKE = 0x40;
const int KANAL_ROT = 0;
const int KANAL_GRÜN = 1;
const int KANAL_BLAU = 2;
I2cDevice I2CDevice = System.Device.I2c.I2cDevice.Create(new System.Device.I2c.I2cConnectionSettings(BUS_ID, PCF8591_ADRESS));
// Defintionen für den GPIOController und die GPIO-Pins
const int LED_ROT = 22;
const int LED_GRÜN = 27;
const int LED_BLAU = 17;
Etwas überraschender ist der zweite Teil:
In der Freenove-Python-Version wird einfach nur drei GPIO-Pins für die Ausgabe der umgewandelten analogen Werte verwendet. Bei der Entwicklung des Programms unter C# führte das, wenig überraschend, zu keinem richtigen Ergebnis: Ein GPIO-Pin kann nur zwei Zustände annehmen: High/Low, true/false, 1/0, 3,3V/0V. Aber nichts dazwischen. Unter Python scheint das irgenwie zu funktionieren.
Die Magie liegt in der (versteckten) Anwendung der Pulsweitenmodulation. Und die können mittels der SoftwarePwmChannel auf die GPIO-Pins gelegt werden. Also identisch mit der Python-Programmierung, nur unter einem anderen Namen:
// Kanäle für die Ausgabe der Werte
SoftwarePwmChannel ErzeugePWMKanal(int pinNr)
{
SoftwarePwmChannel pwmKanal = new SoftwarePwmChannel(
pinNr, 400, 0.5, true);
pwmKanal.Start();
return pwmKanal;
}
SoftwarePwmChannel PwmRot = ErzeugePWMKanal(LED_ROT);
SoftwarePwmChannel PwmGrün = ErzeugePWMKanal(LED_GRÜN);
SoftwarePwmChannel PwmBlau = ErzeugePWMKanal(LED_BLAU);
Der SoftwarePwmChannel wird mit der Pin-Nr, der Frequenz für die Pulsweitenmodulation (400 Hz), dem dutyCycle-Wert von 0,5 und der Verwendung eines Präzisionstimer (true-Parameter) konfiguriert.
Der dritte Teil hat etwas Datenanalysle bei den Tests erfordert.
Der verwendete Analog-Digitalwandler erfordert ein bestimmtes Verfahren zum Auslesen der Werte:
- Vom I2C-Busmaster (Der Raspberry) wird der Baustein und der Kanal zum Lesen aufgerufen. Dazu schreibt der Busmaster den Lesebefehl auf dem Bus.
- Der PCF8591 wird nun beim Lesen die Werte des gewünschten Kanals zurückgeben.
Dafür kann die I2CDevice.WriteRead-Funktion genutzt werden. Die Erfahrung beim Testen hat jedoch gezeigt, das der erste gelesene Wert noch vom vorher angesprochenen Kanal kommt. Deshalb liest die Routine zwei Werte aus (Ergebnis = new byte[2]) und verwendet nur den zweiten Wert:
// Lesen und ausgeben der Werte eines Kanals
void KanalLesenUndAusgeben(
int kanal,
string name,
SoftwarePwmChannel pwmKanal)
{
Span Lesebefehl = new byte[1];
Lesebefehl[0] = (byte)(ANALOG_IN_MASKE + kanal);
Span Ergebnis = new byte[2];
I2CDevice.WriteRead(Lesebefehl, Ergebnis);
Console.WriteLine(name + "(" + Lesebefehl[0] + "):" +
Ergebnis[1].ToString("000") + ", ");
pwmKanal.DutyCycle = Ergebnis[1] / 255f;
}
Das war die ganze erforderliche Magie. Mit dieser Umsetzung funktionert das Freenove-Projekt unter C# exakt gleich:
Console.Clear();
Console.WriteLine("Hello, I2C (Strg-C für Abbruch):");
while (true)
{
Console.SetCursorPosition(0, 2);
KanalLesenUndAusgeben(KANAL_ROT, "rot ", PwmRot);
KanalLesenUndAusgeben(KANAL_GRÜN, "gruen", PwmGrün);
KanalLesenUndAusgeben(KANAL_BLAU, "blau ", PwmBlau);
System.Threading.Thread.Sleep(100);
}
Nun kann das Projekt veröffentlicht werden:

Am einfachsten in einem Verzeichnis direkt auf dem Raspberry Pi:



Über Laufwerk R ist in dieser Installation der Raspberry auf dem Windowsrechner zu erreichen.
In diesem Verzeichnis kann nun auf dem Raspberry das Programm gestartet werden:
dotnet RGBLED.dll

Diese erste Umsetzung hat unter C# sehr gut funktioniert. Es war zwar etwas Jugend-forscht-Motivation erforderlich (Einlesen der Werte über den I2C-Bus aus dem PCF8591-Baustein, Ausgabe der Werte über die Pulsweitenmodulatin), aber nichts, wo vor man sich fürchten muss.
Für die verwendete Pulsweitenmodulation wurde unter Einsatz etwas älterer Elektronikausrüstung ein kleines Video erstellt:
Auf zum nächsten Projekt.