uint8_t auf int16_t erweitern

OP #7660487
Lesenswert?

Suche nach einem praktikablen c-Syntax, der Rechenfehler beim Erweitern von uint8_t auf int16_t bei unsigned Werten >127 vermeidet.

Weiß auch nicht, warum das gerade jetzt nicht klappt, das Beispiel:

1
//...
2
const uint8_t anzArray[] PROGMEM={64,76,84,92,98,104,111,120,132,161,190};
3
volatile int16_t z;
4

5
z = anzArray[i+1] - anzArray[i];
6
// oder auch
7
z = (int16_t)(anzArray[i+1]&0x00ff) - (int16_t)(anzArray[i]&0x00ff);

Letzteres liefert im Microchip Studio Watch

1
anzArray[i+1]    190    uint8_t{prog}@0x0028
2
anzArray[i]      161    uint8_t{prog}@0x0027
3
z               -131    int16_t{data}@0x0156 ([R28]+4)

Wie bekommt man ein mathematisch korrektes Ergebnis trotz Datentypen-Mix?

(Firma: L-E-A) #7660505
Lesenswert?

Wulf D. schrieb:

Ja, denke auch dass es am PROGMEM liegt, sonst wäre ich bereits früher drüber gestolpert. Das erwartete Rechenergebnis wäre +29 statt -131, also 190-161=29.

Wenn Du (Byte-)Daten mittels PROGMEM ablegst, musst Du auf diese mittels pgm_read_byte() zugreifen.

Warum verwendest Du nicht __flash? Dann geht's auch ohne pgm_read_byte().

Grüßle, Volker

Gast #7660603
Lesenswert?

z = (int16_t)(anzArray[i+1]&0x00ff) - (int16_t)(anzArray[i]&0x00ff);

Wenn das Array im normalen RAM liegen würde... könnte diese Zeile so vereinfacht werden:

z = (int16_t)anzArray[i+1] - anzArray[i];

Der Teil &0x00ff kann entfallen, da er bei 8 Bit gar nichts bewirkt. Der rechte Teil des Ausdrucks muss nicht zu int16 konvertiert werden, da der linke Teil bereits ein int16 ist.

Persönliche Seite #7660613
Lesenswert?

Wastl schrieb:

Volker B. schrieb:

Warum verwendest Du nicht __flash? Dann geht's auch ohne pgm_read_byte().

Kommt meiner dunklen Erinnerung nach darauf an welche GCC Version man verwendet.

Wird unterstützt ab GCC v4.7 (Release 2012).

https://gcc.gnu.org/gcc-4.7/changes.html#avr

Allerdings wird es nicht in C unterstützt sondern nur mit GNU-C; mit -std=c99 funktioniert es also nicht.

OP #7660616
Lesenswert?

Steve van de Grens schrieb:

Wenn das Array im normalen RAM liegen würde... könnte diese Zeile so vereinfacht werden: z = (int16_t)anzArray[i+1] - anzArray[i];

Ja, hatte ich zuerst so codiert, ein cast auf int16. Ist so wieder drin, nur halt mit dem __flash in der Definition des Arrays.

Wahrscheinlich wäre pgm_read_byte() portabler, weiß gar nicht genau das der __flash Präfix bewirkt.

War ein älterer Code wo die Anzeige manchmal Unsinn anzeigte und wollte der Sache nun auf den Grund gehen. Seltsam, dass es trotz des fehlerhaften Zugriffs überhaupt einigermaßen funktionierte.

Wegen der gcc-Version: im MAP-File fand ich dies: gcc/avr/5.4.0

Gruß Wulf

#7660752
Lesenswert?

Volker B. schrieb:

Wenn Du (Byte-)Daten mittels PROGMEM ablegst, musst Du auf diese mittels pgm_read_byte() zugreifen.

Wulf D. schrieb:

Seltsam, dass es trotz des fehlerhaften Zugriffs überhaupt einigermaßen funktionierte.

@TO: Denkfehler. Die Variablenüberwachung sieht anhand des Codes, dass Daten aus dem Flash angezeigt werden sollen.

Das übersetzte Programm zieht sich die Werte aus dem RAM: Sie mal nach, was 'Wert aus Adresse 0x28' minus 'Wert aus Adresse 0x27' ergibt...

#7660806
Lesenswert?

Wulf D. schrieb:

Na ja, auf eine andere Toolchain mit anderem Compiler, wenn man den Code in ein paar Jahren nochmal anfassen würde.

Als da wäre?

Für einen anderen (neueren) gcc passts immer, und für alle anderen existierenden AVR-Compiler musst du die Flash-Zugriffe eh umschreiben, weil sowohl PROGMEN als auch _flash gcc-spezifisch sind. Für andere Prozessoren gilt das dann sowieso. Ist aber auch völlig egal, denn du wirst das eh niemals irgendwo hin portieren.

Oliver

Persönliche Seite #7660845
Lesenswert?

Wulf D. schrieb:

Oliver S. schrieb:

Wulf D. schrieb:

Wahrscheinlich wäre pgm_read_byte() portabler

Wohin könnte das denn eventuell mal irgendwann portiert werden?

Oliver

Na ja, auf eine andere Toolchain mit anderem Compiler, wenn man den Code in ein paar Jahren nochmal anfassen würde.

Am einfachsten und portabelsten geht das mit __flash, weil das im Gegensatz zu PROGMEM kein Attribute sondern ein Qualifier ist. Zu Beispiel so:

1
#ifndef __FLASH
2
#define __flash /* empty */
3
#endif

Damit hättest du das Programm dann auf Reduced Tiny wie ATtiny portiert, wo es keine Named Address-Spaces gibt. (Auf den Reduced Tiny braucht's eh kein PROGMEM oder Flash, weil .rodata ins Flash allokiert ist, und nicht ins RAM wie bei den meisten anderen AVRs).

#7660874
Lesenswert?

Steve van de Grens schrieb:

z = (int16_t)(anzArray[i+1]&0x00ff) - (int16_t)(anzArray[i]&0x00ff);

Wenn das Array im normalen RAM liegen würde... könnte diese Zeile so vereinfacht werden:

z = (int16_t)anzArray[i+1] - anzArray[i];

Der Teil &0x00ff kann entfallen, da er bei 8 Bit gar nichts bewirkt. Der rechte Teil des Ausdrucks muss nicht zu int16 konvertiert werden, da der linke Teil bereits ein int16 ist.

Noch nicht einmal dieser Cast ist nötig, da die Operanden sowieso implizit zu "int" konvertiert werden ("integer promotion").

OP #7660962
Lesenswert?

Ralf G. schrieb:

Wulf D. schrieb:

Seltsam, dass es trotz des fehlerhaften Zugriffs überhaupt einigermaßen funktionierte.

@TO: Denkfehler. Die Variablenüberwachung sieht anhand des Codes, dass Daten aus dem Flash angezeigt werden sollen.

Das übersetzte Programm zieht sich die Werte aus dem RAM: Sie mal nach, was 'Wert aus Adresse 0x28' minus 'Wert aus Adresse 0x27' ergibt...

Das liefert auch nicht diese Differenz. Im unterem RAM ab 0x0060,data ist alles auf default 0x00. Es wird nur wenig SRAM benutzt.

#7661104
Lesenswert?

Wulf D. schrieb:

Wie bekommt man ein mathematisch korrektes Ergebnis trotz

Indem du z.B. die PROGMEM-Methoden zum Lesen der Daten aus dem Flashspeicher verwendest. Mit

Wulf D. schrieb:

z = (int16_t)(anzArray[i+1]&0x00ff) - (int16_t)(anzArray[i]&0x00ff);

liest du nämlich nur aus, was an den entsprechenden Addressen im RAM steht, du holst dir hier gar keine Daten aus dem Flash ab und deshalb stimmt auch die Rechnung nicht mit dem Erwartungswert überein. Im Gegensatz zu dir holt sich das Microchip Studio Watch die Daten richtig aus dem Flash ab. Das richtige Ergebnis müsstest du erhalten indem du

1
z = (int16_t)pgm_read_byte(&anzArray[i+1]) - (int16_t)pgm_read_byte[&anzArray[i]);

rechnest.

OP #7661171
Lesenswert?

M. K. schrieb:

Wulf D. schrieb:

Wie bekommt man ein mathematisch korrektes Ergebnis trotz

Indem du z.B. die PROGMEM-Methoden zum Lesen der Daten aus dem Flashspeicher verwendest. ...

1
z = (int16_t)pgm_read_byte(&anzArray[i+1]) - 
2
> (int16_t)pgm_read_byte[&anzArray[i]);

Ja richtig, der Vorschlag kam schon von Volker ganz oben im 4. Post, mit einer Alternative der __flash-Definition des Array. Beides funktioniert in dem Fall korrekt. Die Diskussion ging jetzt hier noch darum, was der Code eigentlich für Daten im Fehlerfall verarbeitet.

Bruno V. schrieb:

Wulf D. schrieb:

Im unterem RAM ab 0x0060,data ist alles auf default 0x00

Also konkret: RAM an Adresse 0x28 und 0x29 ist 0?

Alternativ lade anzArray[i] in eine uint8 und in eine uint16 variable (am Besten volatile) und lass die dann ausgeben.

Ja, im SRAM steht fast nur 0x0. Habe deinen Vorschlag getestet:

1
volatile anz_uint8_i, anz_uint8_i1;
2
volatile anz_uint16_i, anz_uint16_i1;
3
anz_uint8_i=anzArray[i];
4
anz_uint16_i=anzArray[i];
5
anz_uint8_i1=anzArray[i+1];
6
anz_uint16_i1=anzArray[i+1];
7

8
z=anzArray[i+1]-anzArray[i];

Der Watch vom Debugger:

1
  anzArray[i]     161   uint8_t{prog}@0x0027
2
  anzArray[i+1]   190   uint8_t{prog}@0x0028
3
  z              -131   int16_t{data}@0x014e ([R28]+4)
4

5
  anz_uint8_i     177   int{data}@0x0152 ([R28]+8)
6
  anz_uint16_i    177   int{data}@0x0156 ([R28]+12)
7
  anz_uint8_i1    48    int{data}@0x0154 ([R28]+10)
8
  anz_uint16_i1   48    int{data}@0x0158 ([R28]+14)

Das SRAM fängt bei Adresse 0x60 beim ATTiny45 an:

1
data 0x0060  09 00 06 00 25 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ....%................
2
data 0x0075  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 00 3a 00  .................ÿ.:.
3
data 0x008A  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  .....................
4
data 0x009F  00 00 00 00 00 00 00 00 00 00 04 00 02 00 00 00 00 00 00 00 00  .....................

Aus dem SRAM kommen die Daten nicht, jedenfalls nicht aus der vermuteten Stelle. Da das eigentliche Problem gelöst ist, sollte man den Thread beenden.

P.S.: Vielleicht aus den Registern, wenn da ab Adr.0x27 b1 30 stehen würde, aber da steht b3 30. Vielleicht wird beim Lesen etwas verbogen.

1
data 0x0000  12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b3 06 4a  ....................J
2
data 0x0015  04 00 00 7d ff 01 00 4a 01 27 00 00 00 00 00 40 bc fd b3 30 30  ...}ÿ..J.'.....@.ý.00
3
data 0x002A  00 00 00 00 00 00 00 00 00 00 00 10 39 03 10 00 00 00 00 4d 0b  ............9......M.
#7661229
Lesenswert?

Wulf D. schrieb:

Da das eigentliche Problem gelöst ist, sollte man den Thread beenden.

Ja. Aber kein Grund, diesen konstruktiven Thread zu sperren. Selbst in einem Jahr wird jemand "Lösungen" zum "Überlauf" präsentieren. Doch: Beide Gleichungen für z im Ausgangspost sind korrekt, für jeden C-Compiler (zumindest für 8-Bit-Bytes, evt. auch für 9-Bit-Bytes).

#7661525
Lesenswert?

Wenn der Compiler den Wert von i kennt, dann rechnet der aus den konstanten Werten des Arrays bei eingeschalteter Optimierung das Ergebnis gleich selber aus. Der „falsche“ Speicherzugriff findet dann gar nicht statt. Zufall ist das dann nicht.

Ob der i kennen kann, ist aus dem Code oben nicht zu erkennen.

Oliver

Persönliche Seite #7661538
Lesenswert?

Arduino F. schrieb:

Die Optimierung lässt hier falschen/kaputten Code so aussehen, als würde er funktionieren. Das war ein Teil der Fragen hier, warum der falsche Code auch mal funktioniert.

Das hat aber nicht unbedingt was mit Optimierung zu tun.

Ganz allgemein hat man die äquivalenten Implikationen

  1. Code funktioniert nicht => Programm[*] ist nicht korrekt

  2. Programm[*] ist korrekt => Code funktioniert

wobei [*] auch Transformatoren wie Compiler, Assembler, Linker etc. beinhaltet sowie Microcode/Silizium. Oft wird aber angenommen es sei auch

  1. Code funktioniert => Programm korrekt

gültig, was idR nicht der Fall ist.

So kann man zum Beispiel aus bestandenen Tests idR /nicht/ schlussfolgern, das Programm sei korrekt, ganz egal ob nun Optimierungen aktiv sind oder nicht.

#7661782
Lesenswert?

Arduino F. schrieb:

Die Optimierung lässt hier falschen/kaputten Code so aussehen, als würde er funktionieren. Das war ein Teil der Fragen hier, warum der falsche Code auch mal funktioniert. Das ist eben die Optimierung, die einem eben hier genau diesen Streich spielt.

Ob ohne oder mit Optimierung spielt überhaupt keine Rolle.*) Das Ergebnis ist immer gleich. (Ohne Optimierung kann man vielleicht im Simulator ein paar mehr Variablen überprüfen, die sonst in Registern gehalten werden (AVR-Studio) oder 'wegoptimiert' wurden und somit im Watch-Fenster nicht angezeigt werden können.)

Hier: Das Programm macht genau das, was programmiert wurde! Einen Zugriff auf den RAM. Will man auf den Flash zugreifen, muss 'pgm_read_XXXX' verwendet werden. (bzw. '__flash' ... C, gcc)

*) ja, es gibt ein seltene Ausnahmen...

(Firma: Gast) #7661788
Lesenswert?

Ralf G. schrieb:

Ob ohne oder mit Optimierung spielt überhaupt keine Rolle.

Bei dem Eingangsproblem, und ähnlichen, spielt das schon eine Rolle

Durch die Optimierung wird der Logische Fehler verdeckt. Das ist der Streich, der einem da die Optimierung spielt.

Hier vereinfacht:

1
const int a PROGMEM {4};
2
const int b PROGMEM {5};
3

4

5
void setup() 
6
{
7
  Serial.begin(9600);
8
  Serial.println(a+b); 
9
}
10

11
void loop() 
12
{
13

14
}

Mit -Os kommt, trotz falschem Zugriff auf die Konstanten, eine 9. Reproduzierbar. Was mit -O0 (oder wenn man die Konstanten volatile macht) kommt, ist nicht wirklich vorhersehbar.

Das gilt genauso für die .eeprom Section

Ralf G. schrieb:

Hier: Das Programm macht genau das, was programmiert wurde! Einen Zugriff auf den RAM.

Eben nicht! Mit -Os tut es das eben nicht, oder nicht unbedingt. Mit -O0 greift es ins RAM und liefert sicherlich unerwünschtes.

Will man auf den Flash zugreifen, muss 'pgm_read_XXXX' verwendet werden. (bzw. '__flash' ... C, gcc)

Das musst du mir nicht erklären!

#7661864
Lesenswert?

Arduino F. schrieb:

Mit -Os kommt, trotz falschem Zugriff auf die Konstanten, eine 9.

OK, so betrachtet hast Du Recht: Der Code definiert eine Gleichsetzung, die in HW nicht gegeben ist, die der C-Compiler aber wegoptimieren darf.

Wenn ich (fälschlicherweise und ohne volatile) beschreibe, dass x @ 0x100 konstant 5 ist, liest er ohne Optimierung bei y=x was da wirklich steht. Mit Optimierung setzt er y=5 direkt, ohne zu lesen.

(Firma: Gast) #7662076
Lesenswert?

Bruno V. schrieb:

OK, so betrachtet hast Du Recht:

Ich danke dir für die Zustimmung.

Bruno V. schrieb:

aber wegoptimieren darf.

Darf, nicht darf... Naja.

Ich sehe das eher so: Der Compiler und seine Optimierungsstrategien haben (offensichtlich) keine/wenig Ahnung von den Speicher Sections, melden darum auch keinen Fehler. Haben keine Chance.

Die Section Attribute werden vom Compiler einfach nur an den Linker übergeben, welcher dann die Daten in den Sections verteilt und die Adressen in den generierten Code einträgt.

Ob man sagt "es hat keine andere Chance", oder "der darf das", hilft in der Praxis nicht wirklich weiter.

Gerade auch in Verbindung mit Schleifen kann einen der Effekt/Streich überraschen. Programm läuft gut und scheinbar richtig (mit -Os). Nur eine Schleifeniteration hinzugefügt, und es kommt nur noch Müll raus.

#7662114
Lesenswert?

Arduino F. schrieb:

Ob man sagt ... "der darf das", hilft in der Praxis nicht wirklich weiter.

Da bin ich anderer Meinung. C ist in vielen Bereichen genau definiert. Und wenn ich explizit sage, dass eine Variable immer (konstant) 5 ist, dann "darf" der Compiler das berücksichtigen, muss es aber nicht.

Sie es anders rum: Wenn der Compiler sich nicht auf meine Angaben verlassen kann, worauf dann?

Der Klassiker ist sicher das fehlende volatile eines flags oder eines Schleifenzählers. Hier darf der Compiler annehmen, dass es nicht volatile ist.

#7662132
Lesenswert?

Arduino F. schrieb:

Der Compiler und seine Optimierungsstrategien haben (offensichtlich) keine/wenig Ahnung von den Speicher Sections, melden darum auch keinen Fehler. Haben keine Chance.

Das ist ja jetzt nichts neues. Das Konzept der verschiedenen Speichertypen kennt C nicht, und damit kennt das auch der Compiler nicht.

Was nichts daran ändert, daß ein fehlerhafte Programm fehlerhaft ist, auch wenn es anscheinend die erwarteten Ergebnisse liefert.

Oliver

(Firma: Gast) #7662135
Lesenswert?

Bruno V. schrieb:

Wenn der Compiler sich nicht auf meine Angaben verlassen kann, worauf dann?

Menschen machen Fehler, übersehen was, vergessen was. Das dürfte der Grund dafür sein, dass den Compilern über die Jahre beigebracht wurde immer mehr von diesen Problemstellen zu erkennen und Meldungen zu werfen.

Diese Meldungen sind durchaus hilfreich um (potentielle) Fehler auszubügeln. Hier kommt leider keine Meldung! Das ist schade.

In C weniger Problem, da man die Alternative __flash hat. Aber in C++ mit seinem PROGMEM kann einem das schon auf die Füße fallen, bzw. als Falle Jahrelang in häufig wiederverwendeten Programmteilen schlummern.

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