Hallo Community,
mein Frage bezieht sich auf Best Practice bezüglich der Rückgabe von
Error Codes aus Funktionen in der Programmiersprache C (Embedded).
1. Möglichkeit
Man definiert in einem Header einen enum mit möglichen Error Codes, wie
z.B.
mytype.h
1
typedefenum
2
{
3
ERROR_CODE_FAILURE,
4
ERROR_CODE_SUCCESS,
5
ERROR_CODE_NULL_POINTER,
6
ERROR_CODE_INDEX_OUT_OF_RANGE,
7
ERROR_CODE_INVALID_PARAMETER,
8
...
9
}
10
error_codes_t;
Diese Header-Datei wird nun in den zu entwickelnden Modulen inkludiert
und die entsprechenden Funktionen geben als Return-Value eben einen Wert
von error_codes_t zurück.
Auf diese Art und Weise wird es auch in der STM32 SPL (Standard
Periphery Library) gemacht. Dort wird in stm32f10x.h ein typedef enum
ErrorStatus definiert und dieser in sämtlichen Modulen der SPL
benutzt.
2. Möglichkeit
Jedes Modul definiert in seinem Header einen eigenen Typedef mit Error
Codes, wie z.B.
Modul_A.h
1
typedefenum
2
{
3
MODUL_A_FAILURE,
4
MODUL_A_SUCCESS,
5
...
6
}
7
module_a_error_codes_t;
Modul_B.h
1
typedefenum
2
{
3
MODUL_B_FAILURE,
4
MODUL_B_SUCCESS,
5
...
6
}
7
module_b_error_codes_t;
Wie handhabt ihr Error Codes in C (Embedded) Projekten, bzw. welche der
genannten zwei Möglichkeiten bevorzugt ihr.
Oder vielleicht habt ihr noch eine nicht genannte Möglichkeit?!
Vielen Dank schon einmal.
Gruß Leo
Ich habe es bis jetzt hauptsächlich als eine Mischung von beiden
gesehen. Und mach das auch selbst ausschließlich so.
Es gibt halt Modulspezifische Rückgabewerte und auch globale.
Habe früher beides gemacht, bin mittlerweile jedoch vollständig bei
Möglichkeit 1. angelangt und definiere Fehlercodes nur noch global.
Das hat zwar den Nachteil, dass man sämtliche Fehlercodes zentral und
sauber hinterlegen muss. Jedoch den großen Vorteil, dass alle
Fehlercodes in einer großen Übersicht verfügbar sind und
Doppelbelegungen weitestgehend vermieden werden. So vermeide ich
weitestgehend, dass ich bei der Fehleranalyse später feststelle, dass in
zwei Modulen gleiche Fehlercodes verwendet wurden und die Aussagekraft
bei Null liegt.
Hallo,
vielen Dank für die Antworten!
Ich habe noch ein Frage zu diesem Thema.
Wie handhabt ihr folgenden Fall:
Es existieren eine Reihe von Error Codes in einem gemeinsamen Enum...
1
typedefenum
2
{
3
ERROR_CODE_FAILURE,
4
ERROR_CODE_SUCCESS,
5
ERROR_CODE_NULL_POINTER,
6
ERROR_CODE_INDEX_OUT_OF_RANGE,
7
ERROR_CODE_INVALID_PARAMETER,
8
...
9
}
10
error_codes_t;
Dazu existieren zwei Funktionen, welche eben diesn Enum als Rückgabe
besitzen.
/* Function_A liefert aus dem Enum jedoch nur folgende Werte:
ERROR_CODE_FAILURE,
ERROR_CODE_SUCCESS,
*/
1
error_codes_tFunction_A(void)
2
{
3
...
4
}
/* Function_B liefert aus dem Enum jedoch nur folgende Werte:
ERROR_CODE_FAILURE,
ERROR_CODE_SUCCESS,
ERROR_CODE_INVALID_PARAMETER,
*/
1
error_codes_tFunction_B(uin8_tnew_value)
2
{
3
...
4
}
Sollte eine Funktion welche diesen Enum als Rückgabe benutzt, immer alle
Enum Werte auch bedienen oder ist es in Ordnung, wenn die Funktion nur
z.b nur 2 von 5 Werten aus dem Enum benutzt?
Danke schon einmal im Voraus!
Gruß Leo
Hallo,
vielen Dank für die Antwort.
Jetzt gleich noch eine Fragen zu Error Codes.
Was ist eurer Meinung nach Best Practice für die Wertigkeit der Error
Codes?
Sollte ERROR_CODE_FAILURE = 0 sein
oder
sollte ERROR_CODE_SUCCESS = 0 sein?
Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS =
!ERROR.
Wenn man jedoch folgenden Enum verwendet...
1
typedefenum
2
{
3
ERROR_CODE_SUCCESS=0,
4
ERROR_CODE_FAILURE,
5
ERROR_CODE_NULL_POINTER,
6
ERROR_CODE_INDEX_OUT_OF_RANGE,
7
ERROR_CODE_INVALID_PARAMETER,
8
...
9
}
10
error_codes_t;
... kann man innerhalb einer Funktion auftretende Error zählen
Leo schrieb:> ERROR_CODE_FAILURE
Und so ein Bandwurm-Wort ist für dich aussagekräftig genug?
Mir wäre das zu albern: 18 Zeichen und es steht nur drin "Fehler" -
jedoch nix konkreteres.
Im Grunde kannst du das ganze Wort knicken. Wenn in deinem Almanach was
Aussagekräftigeres stände, wäre es besser. Also sowas wie errTimeout,
errOutOfRange, errParity, errWrongID, errStuckLow und so weiter.
W.S.
Hallo Leo
Leo schrieb:> Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS => !ERROR.
In der Regel wird für Failure 0 verwendet (false), !false ist true also
Success. Einem Standard gibt es da aber nicht. Ich würde im Enum Success
als 1 wählen.
Aber deine Intention verstehe ich, weil ja danach wieder Fehlercodes
kommen.
Grüsse,
René
René H. schrieb
> In der Regel wird für Failure 0 verwendet (false), !false ist true also> Success.
Gerade andersrum, Fehler=0 heisst kein Fehler, und dann gibt es
verschiedene Fehler-Arten (1,2,3...). In deinem Fall muesste es einen
Fehler und mehrere Erfolgscodes geben, damit es sinnig ist.
Moin,
Vielleicht sollte man da auch nicht zuviel Wissenschaft draus machen.
Letztlich ist's wurscht, was schiefgelaufen ist, wenn was schiefgelaufen
ist (Ausser vielleicht fuer Debugzwecke).
Aber ob in der Waschmaschine kurz vorm Schleudern ein NULL-Pointer
auftritt oder irgendwas auf -EINVAL steht, wird Klementine wenig
kratzen...
Gruss
WK
Leo schrieb:> Sollte eine Funktion welche diesen Enum als Rückgabe benutzt, immer alle> Enum Werte auch bedienen oder ist es in Ordnung, wenn die Funktion nur> z.b nur 2 von 5 Werten aus dem Enum benutzt?
Wenn dein Enum alle Fehlermöglichkeiten enthält, die es im ganzen
Programm geben kann, ist es wenig sinnvoll, die bei jedem
Funktionsaufruf allesamt abzuprüfen. Und was sollte denn auch die
Reaktion auf einen Fehlercode sein, der gar nicht auftreten kann?
Es muss aber auf jeden Fall dokumentiert sein, welche Fehlercodes die
Funktion zurückliefern kann und für jeden Fehlercode die exakte
Situation, in der er im Kontext dieser Funktion auftritt.
Leo schrieb:> ... kann man innerhalb einer Funktion auftretende Error zählen
Braucht man das denn? In der Regel bricht doch eine Funktion beim ersten
Fehler ab, weil sie danach eh nichts sinnvolles mehr machen kann. Und
selbst wenn sie weitermachen kann: Was hab ich davon, zu wissen, dass
jetzt genau 3 Fehler aufgetreten sind, wenn ich nicht weiß, welche?
Leo schrieb:> Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS => !ERROR.
Diese Definition über die Negierung finde ich ziemlich blöd. Meisten
wird sie von Leuten verwendet, die ein paar grundlegende Dinge nicht
verstanden haben. In der Regel steht da die falsche Annahme dahinter,
dass !0 nicht das selbe sei wie 1.
Dergute W. schrieb:> Vielleicht sollte man da auch nicht zuviel Wissenschaft draus machen.> Letztlich ist's wurscht, was schiefgelaufen ist, wenn was schiefgelaufen> ist (Ausser vielleicht fuer Debugzwecke).
Das heißt, dass du grundsätzlich nur auf "Fehler" oder "nicht Fehler"
prüfst und es dir immer egal ist, was für ein Fehler aufgetreten ist?
> Aber ob in der Waschmaschine kurz vorm Schleudern ein NULL-Pointer> auftritt oder irgendwas auf -EINVAL steht, wird Klementine wenig> kratzen...
Der Klementine nicht, aber die Waschmaschine will vielleicht
unterschiedlich darauf reagieren, damit die Trommel beim schleudern der
Klementine nicht um die Ohren fliegt.
Achim S. schrieb:> Entweder bool: (success =1, Fehler 0)
Dann aber eher true/false.
> Oder Codes, dann success = 0.>> Niemals success=1 und 2 ist ein Fehler.
Würde ich auch so machen, wobei es an sich keine Rolle spielt, wenn man
den Fehlercodes eh Namen gibt.
Leo schrieb:> typedef enum> {> ERROR_CODE_SUCCESS = 0,> ERROR_CODE_FAILURE,> ERROR_CODE_NULL_POINTER,> ERROR_CODE_INDEX_OUT_OF_RANGE,> ERROR_CODE_INVALID_PARAMETER,> ...> }> error_codes_t;
Noch eine kleine Anmerkung die allerdings eher was mit der Formatierung
zu tun hat: ich spendiere dem enum meist noch einen Namen also keine
anonyme Deklaration:
1
typedefenum_error_code
2
{
3
...
Dazu noch den Singular anstelle des Plurals und keine ungarische
Notation:
1
...
2
}error_code;
Aber das sind Details die auch eher vom Geschmack abhängen.
J. F. schrieb:> Leo schrieb:>> typedef enum>> {>> ERROR_CODE_SUCCESS = 0,>> ERROR_CODE_FAILURE,>> ERROR_CODE_NULL_POINTER,>> ERROR_CODE_INDEX_OUT_OF_RANGE,>> ERROR_CODE_INVALID_PARAMETER,>> ...>> }>> error_codes_t;>> Noch eine kleine Anmerkung die allerdings eher was mit der Formatierung> zu tun hat: ich spendiere dem enum meist noch einen Namen also keine> anonyme Deklaration:>>
1
>typedefenum_error_code
2
>{
3
>...
4
>
Ja, aber ... Ein führender "_" bedeutet du bist im reservierten
Namensraum von Bezeichnern. Gerade bei so generischen Bezeichnungen wie
"_error_code" würde ich das vermeiden. Wer weiß was irgendwo intern
gemacht wird.
Auch wenn dir bis heute nichts passiert ist:
1000 Mal berührt, 1000 Mal ist nichts passiert, 1000 und eine Nacht
und es hat Zoom gemacht.
Daher lieber zur Risikolimitierung und weil es kein besonderer Aufwand
ist folgendes:
1
typedefenumerror_code_
> Dazu noch den Singular anstelle des Plurals
Ja.
> und keine ungarische> Notation:
Ja, aber "_t" sollte man aus anderen Gründen weglassen:
Die gute Nachricht zuerst, ein abschließendes "_t" ist keine ungarische
Notation. Der Scheißdreck, der sich ungarische Notation nennt, sieht
anders aus. Man könnte das "_t" durchgehen lassen, aber das
abschließende "_t" ist reserviert!
> Aber das sind Details die auch eher vom Geschmack abhängen.
Nicht alles ist Geschmack. Das sind zum Teil Details, die Compiler- und
Bibliotheks-Interna betreffen.
Der GCC hat eine Liste:
https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html
Hannes J. schrieb:> Ja, aber ... Ein führender "_" bedeutet du bist im reservierten> Namensraum von Bezeichnern. Gerade bei so generischen Bezeichnungen wie> "_error_code" würde ich das vermeiden. Wer weiß was irgendwo intern> gemacht wird.
Ein guter Punkt. Wie du siehst hat es bei mir noch zu Konflikten
geführt, danke für den Hinweis.
> Der GCC hat eine Liste:> https://www.gnu.org/software/libc/manual/html_node...
Auch danke dafür. Ein paar Sachen sieht man immer mal wieder, allerdings
nicht zusammengefasst.
Leo schrieb:> Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS => !ERROR.
Und es sollte (meiner Meinung nach) gerade umgekehrt sein, also
SUCCESS = 0 und ERROR = !SUCCESS.
Warum ?
Weil jeder Fehler normalerweise einen bestimmten bit setzt oder einen
bestimmten Wert zurückliefert - keine bits gesetzt, kein Fehler.
Mit enum kann man aber meistens nur einen Fehler zurückgeben, im
Gegensatz dazu sind bei bits mehrere Fehlercodes möglich.
Deswegen sollte man sich auch überlegen, ob enum mit bestimmten Werten
anstatt mit bitpositionen immer die bessere Wahl ist.
Sicher, bei 90% ist das der Fall, aber es gibt auch Situationen wo
man nur bestimmte Parameter prüft, markiert diese als ERR oder OK und
erst am Ende zurückkehrt und nicht sofort nach dem ersten Fehler
abbricht.
Es hat sich eingebürgert, daß 0 für Success steht. Z.B. das main gibt
bei fehlerfreier Ausführung den Exitcode 0 zurück an das OS.
Es gibt aber auch Ausnahmen. Z.B. getchar gibt den Zeichencode (0..255)
zurück bei Erfolg und einen negativen Wert bei Fehler (EOF = -1).
WIMRE war das irgendeine Uneinigkeit zwischen BSD und anderen Unixen,
die einen machten ERROR = 0, die anderen SUCCESS = 0.
Es gibt für beide Varianten gute Gründe. Wichtig ist eigentlich nur,
nach der Entscheidung für eine das konsequent durchzuziehen. Am besten
mit Zugriff nur über Makros (z.B. SUCCEEDED), dann kannst du die
Implementierung abstrahieren.
Hallo,
vielen Dank für die vielen Antworten und Anregungen.
[c]
typedef enum
{
ERROR_CODE_SUCCESS = 0,
ERROR_CODE_FAILURE,
ERROR_CODE_INVALID_PARAMETER,
...
}
error_code_td;
Ich tendiere auch dazu SUCCESS = 0 zu definieren und dann aufsteigend
alle anderen Error Codes.
@Rolf Magnus
Ja, wenn man "globale" Error Codes verwendet muss jede Funktion
dokumentieren, welche Error Codes sie liefert. z.B
/* @brief ...
* @param none
* @return ERROR_CODE_FAILURE ...
* @return ERROR_CODE_SUCCESS ...
*/
error_code_td Do_Some_Stuff (void)
{
...
}
Rolf M. schrieb:> Braucht man das denn? In der Regel bricht doch eine Funktion beim ersten> Fehler ab, weil sie danach eh nichts sinnvolles mehr machen kann. Und> selbst wenn sie weitermachen kann: Was hab ich davon, zu wissen, dass> jetzt genau 3 Fehler aufgetreten sind, wenn ich nicht weiß, welche?
Es geht vielmehr weniger um das Zählen von Fehlern, eher der Ermittlung
ob innerhalb einer Funktion ein Fehler aufgetreten ist.
MISRA-C erlaubt z.B. nur ein return-Statement und so kann man am Ende
einer Funktion ermitteln, ob ein Fehler aufgetreten ist.
J. F. schrieb:> Noch eine kleine Anmerkung die allerdings eher was mit der Formatierung> zu tun hat: ich spendiere dem enum meist noch einen Namen also keine> anonyme Deklaration:> typedef enum _error_code> {> ...
Welchen Vorteil hat die Vergabe eines Namen für den Enum, wenn man
diesen eh mit typedef definiert?
https://www.gnu.org/software/libc/manual/html_node/https://www.gnu.org/software/libc/manual/html_node/Error-Reporting.html#Error-Reporting
Das Error-Reporting wie es GCC nutzt, sehe ich für den Gebrauch bei
Embedded Programming ohne OS, RTOS, usw. weniger geeignet.
Ich suche eher nach der Best Practice für Error Handling auf unterster
Ebene (nur in Verbindung mit Libs wie, STM32 SPL / HAL oder ähnliche.
Ich würde gerne ein allgemeines Error Handling "Interface" für alle
meine eigens entwickelten C-Module entwickeln.
Benutzt ihr nun globale Error Codes in euren Projekten / Bibliotheken?
Leo schrieb:> Benutzt ihr nun globale Error Codes in euren Projekten / Bibliotheken?
Nein, allerdings hantiere ich nur mit eher kleinen Programmen und da
reicht ein Ad-Hoc-Ansatz aus. Allerdings kein Industriemaßstab.
Meine Treiber geben meist nur {0 = Success, 1 = Fehler} zurück, genauere
Angaben brauche ich nur selten, weil die Reaktion darauf meist eine
sofortige Endlosschleife oder ein Reset ist.
Bringt ja nichts, tausende Fehlerquellen zu definieren, die am Ende
ohnehin alle die selbe Reaktion hervorrufen.
Hannes J. schrieb:> Die gute Nachricht zuerst, ein abschließendes "_t" ist keine ungarische> Notation. Der Scheißdreck, der sich ungarische Notation nennt, sieht> anders aus.
Der Scheißdreck, der von manchen (vor alle Microsoft) für ungarische
Notation missverstanden wird, meinst du wohl eher. Den Datentyp in den
Namen zu schreiben, ist eigentlich nicht der Sinn der ungarischen
Notation.
Die grundsätzliche Frage ist doch, ob und wer was mit den Error-codes
anfangen soll. Dazu sind auch verschiedene Fehler-*Qualitäten* zu
unterscheiden:
- kritisch / unkritisch
- im Normallfall möglich / nur bei Programmierfehlern möglich
Wenn jeder Rückgabewert abgefragt werden muss, dann ist keine sinnvolle
Applikation mehr möglich. Aus diesem Grunde ist es notwendig, die
allermeisten Funktionen so auszulegen, dass Rückgabewerte nicht
ausgewertet werden müssen, sondern, dass
- dies in einer Art Log protokolliert wird
- ein gefahrloser Weiterbetrieb ermöglicht wird, falls eben möglich.
Wenn ich unkonditional "InitPort(X1, PortModeUART);" schreibe, und X1
kann kein UART sein, dann nützt mir ein Rückgabewert nichts. Ich muss
dafür sorgen, dass es im Error-Log erkannt wird UND dass "SendePort(X1,
"Hallo Welt");" nicht zum unkontrollierten Absturz führt.
Der einfachste "Log" ist dabei ein Fehler-Flag, dass ab dem ersten
Fehler 1 bleibt. Das kann dann auf beliebige Bitfelder, Fehlerspeicher
oder Log-Einträge mit _FILE__ und __LINE_ erweitert werden. Und auf
Callbacks oder geregeltem Shutdown für kritische Fehler.
Wenn ich keinen Benutzer habe (also oft im embedded Bereich) reicht
meist der boolsche Rückgabewert: OK oder !OK. Die "Fehler", die im
Normalbetrieb vorkommen können, (z.B. Sensor defekt) sind eh keine
Fehler, sondern "normale Betriebszustände", die auch normal gehandhabt
werden müssen.
Beispiel:
1
errcodeGetValue(PortX,int*Value);
Das ist meistens Quatsch. Man kommt schnell dazu, dass Value kein Skalar
sein sollte, sondern ein Objekt mit Wert und Status. Und dass Getvalue
dieses Objekt statt Fehlercode zurückliefern sollte, also:
1
tValueGetValue(PortX);
Der Applikationscode wird damit klarer. Und wenn X nicht initialisiert
ist oder out of range oder Kabelbruch hat, dann sollte mindestens ein
"Valid-Bit" in tValue auf 0 stehen. Und die nachfolgenden Routinen (z.B.
zur Anzeige) sollten tValue nativ verarbeiten. Das ist etwas fundamental
anderes wie parallel "Error-Codes" zu berücksichtigen.
Achim S. schrieb:> Wenn ich keinen Benutzer habe (also oft im embedded Bereich) reicht> meist der boolsche Rückgabewert: OK oder !OK.
Das ist einer der wenigen wirklich harten Nachteile von C: Es gibt nur
einen Rückgabewert. In Assembler kann man einfach ein Flag
(üblicherweise Carry) dafür "missbrauchen".
Aber wie gesagt, eine wirkliche Lösung habe ich nicht.
S. R. schrieb:> Das ist einer der wenigen wirklich harten Nachteile von C: Es gibt nur> einen Rückgabewert.
Der aber auch ein struct sein kann, womit dieser Nachteil nicht wirklich
existiert.
Nop schrieb:>> Das ist einer der wenigen wirklich harten Nachteile von C:>> Es gibt nur einen Rückgabewert.>> Der aber auch ein struct sein kann, womit dieser Nachteil nicht wirklich> existiert.
Eine struct zurückgeben ist aufwändiger, als einen einzelnen Wert
zurückzugeben, und wird nicht von jedem Compiler unterstützt (der SDCC
kann es z.B. nicht).
Best Pactice? Million Fliegen können nicht irren, Sch*sse ist gut?
Ganz ehrlich? Modulübergreifende, globale Error-Codes sind Grütze!
Warum? Sie erschweren die Wiederverwendung da sie alle Module über den
Fehlertyp koppeln. Auch ist anhand des Interface nicht ersichtlich
welche Fehler eine Funktion erzeugen kann.
ERROR_SUCCESS ???
Semantischer Nonsense! Sein oder Nicht-Sein? Fehler oder Erfolg?
ERROR_CODE_NULL_POINTER,
ERROR_CODE_INVALID_PARAMETER,
Wann wird der Null-Pointer zum ungültigen Parameter?
Noch mehr Semantik: Wer will eine Funktion aufrufen, die immer einen
Fehlercode zurück gibt?
Eine Funktion/Methode/Prozedur etc. sollte dem Aufrufer besser sagen ob
sie erfolgreich war oder nicht! Das nennt sich "Status". Dieser Status
kennt nur zwei Werte: "Alles gut", oder "Hat nicht geklappt". (Das passt
übrigens sehr gut in ein 'bool'...)
Erst wenn etwas nicht geklappt hat, gibt es (möglicherweise) eine
genauere Beschreibung für das Problem! Die semantische Hierarchie sieht
so aus:
--+-- Alles Ok
|
+-- Fehler --+-- Kein Speicher frei
|
+-- Datei nicht gefunden
|
+-- [...]
Selbstverständlich hat jede Funktion/Modul seinen eigenen Fehlertyp!
S. R. schrieb:> Eine struct zurückgeben ist aufwändiger, als einen einzelnen Wert> zurückzugeben
Das stimmt. Für den Fall kann man aber auch das return-struct by
reference übergeben und ausfüllen. Das ist zwar syntaktisch kein
return-Wert, ist aber ein semantisch übliches C-Idiom. Erkennt man auch
daran, daß im Prototypen der Funktion kein const steht (sofern der
Programmierer da halbwegs sauber gearbeitet hat).
Das ist eigentlich die Art, wie ich gerne komplexere Sachen zurückgebe,
zumal man so einen Zeiger auch ohne großen Kopieroverhead über mehrere
Funktionsebenen durchreichen kann.
> und wird nicht von jedem Compiler unterstützt (der SDCC kann es z.B. nicht).
Das ist ja nun kein Nachteil von C, sondern vom Compiler.
Nop schrieb:> Für den Fall kann man aber auch das return-struct by> reference übergeben und ausfüllen.
Dann kann man auch gleich den Fehlercode zurückgeben und
by-reference-Parameter nutzen. Macht (in meinen Augen) den Code besser
lesbar.
Nop schrieb:>> und wird nicht von jedem Compiler unterstützt>> (der SDCC kann es z.B. nicht).> Das ist ja nun kein Nachteil von C, sondern vom Compiler.
Eher der Methode, denn sie funktioniert nicht mit jedem Compiler (und
offiziell erst seit 28 Jahren). ;-)
Leo schrieb:> Welchen Vorteil hat die Vergabe eines Namen für den Enum, wenn man> diesen eh mit typedef definiert?
Durch dieses Vorgehen kann man bspw. unter Eclipse im "Outline" Fenster
die verwendeten Enumerationen samt Inhalten sehen, die sonst alle gleich
mit (anonymous) geführt würden.
Ich persönlich bin eigentlich schon ein Freund globaler Error-Codes.
Meist nutz ich auch einfach das, was C/C++ ohnehin in der Umgebung
definiert. Grad was klassische Peripherie angeht teilt sich ein Großteil
sowieso die gängisten Möglichkeiten.
Ein "Protocol Error" macht unter SPI und I2C Sinn, ein "Timeout" kanns
für so gut wie alles geben, etc.
Mehr als den Funktionsaufruf zu wiederholen oder schlichtweg nur den
Fehler irgendwohin zu leiten ist bei vielen embedded Systemen eh nicht
drin.
Tuts ein klassischer "std::errc" nicht, dann neige ich eher dazu die
Unix-typische Herangehensweise zu nutzen. Sprich Integer Rückgabewert
und alles was nicht Null ist (und meist <0) tut weh.
/edit
Als C++ Freund ist meine tatsächliche Implementierung natürlich ein
optional<errc>.