Rückgabewerte von va_list auf einem Arduino Mega2560

OP #7611971
Lesenswert?

Hallo Community,

ich arbeite an einem C-Projekt, bei dem ich die variable Argumentliste (va_list) in einem Arduino Mega2560 verwenden muss. In meinem Code verwende ich die folgende Konstruktion:

1
#include <stdarg.h>
2

3
#define jprint(...) jprint_impl(__VA_ARGS__, NULL)
4
void jprint_impl(const char* first_arg, ...) {
5
    va_list args;
6
    va_list num_args;
7
    va_start(args, first_arg);
8

9
    const char* arg=first_arg; 
10
    // Weitere Verarbeitung der Argumentenliste
11
    do
12
      {
13

14
      // >>>>>>>>> was steht in arg drin, das ist die Frage
15

16
      // Nächstes Argument aus der Argumentenliste holen
17
      arg = va_arg(args, const char*);
18
      }
19
    while (arg != NULL);   
20
  
21
    va_end(args); // Argumentenliste aufräumen
22
    }

Ich würde gerne wissen, welche Werte die va_list-Variablen args und num_args auf diesem spezifischen System zurückgeben.

Ich verwende das so:

1
  void Check_MOD (uint16_t ZeilenNr, uint8_t MOD_ix) 
2
    {
3
    jprint("MOD RangeErr in ",ZeilenNr,": ",MOD_ix," ",4711);
4
    }

und jprint fasst alle Argumente zusammen.

Kann mir jemand helfen, diese Rückgabewerte richtig zu verstehen für Argumente vom Typ char*, uint16_t und uint8_t und zu erklären, wie sie auf einem Arduino Mega2560 interpretiert werden können? Ich möchte dem Argument keine Info mitgeben, welchen Typ er hat.

Im Kern geht es um die Frage: Was steht in arg?

Vielen Dank für Eure Hilfe!

Moderator Persönliche Seite #7611988
Lesenswert?

Reinhard schrieb:

1
  arg = va_arg(args, const char*);

Falsch!

Ich möchte dem Argument keine Info mitgeben, welchen Typ er hat.

Das funktioniert nicht. va_arg() funktioniert nur richtig, wenn Du den Typ des Arguments korrekt mitteilst. printf() und Konsorten machen das auch, nämlich durch den Format-String.

Im Kern geht es um die Frage: Was steht in arg?

So, wie Du das aufrufst: Müll. Spätestens bei der Verarbeitung des Arguments 4711, was kein C-String ist.

Gib mal bei Google "man va_arg" ein und lies erstmal das Manual.

#7611991
Lesenswert?

Reinhard schrieb:

Was steht in arg?

ein paar Bits. Ob das als uint16_t oder Pointer übergeben wurde, kann man innerhalb der Funktion nicht mehr feststellen. Insbesondere kann man NULL (das Ende der Liste) nicht von MOD_ix=0 unterscheiden. Eine Notlösung wäre, immer abwechselnd char * und int zu übergeben. Dann wäre der Typ aller Parameter fest und NULL am Ende wieder eindeutig. Trotzdem könnte die Liste beliebig lang sein.

Nachdem die Ähnlichkeit mit printf() nicht zu übersehen ist: was ist der Vorteil von dieser Konstruktion?

Moderator Persönliche Seite #7611994
Lesenswert?

Reinhard schrieb:

ich arbeite an einem C-Projekt, bei dem ich die variable Argumentliste (va_list) in einem Arduino Mega2560 verwenden muss.

Arduino-Code ist C++-Code, nicht C.

jprint("MOD RangeErr in ",ZeilenNr,": ",MOD_ix," ",4711);

Warum schreibst Du nicht einfach C++:

1
  String str = (String) "MOD RangeErr in " + ZeilenNr + ": " + MOD_ix + " " + 4711;
2
  Serial.write(str); // oder irgendeine andere Ausgabefunktion

Du kannst auch auf die Variable str verzichten und das Ganze rechts vom Gleichheitszeichen direkt Deiner Ausgabefunktion übergeben - vorausgesetzt, sie kann mit einem C++-String als Argument umgehen. Notfalls, wenn Du wirklich noch mit "char *" arbeitest, musst Du den C++-String in einen C-String zurückwandeln.

OP #7612013
Lesenswert?

Bauform B. schrieb:

Eine Notlösung wäre, immer abwechselnd char * und int zu übergeben. Dann wäre der Typ aller Parameter fest und NULL am Ende wieder eindeutig. Trotzdem könnte die Liste beliebig lang sein.

Das ist vielleicht eine Idee.

Nachdem die Ähnlichkeit mit printf() nicht zu übersehen ist: was ist der Vorteil von dieser Konstruktion?

printf() hat einen ziemlich großen Overhead, und das ist auf einem Arduino nicht so toll. Ich programmiere auch auf dem Uno und Nano. Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen, ohne ihn auszugeben? Also in ein char output[100] ablegen?

#7612015
Lesenswert?

Frank M. schrieb:

Reinhard schrieb:

ich arbeite an einem C-Projekt, bei dem ich die variable Argumentliste (va_list) in einem Arduino Mega2560 verwenden muss.

Arduino-Code ist C++-Code, nicht C

Ein Arduino Mega2560 ist erst mal nur ein Stück Hardware, auf dem man auch C-Programme laufen lassen kann.

Reinhard schrieb:

Ich möchte dem Argument keine Info mitgeben, welchen Typ er hat.

Wie schon gesagt wurde, geht das mit varargs nicht. Du musst in deiner Funktion beim Aufruf von va_arg() angeben, welchen Typ das Argument hat. Es liegt also an dir, den Typ zu kennen und korrekt anzugeben. Wie du an den Typ kommst, ist dir überlassen.

In C++ könnte man sowas mit einem variadischen Template machen.

Reinhard schrieb:

Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen, ohne ihn auszugeben? Also in ein char output[100] ablegen?

Dazu gibt es sprintf, bzw. besser snprintf. https://en.cppreference.com/w/c/io/fprintf

Moderator Persönliche Seite #7612021
Lesenswert?

Reinhard schrieb:

printf() hat einen ziemlich großen Overhead

Den hat es vor allem deshalb, weil es eben "allen Furz und Feuerstein" unterstützen muss und erst zur Laufzeit weiß, was davon wirklich gebraucht wird.

Daher gibt es in der avr-libc auch noch Varianten ohne Gleitkomma-Support sowie völlig minimalistische, bei denen noch einige andere Features weggelassen wurden.

Gerade auf dem ATmega2560 würde ich mich aber an deiner Stelle nicht verrenken und erst einmal so lange printf (oder sprintf) benutzen bis klar ist, dass wirklich die Säge klemmt und auch diese zu viel sind. Davor lohnt sich der Bau eigener Ersatzfunktionen eigentlich eher nicht, zumal du ja immer das Risiko eingehst, statt relativ gut debuggter Software eigene neue Fehler einzubauen.

#7612147
Lesenswert?

Bauform B. schrieb:

Rolf M. schrieb:

Reinhard schrieb:

Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen, ohne ihn auszugeben? Also in ein char output[100] ablegen?

Dazu gibt es sprintf, bzw. besser snprintf. https://en.cppreference.com/w/c/io/fprintf

Für das Beispiel aus dem ersten Beitrag würde auch etwas wie itoa() ausreichen.

Würde es? Soweit ich verstehe, zeigt es eine Ausgabefunktion im Stile von printf (nur ohne Formatstring), die entsprechend auch mit mehreren Argumenten von unterschiedlichem Typ zurecht kommen und das dann zu einem Gesamtstring zusammenbauen soll.

Persönliche Seite #7612176
Lesenswert?

In C++ wäre es ziemlich einfach mit variadischen templates. Hier gezeigt mit Unterstützung für signed Integers, unsigned Integers, char, char*, String Literale. Man kann beliebig eigene "convert" Funktionen hinzufügen für weitere Typen (z.B. float oder auch Klassen). Es wird automatisch auf Buffer Overflows geprüft. Die Ausgabe erfolgt testweise auf stdout, kann natürlich beliebig geändert werden. Es ist potentiell auch effizienter als sprintf, weil kein Parsen eines Format-Strings und Laufzeitunterscheidung nötig ist sowie keine komplizierte Stack-Akrobatik (va_arg).

Das Atmel Studio kann auch C++, du musst vermutlich nur die Datei mit der Endung .cpp speichern.

Angehängte Dateien:
#7612181
Lesenswert?

Rolf M. schrieb:

Bauform B. schrieb:

Rolf M. schrieb:

Reinhard schrieb:

Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen, ohne ihn auszugeben? Also in ein char output[100] ablegen?

Dazu gibt es sprintf, bzw. besser snprintf. https://en.cppreference.com/w/c/io/fprintf

Für das Beispiel aus dem ersten Beitrag würde auch etwas wie itoa() ausreichen.

Würde es? Soweit ich verstehe, zeigt es eine Ausgabefunktion im Stile von printf (nur ohne Formatstring), die entsprechend auch mit mehreren Argumenten von unterschiedlichem Typ zurecht kommen und das dann zu einem Gesamtstring zusammenbauen soll.

Naja, wenn va_list für die ursprüngliche Idee funktionieren würde, brauchte man doch kaum mehr als itoa() und strncat(). Aber snprintf() ist wirklich vernünftiger, besonders, wenn es abgespeckte Varianten gibt.

Wenn die immer noch zu groß sind, würde ich eher einen eigenen snprintf()-Ersatz bauen, kleiner, aber voll kompatibel. Schon alleine, weil der gcc dann nette Warnungen ausgeben kann

1
jprint(char *fmt, ...) __attribute__((format (printf, 1, 2)));
Persönliche Seite #7612195
Lesenswert?

Jörg W. schrieb:

Aber ob das Ergebnis dann wirklich ressourcenschonender wird als snprintf()?

Schon möglich. Hängt davon ab wie gut der Compiler es optimiert bekommt (inlining & Co). Eventuell wird bei zahlreichen Aufrufen der Code länger, aber die Ausführungszeit dürfte kürzer sein. Man könnte alternativ auch den Format-String per templates automatisch generieren und dann snprintf aufrufen ;-)

An den Integer-Convert-Funktionen kann man bestimmt auch noch was optimieren, hab da einfach die erstbeste Implementierung hin geschrieben. Aber das ist ein gelöstes Problem

OP #7612241
Lesenswert?

Jetzt gibt es Zahlen über die Speicherbelegung.

ohne snprintf: Program Memory Usage : 33284 bytes Data Memory Usage : 6690 bytes
mit snprintf: Program Memory Usage : 34796 bytes
Data Memory Usage : 6714 bytes

Ich habe das Makro P() dazugenommen. P() ist ein Makro, das den String im PROGMEM platziert und einen const char* zurückgibt. Ich verwende es so:

1
  snprintf(jou,sizeof(jou),P("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);

Dadurch reduziert sich der Verbrauch des RAM, und das ist ja der Knackpunkt beim Arduino.

mit P(): Program Memory Usage : 34810 bytes
Data Memory Usage : 6690 bytes

Der String in jou ist "MOD RangeErr in 1945: 1", so wie ich es möchte.

Moderator Persönliche Seite #7612667
Lesenswert?

Reinhard schrieb:

Jörg W. schrieb:

Dann musst du aber auch snprintf_P() benutzen. Das ist die Variante der Funktion, die den Format-String im Progmem erwartet.

Habe ich getestet: verbraucht mehr RAM als meine Version.

Dann solltest du, statt hier irgendwie herum zu stochern, das genau analysieren.

Format-String im Flash braucht die _P-Funktionen, sonst kommt Käse raus. Die normale s[n]printf-Funktion hat schlicht keine Idee, dass der ihr übergebene Zeiger in den Flash zeigen soll, sodass sie an der gleichen Adresse im RAM stattdessen liest.

Intern geht das eh alles auf das gleiche Backend (vfprintf), das wird nur mit unterschiedlichen Flags aufgerufen je nach dem benutzten Frontend. Kannst du gut erkennen, wenn du dir den Sourcecode ansiehst:

1
% ls -sk
2
total 367
3
 5 Files.am        5 fscanf.c       5 sprintf_p.c
4
45 Makefile        5 fscanf_p.c     5 sscanf.c
5
 5 Makefile.am     5 fwrite.c       5 sscanf_p.c
6
49 Makefile.in     5 getc.S         5 stdio_private.h
7
 5 Rules.am        5 getchar.c      9 ultoa_invert.S
8
 5 clearerr.c      5 gets.c         5 ungetc.c
9
 5 fclose.c        5 iob.c         21 vfprintf.c
10
 9 fdevopen.c      5 printf.c       5 vfprintf_p.c
11
 5 feof.c          5 printf_p.c    25 vfscanf.c
12
 5 ferror.c        5 putc.S         5 vfscanf_p.c
13
 5 fgetc.c         5 putchar.c      5 vprintf.c
14
 5 fgets.c         5 puts.c         5 vscanf.c
15
 5 fprintf.c       5 puts_p.c       5 vsnprintf.c
16
 5 fprintf_p.c     5 scanf.c        5 vsnprintf_p.c
17
 5 fputc.c         5 scanf_p.c      5 vsprintf.c
18
 5 fputs.c         5 snprintf.c     5 vsprintf_p.c
19
 5 fputs_p.c       5 snprintf_p.c   5 xtoa_fast.h
20
 5 fread.c         5 sprintf.c

Gut zu erkennen, dass der komplette Code für stdio in den beiden Implementierungen für vpfprintf.c und vfscanf.c steckt. Alles andere sind Wrapper darum.

OP #7612669
Lesenswert?

Jörg W. schrieb:

Reinhard schrieb:

Jörg W. schrieb:

Dann musst du aber auch snprintf_P() benutzen. Das ist die Variante der Funktion, die den Format-String im Progmem erwartet.

Habe ich getestet: verbraucht mehr RAM als meine Version.

Dann solltest du, statt hier irgendwie herum zu stochern, das genau analysieren.

Meine Güte, warum gleich so aggressiv? Arbeite mal dran.

Ich habe für mich die Lösung, die am wenigsten den RAM belastet. Warum soll ich da weitere Zeit reinstecken?

Moderator Persönliche Seite #7612677
Lesenswert?

Reinhard schrieb:

Dann solltest du, statt hier irgendwie herum zu stochern, das genau analysieren.

Meine Güte, warum gleich so aggressiv? Arbeite mal dran.

Das war nicht aggressiv gemeint. Kommunikation über Computer ist halt zuweilen missverständlich.

Ich habe für mich die Lösung, die am wenigsten den RAM belastet. Warum soll ich da weitere Zeit reinstecken?

Weil sie sehr wahrscheinlich Amok laufen wird (und damit keine "Lösung" ist). Den Grund habe ich dir versucht zu erklären.

Moderator Persönliche Seite #7612684
Lesenswert?

Reinhard schrieb:

Spekulation, oder hast du es selbst getestet?

Ich kann nichts testen, was ich nicht habe.

Ich habe nur versucht nachzuvollziehen, was du hier erzählt hast, und ich weiß, wie der stdio-Code gestrickt ist, da ich insbesondere diesen Teil mit den vfprintf/vfscanf-Backends und den diversen Wrappern selbst entworfen habe (auch wenn Dmitry Xmelkov dann in den Backends vieles optimiert hat).

Bei mir funzt es bestens

Dann machst du was anderes als was ich deinen Ausführungen entnommen habe.

OP #7612707
Lesenswert?

Ich mache Folgendes:

1
// aus einem String im PROGMEM einen char* machen
2
#define P(str) ({ \
3
  char buffer[strlen_P(str)]; \
4
  strcpy_P(buffer, PSTR(str)); \
5
  buffer; \
6
})
7

8
void RangeCheck_MOD (uint16_t ZeilenNr, uint8_t MOD_ix) {
9
  char jou[30];
10
  snprintf(jou,sizeof(jou),P("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);
11
  println(jou); 
12
  }

Verwende ich stattdessen

1
  snprintf_P(jou,sizeof(jou),"MOD RangeErr in %d: %d",ZeilenNr,MOD_ix);

wird mehr RAM verbraucht. Logisch.

Mache ich

1
  snprintf_P(jou,sizeof(jou),PSTR("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);

dann ist die RAM-Nutzung auch wie bei meiner Lösung. Dann bleibt es Geschmacksache oder eine Stilfrage.

Moderator Persönliche Seite #7612729
Lesenswert?

Reinhard schrieb:

#define P(str) ({ \

char buffer[strlen_P(str)]; \

strcpy_P(buffer, PSTR(str)); \

OK, das erklärt es.

Das braucht aber trotzdem mehr RAM als die Lösung mit sprintf_P() – nur auf dem Stack, dadurch fällt dir das nicht direkt auf. Es gibt aber eigentlich keinen Grund, das erst in den RAM umzukopieren, da eben vfprintf() in der Lage ist, den Format-String auch gleich direkt aus dem Flash zu lesen.

#7613139
Lesenswert?

Diese Gehampel sehe ich sehr häufig:

1
    char jou[30];
2
    snprintf(jou,sizeof(jou), ...);
3
    println(jou);

Warum??? Es ist umständlich, gefährlich, langsam, braucht zusätzlichen (Stack-/)Speicher. Einen stdio-Stream zu erstellen ist so trivial (Beispiel-wrapper für uart_putc/getc):

1
static int put(char c, FILE *str)
2
{
3
    uart_putc(c);
4
    return 0;
5
}
6
static int get(FILE *str)
7
{
8
    return uart_getc();
9
}
10
FILE uart[1] = { FDEV_SETUP_STREAM(put, get, _FDEV_SETUP_RW) };
11
// Fertig.
12

13
// Usage:
14
   ...
15
   fprintf(uart, "%s\n", str);
16
   fputs("Hello World!\n", uart);
17
   c = fgetc(uart);
18
   ...
19
   // oder gleich stdin/out/err umleiten:
20
   stdin = stdout = stderr = uart;
21
   ...
22
   printf("%s\n", str);
23
   puts("Hello World!");
24
   c = getchar();
#7613205
Lesenswert?

Btw, so wie ich das sehe, ist dieses Makro

1
#define P(str) ({ \
2
  char buffer[strlen_P(str)]; \
3
  strcpy_P(buffer, PSTR(str)); \
4
  buffer; \
5
})

fehlerhaft - es liefert eine Adresse, die beim Erreichen der "}" ungültig wird. Dazu kommt noch, dass str mehrmals ausgewertet wird. Und lokale var-arrays ohne Längenbeschränkung sind (insb auf Systemen ohne dynamischen Stack, wie Mikrocontrollern) eh Buggy.

PS: ne +1 fehlt auch noch.

PPS: Zu meinen, allein durch Benutzung von "n"-Funktionen (snprintf, strncpy, etc), "sicher" zu programmieren, ist ein Irrtum - man verschiebt nur den Angriffsvektor: von Buffer overflow nach String truncation.

Moderator Persönliche Seite #7613282
Lesenswert?

Wie ich weiter oben schrieb: eigene Versuche produzieren meist eine höhere Bug-Wahrscheinlichkeit, als wenn man gleich die "gut abgehangene" fertige Implementierung benutzt. Allein der zusätzliche RAM-Bedarf für das Umkopieren des Strings frisst ja den durch den im Flash platzierten Format-String angestrebten Vorteil zur Hälfte schon wieder auf.

#7613286
Lesenswert?

Ich finde ja das Konzept der generic Pointer beim Keil C51 genial. Der Programmierer muß keinerlei Rücksicht darauf nehmen, in welchem Segment eine Variable angelegt wurde. Ein zusätzliches Byte des Pointers merkt sich den Segmenttyp und es wird automatisch zu dem richtigen Befehl compiliert. Läßt sich das zur Compilezeit noch nicht auflösen, wird eben eine Unterfunktion aufgerufen, die dann das Typ-Byte prüft und entsprechend den Zugriff ausführt. Ohne irgendwelche Sonderlocken schreiben zu müssen, wird der Formatstring des printf automatisch im Flash abgelegt.

Hier mal ein Beispiel:

1
320          static void RcmdError(char* err_str)
2
 321          {
3
 322   1        sprintf(reply_buf, "SY%s\r\n", err_str);
4
 323   1        RemotePutString(reply_buf);
5
 324   1      }

Listing:

1
             ; FUNCTION _RcmdError (BEGIN)
2
0000 8B00    R     MOV     err_str,R3
3
0002 8A00    R     MOV     err_str+01H,R2
4
0004 8900    R     MOV     err_str+02H,R1
5
                                           ; SOURCE LINE # 320
6
                                           ; SOURCE LINE # 321
7
                                           ; SOURCE LINE # 322
8
0006 7500FF  E     MOV     ?_sprintf?BYTE+03H,#0FFH
9
0009 750000  R     MOV     ?_sprintf?BYTE+04H,#HIGH ?SC_0
10
000C 750000  R     MOV     ?_sprintf?BYTE+05H,#LOW ?SC_0
11
000F 8B00    E     MOV     ?_sprintf?BYTE+06H,R3
12
0011 8A00    E     MOV     ?_sprintf?BYTE+07H,R2
13
0013 8900    E     MOV     ?_sprintf?BYTE+08H,R1
14
0015 7B01          MOV     R3,#01H
15
0017 7A00    R     MOV     R2,#HIGH reply_buf
16
0019 7900    R     MOV     R1,#LOW reply_buf
17
001B 120000  E     LCALL   _sprintf

0FFH dürfte der Typ Code (= Flash) bedeuten.

Moderator Persönliche Seite #7613315
Lesenswert?

Peter D. schrieb:

Jörg W. schrieb:

Außer halt, dass es Overhead kostet, sowohl im Zeiger selbst als auch für die Auswertung zur Laufzeit.

Das hatte selbst bei 12MHz / 12 = 1MIPS nicht sonderlich gestört. Die Ableserate des Menschen ist oftmals der Flaschenhals.

In diesem Falle schon, aber generic pointer waren ja ein allgemeines Konzept, nicht nur hinsichtlich stdio, oder?

Aber wie schon geschrieben: wird bei AVR keiner mehr machen. Zu viel Sonderlocke, und die Domäne für größere Flashs geht eh klar in Richtung ARM oder RISC-V. Bei kleineren Flashs wiederum gibt es mittlerweile die Option, den Flash in den normalen Adressbereich einzublenden, da braucht man solche Verrenkungen dann auch nicht mehr.

Moderator Persönliche Seite #7613327
Lesenswert?

Peter D. schrieb:

Ist aber wurscht: hat beim AVR keiner implementiert und wird auch keiner mehr implementieren.

Ja, für die ollen 8-Bitter macht keiner mehr was.

Du musst mir die Worte nicht im Mund rumdrehen. Microchip konstruiert nach wie vor neue AVRs am laufenden Band, aber sie müssen natürlich Dinge produzieren, die sich auch verkaufen lassen. Da haben sie wohl erkannt, dass es jenseits von 128 KiB Flash (die man noch ohne irgendwelche Verrenkungen adressieren kann) keinen Sinn hat, auf 8-Bitter zu setzen.

#7613361
Lesenswert?

Jörg W. schrieb:

Microchip konstruiert nach wie vor neue AVRs am laufenden Band

Ja, das habe ich auch nicht verstanden, die 0-, 1-, 2-Serie mit nur minimalen Unterschieden in schneller Folge rauszuhauen. Ich vermute darin öfteren Führungswechsel, wo jeder seine Duftmarke setzen will.

Ich werde schon schief angesehen, wenn ich noch was mit dem AVR machen will. Bisher eingesetzte Typen haben Bestandsschutz, weitere anzulegen ist verboten. Für das aktuelle Projekt ist ein i.MX RT1176 vorgesehen. https://www.embeddedartists.com/products/imx-rt1176-ucom/

Moderator Persönliche Seite #7613374
Lesenswert?

Peter D. schrieb:

Ich vermute darin öfteren Führungswechsel, wo jeder seine Duftmarke setzen will.

Ich vermute, dass deine Vermutung reichlich daneben ist.

Die Entwicklung eines IC kostet Millionen – selbst, wenn man vielleicht auf eine Art „Baukastensystem“ für die IP-Blöcke zurück greifen kann. Diese Millionen muss der Chip dann auch erstmal einspielen, ansonsten wäre ziemlich schnell Schicht im Schacht.

Das Maxim-Prinzip (viele Ideen auf MPWs und dann erst gucken, was sich verkaufen lässt, den Rest wieder einstampfen) scheint bei Microchip nun eher nicht vorzuherrschen.

#7613378
Lesenswert?

Peter D. schrieb:

Ja, für die ollen 8-Bitter macht keiner mehr was. Blinklicht nicht unter 32Bit ist der neue Scheiß.

Ob man generische Pointer jetzt für ein Blinklicht so dringend braucht, ist dann die andere Frage.

Peter D. schrieb:

Für das aktuelle Projekt ist ein i.MX RT1176 vorgesehen. https://www.embeddedartists.com/products/imx-rt1176-ucom/

Das ist aber ein ganz anderes Kaliber als ein AVR. Ich hoffe doch, dass der nachher etwas mehr macht als ein Blinklicht 😉.

#7613502
Lesenswert?

Jörg W. schrieb:

Die Entwicklung eines IC kostet Millionen

Da wundert es mich aber doch, warum sie in kurzer Folge die 8-Pinner ATtiny12, 15, 22, 13, 25-85 rausgebracht haben. Ich habe sie alle gekauft und mich geärgert, als die besseren verfügbar waren. Nun liegen sie rum, außer dem ATtiny25, der alle Vorgänger ersetzen kann. Und mit den neuen Serien geht dieses Spielchen ja munter weiter. Nur der ATmega328PB ist irgendwie ein Ausreißer, da ist recht viel hinzugekommen.

Moderator Persönliche Seite #7613529
Lesenswert?

Peter D. schrieb:

Da wundert es mich aber doch, warum sie in kurzer Folge die 8-Pinner ATtiny12, 15, 22, 13, 25-85 rausgebracht haben.

Da schmeißt du aber einige Jahre, wenn nicht Jahrzehnte in einen Topf.

25 … 85 (die letztlich eine IC-Entwicklung erstmal sind) ist eine Art Nachfolger / Ablösung des 15 gewesen, mehr oder weniger aufwärtskomptabibel.

12 (und dessen Bruder 11) waren wohl die ersten Tinys überhaupt, mit dem 13 hat man dann ein deutlich universelleres Teil nachgeliefert, das bis heute noch Bestand hat.

22 … keine Ahnung. Ich glaube, ich habe mal einen in den Fingern gehabt. Möglich, dass der eher ein Verlustgeschäft für Atmel war damals. Auch möglich, dass da ein einzelner großer Kunde als Auftraggeber dahinter stand, sowas ist nicht ungewöhnlich in der IC-Industrie.

#7613535
Lesenswert?

Joachim B. schrieb:

Man kann nicht mal so dumm denken wie manche Führungsetage ist!

Frage vom Abteilungsleiter: "kennen sie sich aus mit Commodore mit kleiner Tastatur? (PET2001 mit Datasette)"

Anwort vom Bewerber "ja (und meinte den Commodore Taschenrechner)" Vom 6502 und Innenleben hatte er NULL Ahnung, auch nicht das man beim Programmieren ENTER drückt und nicht mit den Cursortasten in die nächste Zeile geht.

Ein anderer Entscheider stolz wie Bolle, er hat seinen ersten Dr.Ing eingestellt, einer der Prüfautomaten und Programme baute die auch ohne angeschlossenen Prüfling gut testeten!

Moderator Persönliche Seite #7613561
Lesenswert?

Peter D. schrieb:

Jörg W. schrieb:

Da schmeißt du aber einige Jahre, wenn nicht Jahrzehnte in einen Topf.

ATtiny22: 04/1999 ATtiny12: 10/1999 ATtiny15: 12/2001 ATtiny13: 06/2003 ATtiny25..85: 02/2005

Keine Jahrzehnte, aber schon einige Jahre dazwischen. Bedenke auch, dass die die Anfangszeit der AVRs war. Der AT90S1200 hatte das Feld ausgelotet (kurz danach der ATmega103), ab da konnte man dann Feedback von potentiellen und realen Kunden sammeln.

Dass eine Entwicklung einer kompletten MCU-Serie "from scratch" zu Anfang defizitär ist, dürfte normal sein.

Persönliche Seite #7614012
Lesenswert?

Reinhard schrieb:

Ich würde gerne wissen, welche Werte die va_list-Variablen args und num_args auf diesem spezifischen System zurückgeben.

num_args kann so nicht verwendet werden, und eine Dokumentation / Beispiel, die das so verwendet, kann du getrost in die Tonne kloppen. Der Typ von args wird durch den 2. Parameter von va_arg festgelegt.

Am einfachsten verwendet man in solchen Fehlerausgaben die printf Funktionen die Libc:

diagnostic.h

1
#include <avr/pgmspace.h>
2

3
#define error(msg, ...) \
4
    error_loc_P (PSTR(__FILE__), __LINE__, PSTR(msg), __VA_ARGS__)
5

6
extern void error_loc_P (const char *fname, int line, const char *fmt, ...);

diagnostic.c

1
#include <stdarg.h>
2
#include <stdio.h>
3

4
#include "diagnostic.h"
5

6
void error_loc_P (const char *fname, int line, const char *fmt, ...)
7
{
8
    va_list args;
9
    va_start (args, fmt);
10

11
    fprintf_P (stderr, PSTR ("\n%S:%d: error: "), fname, line);
12
    vfprintf_P (stderr, fmt, args);
13
    fprintf_P (stderr, PSTR ("\n\n"));
14

15
    va_end (args);
16
}

main.c

1
#include "diagostic.h"
2

3
int main (void)
4
{
5
    error ("Fehler %d", 123);
6
}

Der Format-String von error() muss dabei ein gültiges Argument für PSTR() darstellen. Oder man lässt das PSTR für den Format-String weg, aber dann belegt der String i.d.R. RAM.

[v]fprintf ist ein zielmlicher Brummer, und bei Platzproblemen -- die man auf einem ATmega2560 eher weniger hat -- implementiert man ienfach eine abgespeckte Version, die nur das nötogste mitbringt wie %s, %S, %d, %u und %x.

Persönliche Seite #7614090
Lesenswert?

Jörg W. schrieb:

Johann L. schrieb:

implementiert man einfach eine abgespeckte Version, die nur das nötigste mitbringt wie %s, %S, %d, %u und %x.

Wobei es ja schon eine abgespeckte Variante gibt, die man erstmal stattdessen probieren kann. ;-)

Ja, aber das printf aus der Libc ist immer noch ziemlich fett. Das liegt nicht zuletzt auch dran, dass viele Spezialitäten wie %*, %+, %-, %0 etc unterstützt werden, die man meist nicht braucht; schon gar nicht bei Fehlerausgabe.

#7614271
Lesenswert?

Ich bin kein Freund von Logging per printf/fprintf/snprintf…

Sie kosten einiges an Platz und die Formatierung zur Laufzeit braucht Zeit und ist fehleranfällig (wobei es ja Compiler gibt, die Forrmierstring Fehler erkennen.)

Wenn möglich würde ich (wie oben schon empfohlen) eher auf C++ variadische Template umsteigen. Das ist flexibler und ressourcenschonender.

Unter C11 kann man so etwas auch per Makros und _Generic() nachbauen, geht dann aber nur mit C-Compiler die C11 unterstützen und sieht irgendwie gemurkst aus….

Für die schnelle Ausgabe von Messages wäre TRICE (Beitrag "Open Source Projekt TRICE - ein Software-Tracer & Logger für alle µC: PIC, AVR, ARM, TI, Infinion,..") von Thomas Höhenleitner noch empfehlenswert.

#7614299
Lesenswert?

Klaus schrieb:

Ich bin kein Freund von Logging per printf/fprintf/snprintf…

Sie kosten einiges an Platz und die Formatierung zur Laufzeit braucht Zeit [...]

Mit dem Platz ist relativ: bei vielen libc-stdio-Implementation zieht die Benutzung von sprintf gleich das gesamte stdio-Subsystem mit rein - bei shared-libs kein Problem, bei statischen Builds wird's eins, ja. Der meiste Platz dürfte aber für die Float-to-String conversion drauf gehen (eine qualitativ hochwertig Implementation frißt mehrere dutzend kB, wenns schnell sein soll auch mal deutlich über 100kB).

Die avr-libc ist eine mMn sehr gute Implementation für µC - die Grundroutinen sind schlank, sie ziehen keinen Rattenschwanz an abhängigen Funktionen rein und die FP-version ist optional.

Und meist sieht es ja so aus: anfangs nur einen String ausgeben - wozu printf, einfach println o.ä. Aber je mehr das Projekt wächst, umso mehr steigen die Anforderungen und man schreibt sich immer mehr Hilfsroutinen (für signed/unsigned dezimal-Zahlen, hex-Zahlen, fix-width, etc) und bevor man sich versieht, hat man sprintf neu erfunden.

Und was die Geschwindigkeit angeht: printf ist nicht langsam! Das Bottleneck ist üblicherweise die anschließende Ausgabe, nicht die Formatierung (bei Ausgabe über UART sowieso).

Wenn möglich würde ich (wie oben schon empfohlen) eher auf C++ variadische Template umsteigen. Das ist flexibler und ressourcenschonender.

Flexibler mag sein, das mit dem ressourcenschonender möchte ich bezweifeln. Statt in einer zentralen Formatierungsfunktion findet bei den Templates die Formatierung bei jedem einzelnen Aufrufer statt. Sieht kurz auf, es wird aber für jedes Argument extra Code generiert. Das läppert sich ... Gewinnen kann man da nur, wenn die Argumente statisch sind, so dass der Compiler das wegoptimieren kann.

OP #7614302
Lesenswert?

Foobar schrieb:

Und meist sieht es ja so aus: anfangs nur einen String ausgeben - wozu printf, einfach println o.ä. Aber je mehr das Projekt wächst, umso mehr steigen die Anforderungen und man schreibt sich immer mehr Hilfsroutinen (für signed/unsigned dezimal-Zahlen, hex-Zahlen, fix-width, etc) und bevor man sich versieht, hat man sprintf neu erfunden.

Genau so siehts bei mir aus.

#7614311
Lesenswert?

Foobar schrieb:

Flexibler mag sein, das mit dem ressourcenschonender möchte ich bezweifeln. Statt in einer zentralen Formatierungsfunktion findet bei den Templates die Formatierung bei jedem einzelnen Aufrufer statt.

Das kommt auf die Implementierung an. Wenn für jeden Parameter ein (überladener) Funktionsaufruf genutzt wird, ist der Speicherplatz nicht viel größer als bei ...printf Übergabe, man spart aber die Dekodierung des Formatstrings und ist typsicher. Klar man hat mehrere Funktionsaufrufe, aber das parsen des Formatstrings kostet auch Zeit.

OP #7614316
Lesenswert?

Ich mache das jetzt so:

1
const char dataloc tx_destSIZE_zu_klein[]   
2
                      = "destSIZE zu klein (jstring). Muss >= "; 
3

4
int jstring(char* dest, uint8_t destSIZE, const char* format, ...) {
5
    int num_written;
6
    char errtx[sizeof(tx_destSIZE_zu_klein)+3];
7
    
8
    // Formatargumente direkt verwenden
9
    va_list args;
10
    va_start(args, format);
11
    
12
    // Formatieren des Texts in dest
13
    num_written = vsnprintf_P(dest, destSIZE, format, args);
14
    
15
    // Überprüfen, ob der Text abgeschnitten wurde (destSIZE)
16
    if (num_written >= (destSIZE - 1))
17
        {
18
        strcpy_P(errtx, tx_destSIZE_zu_klein);
19
        char num_str[10];
20
        ltoa(num_written,num_str,10);
21
        strcat(errtx,num_str);
22
        va_end(args);
23
        emergency_stop(errtx);
24
        }
25
        
26
    // Aufräumen der Argumentenliste
27
    va_end(args);
28
    
29
    return num_written; // Rückgabe der Anzahl geschriebener Zeichen
30
}

emergency_stop(errtx); schaltet auf dem Arduino und der angeschlossenen Elektronik alles aus, und gibt den errtx aus.

Aufruf z.B. in

1
void RangeCheck_MOD (uint16_t ZeilenNr, uint8_t MOD_ix) {..}
1
  char jou[30];
2
  jstring(jou,sizeof(jou),PSTR("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);
3
  journal_print(jou); // ich habe da meine eigene Ausgabeprozedur, die in einen Ringpuffer schreibt... bla bla

Jetzt kann ich das prima verwenden.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren