Das Custom CAN ID-Protokoll hat sich in der industriellen Automatisierung als eine Lösung zur Gerätesteuerung etabliert. In diesem Zusammenhang eignet sich ein Arduino-Board sowohl für die Systemintegration als auch als Host-Controller für Smart Displays, insbesondere für Modelle ohne Touch-Funktion. Über das Custom CAN ID-Protokoll lassen sich Smart Displays ohne Touch-Funktion effektiv mit einem Arduino-Board steuern.
Dieser Artikel stellt ein Demoprogramm vor, bei dem ein Arduino UNO zusammen mit einem CAN Shield eingesetzt wird, um ein Smart Display über das Custom CAN ID-Protokoll zu steuern. Als Bedienelement kommt dabei ein OLED Knob Display zum Einsatz. Anhand des Programms werden verschiedene Aufgaben und Funktionen eines Smart Displays ohne Touch-Funktion demonstriert. Für Entwickler, die ihre Kenntnisse in der Systemintegration sowie in der Steuerung von Smart Displays mit und ohne Touch-Funktion über ein OLED Knob Display vertiefen möchten, bietet dieses Beispiel eine praxisnahe Referenz. Das folgende Diagramm zeigt die Umsetzung der Anwendung.

Für diese Anwendung werden folgende Komponenten benötigt:
- 10,1” Smart Display mit Custom CAN ID
- Arduino UNO
- CAN Bus Shield für Arduino UNO
- OLED Display Knob Rotator-Modul
- ST-Link CAN Bus Dongle
- GUI Builder v0.4.2 oder höher
- Arduino IDE v2.0.4
Die Demo ist in drei Schritte unterteilt:
- Demoprojekt in GUI Builder erstellen
- Projekt erstellen und hochladen
- Arduino-Host programmieren
Demoprojekt in GUI Builder erstellen
Starten Sie zunächst eine aktuelle Version von GUI Builder und erstellen Sie ein neues Projekt. Geben Sie den gewünschten Projektnamen ein und prüfen Sie, ob der Projektordner automatisch ausgewählt wurde. Wählen Sie anschließend den Gerätetyp aus; in diesem Beispiel wird ein 10,1"-Display verwendet. Wählen Sie danach das gewünschte Protokoll und belassen Sie die Displayausrichtung im voreingestellten Querformat. Wählen Sie ein leeres UI-Template und klicken Sie abschließend auf die Schaltfläche Create. Die folgende Abbildung zeigt die beschriebenen Schritte.

Nach dem Erstellen des Projekts wird die folgende Seite angezeigt.

Für das vorgesehene Demo-Szenario werden insgesamt vier Seiten benötigt. Daher müssen drei weitere Seiten hinzugefügt werden. Klicken Sie dazu auf die Schaltfläche Page und anschließend auf die Plus-Schaltfläche, um die drei zusätzlichen Seiten anzulegen.

Im nächsten Schritt wird ein benutzerdefinierter Hintergrund hinzugefügt. Wechseln Sie zum Bereich Resource und wählen Sie die Option Background. Unter Option 2 stehen drei vordefinierte Hintergründe für die Seiten Industry, Medical und Vehicle zur Verfügung. Um einen weiteren Hintergrund hinzuzufügen, klicken Sie unter Option 3 auf Add Widget. Klicken Sie anschließend unter Option 4 auf die Schaltfläche mit den drei Punkten (...), die sich links neben der Reset-Schaltfläche befindet. Hier können Sie einen benutzerdefinierten Hintergrund hinzufügen und damit den standardmäßig für die erste Seite verwendeten Hintergrund ersetzen.

Anschließend kann im Bereich Pages der gewünschte Hintergrundstil aus den verfügbaren Optionen ausgewählt werden, wie in der folgenden Abbildung dargestellt.

Als Nächstes wird ein benutzerdefiniertes Widget hinzugefügt. Für das Demo-Szenario wird ein benutzerdefiniertes Multistate Widget benötigt, über das die Seiten Industry, Medical und Vehicle ausgewählt werden können. Wechseln Sie zunächst zurück zum Bereich Controls auf der ersten Seite. Öffnen Sie dort den Bereich Resource und wählen Sie die Option Multistate Widget. Klicken Sie unter Option 2 auf Add Widget und anschließend unter Option 3 auf Modify. Löschen Sie im vierten Schritt alle dem Widget zugeordneten Standardbilder. Klicken Sie danach im fünften Schritt unter Option 5 auf Add und wählen Sie im sechsten Schritt die benutzerdefinierten Widget-Bilder einzeln aus dem lokalen Ordner auf Ihrem Computer aus. Klicken Sie abschließend im siebten Schritt auf OK, um die benutzerdefinierten Bilder zur Liste des Multistate Widgets hinzuzufügen, wie in der folgenden Abbildung dargestellt.

Wechseln Sie anschließend erneut zum Bereich Controls auf der ersten Seite. Wählen Sie entsprechend dem Demo-Szenario das benutzerdefinierte Multistate Widget zur Auswahl der Seiten Industry, Medical und Vehicle aus. Ziehen Sie das Widget per Drag-and-drop in den vorgesehenen Layoutbereich der Seite. Damit ist die Konfiguration der ersten Seite abgeschlossen.

Wenden Sie die gleiche Vorgehensweise auf die drei weiteren erstellten Seiten an. Wählen Sie auf der zweiten Seite ein Widget für Industry, auf der dritten Seite ein Widget für Medical und auf der vierten Seite ein Widget für Vehicle. Ziehen Sie anschließend jedes Widget per Drag-and-drop in den entsprechenden Layoutbereich der jeweiligen Seite.

Projekt erstellen und hochladen
Nachdem die vier Seiten erfolgreich erstellt und die jeweiligen Widgets platziert wurden, prüfen Sie, ob auf der Startseite das benutzerdefinierte Multistate Widget zur Auswahl der Seiten Industry, Medical und Vehicle vorhanden ist. Anschließend kann das Projekt erstellt werden. Wechseln Sie zum Bereich Page Information und klicken Sie auf die Schaltfläche Device. Wählen Sie dort Device Properties aus. Prüfen Sie, ob die Protokolleinstellungen korrekt sind, wählen Sie den passenden COM-Port und stellen Sie die gewünschte Baudrate ein. Klicken Sie anschließend auf „Connect to Device“, um die Verbindung zum Gerät herzustellen. Starten Sie danach den Upload über die Schaltfläche Upload, wie in der folgenden Abbildung gezeigt, und warten Sie, bis der Vorgang vollständig abgeschlossen ist.

Nach Abschluss des Projekt-Uploads klicken Sie auf OK, wie in der folgenden Abbildung gezeigt.

Nach erfolgreichem Upload wird die Startanzeige eingeblendet, wie in der folgenden Abbildung dargestellt.

Anschließend wechselt das Display von der Startanzeige zur ersten Seite. Damit sind Build und Upload des Projekts über GUI Builder abgeschlossen, wie in der folgenden Abbildung gezeigt.

Arduino-Host programmieren
Der folgende Code beginnt mit dem Abschnitt includes, in dem die für das Programm benötigten Bibliotheken und Header-Dateien eingebunden werden. Dazu gehören unter anderem Adafruit_SSD1327, Adafruit_FT6206, Adafruit_GFX und SPI. Zusätzlich werden benutzerdefinierte Header-Dateien wie mcp2515_can.h, lcdgfx_gui.h und lcdgfx.h eingebunden.
Nach dem Abschnitt includes folgt der Abschnitt defines. Dort werden bestimmte Konstanten und Pin-Konfigurationen festgelegt. Dazu gehören unter anderem Definitionen für den Drehencoder, die Abmessungen des OLED-Displays, CAN-Bus-Einstellungen sowie Widget-Konfigurationen.
Anschließend prüft eine bedingte Anweisung den verwendeten Mikrocontrollertyp und wählt die passende serielle Schnittstelle aus. Wird der Code auf einem SAMD-Mikrocontroller ausgeführt, wird die SerialUSB-Schnittstelle verwendet. Andernfalls kommt die standardmäßige Serial-Schnittstelle zum Einsatz.
Zusammenfassend bindet der folgende Code die benötigten Bibliotheken und Header-Dateien ein, definiert wichtige Konstanten und Pin-Konfigurationen und wählt abhängig vom verwendeten Mikrocontrollertyp die entsprechende serielle Schnittstelle aus.

Anschließend werden mehrere für das Programm benötigte Variablen und Pins deklariert und initialisiert. Dazu gehören beispielsweise:
SPI_CS_PIN: Diese Konstante ist auf den Wert 9 gesetzt und definiert den Chip-Select-Pin für die SPI-Kommunikation.
CAN_INT_PIN: Diese Konstante ist auf den Wert 2 gesetzt und definiert den Interrupt-Pin für den CAN-Bus.
CAN: Es wird eine Instanz der Klasse mcp2515_can erstellt und mit dem Wert von SPI_CS_PIN initialisiert. Sie wird für die Kommunikation über den CAN-Bus verwendet.
ts: Es wird eine Instanz der Klasse Adafruit_FT6206 erstellt, die für die Touchscreen-Funktion verwendet wird.
display: Es wird eine Instanz der Klasse DisplaySSD1327_128x128_SPI erstellt und mit den für die SPI-Kommunikation mit dem SSD1327-Display erforderlichen Pin-Konfigurationen initialisiert.

Im folgenden Codeabschnitt werden mehrere Arrays vom Typ unsigned char deklariert und initialisiert. Die einzelnen Arrays haben folgende Funktionen:
configMsg: Ein Array mit 8 unsigned-char-Elementen, das eine Konfigurationsnachricht repräsentiert.
PageIndex_0 bis PageIndex_3: Diese Arrays bestehen jeweils aus 8 unsigned-char-Elementen und repräsentieren die Seitenindizes 0 bis 3.
displayMode: Ein Array mit 8 unsigned-char-Elementen, das den Anzeigemodus repräsentiert.
Zusätzlich sind mehrere Arrays mit den Bezeichnungen stmp, stmp1, stmp2 bis stmp12 definiert. Auch diese Arrays bestehen jeweils aus 8 unsigned-char-Elementen und dienen zum Speichern von Datenbytes für verschiedene Widgets oder Komponenten. Anhand der Array-Namen lässt sich der jeweilige Verwendungszweck beziehungsweise die Zuordnung zu den entsprechenden Widgets unterscheiden.
Zusammenfassend speichern diese Arrays wichtige Datensätze für das Programm, darunter Konfigurationsnachrichten, Seitenindizes, Anzeigemodi und Widget-bezogene Daten.

Der folgende Code definiert die Funktion bitmapDemo, die den ganzzahligen Parameter imageIndex verwendet. Die Funktion ist dafür zuständig, ein Bild auf dem OLED Knob Display darzustellen.
Die jeweiligen Bilder sowie deren Positionen und Abmessungen sind in der Header-Datei Image.h definiert.

Die folgende Funktion wartet auf eine Antwort über den CAN-Bus. Dazu wird wiederholt für einen bestimmten Zeitraum gewartet und anschließend der Wert des Flags Recv geprüft. Sobald eine Antwort empfangen wurde, wird das Flag Recv auf 0 zurückgesetzt.

Der folgende Code definiert die Funktion changePageFunction, die für das Ändern des Seitenwerts innerhalb der CAN-Bus-Kommunikation zuständig ist. Abhängig vom übergebenen Parameter pageValue sendet die Funktion entsprechende Nachrichten über den CAN-Bus, um die Seite zu wechseln. Welche Nachricht gesendet wird, richtet sich nach dem Wert von pageValue und dem jeweils zugehörigen PageIndex-Array.

Die Funktion handleTouchEvents verarbeitet Touch-Ereignisse auf dem Touchscreen. Sie erkennt Touch-Eingaben, konvertiert die Koordinaten und führt abhängig von Druck und Dauer der Berührung die entsprechenden Aktionen aus. Zusätzlich verwendet sie die Funktion changePageFunction, um die aktuelle Seite entsprechend der Touch-Eingabe zu aktualisieren. Wird beispielsweise eine lange Berührung erkannt, wird changePageFunction mit dem Wert nowPage aufgerufen, um zur Home Page zu wechseln. Bei einer kurzen Berührung wird changePageFunction ebenfalls mit dem Wert nowPage aufgerufen, um zur ausgewählten Seite zu wechseln.

Der folgende Codeabschnitt zeigt die Arduino-Funktion setup, die beim Start des Programms einmal ausgeführt wird. Sie ist für die Initialisierung der Pins, den Aufbau der seriellen Kommunikation, die Konfiguration des Displays, die Einrichtung des CAN-Busses sowie die Initialisierung des Touchscreen-Controllers zuständig.
Zunächst werden die Modi der Pins pinA, pinB, pinC und EN_Pin festgelegt. Die Pins werden entweder als Eingänge mit aktiviertem Pull-up-Widerstand oder als Ausgang konfiguriert. Der Ausgangspin EN_Pin wird anschließend auf den logischen Pegel High gesetzt.
Danach wird die serielle Kommunikation mit einer Baudrate von 115200 gestartet. Der Programmablauf wartet, bis die Verbindung hergestellt ist.
Anschließend wird das Display-Objekt initialisiert und Einstellungen wie Schriftart und Farbe werden konfiguriert. Danach wird ein Bitmap-Bild an der vorgesehenen Position auf dem Display dargestellt, gefolgt von einer Wartezeit von 2 Sekunden.
Für den Pin CAN_INT_PIN wird ein Interrupt eingerichtet. Als auszuführende Interrupt-Service-Routine wird die Funktion MCP2515_ISR festgelegt. Der Interrupt wird durch eine fallende Signalflanke ausgelöst.
Anschließend wird der CAN-Bus mit einer Baudrate von 250k initialisiert. Schlägt die Initialisierung fehl, wartet das Programm kurz und versucht es erneut. Nach erfolgreicher Initialisierung wird der CAN-Bus in den Normal Mode versetzt und eine entsprechende Erfolgsmeldung über die serielle Schnittstelle ausgegeben.
Danach werden Filter Masks und Filter so konfiguriert, dass alle Frames einschließlich Extended Frames empfangen werden können.
Anschließend wird der Touchscreen-Controller initialisiert. Schlägt die Initialisierung fehl, wird eine Fehlermeldung ausgegeben und das Programm wechselt in eine Endlosschleife.
Nach erfolgreicher Initialisierung des Touchscreens wird abschließend eine entsprechende Erfolgsmeldung über die serielle Schnittstelle ausgegeben.

Bei dieser Funktion handelt es sich vermutlich um eine Interrupt-Service-Routine (ISR) für den CAN-Controller MCP2515. Wird ein Interrupt ausgelöst, setzt die Funktion ein Flag und gibt eine entsprechende Meldung aus.

Die folgenden Funktionen dienen zur Verwaltung des Knob-Status. Die Funktion setKnobState aktualisiert den Status des Knobs und setzt einen Timer zur Erkennung des Idle-Zustands. Die Funktion isKnobIdle prüft anhand des Timerwerts, ob sich der Knob aktuell im Idle-Zustand befindet.

Die Funktion redrawScreen() aktualisiert die Displayanzeige entsprechend dem aktuellen Status des Knobs. Sie prüft, ob sich der Status geändert hat, löscht die aktuelle Anzeige und stellt abhängig vom Knob-Status das entsprechende Bitmap-Bild dar.

Der folgende Abschnitt der Funktion loop() liest die Zustände von drei Pins ein und bestimmt anhand ihrer Werte einen neuen Status. Unterscheidet sich der neue Status vom vorherigen, werden verschiedene Aktionen ausgeführt. Dazu gehören unter anderem die Aktualisierung des Counters, von MultiStateValue, GaugeValue sowie der Werte weiterer Widget-Variablen.

Im darauffolgenden Abschnitt der Funktion loop() wird der Fall verarbeitet, dass der Knob in eine bestimmte Richtung gedreht wird. Dabei werden die entsprechenden Variablen aktualisiert und die erforderlichen Funktionen aufgerufen. Anschließend wird die Variable für den vorherigen Status aktualisiert, damit sie bei der nächsten Ausführung von loop() verwendet werden kann.

Der folgende Abschnitt zeigt den weiteren Ablauf innerhalb der Funktion loop(). Nach dem vorherigen Codeblock werden die folgenden Schritte ausgeführt.
Die Variable oldState wird mit dem Wert von newState aktualisiert.
Wenn nowPage gleich 0 ist bzw. die Seitennummer 1 entspricht, prüft der Code, ob sich MultiStateValue von LastMultiStateValue unterscheidet. Ist dies der Fall, wird LastMultiStateValue mit dem aktuellen Wert von MultiStateValue aktualisiert und MultiStateValue im Array stmp1 gespeichert. Anschließend wird eine Nachricht mit dem Wert von MultiStateValue über den CAN-Bus unter Verwendung der Multi-State-ID 0x716 gesendet.

Wenn nowPage gleich 1 ist bzw. die Seitennummer 2 entspricht, werden ähnliche Prüfungen für verschiedene Widget-Variablen durchgeführt, darunter CircleProgressValue, HorizontalSliderValue, VerticalSliderValue, TemperatureValue, GaugeValue, FixedWidgetValue und IndicatorValue. Unterscheidet sich eine dieser Variablen vom jeweils vorherigen Wert, wird der vorherige Wert aktualisiert und der neue Wert über den CAN-Bus mit der entsprechenden ID gesendet.

Wenn nowPage gleich 2 ist bzw. die Seitennummer 3 entspricht, werden vergleichbare Prüfungen für die auf Seite 3 verfügbaren Widgets durchgeführt, beispielsweise CircleProgressValue, HorizontalSliderValue, GraphValue und AnimationValue. Unterscheidet sich eine dieser Variablen vom jeweils vorherigen Wert, wird der vorherige Wert aktualisiert und der neue Wert über den CAN-Bus mit der entsprechenden ID gesendet.

Wenn nowPage gleich 3 ist bzw. die Seitennummer 4 entspricht, werden entsprechende Prüfungen für die auf dieser Seite verfügbaren Widgets durchgeführt, darunter HorizontalSliderValue, GaugeValue, FixedWidgetValue, AnimationValue, IndicatorValue und ToggleButtonValue. Unterscheidet sich eine dieser Variablen vom jeweils vorherigen Wert, wird der vorherige Wert aktualisiert und der neue Wert über den CAN-Bus mit der entsprechenden ID gesendet.

Nach dem vorherigen Codeblock innerhalb der Funktion loop() wird die Funktion handleTouchEvents() aufgerufen, um Touch-Ereignisse zu verarbeiten.
Befindet sich der Knob im Idle-Zustand, d. h. isKnobIdle() gibt den Wert 1 zurück, wird der Knob-Status über die Funktion setKnobState() auf KNOB_IDLE gesetzt. Anschließend wird redrawScreen() aufgerufen, um die Anzeige neu aufzubauen. Damit ist der Programmcode für das Demoprojekt abgeschlossen.

Nachdem das Programm in der Arduino IDE erfolgreich überprüft und hochgeladen wurde, ändert sich die Anzeige des OLED Knob Displays. Statt des Smart-Display-Bildes wird nun das Touch- oder Rotate-Bild angezeigt. Zu diesem Zeitpunkt zeigen sowohl das Smart Display als auch das OLED Knob Display die voreingestellte Home Page an.

Nun kann das Demo-Szenario ausgeführt werden. Auf der Home Page lässt sich der Rotator drehen, um eine der drei verfügbaren Seiten Industry, Medical oder Vehicle auszuwählen, wie in der folgenden Abbildung dargestellt.

Nach der Auswahl genügt eine kurze Berührung des OLED Knob Displays, um das Smart Display unmittelbar auf die ausgewählte Seite umzuschalten.

Das Demo-Szenario lässt sich wie folgt wiederholen: Beginnen Sie auf der Home Page und drehen Sie den Rotator, um eine der drei weiteren Seiten auszuwählen. Im folgenden Beispiel wird die Seite Medical ausgewählt.

Nach Auswahl der Seite Medical genügt eine kurze Berührung des OLED Knob Displays, um auf dem Smart Display zur ausgewählten Medical-Seite zu wechseln.

Durch Drehen des Rotators am OLED Knob Display nach links oder rechts können die verschiedenen Widgets auf der jeweiligen Seite gesteuert werden, wie in der folgenden Abbildung dargestellt.

Bei einer langen Berührung des OLED Knob Displays kehrt das System automatisch zur Home Page, also Page 1, zurück. Wird das OLED Knob Display 10 Sekunden lang weder gedreht noch berührt, wird die Anzeige ebenfalls zurückgesetzt und kehrt zur Home Page zurück.

Damit ist die Demo für das 10,1" Smart Display und das OLED Knob Rotator Display abgeschlossen. Über kurze oder lange Berührungen des OLED Knob Rotator Displays können verschiedene Seiten gesteuert und gewechselt werden. Darüber hinaus lassen sich die unterschiedlichen Widgets auf den jeweiligen Seiten bedienen.
Das oben beschriebene Programm sowie weitere Informationen zum Demoprojekt finden Sie auf GitHub: https://bit.ly/3oT0o5M.
Die tatsächliche Hardware-Konfiguration ist in der folgenden Abbildung dargestellt.

Dieses Programm zeigt anhand eines praxisnahen Beispiels, wie ein Arduino-Board zusammen mit einem OLED Knob Rotator Display und dem Custom CAN ID-Protokoll eingesetzt werden kann, um verschiedene Seiten zu steuern und über kurze oder lange Berührungen zwischen ihnen zu wechseln. Darüber hinaus ermöglicht es die Bedienung und Steuerung verschiedener Widgets auf den jeweiligen Seiten. Das Programm bietet sowohl Einsteigern als auch erfahrenen Entwicklern eine hilfreiche Referenz, um sich mit der Host-Programmierung und deren Anwendung bei der Steuerung von Smart Displays vertraut zu machen, insbesondere bei Modellen ohne Touch-Funktion.