Hallo zusammen,
Ich sitze gerade an einem Projekt in C++, wo ich aus einer CSV-Datei Daten auslesen und in anderen Unterprozeduren weiter verarbeiten möchte.
Das Auslesen der Dateien und Verarbeiten der Daten an sich funktioniert schon mal. Leider steh ich gerade komplett auf dem Schlauch, wenn es darum geht die eingelesenen Daten an andere Prozeduren zu übergeben, damit ich die Daten dann weiter verwenden kann.
Hier mal ein Auszug aus dem Code:
----------
void READCSV::read_config_files(const std::string& csv_file_name) { ifstream csv_in_file;
string delimiter = ";"; // Spaltentrenner string elements[40][2]; // 2d string zum Speichern der Daten
----------
Ich möchte nun die Variable "elements" zurückgeben.
Ein "return elements" wirft mir nat. nur den Fehler:
----------
root@milchtaxi-1:/usr/src/packages/Zero_Controller_SNR# make g++ -c -o src/lib/read_csv.o src/lib/read_csv.cpp src/lib/read_csv.cpp:In member function ‘void READCSV::read_config_files(const string&)’: src/lib/read_csv.cpp:95:10:error: return-statement with a value, in function returning ‘void’ [-fpermissive] 95 | return elements; | ^~~~~~~~ make: *** [<eingebaut>: src/lib/read_csv.o] Fehler 1 ----------
Ich habe schon versucht eine Anleitung im Netz zu finden, aber ich komme hier einfach nicht weiter.
Vielleicht hat jemand einen Tipp, wie das funktionieren könnte. Danke schon mal im Voraus.
Hallo Sebastian,
du hast hier wie in C++ üblich mehrere Möglichkeiten. Zunächst hast du aber einen Denkfehler. Das Array elements, welches du in der Funktion read_config_csv anlegst kannst du nicht außerhalb dieses Contextes Nutzen. Du musst den Speicher am vorher bereitstellen (Stack oder Heap) und dann kannst du den Nutzen.
Die einfachste Variante ist, wenn du das Array vorher definierst und dann der Funktion als Parameter mit übergibst.
std::string elements[40][2];
void READCSV::read_config_files(const std::string& csv_file_name, std::string elements[40][2]);
Je nachdem wie deine Daten aussehen lohnt es sich ja aber vllt schon diese in einer Struktur abzulegen und dann nur ein 1D-Array der Struktur zurückzugeben
Viele Grüße Robert
Hallo Sebastian,
vielleicht besser das 2D-Array den Funktionsaufrufer definieren lassen und als Referenz der Funktion übergeben?
Dann hat die Funktion die Ownership vom Hals. ;-)
VG,
Erik
Am 01.05.22 um 22:13 schrieb Sebastian Reinhardt:
Hallo zusammen,
Ich sitze gerade an einem Projekt in C++, wo ich aus einer CSV-Datei Daten auslesen und in anderen Unterprozeduren weiter verarbeiten möchte.
Das Auslesen der Dateien und Verarbeiten der Daten an sich funktioniert schon mal. Leider steh ich gerade komplett auf dem Schlauch, wenn es darum geht die eingelesenen Daten an andere Prozeduren zu übergeben, damit ich die Daten dann weiter verwenden kann.
Hier mal ein Auszug aus dem Code:
void READCSV::read_config_files(const std::string& csv_file_name) { ifstream csv_in_file;
string delimiter = ";"; // Spaltentrenner string elements[40][2]; // 2d string zum Speichern der Daten
Ich möchte nun die Variable "elements" zurückgeben.
Ein "return elements" wirft mir nat. nur den Fehler:
root@milchtaxi-1:/usr/src/packages/Zero_Controller_SNR# make g++ -c -o src/lib/read_csv.o src/lib/read_csv.cpp src/lib/read_csv.cpp:In member function ‘void READCSV::read_config_files(const string&)’: src/lib/read_csv.cpp:95:10:error: return-statement with a value, in function returning ‘void’ [-fpermissive] 95 | return elements; | ^~~~~~~~ make: *** [<eingebaut>: src/lib/read_csv.o] Fehler 1
Ich habe schon versucht eine Anleitung im Netz zu finden, aber ich komme hier einfach nicht weiter.
Vielleicht hat jemand einen Tipp, wie das funktionieren könnte. Danke schon mal im Voraus.
Hallo,
On Sun, May 01, 2022 at 10:13:56PM +0200, Sebastian Reinhardt wrote:
void READCSV::read_config_files(const std::string& csv_file_name) { ifstream csv_in_file;
string delimiter = ";"; // Spaltentrenner string elements[40][2]; // 2d string zum Speichern der Daten
Ich möchte nun die Variable "elements" zurückgeben.
Ein "return elements" wirft mir nat. nur den Fehler:
Bist Du Dir sicher, dass Du das in C++ programmieren willst? Ich denke Python oder Perl, oder wegen mir Javascript oder PHP wären deutlich besser geeignet.
Und ja, der C++ Compiler mag das nicht, wenn man den Return Typ als "void" declariert und dann std::string[40][20] returned. Wobei das auch kein "gute Idee" (TM) ist. Wenn es unbedingt C++ sein muss, nimm std::vector.
Aber siehe oben... nimm ne andere Programmiersprache.
Grüsse Andreas
Hallo.
string elements[40][2]; // 2d string zum Speichern der Daten
Stack arrays sind eher ein Überbleibsel aus C. Am Ende ist das ein Pointertyp, wenn du den zurückgibst, e.g.: ``` auto f(...) { string elements[40][2]; //... return elements; } ``` Dann gibt du einen Pointer auf eine lokale Variable zurück. Also einenen Pointer auf Speicher der nach dem Ende dieser Funktionier wieder freigegeben ist. (Der Compiler wird da auch warnen.)
Wenn due Größe zur Compilierzeit bekannt ist, nimm std::array. In diesem Fall wäre das ``` std::array<std::array<std::string, 2>, 40> ```
Wenn du eine Datei einließt, ist es aber eher unwahrscheinlich, dass du die Größe vorher kennst. In dem Fall nimmst du std::vector, also:
``` std::vector<std::vectorstd::string> rowCol;
rwoCol.resize(40); for(auto & row: rowCol) row.resize(2); ```
Eine andere Möglichkeit wäre, einen flachen vector zu nehmen und selbst 2d zu adressieren: ``` std::vectorstd::string arr; const int nRows = 40, nCols = 2; arr.resize(nRows * nCols);
int row = 3, col = 1; arr[nCols*row + col] = "Hello"; // das wäre row-major ```
Als Rückgabetyp schreibst du in jedem Fall jeweils den Typ hin als den du die zurückzugebende Varaible deklariert hast
Es gibt leider noch keine matizen in der STL.
Viele Grüße
Jeffrey
Ich möchte nun die Variable "elements" zurückgeben.
Ein "return elements" wirft mir nat. nur den Fehler:
Bist Du Dir sicher, dass Du das in C++ programmieren willst? Ich denke Python oder Perl, oder wegen mir Javascript oder PHP wären deutlich besser geeignet.
Und ja, der C++ Compiler mag das nicht, wenn man den Return Typ als "void" declariert und dann std::string[40][20] returned. Wobei das auch kein "gute Idee" (TM) ist. Wenn es unbedingt C++ sein muss, nimm std::vector.
Aber siehe oben... nimm ne andere Programmiersprache.
Grüsse Andreas
Am 02.05.22 um 08:58 schrieb Jeffrey Kelling:
Hallo.
string elements[40][2]; // 2d string zum Speichern der Daten
Stack arrays sind eher ein Überbleibsel aus C. Am Ende ist das ein Pointertyp, wenn du den zurückgibst, e.g.:
auto f(...) { string elements[40][2]; //... return elements; }
Dann gibt du einen Pointer auf eine lokale Variable zurück. Also einenen Pointer auf Speicher der nach dem Ende dieser Funktionier wieder freigegeben ist. (Der Compiler wird da auch warnen.)
Wenn due Größe zur Compilierzeit bekannt ist, nimm std::array. In diesem Fall wäre das
std::array<std::array<std::string, 2>, 40>
Wenn du eine Datei einließt, ist es aber eher unwahrscheinlich, dass du die Größe vorher kennst. In dem Fall nimmst du std::vector, also:
std::vector<std::vector<std::string>> rowCol; rwoCol.resize(40); for(auto & row: rowCol) row.resize(2);
Eine andere Möglichkeit wäre, einen flachen vector zu nehmen und selbst 2d zu adressieren:
std::vector<std::string> arr; const int nRows = 40, nCols = 2; arr.resize(nRows * nCols); int row = 3, col = 1; arr[nCols*row + col] = "Hello"; // das wäre row-major
Als Rückgabetyp schreibst du in jedem Fall jeweils den Typ hin als den du die zurückzugebende Varaible deklariert hast
Es gibt leider noch keine matizen in der STL.
Viele Grüße
Jeffrey
Ich möchte nun die Variable "elements" zurückgeben. Ein "return elements" wirft mir nat. nur den Fehler:
Bist Du Dir sicher, dass Du das in C++ programmieren willst? Ich denke Python oder Perl, oder wegen mir Javascript oder PHP wären deutlich besser geeignet.
Und ja, der C++ Compiler mag das nicht, wenn man den Return Typ als "void" declariert und dann std::string[40][20] returned. Wobei das auch kein "gute Idee" (TM) ist. Wenn es unbedingt C++ sein muss, nimm std::vector.
Aber siehe oben... nimm ne andere Programmiersprache.
Grüsse Andreas
Hallo,
Danke schon mal für die Anregungen.
Thema: "Nimm 'ne andre Programmiersprache.." -> Klares: Jaein.
Ja, ich habe bisher viel mit Perl geschrieben, aber ich nutze die Gelegenheit mich doch mal in die C's einzuarbeiten. Also bleiben wir erstmal bei C++. Irgendwann muss ich mich ja mal damit befassen, um andere Programme ggf. zu erweitern usw., aber das ist dann schon wieder das nächste Thema.
Kleine Hintergrundinfo noch zu den Daten in der csv bzw. deren Verwendung:
Es sind Pinzurodnungen für 'ne "Himbeere". Dort steht z.B. "4;output;LCD_LED". Bedeutet, dass auf BCM4 ein Ausgang hängt und der die Hintergrundbeleuchtung einer Anzeige ein-/ausschalten kann.
Nun kenn ich aus Perl die Vorzüge von "Hashes". Darin lassen sich sehr gut diese Informationen speichern und mit den "keys" die "values" oder mit den "values" die "Keys" abrufen. Wenn ich also im Programm den GPIO ansteuern will, kann ich mit dem "value"=LCD_LED den "key"=4 in Funtkionen, wie etwa "gpioSetMode(4, PI_OUTPUT); " verwenden und eine Schleife darüber laufen lassen. Somit kann ich z.B. auch den LCD_LED- Ausgang auf einen anderen PIN legen, ohne das ganze Programm neu kompilieren zu müssen.
D.h. auch, dass ich nat. weiss, wie groß der Datenraum max. ist. Hier: 40-Pins -> 40-Werte (abzügl. 3,3V, 5V, GND-Pins usw.). Ein dynamisch angepasster Vector ist nat. wahrscheinlich sinnvoller. Vectoren habe ich auch schon im Code und habe mir mal angesehen, wie man eigene "Struct"'s anlegt. Kommt den Perl-Hashes schon sehr nahe.
Ich habe das jetzt so angelegt:
--------
struct csv_values { string pin_number; string in_out_name; string in_out_function;
};
auto READCSV::read_config_files(const string& csv_file_name) { ifstream csv_in_file;
string delimiter = ";"; // Spaltentrenner vector <csv_values> elements; // 2d string zum Speichern der Daten
//......
return elements;
}
--------
Das rufe ich dann mit folgender Zeile in der übergeordneten Funktion auf:
----------
//...
READCSV read_csv; if (file_names[0].length() > 0) { auto pin_defs = read_csv.read_config_files(file_names[0]); }
//...
----------
Wirft aber (erwartungsgemäss) wieder nur Fehler.....
---------
make
g++ -c -o src/lib/init_hw.o src/lib/init_hw.cpp src/lib/init_hw.cpp:In member function ‘void ZEROHW::init_hw(const std::vector<std::__cxx11::basic_string<char> >&)’: src/lib/init_hw.cpp:52:69:error: use of ‘auto READCSV::read_config_files(const string&)’ before deduction of ‘auto’ 52 | auto pin_defs = read_csv.read_config_files(file_names[0]); | ^ make: *** [<eingebaut>: src/lib/init_hw.o] Fehler 1
---------
Irgendwie steh ich noch auf dem Schlauch....
Hallo.
Nun kenn ich aus Perl die Vorzüge von "Hashes". Darin lassen sich sehr gut diese Informationen speichern und mit den "keys" die "values" oder mit den "values" die "Keys" abrufen.
Eine Hasmap ha die STL auch: https://en.cppreference.com/w/cpp/container/unordered_map
D.h. auch, dass ich nat. weiss, wie groß der Datenraum max. ist. Hier: 40-Pins -> 40-Werte (abzügl. 3,3V, 5V, GND-Pins usw.). Ein dynamisch angepasster Vector ist nat. wahrscheinlich sinnvoller. Vectoren habe ich auch schon im Code und habe mir mal angesehen, wie man eigene "Struct"'s anlegt. Kommt den Perl-Hashes schon sehr nahe.
Ich habe das jetzt so angelegt:
struct csv_values { string pin_number; string in_out_name; string in_out_function;
};
Es ist durchaus sinnvoll das was du über die Datan/Domäne weißt in Code zu gießen.
make
g++ -c -o src/lib/init_hw.o src/lib/init_hw.cpp src/lib/init_hw.cpp:In member function ‘void ZEROHW::init_hw(const std::vector<std::__cxx11::basic_string<char> >&)’: src/lib/init_hw.cpp:52:69:error: use of ‘auto READCSV::read_config_files(const string&)’ before deduction of ‘auto’ 52 | auto pin_defs = read_csv.read_config_files(file_names[0]); | ^ make: *** [<eingebaut>: src/lib/init_hw.o] Fehler 1
Hast du vllt die Funktion mit `auto` Rückgabetyp in einem Header nur deklariert, aber nicht definiert? `auto` geht nur, wenn der Compiler beim Aufruf auch sieht was in der Funktion zurückgegeben wird, wenn sie als auf irgendeineweise inline bzw. in der gleichen Kompiliereinheit definiert wird. Wenn die Definition in einer separaten Einiheit ist, dann musst du explizit sagen was der Rückgabetyp ist, also `std::vector<csv_values>`.
Viele Grüße
Jeffrey
Hi,
On 02/05/2022 21:58, Jeffrey Kelling wrote:
make
g++ -c -o src/lib/init_hw.o src/lib/init_hw.cpp src/lib/init_hw.cpp:In member function ‘void ZEROHW::init_hw(const std::vector<std::__cxx11::basic_string<char> >&)’: src/lib/init_hw.cpp:52:69:error: use of ‘auto READCSV::read_config_files(const string&)’ before deduction of ‘auto’ 52 | auto pin_defs = read_csv.read_config_files(file_names[0]); | ^ make: *** [<eingebaut>: src/lib/init_hw.o] Fehler 1
Hast du vllt die Funktion mit `auto` Rückgabetyp in einem Header nur deklariert, aber nicht definiert? `auto` geht nur, wenn der Compiler beim Aufruf auch sieht was in der Funktion zurückgegeben wird, wenn sie als auf irgendeineweise inline bzw. in der gleichen Kompiliereinheit definiert wird. Wenn die Definition in einer separaten Einiheit ist, dann musst du explizit sagen was der Rückgabetyp ist, also `std::vector<csv_values>`.
Noch ein kleiner Nachschlag dazu: auto ist eines der praktischsten Features in C++-11, aber wie immer in C++ kann/sollte man es nicht immer und überall einsetzen.
Wann man auto unbedingt einsetzen sollte:
Wenn man einen numerischen Typ von einer Funktion zurückbekommt: auto zahl = meineFunktion(); Das verhindert dass man den falschen Typ für "zahl" deklariert und ein automatischer Cast den Wertebereich kaputt macht (beliebt sind "int" und "sizeof" auf 64-bit Maschinen).
Als Rückgabetyp wenn man Templates definiert (Profi-Tip: wenn Du innerhalb der ersten 5 Jahre C++ ein Template definierst, dann machst Du wahrscheinlich was falsch; Templates benutzen ist dagegen okay, gewünscht und sogar hilfreich).
Bei komplexen Operationen bei denen es nicht intuitiv ist was der Ergebnistyp ist: auto ergebnis = getMyCollection().at(42).transform(otherGadget).last();
Wan man NICHT auto einsetzen sollte:
Wenn man selbst Funktionen/Methoden deklariert, es sei denn es ist eine triviale inline-Funktion. Also alles was komplexer als "inline auto square(int a){return a*a;}" ist sollte seinen Typ explizit deklarieren. Sprich: wenn die Definition der Funktion inline im Header steht und auf eine Zeile passt, dann auto, sonst explizit.
Wenn man weiß dass mit Referenzen oder Pointern herumhantiert wird und man den Wert weitergeben oder aufheben will, dann ist es einfacher explizit zu sagen welcher nicht-Referenz-Typ es sein soll.
In der Parameterliste von Funktionen bringt es nur unverständliche Fehlermeldungen. ;-)
Vorsicht bei der Initialisierung von Member-Variablen: auto mysize=5; ist wahrscheinlich nicht so gewollt - es erzeugt eine int Variable, aber "size" sollte lieber size_t, ssize_t oder long sein...
Für alle anderen Fälle gilt:
Wenn eine Ergebnis-Variable auto-Typing bekommt hilft es im Allgemeinen Verwirrung zu vermeiden, aber es gibt Ausnahmen.
Wo wir gerade bei Vereinfachungen und Hinweisen sind:
* deklariere so viel wie möglich const - dann sagt Dir der Compiler wenn Du ausversehen irgendwo schreibst wo Du es nicht solltest, z.B. if(a=3)... * bei großen Datentypen (string, array, vector, ...): deklariere Funktionsparameter als const-Referenz (geht schneller), Rückgabe-Typen als non-Referenz (crasht sonst gerne mal); merke Dir niemals eine Referenz, sondern mach' Kopien * wenn Du schon Pointer benutzt, dann initialisiere sie auf nullptr und setze sie nach delete auch immer auf nullptr zurück - das macht die Fehlerdiagnose einfacher! * wenn Du eine Referenz zurückgibst machst Du zu 99% einen Fehler, das verbleibende 1% ist zum größten Teil schon von der STL abgedeckt * Pointer sind Teufelswerk; Referenzen sind besser aber nicht ganz ungefährlich(*); Value-Typen sind fett und langsam - wähle Dein Gift vorsichtig ;-) * Optimiere erst wenn Du merkst dass es nötig ist, nicht schon wenn Du glaubst cleverer zu sein als Dein Computer (moderne Compiler sind Wunderwerke der Technik) * C-Strings sind okay um std::string zu initialisieren (z.B. std::string s="Hallo Welt"), aber sonst sollten sie vermieden werden. * Kommentare sind gesund, erhöhen die Biodiversität im Code-Dschungel und sollten gut gepflegt werden. Fehlende Kommentare führen zu tragischen Code-Verlusten.
(*)Apropos Referenzen:
* Referenzen auf globale Variablen sind okay, aber eigentlich sollte man globale Werte vermeiden * Referenzen auf statische Klassen-Variablen sind okay und besser als ganz globale Werte * Referenzen auf Variablen in main() sind meistens okay, können aber schiefgehen wenn sie in im Exit-Handler benutzt werden (z.B. im Destruktor einer globalen Variable, ja der wird aufgerufen!) * Referenzen auf lokale Werte sollte man vermeiden - es ist okay sie in einen tieferen Stack-Frame zu geben, solange der sich die Referenz nicht merkt * Referenzen auf Werte aus einem tieferen Stack-Frame sind lebensgefährlich, weil der Speicher entweder schon freigegeben ist oder Unsinn enthält * Referenzen, die von einer Funktion zurückgegeben werden sollte man nur auf der selben Zeile verwenden und dann vergessen, wenn Du es länger brauchst mach eine Kopie * Nimm niemals an dass eine Referenz aus einem Parameter länger lebt als die aktuelle Funktion!
Konrad
Hallo,
den Ausführungen von Konrad stimme ich zu. Nur eine Sache möchte ich gerne korrigieren.
- wenn Du schon Pointer benutzt, dann initialisiere sie auf nullptr und setze sie nach delete auch immer auf nullptr zurück - das macht die Fehlerdiagnose einfacher!
So ziemlich alle gültigen Anwendungen für new und delete für die ersten zehn Jahre sind in der STL abgehandelt. Für den klassischen Fall gibt es std::unique_ptr<>, für Felder std::vector<> und dann gibt es noch std::shared_ptr<> und std::weak_ptr<> falls man refcounting braucht (das nimmt man nur wenn man absolut keine klare Ownership definieren kann.) (das Stichwort dazu ist "RAII")
- wenn Du eine Referenz zurückgibst machst Du zu 99% einen Fehler, das verbleibende 1% ist zum größten Teil schon von der STL abgedeckt
Ich glaube der häufigste und einfachste Fall für Referenzen die Zurückgegeben werden sind Getter von Klassen, die dürften schon mehr als 1% sein. Aber sonst fällt mir auch nichts normales ein wo die Rückgabe von Referenzen Sinn macht.
- Pointer sind Teufelswerk; Referenzen sind besser aber nicht ganz ungefährlich(*); Value-Typen sind fett und langsam - wähle Dein Gift vorsichtig ;-)
Ab C++17 ist Copy elision bei Return in vielen Fällen für den Compiler pflicht, das mach die Rückgabe von Value-Typen kostenlos, wenn man aufpasst. In den Übrigen fällen, kann move-Semantik (ab C++11) helfen.
Viele Grüße
Jeffrey
Am 02.05.22 um 21:58 schrieb Jeffrey Kelling:
Hallo.
Nun kenn ich aus Perl die Vorzüge von "Hashes". Darin lassen sich sehr gut diese Informationen speichern und mit den "keys" die "values" oder mit den "values" die "Keys" abrufen.
Eine Hasmap ha die STL auch: https://en.cppreference.com/w/cpp/container/unordered_map
D.h. auch, dass ich nat. weiss, wie groß der Datenraum max. ist. Hier: 40-Pins -> 40-Werte (abzügl. 3,3V, 5V, GND-Pins usw.). Ein dynamisch angepasster Vector ist nat. wahrscheinlich sinnvoller. Vectoren habe ich auch schon im Code und habe mir mal angesehen, wie man eigene "Struct"'s anlegt. Kommt den Perl-Hashes schon sehr nahe.
Ich habe das jetzt so angelegt:
struct csv_values { string pin_number; string in_out_name; string in_out_function;
};
Es ist durchaus sinnvoll das was du über die Datan/Domäne weißt in Code zu gießen.
make
g++ -c -o src/lib/init_hw.o src/lib/init_hw.cpp src/lib/init_hw.cpp:In member function ‘void ZEROHW::init_hw(const std::vector<std::__cxx11::basic_string<char> >&)’: src/lib/init_hw.cpp:52:69:error: use of ‘auto READCSV::read_config_files(const string&)’ before deduction of ‘auto’ 52 | auto pin_defs = read_csv.read_config_files(file_names[0]); | ^ make: *** [<eingebaut>: src/lib/init_hw.o] Fehler 1
Hast du vllt die Funktion mit `auto` Rückgabetyp in einem Header nur deklariert, aber nicht definiert? `auto` geht nur, wenn der Compiler beim Aufruf auch sieht was in der Funktion zurückgegeben wird, wenn sie als auf irgendeineweise inline bzw. in der gleichen Kompiliereinheit definiert wird. Wenn die Definition in einer separaten Einiheit ist, dann musst du explizit sagen was der Rückgabetyp ist, also `std::vector<csv_values>`.
Viele Grüße
Jeffrey
Hallo,
Danke für den Hinweis auf die "unordered map". Das was ich bisher mit der Suche nach "C++ Hash" gefunden habe, funktioniert anders, als eine Hash-Variable in Perl. Die von Dir verlinkte "unordered map" ist da eher das Richtige. Allerdings gefällt mir der "Vector" mit der Möglichkeit eine eigene Struktur zu definieren auch sehr gut. Ich kann damit genau das machen, was ich will (hoffe ich), d.h in dem später aufgerufenen Code möchte ich erstmal über alles, was mit "Output" bezeichnet ist (z.B.: "4;output_1;LCD_LED"), den GPIO richtig mit "gpioSetMode(OUTPUT_1, PI_OUTPUT);" usw. setzen (dafür benötige ich die Pin-Mummer) und in den eigentlichen Programmteilen (Messen und Regeln) eben z.B. immer "gpioWrite(LCD_LED, 1)" oder "gpioWrite(LCD_LED, 0)" zum Ein-/Ausschalten nutzen. Das sollte mit dem Vector machbar sein.
Ok, da ich nicht sinnlos zwischen den Techniken hin und her wechseln möchte, habe ich mich auf den Vector festgelegt. Außerdem habe ich auch noch mal hier [1] nachgeschaut, wie das mit den Referenzen funktioniert, da ich diese ja z.B. auch schon zum Übertragen des Dateinamens verwende. Nur klappt es noch nicht mit der Rückgabe des Vectors.
In der Lesefunktion habe ich das folgendermaßen definiert/ geschrieben:
------ read_csv.cpp -----------
#include "../headers/read_config_files.hpp" using namespace std; struct csv_values { string pin_number; string in_out_name; string in_out_function;
};
void read_config_files (const std::string& csv_file_name, vector<csv_values>& elements) {
//....
------ read_csv.cpp -----------
Die zugehörige Headerdatei:
------ read_config_files.hpp -----------
#ifndef READ_FILES_H #define READ_FILES_H using namespace std;
class READCSV { private: struct csv_values { string pin_number; string in_out_name; string in_out_function; };
public: void read_config_files (const std::string& csv_file_name, vector<csv_values>& elements); }; #endif
------ read_config_files.hpp -----------
Aufgerufen wird das aus:
------ init_hw.cpp -----------
#include "../headers/init_hw.hpp" #include "../headers/read_config_files.hpp"
using namespace std;
struct csv_values { string pin_number; string in_out_name; string in_out_function; };
void ZEROHW::init_hw(const std::vector<string> &file_names) { READCSV read_csv; vector<csv_values> elements; if (file_names[3].length() < 0) { read_csv.read_config_files(file_names[0], elements); }
//...
------ init_hw.cpp -----------
und die Headerdatei:
------ init_hw.hpp -----------
#ifndef INITHW_H #define INITHW_H using namespace std; class ZEROHW { private:
public: void init_hw(const std::vector<string> &file_names); void set_gpio(); }; #endif
------ init_hw.hpp -----------
Wenn ich das nun versuche zu Kompilieren, kommt ein Fehler:
------------------
make g++ -c -o src/lib/init_hw.o src/lib/init_hw.cpp src/lib/init_hw.cpp:In member function ‘void ZEROHW::init_hw(const std::vector<std::__cxx11::basic_string<char> >&)’: src/lib/init_hw.cpp:52:54:error: cannot convert ‘std::vector<csv_values>’ to ‘std::vectorREADCSV::csv_values&’ 52 | read_csv.read_config_files(file_names[0], elements); | ^~~~~~~~ | | | std::vector<csv_values> In file included from src/lib/init_hw.cpp:33: src/lib/../headers/read_config_files.hpp:28:91:note: initializing argument 2 of ‘void READCSV::read_config_files(const string&, std::vectorREADCSV::csv_values&)’ 28 | void read_config_files (const std::string& csv_file_name, vector<csv_values>& elements); | ~~~~~~~~~~~~~~~~~~~~^~~~~~~~ make: *** [<eingebaut>: src/lib/init_hw.o] Fehler 1
------------------
Was ich nicht verstehe ist, warum er aus "std::vector<csv_values>" nun ‘std::vectorREADCSV::csv_values&' macht bzw. wo das Problem liegt.
Ich habe auch schon mit "read_config_files(file_names[0], elements);" statt "read_csv.read_config_files(file_names[0], elements);" versucht. Aber dann kommt nat. erwartungsgemäß, dass die Funktion "read_config_files" nicht definiert/ deklariert ist.
Was nun? Wo liegt mein Fehler? Fehlt noch was?
----
Links:
[1] https://www.educba.com/c-plus-plus-pass-by-reference/
Hi,
warum definierst Du die Struktur csv_values mehrfach? Einmal reicht. Insbesondere die Definition als private Sub-Class in READCSV ist sinnlos.
Einmal in exakt einer Header-Datei deklarieren und diese Datei überall #include'n wo du sie brauchst.
Noch ein Tipp:
der Include-Guard: #ifndef XX_H #define XX_H //... #endif
...also ich verzähle mich da immer. Das hier ist einfacher:
#pragma once
Du schreibst es ganz an den Anfang der Datei (dort wo Du #ifndef hast) und vergisst den ganzen ifndef/define/endif Kram einfach. Alle modernen Compiler verstehen dieses Pragma - es bedeutet dass der Compiler sicherstellt dass diese Datei nur einmal interpretiert wird, auch wenn sie mehrfach eingebunden wird.
Konrad
Am 03.05.22 um 21:51 schrieb Konrad Rosenbaum:
Hi,
warum definierst Du die Struktur csv_values mehrfach? Einmal reicht. Insbesondere die Definition als private Sub-Class in READCSV ist sinnlos.
Einmal in exakt einer Header-Datei deklarieren und diese Datei überall #include'n wo du sie brauchst.
Noch ein Tipp:
der Include-Guard: #ifndef XX_H #define XX_H //... #endif
...also ich verzähle mich da immer. Das hier ist einfacher:
#pragma once
Du schreibst es ganz an den Anfang der Datei (dort wo Du #ifndef hast) und vergisst den ganzen ifndef/define/endif Kram einfach. Alle modernen Compiler verstehen dieses Pragma - es bedeutet dass der Compiler sicherstellt dass diese Datei nur einmal interpretiert wird, auch wenn sie mehrfach eingebunden wird.
Konrad
Hallo Konrad,
Danke für den Tipp und die ganzen Hinweise. Ich habe mir nun eine Headerdatei mit der Deklaration erstellt und diese in die Datei mit den eigentlichen Leseroutinen eingefügt. Auch in der Datei mit dem Aufruf der Funktion habe ich die Headerdatei eingefügt. Nun läuft zwar der Compiler durch, aber der Linker wirft einen Fehler:
-------------
make g++ -g src/zero_controller_SNR.o src/lib/i2c_lcd/lcdDriver.o src/lib/i2c_lcd/i2cControl.o src/lib/display.o src/lib/sensor.o src/lib/read_csv.o src/lib/init_hw.o -o zero_controller_SNR -lpthread -lpigpio -lrt /usr/bin/ld: src/lib/init_hw.o: in function `ZEROHW::init_hw(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<cha r> > > > const&)': init_hw.cpp:(.text+0x60): undefined reference to `READCSV::read_config_files(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<csv_values, std::allocator<csv_values> >&)' collect2: error: ld returned 1 exit status make: *** [Makefile:20: all] Fehler 1 -------------
Was ich nicht verstehe ist, warum die Funktion undefiniert ist, wenn ich in der "init_hw.cpp" über die Header-Datei "read_config_files.hpp" diese Funktion und auch die Struktur ja eigentlich definiert haben sollte und der Compiler diese auch kennt/ findet......?
Die fraglichen Teile der Dateien sehen so aus:
-------------init_hw.cpp-------------
#include "../headers/glob_pragma_001.hpp" #include "../headers/init_hw.hpp" #include "../headers/read_config_files.hpp"
using namespace std;
void ZEROHW::init_hw(const std::vector<string> &file_names) { READCSV read_csv; vector<csv_values> elements;
if (file_names[3].length() < 0) { read_csv.read_config_files(file_names[0], elements); }
-------------init_hw.cpp-------------
-------------read_csv.cpp-------------
#include "../headers/glob_pragma_001.hpp"
#include "../headers/init_hw.hpp" #include "../headers/read_config_files.hpp"
using namespace std;
void read_config_files (const std::string& csv_file_name, vector<csv_values> &elements) { ifstream csv_in_file; -------------read_csv.cpp-------------
-------------read_config_files.hpp-------------
#include "glob_pragma_001.hpp"
using namespace std; // Define some device parameters class READCSV { private: public: void read_config_files (const std::string& csv_file_name, vector<csv_values> &elements); };
-------------read_config_files.hpp-------------
-------------glob_pragma_001.hpp-------------
#pragma once
using namespace std;
struct csv_values { string pin_number; string in_out_name; string in_out_function; };
-------------glob_pragma_001.hpp-------------
Hallo,
On Wed, May 04, 2022 at 10:59:16PM +0200, Sebastian Reinhardt wrote:
Danke für den Tipp und die ganzen Hinweise. Ich habe mir nun eine Headerdatei mit der Deklaration erstellt und diese in die Datei mit den eigentlichen Leseroutinen eingefügt. Auch in der Datei mit dem Aufruf der Funktion habe ich die Headerdatei eingefügt. Nun läuft zwar der Compiler durch, aber der Linker wirft einen Fehler:
make g++ -g src/zero_controller_SNR.o src/lib/i2c_lcd/lcdDriver.o src/lib/i2c_lcd/i2cControl.o src/lib/display.o src/lib/sensor.o src/lib/read_csv.o src/lib/init_hw.o -o zero_controller_SNR -lpthread -lpigpio -lrt /usr/bin/ld: src/lib/init_hw.o: in function `ZEROHW::init_hw(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<cha r> > > > const&)': init_hw.cpp:(.text+0x60): undefined reference to `READCSV::read_config_files(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<csv_values, std::allocator<csv_values> >&)' collect2: error: ld returned 1 exit status make: *** [Makefile:20: all] Fehler 1
Was ich nicht verstehe ist, warum die Funktion undefiniert ist, wenn ich in der "init_hw.cpp" über die Header-Datei "read_config_files.hpp" diese Funktion und auch die Struktur ja eigentlich definiert haben sollte und der Compiler diese auch kennt/ findet......?
Der Compiler hat kein Problem. Dh. Header File etc. is ok. Dort hat der Linker nen Problem. Dh. einer der .o Files die Du in einem Aufruf übergibst referenziert die Funktion, aber der .o File in dem der Code dafür ist steht nicht mit auf der Commandline (oder das was in dem .o File ist entspricht nicht dem was im Header declariert wurde)
Grüsse Andreas
Hallo Sebastian,
Am 04.05.22 um 22:59 schrieb Sebastian Reinhardt:
[...] undefined reference to `READCSV::read_config_files(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<csv_values, std::allocator<csv_values> >&)'
[...] -------------read_csv.cpp------------- void read_config_files (const std::string& csv_file_name, vector<csv_values> &elements)
^- Hier fehlt ein "READCSV::", wie man dem Quelltext entnehmen kann (es handelt sich schließlich um eine Klasse):
[...] -------------read_config_files.hpp------------- class READCSV { private: public: void read_config_files (const std::string& csv_file_name, vector<csv_values> &elements); };
Viele Grüße, Mathias
Am 04.05.22 um 23:49 schrieb Mathias Krause:
Hallo Sebastian,
Am 04.05.22 um 22:59 schrieb Sebastian Reinhardt:
[...] undefined reference to `READCSV::read_config_files(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<csv_values, std::allocator<csv_values> >&)' [...] -------------read_csv.cpp------------- void read_config_files (const std::string& csv_file_name, vector<csv_values> &elements)
^- Hier fehlt ein "READCSV::", wie man dem Quelltext entnehmen
kann (es handelt sich schließlich um eine Klasse):
[...] -------------read_config_files.hpp------------- class READCSV { private: public: void read_config_files (const std::string& csv_file_name, vector<csv_values> &elements); };
Viele Grüße, Mathias
Jaaaaaa, das wars. Typischer Fall von "betriebsblind". Man schaut drauf, hat es in allen anderen Dateien/ Funktionen genau so gemacht und übersieht diesen einen Fehler. Ich habe schon einen Fehler im Makefile vermutet, wars aber eben nicht.
Danke, dass ihr noch auf seid und es gesehen habt. Ich mache dann morgen weiter mit der Verwendung dieses Vectors. Mal sehen, worüber ich noch stolpere. ;-)
Hallo,
Ich bin mehrere Schritte weiter und möchte zum Schalten der Ausgänge aus einem Vector mit eigener Struktur mit Hilfe des Namens die jeweilige Pinnummer abrufen bzw. als Substitution in den Funktionen nutzen. Dafür habe ich auch schon eine Anleitung gefunden [1], allerdings klappt es nicht bzw. ich verstehe die Funktionsweise nicht.
Ich habe folgenden Vector:
----------------------------------------
struct config_values { int config_number; string config_name; };
std::vector<config_values> config_vec
----------------------------------------
Als Beispieldaten habe ich folgende Werte:
----------------------------------------
config_vec[1].config_number = 4;
config_vec[1].config_name = led_alarm;
config_vec[2].config_number = 17;
config_vec[2].config_name = led_normal;
config_vec[3].config_number = 18;
config_vec[3].config_name = valve_1;
// usw.........
----------------------------------------
Nun möchte ich z.B. die Funktion "gpioWrite(GPIO_NO,Value)" nutzen, um die LED aus- bzw. einzuschalten. Dafür mochte ich die GPIO-Nummer "4" durch eine Variable ersetzen, in der die Nummer mit dem Suchstring "led_alarm" gefunden wird, da ich per csv-Datei die Nummer neu zuordnen kann, ohne das Programm neu zu kompilieren.
Das tollste wäre nat., wenn ich den Befehl "gpioWrite(config_vec{"led_alarm"},0}" (das ist nat. kein funktionierender Befehl!) aufrufen könnte und der GPIO-Ausgang mit der Nummer "4" geschaltet würde.
Könnt ihr mir da noch mal auf die Sprünge helfen? Eine einigermaßen verständliche Anleitung würde mir schon reichen.
----
[1] https://stackoverflow.com/questions/14225932/search-for-a-struct-item-in-a-v...
Hallo,
On Tue, May 10, 2022 at 11:54:52PM +0200, Sebastian Reinhardt wrote:
struct config_values { int config_number; string config_name; };
std::vector<config_values> config_vec
Als Beispieldaten habe ich folgende Werte:
config_vec[1].config_number = 4;
config_vec[1].config_name = led_alarm;
config_vec[2].config_number = 17;
config_vec[2].config_name = led_normal;
config_vec[3].config_number = 18;
config_vec[3].config_name = valve_1;
// usw.........
Nun möchte ich z.B. die Funktion "gpioWrite(GPIO_NO,Value)" nutzen, um die LED aus- bzw. einzuschalten. Dafür mochte ich die GPIO-Nummer "4" durch eine Variable ersetzen, in der die Nummer mit dem Suchstring "led_alarm" gefunden wird, da ich per csv-Datei die Nummer neu zuordnen kann, ohne das Programm neu zu kompilieren.
nu ja...
namespace {
int lookup(std::vector<config_values> const& v, std::string const& key) { auto it = find_if(begin(config_vec), end(config_vec), [key&](auto const& c) { return c.config_name == key; }); if (it != std::end(config_vec)) { return it->config_number; }
throw std::runtime_error(std::string("invalid config key")); }
}
...
try { gpioWrite(::lookup(config_vec, "led_alarm"), 0); } catch (std::runtime_error const& e) { ... }
oder Du nimmst halt ne std::map gleich vorn weg. Man könnte den vector dazu nachträglich umbauen eg.
auto config_map = std::map<std::string, int>{} ; std::transform(begin(config_vec) end(config_vec), std::inserter(config_map), [](auto const& e) { return std::make_pair(e.config_name, e.config_number); });
gpioWrite(config_map["led_alarm"], 0);
dat sieht zwar hübsch aus... hat aber kein Error handling.. eg. wenn Du nen lookup nach nem String machst, den's nicht gibt kommt 0 zurück...
try { gpioWrite(config_map.at("led_alarm"), 0); } catch (std::out_of_range const& e) { ... }
wäre eine Variante...
ich hab den Code nicht durch den Compiler geschickt. Da mögen Fehler drin sein.. is bloss das Prinzip.
Grüsse Andreas
Hallo zusammen, Ich sitze immer noch an meinem kleinen Programm und bin fast fertig. Ich habe mir in Anlehnung an Andreas Beispiele ein paar eigene Unterprogramme erstellt und kann nun die Vectordaten ordentlich auslesen. Auch habe ich einen Beispielcode von der PiGPIO- Webseite für das Auslesen eines Drehencoders, was auf dem "callback"- Prinzip basiert angepasst und noch den Druckschalter des Encoder und den Sensoreingang für den Ein-/Ausschalter zum Herunterfahren des PI hinzugefügt. Funktioniert ganz gut und ich kann nun das rudimentäre Menü bedienen.
Leider verbleibt noch ein Problem: Ich lasse eine Schleife laufen, die per "gpioSerialRead" einen Ultraschallsensor am UART-Port abfragt. Es ist ein Maxbotics Sonar-Sensor, der nach Anlegen der Spannungsversorgung ohne weiteres Zutun auf der RX-Leitung den gemessenen Abstand sendet. Es wird immer "R123\CR" (R- Range, 123- 3-stelliger Wert in cm, \CR- Carriage-return) gesendet. Nun funktioniert meine Routine grundsätzlich, aber ich bekomme, wenn ich mir den "read_buff" ausgeben lasse immer wieder so was:R105��v5 Je nach "Sleep"-Zeit in der Schleife verändert sich das und ich bekomme leere Antworten oder anderes "Kauderwelsch" oder auch nur Teile (z.B. "R1", "05R1" usw.) des 5-bit langen Wertes. Wenn ich zur gleichen Zeit mit "minicom -D /dev/serial0 -b 9600" auf den Sensor bzw. dessen Ausgabe ansehe, dann funktioniert das problemlos. Es muss also an meinem Code liegen!
Auch mit Blick auf den Code des Drehencoders stellen sich mir nun mal wieder ein paar Fragen: 1. Liegt das an der zeitlichen Abstimmung des Auslesens meiner Schleife? Wie muss ich das auf die Baudrate von "9600" abstimmen? 2. Es kommen neue Werte nur mit Verzögerung (wenn ich z.B. meine Hand vor den Sensor halte), bei "minicom" nicht. Hab ich da ein Problem mit einem Puffer? 3. zu 2.: Kann ich den seriellen Puffer vor dem Lesen löschen, um immer nur "frische" Werte zu bekommen? 4. oder ist es besser, wenn ich das nicht mit einer "Endlosschleife" mache, sondern mit einer Callback-Funktion mache, die bei einem neuen Wert getriggert wird (aber wie könnte so was aussehen, beim Encoder wird das ja durch die Funktion "gpioSetAlertFuncEx" getriggert, wenn ich das richtig verstehe....)?
Mein problematischer Code sieht so aus: ------------------------ while( i < num_samples && j < num_samples + 50 ) { memset(&read_buf, '\0', sizeof(read_buf)); rx_stat_bytes = gpioSerialRead(RXD, read_buf, sizeof(read_buf));
cout << " rx_stat: " << rx_stat_bytes << " readbuf: " << sizeof(read_buf) << endl; cout << read_buf << endl;
if ( rx_stat_bytes > 0 ) { if( memcmp ( read_buf, "R", sizeof(read_buf) ) == 1 ) { r_value.push_back(atoi(read_buf+1)); i++; } } else { // printf("Sensor aus?\n"); } j++; usleep(1000); } ------------------------
Eine Ausgabe der cout-"debug"-Zeile ist: ---------------------- rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5
rx_stat: 1 readbuf: 5 R rx_stat: 3 readbuf: 5 105 rx_stat: 1 readbuf: 5
rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 1 readbuf: 5 R rx_stat: 0 readbuf: 5
rx_stat: 1 readbuf: 5 1 rx_stat: 1 readbuf: 5 2 rx_stat: 1 readbuf: 5 0 rx_stat: 1 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
r_value_mean:120
----------------------
Das Blöde ist, das ich bis Montag 6.30Uhr dafür eine Lösung brauche, das dann Abgabe ist :-(
Wäre schön, wenn ihr mich noch mal "erleuchten" könntet.
Hallo Sebastian,
nutzt Du eine Hardware-UART? Dann solltest Du mal versuchen, die High-Level-Funktionen zu nutzen. Das heißt in dem Fall ist es vermutlich günstiger, Du gehst über den Linux-Treiber, genauso wie das minicom tut. Das sollte ausreichend dokumentiert sein. Such einfach nicht nach Pi + UART, sondern nach linux serial io. Ich würde mal vermuten, dass gpioSerialRead eine Software-UART implementiert, das heißt, sie liest das GPIO-Pin direkt und spielt selber UART.
Was Du nicht machen solltest, ist, Software-UART und Hardware-UART über denselben Pin laufen zu lassen. Dann muss der Pin nämlich zwei Herren dienen. Und jeder, der das mal versucht hat, weiß, dass es früher oder später zu Konflikten führt. Grundsätzlich kann ein Pin zu jedem Zeitpunkt maximal eine Funktion ausführen: GPIO oder UART oder ... (je nachdem, was im Datenblatt steht).
Was auch nicht funktioniert: minicom und Dein Programm gleichzeitig auf /dev/ttyS0 – oder was auch immer es ist – zuzugreifen. Die Datenströme werden nicht dupliziert.
Eine Weitere Fehlerquelle besteht darin, wenn Du das ganze im Polling betreibst und nicht im Interrupt. Wenn Dein Prozess aus irgendeinem Grund zu spät aufwacht (und das ist meist erlaubt), dann kann es passieren, dass der Empfangspuffer schon überschrieben wurde, bevor er ausgelesen wurde (das sollte aber andere Probleme bringen). An der Stelle solltest Du auch immer im Hinterkopf haben, dass Linux kein Echtzeit-System ist.
Wie stellst Du sicher, dass das erste Zeichen im read_buf auch das erste Zeichen einer neuen Zeile ist? Wenn Du Kauderwelsch empfängst solltest Du davon ausgehen, dass die Übertragung gestört war und warten, bis eine <Start of Record>-Nachricht kommt (das ist hier in dem Fall gleich dem <End of Record> und wird durch das Zeichen CR ausgedrückt).
Viele Grüße
Tobias
Am 14.05.22 um 22:13 schrieb Sebastian Reinhardt:
Hallo zusammen, Ich sitze immer noch an meinem kleinen Programm und bin fast fertig. Ich habe mir in Anlehnung an Andreas Beispiele ein paar eigene Unterprogramme erstellt und kann nun die Vectordaten ordentlich auslesen. Auch habe ich einen Beispielcode von der PiGPIO- Webseite für das Auslesen eines Drehencoders, was auf dem "callback"- Prinzip basiert angepasst und noch den Druckschalter des Encoder und den Sensoreingang für den Ein-/Ausschalter zum Herunterfahren des PI hinzugefügt. Funktioniert ganz gut und ich kann nun das rudimentäre Menü bedienen.
Leider verbleibt noch ein Problem: Ich lasse eine Schleife laufen, die per "gpioSerialRead" einen Ultraschallsensor am UART-Port abfragt. Es ist ein Maxbotics Sonar-Sensor, der nach Anlegen der Spannungsversorgung ohne weiteres Zutun auf der RX-Leitung den gemessenen Abstand sendet. Es wird immer "R123\CR" (R- Range, 123- 3-stelliger Wert in cm, \CR- Carriage-return) gesendet. Nun funktioniert meine Routine grundsätzlich, aber ich bekomme, wenn ich mir den "read_buff" ausgeben lasse immer wieder so was:R105��v5 Je nach "Sleep"-Zeit in der Schleife verändert sich das und ich bekomme leere Antworten oder anderes "Kauderwelsch" oder auch nur Teile (z.B. "R1", "05R1" usw.) des 5-bit langen Wertes. Wenn ich zur gleichen Zeit mit "minicom -D /dev/serial0 -b 9600" auf den Sensor bzw. dessen Ausgabe ansehe, dann funktioniert das problemlos. Es muss also an meinem Code liegen!
Auch mit Blick auf den Code des Drehencoders stellen sich mir nun mal wieder ein paar Fragen:
- Liegt das an der zeitlichen Abstimmung des Auslesens meiner
Schleife? Wie muss ich das auf die Baudrate von "9600" abstimmen? 2. Es kommen neue Werte nur mit Verzögerung (wenn ich z.B. meine Hand vor den Sensor halte), bei "minicom" nicht. Hab ich da ein Problem mit einem Puffer? 3. zu 2.: Kann ich den seriellen Puffer vor dem Lesen löschen, um immer nur "frische" Werte zu bekommen? 4. oder ist es besser, wenn ich das nicht mit einer "Endlosschleife" mache, sondern mit einer Callback-Funktion mache, die bei einem neuen Wert getriggert wird (aber wie könnte so was aussehen, beim Encoder wird das ja durch die Funktion "gpioSetAlertFuncEx" getriggert, wenn ich das richtig verstehe....)?
Mein problematischer Code sieht so aus:
while( i < num_samples && j < num_samples + 50 ) { memset(&read_buf, '\0', sizeof(read_buf)); rx_stat_bytes = gpioSerialRead(RXD, read_buf, sizeof(read_buf));
cout << " rx_stat: " << rx_stat_bytes << " readbuf: " << sizeof(read_buf) << endl; cout << read_buf << endl;
if ( rx_stat_bytes > 0 ) { if( memcmp ( read_buf, "R", sizeof(read_buf) ) == 1 ) { r_value.push_back(atoi(read_buf+1)); i++; } } else { // printf("Sensor aus?\n"); } j++; usleep(1000); }
Eine Ausgabe der cout-"debug"-Zeile ist:
rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5 rx_stat: 5 readbuf: 5 ��v5
rx_stat: 1 readbuf: 5 R rx_stat: 3 readbuf: 5 105 rx_stat: 1 readbuf: 5
rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 5 readbuf: 5 8�v0 rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 1 readbuf: 5 R rx_stat: 0 readbuf: 5
rx_stat: 1 readbuf: 5 1 rx_stat: 1 readbuf: 5 2 rx_stat: 1 readbuf: 5 0 rx_stat: 1 readbuf: 5
rx_stat: 0 readbuf: 5
rx_stat: 0 readbuf: 5
r_value_mean:120
Das Blöde ist, das ich bis Montag 6.30Uhr dafür eine Lösung brauche, das dann Abgabe ist :-(
Wäre schön, wenn ihr mich noch mal "erleuchten" könntet.
Am 14.05.22 um 23:07 schrieb Tobias Schlemmer:
Hallo Sebastian,
nutzt Du eine Hardware-UART? Dann solltest Du mal versuchen, die High-Level-Funktionen zu nutzen. Das heißt in dem Fall ist es vermutlich günstiger, Du gehst über den Linux-Treiber, genauso wie das minicom tut. Das sollte ausreichend dokumentiert sein. Such einfach nicht nach Pi + UART, sondern nach linux serial io. Ich würde mal vermuten, dass gpioSerialRead eine Software-UART implementiert, das heißt, sie liest das GPIO-Pin direkt und spielt selber UART.
Was Du nicht machen solltest, ist, Software-UART und Hardware-UART über denselben Pin laufen zu lassen. Dann muss der Pin nämlich zwei Herren dienen. Und jeder, der das mal versucht hat, weiß, dass es früher oder später zu Konflikten führt. Grundsätzlich kann ein Pin zu jedem Zeitpunkt maximal eine Funktion ausführen: GPIO oder UART oder ... (je nachdem, was im Datenblatt steht).
Was auch nicht funktioniert: minicom und Dein Programm gleichzeitig auf /dev/ttyS0 – oder was auch immer es ist – zuzugreifen. Die Datenströme werden nicht dupliziert.
Eine Weitere Fehlerquelle besteht darin, wenn Du das ganze im Polling betreibst und nicht im Interrupt. Wenn Dein Prozess aus irgendeinem Grund zu spät aufwacht (und das ist meist erlaubt), dann kann es passieren, dass der Empfangspuffer schon überschrieben wurde, bevor er ausgelesen wurde (das sollte aber andere Probleme bringen). An der Stelle solltest Du auch immer im Hinterkopf haben, dass Linux kein Echtzeit-System ist.
Wie stellst Du sicher, dass das erste Zeichen im read_buf auch das erste Zeichen einer neuen Zeile ist? Wenn Du Kauderwelsch empfängst solltest Du davon ausgehen, dass die Übertragung gestört war und warten, bis eine <Start of Record>-Nachricht kommt (das ist hier in dem Fall gleich dem <End of Record> und wird durch das Zeichen CR ausgedrückt).
Viele Grüße
Tobias
Hallo Tobias,
Ich will nat. nicht gleichzeitig mit dem C++-Programm und minicom auf die Schnittstelle zugreifen. Das habe ich dann nur mal zur Kontrolle gemacht, nachdem ich das "Kauderwelsch" bekommen habe.
Die Funktion "gpioSerialRead https://abyz.me.uk/rpi/pigpio/cif.html#gpioSerialRead" aus der Lib: [1] macht die Abfrage im BitBang-Modus. D.h., die lauscht nur. Ich will ja auch nichts schreiben, sondern nur den Messdaten "lauschen".
Nun bin ich mir eben unsicher, ob ich die kompletten Routinen für die serielle Kommunikation, die es ja im Netz gibt, benötige. Ich möchte das so einfach, wie möglich halten. Oder befinde ich mich hier auf dem Holzweg? Übersehe ich was ?
----
[1] https://abyz.me.uk/rpi/pigpio/cif.html#gpioSerialRead
Am 15.05.22 um 00:32 schrieb Sebastian Reinhardt:
Am 14.05.22 um 23:07 schrieb Tobias Schlemmer:
Hallo Sebastian,
nutzt Du eine Hardware-UART? Dann solltest Du mal versuchen, die High-Level-Funktionen zu nutzen. Das heißt in dem Fall ist es vermutlich günstiger, Du gehst über den Linux-Treiber, genauso wie das minicom tut. Das sollte ausreichend dokumentiert sein. Such einfach nicht nach Pi + UART, sondern nach linux serial io. Ich würde mal vermuten, dass gpioSerialRead eine Software-UART implementiert, das heißt, sie liest das GPIO-Pin direkt und spielt selber UART.
Was Du nicht machen solltest, ist, Software-UART und Hardware-UART über denselben Pin laufen zu lassen. Dann muss der Pin nämlich zwei Herren dienen. Und jeder, der das mal versucht hat, weiß, dass es früher oder später zu Konflikten führt. Grundsätzlich kann ein Pin zu jedem Zeitpunkt maximal eine Funktion ausführen: GPIO oder UART oder ... (je nachdem, was im Datenblatt steht).
Was auch nicht funktioniert: minicom und Dein Programm gleichzeitig auf /dev/ttyS0 – oder was auch immer es ist – zuzugreifen. Die Datenströme werden nicht dupliziert.
Eine Weitere Fehlerquelle besteht darin, wenn Du das ganze im Polling betreibst und nicht im Interrupt. Wenn Dein Prozess aus irgendeinem Grund zu spät aufwacht (und das ist meist erlaubt), dann kann es passieren, dass der Empfangspuffer schon überschrieben wurde, bevor er ausgelesen wurde (das sollte aber andere Probleme bringen). An der Stelle solltest Du auch immer im Hinterkopf haben, dass Linux kein Echtzeit-System ist.
Wie stellst Du sicher, dass das erste Zeichen im read_buf auch das erste Zeichen einer neuen Zeile ist? Wenn Du Kauderwelsch empfängst solltest Du davon ausgehen, dass die Übertragung gestört war und warten, bis eine <Start of Record>-Nachricht kommt (das ist hier in dem Fall gleich dem <End of Record> und wird durch das Zeichen CR ausgedrückt).
Viele Grüße
Tobias
Hallo Tobias,
Ich will nat. nicht gleichzeitig mit dem C++-Programm und minicom auf die Schnittstelle zugreifen. Das habe ich dann nur mal zur Kontrolle gemacht, nachdem ich das "Kauderwelsch" bekommen habe.
Die Funktion "gpioSerialRead https://abyz.me.uk/rpi/pigpio/cif.html#gpioSerialRead" aus der Lib: [1] macht die Abfrage im BitBang-Modus. D.h., die lauscht nur. Ich will ja auch nichts schreiben, sondern nur den Messdaten "lauschen".
Dann solltest Du
a) die entsprechende Serielle Schnittstelle deaktivieren, und darauf achten, dass der pin auf GPIO geschaltet ist.
b) einen freien GPIO benutzen, auf dem kein Treiber lauscht
c) wie gesagt die Finger vom BitBang-Modus lassen und den Linux-Treiber benutzen
Übrigens: b) und c) lassen sich auch kombinieren, indem Du eine Drahtbrücke zwichen RX und dem GPIO-Pin schaltest. Oder du machst die Brücke und hängst minicom an serial0 und gpioSerialRead an den GPIO pin.
Nun bin ich mir eben unsicher, ob ich die kompletten Routinen für die serielle Kommunikation, die es ja im Netz gibt, benötige. Ich möchte das so einfach, wie möglich halten. Oder befinde ich mich hier auf dem Holzweg? Übersehe ich was ?
Brauchst Du nicht. Must nur 9600 N 1 setzen (das sind 3 Parameter) und dann kannst Du selber von von /dev/serial0 lesen.
Aber wenn ich mir
https://blog.mbedded.ninja/programming/operating-systems/linux/linux-serial-...
durchlese, dann kannst Du den Code parallel zur Erklärung in Deinen Quelltext kopieren und denke, innerhalb von einer halben Stunde läufts.
Das meiste kannst Du erstmal übernehmen,
VMIN=1 und VTIME=0
erlaubt Dir
int get_R(FILE * serial_port) {
std::vector<char> chars;
char buffer;
chars.reserve(8); (etwas mehr, als gebraucht);
bool do_loop = true;
bool error = false;
while(do_loop) {
int n = read(serial_port,&buffer, 1);
switch (buffer) {
case '\r' : if (error) error=false; else do_loop = false; break;
case 'R' : chars.clear(); break;
case '0':
...
case '9':
chars.push_back(buffer);
break;
default:
printf("Unknown character, waiting for CR\n");
break;
}
}
chars.push_back(0);
return atoi(chars.data());
}
Oder so ähnlich (nicht getestet).
Viele Grüße
Tobias
switch state
Hallo,
Entschuldigung, dass ich erst jetzt antworte. Ich habe letzten Sonntag zu Montag noch durch gemacht und es funktioniert erst mal alles soweit. Leider hat sich dann Montag früh herausgestellt, dass die verwendeten Luftventile, die dann das eigentliche Ventil ansteuern, den Vorsteuerdruck am falschen Ende benötigen und sich somit nicht eignen. Wir haben das also erneut verschoben, da die vergangene Woche auch etwas "haarig" war.
Ich benutze für den seriellen Sensor nun, wie vorgeschlagen, die "Standard"- Funktionen und nicht die der PIGPIO-Library mit Bitbang. Mich hatte nur der "Wust" an TERMIO- Einstellungen etwas abgeschreckt. Glücklicher Weise habe ich dann noch eine Vorlage gefunden und angepasst (Baudrate, seriell-Port usw.). Es hatte dann zwar noch das System mit dem Versuch eine serielle Konsole auf dem Port zu öffnen etwas geärgert, aber ein "systemctl disable serial-getty@ttyS0.service" und ein Maskieren des Service hat das Problem gelöst.
Danke an alle, die mir auf die Sprünge geholfen haben.
Morgen geht die Steuerung zum Kunden und leistet hoffentlich gute Dienste.
lug-dd@mailman.schlittermann.de