Spis treści:
Im vorherigen Artikel haben wir einige der Funktionen des RP2040 Mikrocontrollers kennengelernt. Wir haben eine LED mit einem modulierten PWM-Signal gesteuert, ein analoges Signal von einem Fotowiderstand gelesen und eine Kommunikation mit einem Computer hergestellt. In diesem Artikel werden wir die Funktionen verwenden, die wir bereits gelernt haben, und mit ihrer Hilfe ein etwas aufwendigeres Programm erstellen, um es später zu vereinfachen. Unter Beibehaltung seiner Funktionalität.
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.
Inhaltsverzeichnis:
- Raspberry Pi Pico – #1 – los geht’s
- Raspberry Pi Pico – #2 – ein paar Worte zur Programmierung
- Raspberry Pi Pico – #3 – erstes Programm
- Raspberry Pi Pico – #4 – wir beginnen mit der Programmierung
- Raspberry Pi Pico – #5 – Schleifen, Variablen und bedingte Anweisungen
- Raspberry Pi Pico – #6 – PWM, ADC und Kommunikation mit dem Computer
- Raspberry Pi Pico – #7 – Codekorrekturen und eigene Funktionen
- Raspberry Pi Pico – #8 – Unterbrechungen und Alarme
- Raspberry Pi Pico – #9 – Indikatortheorie und Timer
- Raspberry Pi Pico – #10 – Arrays, Strukturen und Zustandsautomaten
Vor dem Start sollte das Team zusammengestellt werden
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.
Projektvorbereitung
In unserem heutigen Projekt verwenden wir wieder die Schaltung, die wir in früheren Tutorials vorbereitet haben. Dieses Mal werden wir auch alle seine Komponenten verwenden, nämlich die LEDs, den Knopf und den Fotowiderstand. Die Aufgabe des Programms, das wir als nächstes schreiben werden, besteht darin, die leuchtenden Elemente in Abhängigkeit von der Lichtintensität zu steuern. Wenn sein Wert in einen der drei Bereiche fällt, die wir erstellen, wird eine bestimmte Leuchtdiode aktiviert. Darüber hinaus werden wir auch eine Taste verwenden, bei deren Betätigung der Mikrocontroller den Wert des Temperatursensors im Inneren des Prozessorkerns ausliest. Wir werden diesen Wert in Grad Celsius umrechnen und über USB senden, damit er auf dem Computerbildschirm abgelesen werden kann.
In diesem Artikel werden wir ein Projekt namens functions_test verwenden. Denken Sie bei der Vorbereitung daran, den USB-Ausgangsstream in der Datei CMakeLists.txt zu aktivieren.
Unschöner, aber funktionierender Code
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#define RED_LED 2
#define YELLOW_LED 1
#define GREEN_LED 0
#define BUTTON 16
int main() {
stdio_init_all();
gpio_init(RED_LED);
gpio_init(YELLOW_LED);
gpio_init(GREEN_LED);
gpio_init(BUTTON);
gpio_set_dir(RED_LED, GPIO_OUT);
gpio_set_dir(YELLOW_LED, GPIO_OUT);
gpio_set_dir(GREEN_LED, GPIO_OUT);
gpio_set_dir(BUTTON, GPIO_IN);
gpio_pull_up(BUTTON);
adc_init();
adc_set_temp_sensor_enabled(true);
adc_gpio_init(26);
while(true) {
adc_select_input(0);
uint16_t photores_read = adc_read();
float voltage_photores = photores_read * (3.3/4095);
printf("Photoresistor ADC: value = %d, voltage = %f V\n", photores_read, voltage_photores);
if(photores_read <= 2500){
gpio_put(GREEN_LED, 1);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 0);
}
else if(photores_read > 2500 && photores_read <= 3500){
gpio_put(GREEN_LED, 0);
gpio_put(YELLOW_LED, 1);
gpio_put(RED_LED, 0);
}
else if(photores_read > 3500){
gpio_put(GREEN_LED, 0);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 1);
}
else
{
printf("Photoresistor error");
}
bool button_state = gpio_get(BUTTON);
if(button_state == false){
adc_select_input(4);
uint16_t adc_temp = adc_read();
float conversion_factor = 3.3 / (1<<12);
float result = adc_temp * conversion_factor;
float temp = 27 - (result -0.706)/0.001721;
printf("Temp = %f C\n", temp);
}
sleep_ms(500);
}
}
Werfen wir einen ersten Blick auf ein Programm, das funktioniert, aber wie Sie sich wahrscheinlich denken können, werden wir es im weiteren Verlauf des Artikels noch optimieren. Der Code besteht größtenteils aus bekannten Befehlen, obwohl wir uns einige von ihnen noch einmal ansehen werden. Darüber hinaus ist es sinnvoll, das Programm als Ganzes zu analysieren, damit wir seine Struktur und die anschließenden Schritte zu seiner Verbesserung besser verstehen können.
Der Hauptfunktion main gingen, wie üblich, Bibliotheken und Definitionen voraus. In dem Programm verwenden wir einen Analog-Digital-Wandler. Daher müssen wir den Code mit hardware/adc.h ergänzen. Um nicht digitale Bezeichnungen für die einzelnen Leads verwenden zu müssen, wurden ihnen außerdem intuitivere Begriffe zugewiesen.
Auch der Anfang des Hauptteils des Programms kommt Ihnen vielleicht bekannt vor. Die Funktionen zur Initialisierung der Mikrocontroller-Pins, an die die LEDs und die Taste angeschlossen sind, finden Sie hier. Außerdem wird der ADC ausgelöst, zusammen mit der zugehörigen Leitung 26, an die der Fotowiderstand angeschlossen ist. Neu in diesem Teil des Codes ist die Funktion adc_set_temp_sensor_enabled(true);, mit der der im Inneren der CPU befindliche Temperatursensor aktiviert wird. Es ist auch erwähnenswert, dass der Befehl adc_select_input(), der festlegt, welches Signal an den A/D-Wandler übergeben wird, noch nicht erschienen ist. Dies ist eine bewusste Entscheidung, denn, wie Sie bereits wissen, ist der RP2040 mit nur einem ADC-Modul ausgestattet, an das mehrere Eingänge angeschlossen sind. In diesem Projekt werden wir zwei davon verwenden, für den Fotowiderstand und den Temperatursensor, so dass wir später im Code zwischen ihnen wechseln werden.
Am Anfang der unendlichen while-Schleife erscheint der Code, den wir bereits im vorherigen Projekt verwendet haben. Hier lesen wir den vom Fotowiderstand erzeugten Wert ab, um ihn später in eine Spannung umzuwandeln und ihn dank der Funktion printf an den Computerbildschirm zu senden. Beachten Sie, dass wir hier auch den Eingang auswählen, von dem das Signal an den ADC weitergeleitet wird. Die Ausgabennummer 26 entspricht der Null, also ist dies die Ziffer, die in die Funktion eingefügt werden muss.
Im nächsten Teil des Codes gab es eine Kaskadenbedingung, die vom Wert von photores_read abhing, einer Variablen, in der die dem Signal des Fotowiderstands entsprechende Zahl gespeichert ist. Wie Sie bereits wissen, können die gelesenen Daten in einem 12-Bit-Konverter einen Wert zwischen 0 und 4095 annehmen. Ich habe beschlossen, den Bereich in drei Teile zu unterteilen: von 0 bis 2500, von 2501 bis 3500 und von 3501 bis 4095. Liegt der Wert von photores_read im ersten dieser Bereiche, wird eine grüne LED aktiviert, beim zweiten Bereich ist es ein gelbes Lichtelement und beim letzten ein rotes Lichtelement. Außerdem ist in der Bedingung eine Sicherung für den Fall vorgesehen, dass die Variable einen ‘anderen’ Wert annimmt. Auf dem Computerbildschirm erscheint dann die Meldung Photoresistor error.
Wir müssen auch über die Konstruktion der Bedingungen selbst sprechen. Bisher haben wir eine Variable mit bestimmten Werten gleichgesetzt, dank der beiden Gleichheitszeichen “==”. In diesem Fall müssen wir sie jedoch mit den zuvor beschriebenen Bereichen vergleichen. Mit anderen Worten, wir müssen prüfen, ob die Variable größer oder kleiner als eine bestimmte Zahl sein wird. Wie in der Mathematik können wir hier die Zeichen Minderheit “<“, Mehrheit “>”, Moll gleich “<=” und Dur gleich “>=” verwenden. In der ersten if-Anweisung prüfen wir den ersten Bereich. Wir vergleichen also, ob photores_read kleiner oder gleich 2500 ist. Es ist jedoch wichtig zu beachten, dass eine solche Notation nicht ideal ist, da die Bedingung auch für negative Zahlen erfüllt wird, die nicht im Bereich enthalten sind, aber damit werden wir uns später beschäftigen. Wir sehen die gleiche Situation bei der dritten Bedingung, wenn der letzte Bereich überprüft wird. Wenn der Wert des Fotowiderstands größer als 3501 ist, wird die rote LED aktiviert. Beachten Sie auch, dass die Bedingung selbst auf zwei Arten geschrieben werden kann: photores_read > 3500 oder photores_read >= 3501. Sie sind gleichwertig und werden aktiviert, wenn der Wert der Variablen mindestens 3501 beträgt. In der zweiten Bedingung, bei der wir den mittleren Wertebereich prüfen, passiert mit Abstand am meisten. In diesem Fall muss ein bestimmter Wertebereich beschrieben werden und alle Zahlen unterhalb oder oberhalb eines bestimmten Wertes können nicht einfach so hingenommen werden. Damit die gelbe LED nur dann aktiviert wird, wenn der photores_read-Wert zwischen 2501 und 3500 liegt, müssen wir sozusagen zwei Bedingungen schaffen, die durch das mysteriöse “&&” verbunden sind. Die erste prüft, ob die Variable größer als 2500 ist – photores_read > 2500, während die zweite prüft, ob die Zahl 3500 nicht überschritten hat – photores_read <= 3500. Die ‘&&’-Konstruktion zwischen zwei Vergleichen ist der logische Operator AND. Sie können logische Operatoren mit Mathematik oder Logikgattern in Verbindung bringen. In der Sprache C ist neben AND auch OR, geschrieben als “||”, verfügbar. Mit dieser Notation wird die bedingte Funktion nur erfüllt, wenn beide Vergleiche wahr sind, d.h. der Wert von photores_read liegt innerhalb des beschriebenen Bereichs. Wenn die der Variablen zugewiesene Zahl außerhalb des Bereichs liegt, ist nur eine der Bedingungen erfüllt, so dass das gesamte if unmöglich ist. Man kann also sagen, dass die bedingte Funktion dank des Operators AND nur ausgeführt wird, wenn alle Bedingungen erfüllt sind. Das logische OR funktioniert etwas anders. Wenn wir es innerhalb einer Funktion platzieren würden, würde die Diode immer leuchten, denn in diesem Fall genügt es, dass eine der Bedingungen erfüllt ist und die bedingte Funktion ausgeführt wird.
Der zweite Teil des Programms enthält den Code für die Bedienung der Taste und die Temperaturanzeige. Zunächst erstellen wir eine Variable button_state, in der der logische Zustand der Schaltfläche gespeichert wird. Wenn diese Taste gedrückt wird, wird der in der bedingten Funktion enthaltene Code ausgeführt. Im ersten Schritt ändern wir das Signal, das an den ADC weitergeleitet wird. Zuvor wurde ein Fotowiderstand ausgewählt, dieses Mal wollen wir den Wert des Temperatursensors lesen, der der vierten Stelle zugewiesen ist. Wir erstellen dann die Variable adc_temp, der der gelesene Wert zugewiesen wird. Um jedoch die Celsius-Temperatur aus einem reinen ADC-Wert zu erhalten, müssen wir ihn entsprechend transformieren. Hierfür benötigen wir den Fließkomma conversion_factor, in dem das Ergebnis der Operation 3.3/(1 gespeichert wird.<<12)-1. Diese etwas rätselhafte Notation ist nichts anderes als die Aktion 3.3/4095, die wir bereits mit dem Fotowiderstand verwendet haben. (1<<12) ist eine Bitverschiebungsoperation nach links. Das bedeutet, dass wir die Eins in der Nullvariablen an die zwölfte Stelle verschieben und so die Zahl 4096 erhalten, von der wir am Ende Eins subtrahieren.
1 = 00000000 00000000 = 1
1<<1 = 00000000 00000010 = 2
1<<8 = 00000001 00000000 = 256
1<<12 = 0010000 00000000 = 4096
Bitverschiebungen sind anhand des obigen Beispiels recht einfach zu veranschaulichen. Wenn Sie die Eins um eine bestimmte Anzahl von Stellen verschieben, erhalten Sie ganz einfach Zahlen, die aufeinanderfolgende Zweierpotenzen sind, die in der Elektronik und Programmierung äußerst nützlich sind. Bits können auch nach links verschoben werden, mit Hilfe des Operators “>>”. Außerdem sollten Sie wissen, dass sich Bitverschiebungen auf die Variable als Ganzes beziehen können, aber mit solchen Beispielen werden wir uns ein anderes Mal beschäftigen.
Sobald wir die Variable result haben, die de facto der vom Temperatursensor gelesene Spannungswert ist, müssen wir sie in Grad Celsius umrechnen. Diese Aufgabe übernimmt der Befehl float temp = 27 – (result -0,706)/0,001721;, der den Fließkommawert der Temperatur in der Variablen temp speichert. Es gibt keine besondere Geschichte hinter der verwendeten Maßnahme, es ist einfach ein Ersatzwert für die in der RP2040-Dokumentation verfügbare Formel. Dank des am Ende platzierten Befehls printf können Sie die abgelesene Temperatur auf dem Monitor der seriellen Schnittstelle sehen.
Wie Sie vielleicht bemerkt haben, habe ich die Positionierung der Kommentare in diesem Programm leicht verändert. In früheren Entwürfen habe ich sie rechts von den Funktionen platziert, aber in diesem Fall sind sie darüber platziert worden.
//conditional function controlling LEDs
if(photores_read <= 2500){
gpio_put(GREEN_LED, 1);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 0);
}
if(photores_read <= 2500){ //conditional function controlling LEDs
gpio_put(GREEN_LED, 1);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 0);
}
In der Tat sind beide Schreibweisen korrekt und es liegt an Ihnen zu entscheiden, welche Sie verwenden möchten. Natürlich können die Schreibweisen auch gemischt werden, indem Sie einmal neben und einmal über der Funktion einen Kommentar hinzufügen. Das Wichtigste ist jedoch das Hinzufügen von Kommentaren selbst, damit der Leser sich leicht ein Bild von der Struktur des Codes machen kann.
Nach dem Kompilieren und Hochladen der Datei .uf2 auf den RPI Pico W können wir die doppelte Funktionalität des vorbereiteten Programms überprüfen. Im Video können Sie sehen, wie ich den Fotowiderstand allmählich beleuchte. Je mehr Licht dieses Element erhält, desto stärker sinkt sein Widerstand, was sich auf den vom Raspberry Pi Pico gelesenen Spannungswert auswirkt. Dadurch ändert sich auch die ausgelesene Variable und je nach Wert wird eine andere LED aktiviert. Wenn es mehr Licht gibt, geht die Variable in den nächsten Bereich, der in der Kaskadenfunktion beschrieben ist.
Außerdem können wir, wenn wir den Monitor der seriellen Schnittstelle laufen lassen, den ADC-Wert und die von der Fotowiderstandsleitung gelesene Spannung sehen. Hier wird auch die interne Temperatur des Mikrocontrollers angezeigt, die gesendet wird, wenn eine Taste auf der Kontaktplatte gedrückt wird. Wenn Sie Ihren Finger auf die Oberfläche des Mikrocontrollers halten, können Sie einen langsam ansteigenden Messwert beobachten. Beachten Sie jedoch, dass er im Vergleich zu externen Sensoren nicht sehr genau ist und keinesfalls als externer Temperatursensor angesehen werden kann. Der Chip selbst kann sich je nach Belastung erwärmen und dieser Sensor dient nur dazu, die Temperatur des Chips selbst zu messen. Bei größeren Projekten kann sie mit einer aktiven Kühlung des Geräts kombiniert werden.
Kleine Änderungen
Das von uns vorbereitete Programm funktioniert einwandfrei, der Fotowiderstand reagiert auf das einfallende Licht und durch Drücken einer Taste können wir die Temperatur des RP2040-Chips ablesen. Ist es dann überhaupt noch sinnvoll, den Code zu verbessern, wenn er funktioniert? Die Antwort ist so positiv wie möglich. In diesem wie auch in anderen Fällen sollten wir aus mehreren wichtigen Gründen zu früher geschriebenen Programmen zurückkehren. Durch die Analyse früherer Lösungen verbessern wir ständig unsere Fähigkeiten und finden mehr als einmal einen besseren Weg, ein Problem zu lösen. Darüber hinaus sollten wir versuchen, mögliche Fehler zu beseitigen und gleichzeitig die Lesbarkeit des Codes selbst zu verbessern, damit er von anderen Entwicklern verstanden werden kann. Letztendlich ist das regelmäßige Betrachten und Verbessern von bereits geschriebenem Codes ein wesentlicher Bestandteil des Prozesses Ihrer eigenen Selbstverbesserung.
Bei dieser Gelegenheit möchte ich auch auf ein Thema im Zusammenhang mit der Programmierung hinweisen, das von vielen Anfängern ignoriert wird, obwohl Kenntnisse in diesem Bereich in vielen Fällen nützlich sein können. Das sind Algorithmen, die eigentlich ein sehr breites Thema sind. In einem Forum bin ich einmal auf den Ausdruck gestoßen, dass ein Programmierer, der sich nicht mit Algorithmen auskennt, wie ein “Code Stitcher” ist. Nun, dem kann man kaum widersprechen. Das Thema ist sehr breit gefächert, aber in Kürze können wir Algorithmen als strukturierte und optimale Wege zur Lösung von Problemen definieren. Viele Programmierprobleme sind den Entwicklern schon vor Jahren begegnet, so dass es wenig Sinn macht, das Rad neu zu erfinden. Es lohnt sich, ein Buch in die Hand zu nehmen oder im Internet nach den Algorithmen zu suchen, die beim Programmieren verwendet werden, denn dieses Wissen wird die Programme, die Sie in Zukunft schreiben, optimieren. Außerdem erleichtern Algorithmen das Lösen von Problemen, was die Essenz des Programmierens ist.
#include <stdio.h>
#include “pico/stdlib.h”
#include “hardware/adc.h”
#define RED_LED 2
#define YELLOW_LED 1
#define GREEN_LED 0
#define BUTTON 16
const float conversion_factor = 3.3/4095;
int main() {
stdio_init_all();
gpio_init(RED_LED);
gpio_init(YELLOW_LED);
gpio_init(GREEN_LED);
gpio_init(BUTTON);
gpio_set_dir(RED_LED, GPIO_OUT);
gpio_set_dir(YELLOW_LED, GPIO_OUT);
gpio_set_dir(GREEN_LED, GPIO_OUT);
gpio_set_dir(BUTTON, GPIO_IN);
gpio_pull_up(BUTTON);
adc_init();
adc_set_temp_sensor_enabled(true);
adc_gpio_init(26);
while(true) {
adc_select_input(0);
uint16_t photores_read = adc_read();
const float voltage_photores = photores_read * conversion_factor;
printf(“Photoresistor ADC: value = %d, voltage = %f V\n”, photores_read, voltage_photores);
if(photores_read >= 0 && photores_read <= 2500){
gpio_put(GREEN_LED, 1);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 0);
}
else if(photores_read > 2500 && photores_read <= 3500){
gpio_put(GREEN_LED, 0);
gpio_put(YELLOW_LED, 1);
gpio_put(RED_LED, 0);
}
else if(photores_read > 3500 && photores_read <= 4095){
gpio_put(GREEN_LED, 0);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 1);
}
else
{
printf(“Photoresistor error”);
}
bool button_state = gpio_get(BUTTON);
if(button_state == false){
adc_select_input(4);
uint16_t adc_temp = adc_read();
float temp = 27 – ((adc_temp * conversion_factor)-0.706)/0.001721;
printf(“Temp = %f C, voltage = %f V\n”, temp, (adc_temp * conversion_factor));
}
sleep_ms(500);
}
}
Da wir nun wissen, dass es nützlich ist, Codes, die wir bereits geschrieben haben, noch einmal zu überprüfen, lassen Sie uns einen Blick auf das Programm werfen, das wir vor einiger Zeit geschrieben haben, und versuchen, ein paar kleine Änderungen daran vorzunehmen.
Beginnen wir mit der Korrektur der Kaskaden-Bedingungsfunktion, die die LEDs steuert. Wie ich bereits erwähnt habe, bestand das Problem darin, dass die Wertebereiche nicht sehr genau beschrieben waren. Man könnte meinen, dass dies kein großes Problem darstellt, da wir wissen, dass der vom Fotowiderstand gelesene Wert nicht kleiner als Null und größer als 4095 sein wird. Wir können jedoch nicht 100%ig sicher sein, dass im Mikrocontroller kein Problem auftritt und dass ein verirrtes Elektron nicht den in einem der Register gespeicherten Wert verändert. Selbst wenn dies der Fall wäre, würde es natürlich kaum einen Unterschied für den Stromkreis auf der Kontaktplatte machen. Es ist jedoch wichtig, daran zu denken, dass nicht jedes Programm nur auf einem Kontaktbrett läuft – in medizinischen oder militärischen Systemen sind Fehler und unvorhersehbares Verhalten im Programm inakzeptabel. Deshalb lohnt es sich, die Bedingungen zu verbessern, selbst bei einem so einfachen Code. Nach der Änderung bezieht sich jede der if-Funktionen auf einen bestimmten Bereich, und die Bedingungen basieren auf dem bereits erwähnten AND-Operator.
Wir können uns auch die Funktionen ansehen, die mit dem Auslesen der ADC-Werte aus dem Fotowiderstand und dem Temperatursensor verbunden sind. In beiden Fällen haben wir die für die Umrechnung benötigte Variable verwendet, in der das Ergebnis der Aktion 3.3/4095 gespeichert wurde. Die gleiche Berechnung zweimal durchzuführen ist eine Verschwendung von Zeit und Ressourcen. Daher können wir die Variable conversion_factor am Anfang des Programms verwerfen und sie zu einer Konstanten machen, so dass sie einen entsprechenden Wert annimmt, der nicht geändert werden kann. Wir definieren eine Konstante, indem wir das Schlüsselwort const vor der type-Anweisung hinzufügen. Konstanten können genau wie Variablen global oder lokal sein. In diesem Fall habe ich die Deklaration conversion_factor oberhalb der Funktion main platziert, so dass sie global ist, was sich später als nützlich erweisen wird.
Durch das Hinzufügen der Konstante wurden die Berechnungen im Zusammenhang mit den ausgelesenen ADC-Signalen etwas verändert. Beim Fotowiderstand habe ich nur die Notation 3.3/4095 in eine Konstantenangabe geändert, aber bei den Berechnungen im Zusammenhang mit dem Temperatursensor hat sich das Ganze um zwei Zeilen verkürzt. Der erste Befehl, der verschwindet, ist natürlich die Bezeichnung conversion_factor, die jetzt korrigiert wurde. Außerdem verzichtete ich auf die Ermittlung des Wertes result, der innerhalb der Temperaturermittlungsaktion platziert war. Stattdessen wurden die relevanten Variablen unter der Formel ersetzt, was den Befehl temp = 27 – ((adc_temp * conversion_factor)-0.706)/0.001721 ergab. Wie Sie sehen, habe ich die Multiplikationsoperation in eine zusätzliche Klammer gesetzt, obwohl diese Operation in der Reihenfolge, in der die Operationen ausgeführt werden, zuerst ausgeführt wird. Es ist zwar überflüssig, aber ich persönlich mag es, Aktionen genau so aufzuzeichnen, um sozusagen die Abfolge der durchgeführten Operationen zu unterstreichen.
Darüber hinaus wurde der printf-Befehl ganz am Ende erweitert. Von nun an wird auf dem Computerbildschirm neben dem Temperaturwert auch der vom Temperatursensor gelesene Spannungswert angezeigt. Wie Sie sehen, hat die Variable, die den zweiten %f-Operator ersetzt, tatsächlich die Form einer mathematischen Operation. In der Sprache C können wir erfolgreich diese Art von Konstrukten erstellen, bei denen bestimmte Variablen tatsächlich die Form anderer Aktionen oder in einigen Fällen sogar ganzer Funktionen haben. Achten Sie jedoch darauf, dass Sie einige Anweisungen nicht überstrapazieren und eine Art ‘Bandwurm’ erzeugen. Längere Anweisungen können mit der Zeit viel schwerer zu verstehen sein.
Vorbereitung von eigenen Funktionen
#include <stdio.h>
#include “pico/stdlib.h”
#include “hardware/adc.h”
#define RED_LED 2
#define YELLOW_LED 1
#define GREEN_LED 0
#define BUTTON 16
const float conversion_factor = 3.3/4095;
void PhotoresRun();
uint8_t TempSensorRun(bool button_state);
int main() {
stdio_init_all();
gpio_init(RED_LED);
gpio_init(YELLOW_LED);
gpio_init(GREEN_LED);
gpio_init(BUTTON);
gpio_set_dir(RED_LED, GPIO_OUT);
gpio_set_dir(YELLOW_LED, GPIO_OUT);
gpio_set_dir(GREEN_LED, GPIO_OUT);
gpio_set_dir(BUTTON, GPIO_IN);
gpio_pull_up(BUTTON);
adc_init();
adc_set_temp_sensor_enabled(true);
adc_gpio_init(26);
while(true) {
PhotoresRun();
bool button_state = gpio_get(BUTTON);
uint8_t return_value = TempSensorRun(button_state);
printf(“return_value = %d”, return_value);
sleep_ms(500);
}
}
void PhotoresRun(){
adc_select_input(0);
uint16_t photores_read = adc_read();
const float voltage_photores = photores_read * conversion_factor;
printf(“Photoresistor ADC: value = %d, voltage = %f V\n”, photores_read, voltage_photores);
if(photores_read >= 0 && photores_read <= 2500){
gpio_put(GREEN_LED, 1);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 0);
}
else if(photores_read > 2500 && photores_read <= 3500){
gpio_put(GREEN_LED, 0);
gpio_put(YELLOW_LED, 1);
gpio_put(RED_LED, 0);
}
else if(photores_read > 3500 && photores_read <= 4095){
gpio_put(GREEN_LED, 0);
gpio_put(YELLOW_LED, 0);
gpio_put(RED_LED, 1);
}
else
{
printf(“Photoresistor error”);
}
}
uint8_t TempSensorRun(bool button_state){
if(button_state == false){
adc_select_input(4);
uint16_t adc_temp = adc_read();
float temp = 27 – ((adc_temp * conversion_factor)-0.706)/0.001721;
printf(“Temp = %f C, voltage = %f V\n”, temp, (adc_temp * conversion_factor));
return 0;
}
return 1;
}
Bisher haben wir in unseren Programmen eine einzelne Hauptfunktion main verwendet und einen Bereich darüber, in dem wir Variablendeklarationen, Konstanten oder eingebundene Bibliotheken platziert haben. Diesmal werden wir auch versuchen, unsere eigene Funktion vorzubereiten, die wir wie viele der anderen Befehle innerhalb der Hauptfunktion main platzieren können. Wir werden den Test mit einem bereits vorbereiteten Programm durchführen, das wir in diesem Artikel besprechen. In diesem Beispiel werden wir versuchen, die Codeblöcke zu extrahieren, die für den Umgang mit dem Fotowiderstand und dem internen Temperatursensor verantwortlich sind, und sie in einer einzigen Funktion zu kapseln.
Lassen Sie uns das obige Beispiel betrachten. Wie Sie sehen können, wurde die Hauptfunktion main und insbesondere der Code innerhalb der while-Schleife erheblich verschlankt. Er besteht jetzt nur noch aus fünf Anweisungen, und der Code, der eben noch da war, steht jetzt in ziemlich geheimnisvoll beschriebenen Blöcken darunter. Genau dies sind unsere eigenen Funktionen namens PhotoresRun und TempSensorRun.
Bei der Ausführung des Codes aus der while-Schleife stößt der Mikrocontroller auf den Befehl PhotoresRun();. In diesem Fall springt er zu dem Code, der in dieser Funktion beschrieben wird. Wir definieren Funktionen als – Typ name(optionale Operanden). In diesem Fall handelt es sich um void PhotoresRun(), eine Funktion vom Typ void namens PhotoresRun, der wir keine Variablen übergeben, da die Klammern leer sind. Der Funktionstyp bestimmt auch, was die Funktion an uns zurückgibt. Wenn wir zum Beispiel adc_read() verwenden, gibt uns die Funktion die digitale Darstellung des Signals zurück, das von einer der ADC-Quellen gelesen wurde. Wenn wir wollen, dass eine Funktion keinen Wert an uns zurückgibt, verwenden wir einfach den Typ void. Der Code, der in PhotoresRun ausgeführt wird, steht in geschweiften Klammern und ist eine Kopie des Programms, das in den früheren Beispielen den Fotowiderstand bedient hat.
Die zweite Funktion ist TempSensorRun, deren Deklaration schon ein wenig komplizierter aussieht – uint8_t TempSensorRun(bool button_state). Dies ist eine Funktion, die Daten vom Typ uint8_t mit dem Namen TempSensorRun zurückgibt, an die wir eine Variable vom Typ bool mit dem Namen button_state übergeben können. Es wurde eine Bedingung in die Funktion eingefügt, um das Drücken der Taste zu unterstützen. Wenn die an die Funktion übergebene Variable den Wert false annimmt, d.h. wenn die Taste gedrückt wird, dann wird der Code ausgeführt, den wir bereits für den internen Temperatursensor verwendet haben. Darüber hinaus können Sie in dieser Funktion Schlüsselwörter return sehen. Sie ermöglichen es einer Funktion, “etwas” zurückzugeben. Wenn wir kurz zum Hauptblock main springen, sehen wir, dass eine Variable vom Typ uint8_t namens return_value erstellt wurde, der der von TempSensorRun zurückgegebene Wert zugewiesen wird. Wenn die Taste gedrückt wird, führt die Funktion den unterstützenden Code aus und sendet Temperatur- und Spannungsdaten über USB, gibt aber auch einen Wert von Null zurück, der return_value zugewiesen wird. Wenn die Taste nicht gedrückt wird und am RPI-Ausgang ein High-Status anliegt, gibt die Funktion den Wert Eins zurück, der ebenfalls einer Variablen zugewiesen wird.
Auf diese Weise können wir unsere eigenen Funktionen erstellen und dabei auch ihre Prototypen berücksichtigen, die oberhalb des Blocks main platziert werden müssen. Es ist nichts weiter als eine kurze Wiederholung der Funktionsdeklaration mit Typ, Name und optionalen Operanden.
Nachdem Sie den Code ausgeführt haben, können Sie die vom Raspberry Pi Pico W übertragenen Daten des Fotowiderstands und den Wert, der return_value zugewiesen wurde, auf dem Computerbildschirm sehen. Wenn die Taste frei bleibt, wird der Variable eine Eins zugewiesen. Die Schaltung führt die Funktion TempSensorRun aus, aber aufgrund des Zustands der Taste gibt sie sofort die Nummer eins zurück und kehrt zur Hauptschleife zurück. Die Situation ändert sich, wenn die Taste gedrückt wird. Dann sehen wir zusätzlich zu den Daten des internen Temperatursensors, dass sich der return_value auf Null ändert.
Die Erstellung eigener Funktionen ist äußerst nützlich, insbesondere bei komplexeren Codes. Damit wird main erheblich verschlankt, was die Lesbarkeit des Programms verbessert. Wenn Sie sich eine bestimmte Funktion ansehen möchten, müssen Sie nur deren Deklaration finden und dann zur Hauptschleife zurückkehren. Benutzerdefinierte Funktionen können auch in anderen Dateien platziert werden, was die Codes noch optimaler macht, aber wir werden uns mit diesem Thema ein anderes Mal beschäftigen.
Ein paar Worte zum Schluss...
Im heutigen Material lernen wir einige theoretische Fragen im Zusammenhang mit der Codeoptimierung kennen. Außerdem habe ich die gute Angewohnheit erwähnt, auf bereits geschriebene Programme zurückzugreifen. Schließlich habe ich Ihnen gezeigt, wie Sie Ihre eigenen Funktionen erstellen können, und dieses Wissen wird sich in Zukunft als nützlich erweisen. Trotz des eher theoretischen Charakters dieses Materials werden Sie auch lernen, wie Sie den internen Temperatursensor des RP2040 bedienen. Wir haben in den Projekten auch logische Operatoren und Bitverschiebungen verwendet. Im nächsten Material werden wir uns mit Unterbrechungen im Mikrocontroller beschäftigen.
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: 10
Bisher keine Bewertungen! Sei der Erste, der diesen Beitrag bewertet.