Raspberry Pi Pico Kurs – #9 – Indikatorentheorie und Timer

Lesezeit 13 min.

In unserem jüngsten Material über den Raspberry Pi Pico haben wir uns eine der einfachsten Arten von Unterbrechungen, die auf dem Mikrocontroller verfügbar sind, genauer angesehen. Außerdem habe ich Ihnen von der recht interessanten Alarmfunktion und dem Problem der vibrierenden Tastenkontakte erzählt und kurz so etwas wie Indikatoren erwähnt. Damit werden wir uns im ersten Teil dieses Materials befassen, und dann werden wir zu den Aspekten der Timer übergehen.

Kaufen Sie ein Set, um das Programmieren mit dem Raspberry Pi Pico W zu erlernen, und nutzen Sie die Vorteile des Kurses, der im Botland Blog verfügbar ist!

Im Set enthalten: Raspberry Pi Pico W-Modul, Kontaktplatte, Leitungen, LEDs, Widerstände, Tasten, Fotowiderstände, digitale Licht-, Temperatur-, Feuchtigkeits- und Drucksensoren, OLED-Display und ein USB-microUSB-Kabel.

Vor dem Start sollte das Team zusammengestellt werden

Ein Satz von Komponenten für den Raspberry Pi Pico Kurs.

Wer das Programmieren anhand von realen Projekten erlernen möchte, braucht natürlich die richtige Ausrüstung, aber keine Sorge – Sie müssen jetzt nicht von einem Artikel zum nächsten springen und eine Liste der benötigten elektronischen Komponenten erstellen. Im Botland-Shop ist ein fertiges Set erhältlich, das alle notwendigen Komponenten enthält, um die in der Tutorial-Reihe beschriebenen Projekte mit dem Raspberry Pi Pico durchzuführen.

In dem fertigen Set von Elementen finden Sie:

  • Raspberry Pi Pico W,
  • MicroUSB-Kabel,
  • Kontaktplatte,
  • Ein Set von Anschlusskabeln in drei Ausführungen,
  • Ein Set von LEDs in drei Farben,
  • Ein Set der in der Elektronik am häufigsten verwendeten Widerstände,
  • Tact Switch-Tasten,
  • Fotowiderstände,
  • Digitaler Lichtsensor,
  • Digitaler Feuchtigkeits-, Temperatur- und Drucksensor,
  • OLED-Display.

Sind Indikatoren wirklich schwierig?

Es hat sich herumgesprochen, dass Indikatoren in der Sprache C ein äußerst schwieriges Thema sind, das selbst erfahrenen Programmierern viele Schwierigkeiten bereitet. Angesichts der Menge an Material, in dem dieser Aspekt der Programmierung diskutiert wird, sowie der ständig auftauchenden Threads in den Foren, können wir daraus schließen, dass dies tatsächlich der Fall ist. Meiner Meinung nach sollten die Indikatoren jedoch nicht zu sehr verteufelt werden, wie es oft der Fall ist, selbst in Artikeln, in denen der Autor versucht, sie so verständlich wie möglich zu beschreiben. Nach meinen Beobachtungen besteht der häufigste Fehler vieler Tutorials darin, dass sie sich ausschließlich auf das Code-Thema konzentrieren. Die Verwendung geeigneter Operatoren und deren grundsätzliche Verwendung, ohne dass zuvor erklärt wird, was Indikatoren eigentlich sind. Deshalb werden wir mit diesem Thema beginnen.

Beispiel einer Speicherstruktur in einem Mikrocontroller.

In den Programmen, die wir bisher durchgeführt haben, haben wir verschiedene Arten von Variablen verwendet. Jede hatte einen Typ und, mehr als einmal, einen Anfangswert. Aus der Sicht des Mikrocontrollers sind sie jedoch alle nur eine Folge von Einsen und Nullen, die im Speicher abgelegt sind. Sie können diese Art von Situation in der ersten Tabelle in der Grafik oben sehen. Ich habe eine Beispiel-Speicherstruktur angelegt, in der die Variablen A, B, C und D gespeichert sind. Jede dieser Variablen hat einen Wert, aber was uns im Zusammenhang mit Indikatoren am meisten interessieren wird, sind ihre Adressen. Wie Gebäude in der realen Welt müssen auch die im Speicher des Mikrocontrollers verborgenen Variablen eine eindeutige Adresse haben. Ohne sie wäre der Speicher nur eine Sammlung von Zufallswerten und wir müssten raten, ob 0x05 die Variable A, B oder etwas ganz anderes ist.

Sobald wir wissen, dass Adressen eine sehr wichtige Rolle spielen, können wir uns die zweite Tabelle ansehen, die den Speicher symbolisiert. Sie speichert die gleichen Variablen A, B, C und D, aber abgesehen davon habe ich auch so etwas wie ‘Indikator auf C’ und ‘Indikator auf B’ dort hineingeschrieben. Der erste befindet sich unter der Adresse 0x00 und speichert den Wert 0x1C. Wie Sie leicht erkennen können, ist dieser Wert identisch mit der Adresse der Variablen C. In ähnlicher Weise speichert der “Indikator auf B” unter der Adresse 0x1D den Wert 0x03, der mit der Adresse der Variablen B identisch ist. Vielleicht wissen Sie jetzt, was es mit Indikatoren auf sich hat. Dies sind spezifische Variablen, die wir als Indikatorvariablen bezeichnen. Auch sie haben einen Typ, der Indikatortyp genannt wird, und speichern Werte, die den Adressen anderer Variablen entsprechen, man könnte sagen, dass sie in gewisser Weise auf diese indizieren. In dem Beispiel in der obigen Grafik zeigt der “Indikator auf C” also auf den Speicherplatz, an dem die Variable C gespeichert ist. In ähnlicher Weise speichert der “Indikator auf B” die Adresse der Variablen B.

Speicherstruktur mit mehreren Indikatoren.

Es ist wichtig zu wissen, dass Indikatoren selbst auch einen bestimmten Platz im Speicher des Mikrocontrollers belegen und ihre eigene eindeutige Adresse haben. Daher steht der Erstellung von Indikatoren, die auf einen anderen Indikator verweisen, der wiederum auf einen anderen Indikator verweisen kann, wie in der obigen Grafik, nichts im Wege. In der Realität werden solche Konstruktionen jedoch recht selten verwendet, meist reicht ein einzelner Indikator, der eine Variable aufruft, bereits aus.

Indikatoren in der Realität

Sobald wir mehr oder weniger wissen, was ‘physische’ Indikatoren sind, können wir dazu übergehen, sie tatsächlich zu verwenden und zu sehen, wie sie funktionieren. Zu diesem Zweck habe ich ein Projekt namens Pointers erstellt. Die Schaltung, die wir für das vorherige Material verwendet haben, kann unverändert bleiben. In diesem Fall werden wir in erster Linie den in Visual Studio Code integrierten seriellen Monitor verwenden. Denken Sie also daran, die entsprechenden Befehle zur Aktivierung des so genannten USB-Ausgabestroms in die Datei CMakeLists.txt aufzunehmen. Im ersten Programm, das wir gleich besprechen werden, erstellen wir einen einzelnen Indikator, um die Adresse einer typischen Variablen zu speichern.

				
					#include 
#include "pico/stdlib.h"

uint8_t variable = 167;
uint8_t *pointer;	//declaration of pointer

int main() {
    stdio_init_all();

    pointer = &variable;	//address assignment to the pointer

    while(true) {
        printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
        sleep_ms(1000);
    }
}

				
			

Dieses Mal erstellen wir im Code eine typische Variable vom Typ uint8_t mit dem Namen variable und dem Anfangswert 167. Aus diesem Element werden wir im folgenden Abschnitt versuchen, die Adresse zu extrahieren. Als nächstes erstellen wir einen Indikator namens pointer. Diese Art von Konstrukt wird erstellt, indem ein Sternchen ‘*’ vor den Namen gesetzt wird. Außerdem stand, wie Sie sehen können, ein Typ uint8_t vor der Indikatordefinition “*pointer”, und doch habe ich gerade eben geschrieben, dass Indikatoren ihren eigenen Typ haben, der einfach ein Indikatortyp ist. Woher kommt dann die für Variablen typische uint8_t? Bei Indikatoren bestimmt der Typ, der dem Indikator vorangestellt ist, auf welchen Datentyp er zeigen wird. In unserem Fall wollen wir die Adresse einer Variablen vom Typ uint8_t extrahieren, weshalb dieser Typ vor dem Indikator stehen sollte. Es kommt häufig vor, dass man auf eine gedankliche Abkürzung stößt wie “wir erstellen einen Indikator vom Typ uint16_t”. Dabei ist zu beachten, dass es sich nicht um den Typ des Indikators handelt, da diese ihren eigenen Typ haben, sondern nur um die Information, welche Art von Daten der Indikator anzeigen wird.

Sobald wir eine geeignete Variable und einen Indikator erstellt haben, können wir ihr eine Adresse zuweisen. Dieser Vorgang wird innerhalb der Hauptfunktion main ausgeführt. Dem Indikator weisen wir dank des so genannten Adressenextraktionsoperators “&” den der Variablen zugewiesenen Speicherplatz zu.

In einer unendlichen while-Schleife werden wir den printf-Befehl verwenden, den wir bereits gelernt haben und der die Daten vom RP2040 an den Computerbildschirm senden wird. Diese Funktion bedarf jedoch einer Erklärung, da ich hier zwei bisher nicht verwendete Spezifizierer verwendet habe. Der erste ist ‘%X’, an seine Stelle kommt die variable, aber bisher haben wir, wenn wir den Wert einer Variablen anzeigen wollten, ‘%d’ oder ‘%f’ verwendet. Hier könnten wir auch ‘%d’ einfügen. In diesem Fall würde 167, der Wert der variable, im seriellen Monitor erscheinen. Dank der Angaben ‘%x’ und ‘%X’ wird die Variable jedoch als hexadezimaler Wert dargestellt. Die Differenz zwischen dem kleinen und dem großen ‘x’ definiert die Form dieses Wertes. 167 in Hexadezimal ist 0xA7 und so sollte die Variable auf dem Computerbildschirm angezeigt werden. Wenn ein kleines ‘%x’ in den Code eingefügt würde, würde der Variablenwert als 0xa7 angezeigt. Der zweite Spezifizierer, das “%p”, ist ebenfalls neu. Dies ist eine spezielle Notation, die sich genau auf Zeiger oder, allgemeiner, Adressen im Speicher bezieht. Der im pointer gespeicherte Wert wird an dessen Stelle eingefügt.

Überwachen Sie das serielle Fenster, nachdem der Code ausgeführt wurde.

Nachdem wir den Code ausgeführt und den seriellen Monitor geöffnet haben, können wir sehen, dass das RPI die gesammelten Informationen über die Variablen sendet. Ihren Wert in hexadezimaler Form – 0xA7 – und die Adresse, an der diese Variable im Speicher des Mikrocontrollers platziert wurde, d.h. 20000BB0.

Indikatoren auf Indikatoren

Lassen Sie uns versuchen, das Programm, das wir gerade gestartet haben, ein wenig zu erweitern. Wir fügen einen weiteren Indikator hinzu, mit dem wir überprüfen können, wo unser ursprünglicher Indikator platziert wurde. Außerdem fügen wir noch eine Konstante hinzu, und auch hier prüfen wir, wo die Speicherung stattgefunden hat.

				
					#include 
#include "pico/stdlib.h"

uint8_t variable = 167;
uint8_t *pointer;	//declaration of pointer
uint8_t **pointer_to_pointer;	//declaration of pointer to pionter

const float conversion_factor = 3.3/4095;
const float *conversion_factor_pointer; //declaration of pointer

int main() {
    stdio_init_all();

    //address assignment to the pointer
    pointer = &variable;
    pointer_to_pointer = &pointer;
    conversion_factor_pointer = &conversion_factor;

    while(true) {
        printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
        printf("pointer addr: %p \n\r", pointer_to_pointer);
        printf("conversion_factor value: %f, conversion_factor addr: %p\n\r", conversion_factor, conversion_factor_pointer);
        sleep_ms(1000);
    }
}

				
			

Diesmal ist der Code etwas länger, und ich habe die Deklaration eines zweiten Zeigers hinzugefügt, der auf den zuvor erstellten Zeiger verweisen wird. Dies ist die Zeiger-zu-Zeiger-Konstruktion, d.h. der erste Zeiger “pointer” speichert die Adresse der Variablen variable, während unter dem zweiten Zeiger “pointer_to_pointer” die Adresse des ersten Zeigers versteckt wird. Wir deklarieren eine solche Kreation, die wir als Zeiger zweiter Ebene bezeichnen können, indem wir ein zweites Sternchen neben dem Zeigernamen “**pointer_to_pointer” hinzufügen. Darüber hinaus habe ich die Konstante conversion_factor in den Code eingefügt, die wir bereits in früheren Artikeln verwendet haben. Sie speichert das Fließkomma-Ergebnis der Operation 3.3/4095. Der Zeiger, der die Adresse dieser Variablen enthält, wird wie zuvor deklariert. Zu beachten ist nur, dass im Falle von Konstanten der Zeiger auch das Schlüsselwort “const” enthalten muss.

Im ersten Teil der Hauptfunktion weisen wir den folgenden Zeigern mit Hilfe des Adress-Chunking-Operators Werte zu. Zunächst weisen wir dem pointer die Adresse der Variablen variable zu, dann speichern wir in pointer_to_pointer die Adresse des pointers und schließlich weisen wir conversion_factor_pointer den Speicherplatz zu, an dem die Variable conversion_factor gespeichert ist.

In der Hauptschleife while werden dank des printf-Befehls die in den Variablen und Zeigern gespeicherten Werte nacheinander angezeigt. Die erste Anweisung ist uns bereits bekannt; sie sendet die in der variable und dem pointer versteckten Werte an den Computerbildschirm. Wir zeigen dann die Zeigeradresse von pointer und im letzten Schritt die in conversion_factor und conversion_factor_pointer gespeicherten Daten an.

Adressen von Variablen und Zeigern aus dem zweiten Beispiel.

Nachdem wir den Code ausgeführt haben, sehen wir die numerische Darstellung der Speicherplätze, an denen die Variablen und der Zeiger gespeichert sind. Wie Sie sehen können, ist die Adresse von variable unverändert geblieben und lautet immer noch 20000BB0. Interessanterweise wurde der pointer in seiner Nähe an der Adresse 200017E4 gespeichert. Die Konstante aus unserem Programm wurde dagegen in einem ganz anderen Teil des Speichers an der Adresse 10006E0C abgelegt.

Damit haben wir die erste Verwendung von Zeigern geübt, indem wir uns die Adressen im RP2040-Speicher angesehen haben. Diese Art von Programmen ist jedoch nur ein Kuriosum und schöpft das volle Potenzial der Indikatoren nicht aus. In den meisten Codes schaut sich niemand die Adressen an, an denen die Variablen platziert wurden, da dieses Wissen für den potenziellen Benutzer des Geräts unnötig ist. Wenden wir uns daher dem eher praktischen Aspekt der Indikatoren zu.

Was kann mit den Indikatoren getan werden?

Wir wissen bereits, dass ein Zeiger ein spezieller Variablentyp ist, der eine Adresse im Speicher speichern kann, an der sich andere Daten befinden können. Auf diese Weise können Operationen sowohl mit der gespeicherten Adresse als auch mit dem Wert, auf den die Adresse zeigt, durchgeführt werden.

Betrachten wir zunächst die Operationen, die wir mit der Adresse des Zeigers selbst durchführen können, denn im Grunde genommen können wir damit nur sehr wenig tun. Wir können nur Adressdaten addieren und subtrahieren, und das ist nicht ganz die übliche Addition und Subtraktion. Darüber hinaus, und das ist wahrscheinlich nicht überraschend, können wir auch anderen Variablen Adressen zuweisen. Lassen Sie uns jedoch für einen Moment bei den ‘ungewöhnlichen’ Operationen der Addition und Subtraktion stehen bleiben. Lassen Sie uns zum Beispiel eine Situation wie die folgende analysieren. Wir haben drei Zeiger, die auf verschiedene Datentypen zeigen, denen wir einen geben. Der Einfachheit halber nehmen wir an, dass die Adresse jedes Zeigers Null ist.

uint8_t *pointer1;
uint16_t *pointer2;
uint32_t *pointer3;

pointer1 ++;
Zeiger2 ++;
pointer3 ++;

Wie werden die Adressen nach einer solchen Operation aussehen? Es mag etwas überraschen, aber der Wert von pointer1 ist gleich 0x01, pointer2 = 0x02 und pointer3 = 0x04. Schon auf den ersten Blick können Sie erkennen, dass hier etwas nicht stimmt.

pointer1 ++; //0x00 + 1 = 0x01
pointer2 ++; //0x00 + 1 = 0x02
pointer3 ++; //0x00 + 1 = 0x04

Obwohl diese Ergebnisse eher unintuitiv sind, sind sie doch so korrekt wie möglich, da wir daran denken müssen, dass wir Operationen mit Adressen und nicht mit gewöhnlichen Zahlen durchführen. Auch die Art der Daten, auf die sich der Zeiger bezieht, muss beachtet werden. Eine einzelne Variable vom Typ uint8_t wird im Speicher an einer einzigen Adresse abgelegt, schließlich besteht sie aus acht einzelnen Bits. Allerdings ist uint16_t bereits aus sechzehn Bits aufgebaut, so dass wir, um eine Adresse zu überspringen, eigentlich zwei Schritte durchlaufen müssen. In ähnlicher Weise wird ein einzelnes uint32_t an vier Adressen im Speicher abgelegt. Indem wir also eine Eins hinzufügen, springen wir vier Zellen im Speicher, so dass die Zeigervariable auf die jüngsten acht Bits der nächsten Variablen zeigt.

Wir gewinnen viel mehr Potenzial, wenn wir Indikatoren verwenden, um uns auf die Daten zu beziehen, die der Indikator anzeigt. Lassen Sie uns nun ein einfaches Beispiel untersuchen, in dem wir uns auf den Wert einer Variablen auf genau solch eine umständliche Weise beziehen. Wir können den Code verwenden, der zuvor ausgeführt wurde, werden aber natürlich einige Änderungen daran vornehmen.

				
					#include 
#include "pico/stdlib.h"

uint8_t variable = 167;
uint8_t *pointer;	//declaration of pointer
uint8_t **pointer_to_pointer;	//declaration of pointer to pointer

const float conversion_factor = 3.3/4095;
const float *conversion_factor_pointer;	//declaration of pointer

int main() {
    stdio_init_all();

    //address assignment to the pointer
    pointer = &variable;
    pointer_to_pointer = &pointer;
    conversion_factor_pointer = &conversion_factor;

    while(true) {
        printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
        (*pointer)++;
        printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
        printf("pointer addr: %p \n\r", pointer_to_pointer);
        printf("conversion_factor value: %f, conversion_factor addr: %p\n\r", conversion_factor, conversion_factor_pointer);
        sleep_ms(1000);
    }
}

				
			

Zuvor haben wir den Wert und die Adresse der Variablen angezeigt. In diesem Programm werden wir bei jedem Durchlauf der unendlichen while-Schleife der Variablen einen Wert hinzufügen. Wir könnten diese Art von Funktionalität natürlich auch über den Befehl variable++; implementieren, aber da wir über Zeiger sprechen, sollten wir uns die Variable mit ihrer Hilfe ansehen. Im Code wurde der Befehl (*pointer)++; hinzugefügt, der genau genommen nichts anderes ist als ein Verweis auf eine Variable und die Erhöhung ihres Wertes um eins. Hier verwenden wir einen Zeiger, der dank des Adress-Chunking-Operators die Adresse einer Variablen speichert. Daher haben wir die Daten auf einem Umweg über eine Indikatorvariable herangezogen. Außerdem habe ich den printf-Befehl im Code verdoppelt, damit ich den vorherigen und den aktuellen Wert der Variablen auf dem Monitor der seriellen Schnittstelle untereinander sehen kann.

Steigender Wert variable aus dem Beispiel.

Nachdem wir den Code ausgeführt und den Monitor der seriellen Schnittstelle geöffnet haben, können wir sehen, wie sich der Wert der Variablen variable ändert: von 0xB7 auf 0xB8, von 0xB8 auf 0xB9 und so weiter. Natürlich gibt es noch viele weitere Operationen, die wir mit Daten unter einem Zeiger durchführen können, und tatsächlich sind das alle Operationen, die wir bisher gelernt haben, denn eine Referenz über einen Zeiger auf Daten kann genauso behandelt werden wie eine Referenz auf eine gewöhnliche Variable.

Timer im RPI Pico W

Der RP2040 ist, wie jeder moderne Mikrocontroller, mit Hardware-Timern ausgestattet. Mit ihrer Hilfe können wir präzise Zeitintervalle erzeugen und Funktionen, so genannte Callbacks, aufrufen. Wir haben diese Art von Funktion bereits beim Aufruf von Unterbrechungen und Alarmen kennengelernt. Der Raspberry Pi Pico verfügt über mehrere Timer, die hauptsächlich für die Zeitmessung verwendet werden. Wenn wir in dem zu erstellenden Projekt eine bestimmte Funktion zyklisch ausführen möchten, können wir einen Timer verwenden. Dies könnte z.B. eine synchrone Temperaturmessung sein, von der die Geschwindigkeit des an das RPI angeschlossenen Lüfters abhängt. Im vorigen Artikel haben wir bereits die Alarmfunktion kennengelernt, die ein Stück Code aufruft, das einmal ausgeführt wird. In dieser Hinsicht sind Timer recht ähnlich, außer dass sie den entsprechenden Callback zyklisch zu jeder angegebenen Zeit aufrufen.

Wir werden die Funktion des Timers mit einem einfachen Programm überprüfen, bei dem wir die LED blinken lassen, ähnlich wie bei dem ersten Programm, das wir ausgeführt haben. Damals haben wir die prozessorsperrende Funktion sleep_ms verwendet, aber dieses Mal wird der Wechsel zwischen dem Einschalten und nur der LED präziser sein. Ich habe ein neues Projekt namens blink_timer zum Testen vorbereitet.

				
					#include 
#include "pico/stdlib.h"

#define GREEN_LED 0
bool led_status = false;

//function called after an interrupt from the timer
void tCallback(repeating_timer_t *timer){
    led_status = !led_status;
    gpio_put(GREEN_LED, led_status);
}

int main() {
    stdio_init_all();

    gpio_init(GREEN_LED);
    gpio_set_dir(GREEN_LED, GPIO_OUT);
    gpio_put(GREEN_LED, false);

    struct repeating_timer timer;	//structure declaration
    add_repeating_timer_ms(500,tCallback,NULL,&timer);	//starting the timer
    

    while(true) {
        tight_loop_contents(); //do nothing functions
    }
}

				
			

Um zu verstehen, wie Timer funktionieren, sollten Sie sich das oben gezeigte Programm ansehen. Es erinnert ein wenig an den Code, der bei der Erörterung von Unterbrechungen ausgeführt wurde, und das ist auch nicht weiter verwunderlich, da seine Struktur recht ähnlich ist. Es lohnt sich zu wissen, dass sie tatsächlich auf Unterbrechungen beruhen. Wenn dieses Modul eine vom Benutzer festgelegte Zeit überschreitet, wird eine Unterbrechung erzeugt, die zum Aufruf eines Callbacks führt, in dem der entsprechende Code platziert werden sollte, um auf die entstandene Situation zu reagieren.

Der erste Teil des Programms umfasst wie üblich Bibliotheksdeklarationen, die Definition der grünen LED und die Erstellung der Variable led_status, die, wie der Name schon sagt, den Zustand speichert, in dem sich die LED gerade befinden sollte. Der Standardwert dieser Variable ist false, was einem low Status entspricht.

Anschließend erstellen wir einen Callback, der ausgelöst wird, wenn der Timer überläuft. Mit anderen Worten, es wird ein Wert erreicht, der einer bestimmten, vom Benutzer festgelegten Zeiteinheit entspricht. Im Argument dieser Funktion übergeben wir einen Zeiger auf *timer, der Teil einer bestimmten Struktur ist, die ich gleich noch näher erläutern werde. Innerhalb des Callbacks befindet sich der Code, der den Status der LED ändert. Im ersten Schritt ändern wir den Zustand der Variable led_status in das Gegenteil und weisen sie dann der LED zu.

In der Hauptfunktion main finden Sie neben den bereits bekannten Befehlen für die Initialisierung der LED auch die Deklaration der Struktur repeating_timer und die Funktion, die den Timer startet. Strukturen sind ein ganz spezielles Konzept in der Sprache C und wir werden uns an dieser Stelle nicht mit ihnen befassen. Das liegt vor allem daran, dass sie hier auf eine ganz bestimmte Weise verwendet wurde. Alles, was Sie zu diesem Zeitpunkt wissen müssen, ist, dass Strukturen es Ihnen ermöglichen, bestimmte Variablen in größeren ‘Paketen’ zu gruppieren und in diesem Fall enthält die Struktur nur eine einzige namens Timer. Seine Verwendung ist hier leider unverzichtbar, daher werden wir im nächsten Artikel darauf zurückkommen, wenn ich Ihnen eine Einführung in die Strukturen in einem etwas breiteren Spektrum geben werde.

Der Befehl, der den Timer startet, heißt add_repeating_timer_ms. In den Argumenten geben wir der Reihe nach an: die Zeit, die der Timer laufen soll, in diesem Fall 500ms, den Namen der Funktion Callback, die aufgerufen werden soll, nachdem der Timer heruntergezählt hat, das nächste Argument können die so genannten Benutzerdaten sein, aber wir übergeben sie nicht, also fügen wir hier das Null-Argument NULL ein. Das letzte Argument ist die Adresse des Elements der zuvor genannten Struktur. Erstellt durch den Adress-Chunking-Operator.

Auf diese Weise ist der Code fertig, aber damit er in der unendlichen while-Schleife richtig funktioniert, müssen Sie den ‘do nothing’-Befehl einfügen, den Sie bereits gelernt haben, d.h. tight_loop_contents.

Nach der Ausführung des Programms können wir sehen, dass die grüne LED alle 500 ms synchron ein- und ausgeschaltet wird, genau wie wir es in dem Befehl zum Starten des Timers angegeben haben. Es ist erwähnenswert, dass der Prozessor in diesem Fall nicht blockiert wird, wenn der Timer abgelaufen ist. Wenn wir wollten, könnten wir auch andere Aufgaben in der while-Schleife ausführen. Ich würde Sie zu einem solchen Experiment ermutigen. Sie könnten versuchen, die interne Kerntemperatur des Prozessors zu messen und auf den Bildschirm zu übertragen, um zu sehen, ob dies das Blinken der LED beeinflusst.

Ein paar Worte zum Schluss...

In diesem Ratgeber haben wir zum ersten Mal Indikatoren verwendet. Bisher waren es einfache Tests, aber dank ihnen wissen wir bereits ein wenig darüber, wie sie funktionieren. Wir werden in zukünftigen Ratgebern wieder auf sie zurückkommen. Außerdem haben wir das klassische blink led eingeführt, allerdings in einem etwas anderen Gewand. Wir haben einen Timer verwendet, damit die Funktion sleep_ms den Prozessor nicht mehr blockiert. Darüber hinaus habe ich auch so etwas wie Strukturen erwähnt, aber wir werden uns mit diesem Konzept in einem späteren Artikel beschäftigen. Außerdem werde ich mich im nächsten Tutorial mit dem Thema ‘Zustandsautomat’ befassen.

Quellen:

  • https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
  • https://datasheets.raspberrypi.com/picow/pico-w-datasheet.pdf
  • https://www.raspberrypi.com/products/rp2040/
  • https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html

Wie hilfreich war dieser Beitrag?

Klicke auf die Sterne um zu bewerten!

Durchschnittliche Bewertung 5 / 5. Stimmenzahl: 3

Bisher keine Bewertungen! Sei der Erste, der diesen Beitrag bewertet.

Teilen:

Picture of Rafał Bartoszak

Rafał Bartoszak

Ein mit Botland kooperierender Elektroniker, der sein Wissen im Internet teilt. Enthusiast für digitale Systeme, programmierbare Schaltungen und Mikroelektronik. Leidenschaftlich für Geschichte, mit besonderem Schwerpunkt auf dem 20. Jahrhundert.

Siehe mehr:

Agata Kosmala

Produkteinführung des Raspberry Pi M.2 HAT+!

Wenn Sie ein Fan von Neuheiten auf dem Technologiemarkt sind, haben wir tolle Neuigkeiten für Sie. Der Raspberry Pi kommt mit einem neuen Produkt! Sie sind sicher neugierig, was dieses Gerät ist und welche Anwendungsmöglichkeiten es bietet. Ohne weitere Diskussionen – Wir präsentieren den Raspberry Pi M.2 HAT+ !

Mateusz Mróz

Transceiver – was ist das und wofür wird er verwendet?

Der vielleicht interessanteste Teil der Geschichte der Transceiver sind die Anekdoten, die zeigen, welchen bedeutenden Einfluss sie auf unser Leben haben – von der Wissenschaft über die humanitäre Hilfe bis hin zur Weltraumforschung. Sehen wir uns jedoch zunächst an, wie sie im Allgemeinen funktionieren.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Aus Sicherheitsgründen ist die Verwendung des reCAPTCHA-Dienstes von Google erforderlich, der den Google-Datenschutzbestimmungen und Nutzungsbedingungen unterliegt..