Forum: Mikrocontroller und Digitale Elektronik Fehlerhafte Adressierung von lokalen Arrays bei tinyAVR(R) 0-series


von Ralph S. (jjflash)


Angehängte Dateien:

Lesenswert?

Seit ca. 4 Wochen beschäftige ich mich jetzt auch mit der tinyAVR(R) 
0-series (bei den Experimenten mit dem ATtiny1604) und bin "eigentlich" 
überrascht und erfreut über den Chip (aber eben nur "eigentlich").

Das erste mal ist mir ein (vllt. vermeintlicher) Bug aufgefallen, den 
ich hier gepostet hatte:

Beitrag "Phänomen tinyAVR-series(R) 0/1"

Schnell stellte sich heraus, dass (aus welchen Gründen auch immer) ein 
in einer Funktion lokal definertes Array wohl nicht korrekt addressiert 
wird. Ursprünglich dachte ich auch an einen Compilerbug, weil die 
Installation der neuesten Version von avr-gcc das Problem scheinbar 
nicht zeigte, aber:

Auch die Version zeigt das gleiche Problem, ich hatte nur nicht dasselbe 
Programm verwendet.

Nachdem ich nun mir so schön langsam meinen eigenen Hardwarelayer und 
mein eigenes Framework zusammenbastle, stellt sich das Problem stärker 
als zuvor da.

Recherchiert habe ich jetzt nach dem schönen Fingerzeig von peda über 
das Problem des ATtiny26 und dem Befehl "lPM Rd, Z+" und vom ihm 
dankenswerterweise gegebenen Link einer Diskussion hierzu.

Beitrag "Problem mit ltoa"

Dieses Problem kann der ATtiny1604 (getestet sind auch 1614, 214 und 
412) nicht haben, denn beim Studium des Listfiles wird nur ein einziges 
mal "lpm 24, Z" (nicht Z+) verwendet, ein "lpm Rd,Y+" überhaupt nicht.

Die Programmsequenz ist folgende:
1
/* -------------------------------------------------------
2
     uart_puts_rom
3
   ------------------------------------------------------- */
4
#define puts_rom(str)      (uart_puts_rom(PSTR(str)))
5
void uart_puts_rom(const uint8_t *dataPtr)
6
{
7
  uint8_t c;
8
9
  for (c=pgm_read_byte(dataPtr); c; ++dataPtr, c=pgm_read_byte(dataPtr))
10
    uart_putchar(c);
11
}
12
13
/* -------------------------------------------------------
14
     puts_ram
15
   ------------------------------------------------------- */
16
void puts_ram(uint8_t *c)
17
{
18
19
  while (*c)
20
  {
21
    uart_putchar(*c++);
22
  }
23
24
}
25
26
char str1[]= "\n\r Hello World\n\n\r";
27
28
/* --------------------------------------------------------
29
                            main
30
   -------------------------------------------------------- */
31
int main(void)
32
{
33
  char str2[]= "\n\r Hallo uC-Community\n\r";
34
35
  CCP= 0xd8; CLKCTRL.MCLKCTRLB= 0;    // Clk-Divider = 1 => F_CPU = 20 MHz
36
37
  uart_init();
38
39
  puts_rom("\n\r Hallo Welt");         // funktioniert
40
  puts_ram(str1);                      // funktioniert
41
  puts_ram(str2);                      // funktioniert nicht !
42
  puts_ram("\n\r Bonjour le monde");   // funktioniert nicht !
43
  while(1);
44
45
}

Die Funktion "puts_ram" funktioniert also nur mit einem globalen, schon 
vorbelegten Array. Natürlich geht bspw. ein Beschreiben des Arrays 
mittels strcpy o.ä. natürlich auch nicht, was meinen "Verdacht" auf 
fehlerhafte Adressierung nur unterstreicht.

Dann dachte ich, dass evtl. der Bootloader mit der Compileroption 
-Wl,--section-start=.text=0x200 stört und habe das compiliert für 
Gebraucht ohne Bootloader (-Wl,--section-start=.text=0x000) mit keiner 
Änderung am Verhalten.

Die Funktion "puts_ram" funktioniert mit jedem von mir getesteten 
Controllern: AVR der älteren Serie (bspw. ATmega328), STM32, MCS51, 
STM8, LPC und sogar auf Padauks PFS154.

Nur eben nicht auf ATtiny1604 !!

Für diejenigen, die immer nach dem gesamten Quellcode "schreien" habe 
ich das gesamte "Programm" mit angehängt und extra auf das nötigste 
(hier eine UART-Anbindung) reduziert.

Im Netz habe ich nichts zu einem Hardwarebug oder einem Bug im Compiler 
zur tinyAVR 0-series gefunden.

Ich suche ja immer noch den Fehler bei mir (am ehesten), dann am 
Compiler (will ich nicht so recht glauben), an einen Hardwarebug (will 
ich noch weniger glauben).

Kennt jemand von Euch einen Weg, mit dem ich bei der tinyAVR 0-series 
mittels avr-gcc ein lokales Array adressieren kann?

:-) einfach nur, damit ich mein Setup weiter ausbauen kann.

Einen Gruß und ein schönes Wochenende,

JJ

PS: jetzt am Wochenende kann ich nur wenig (und selten hier schreiben), 
weil ansonsten meine liebe bessere Hälfte meint, sie komme nicht zu 
ihrem Recht. Zudem: wenn ich einmal anfange bei solchen Dingen die 
Fehler zu suchen, habe ich keinerlei Zeitgefühl. Also erlege ich mir 
selbst auf, die Finger vom Rechner (meistens) zu lassen

von Pit S. (pitschu)


Lesenswert?

Ist 'uart_putchar' bei dem speziellen Controller vielleicht ein #define 
?

von Peter D. (peda)


Lesenswert?

Das Problem könnte sein, daß man bei den AVR0 ja wieder alles komplett 
umgemodelt hat. Die Applikation kann nicht mehr unahängig vom Bootloader 
an 0x0000 starten, sondern erst irgendwo in der Pampa hinter dem 
Bootloader. Es könnte also ein Problem mit dem Linkerscript sein.

Versuch mal, ob es geht, wenn die Applikation wieder an 0x0000 startet, 
also ohne Bootloader über ISP geladen wird.

Man könnte auch den Bootloader wie bei den alten ATtiny benutzen, d.h. 
der Bootloader schnappt sich das RJMP der Applikation an 0x0000, ersetzt 
es durch ein RJMP zu sich selbst und programmiert den Sprung zur 
Applikation auf die letzte Adresse vor sich selbst. Dann steht der 
Bootloader wieder hinten und die Applikation braucht kein spezielles 
Linkerscript.

von Peter D. (peda)


Lesenswert?

Sehe gerade, das mit 0x0000 scheinst Du schon probiert zu haben.

Als weitere Abweichung ist mir aufgefallen, daß der Flash als Data an 
0x8000 gemappt wird. Es kann also sein, daß der LPM-Befehl nicht mehr 
verfügbar ist, sondern LD mit 0x8000 Offset benutzt werden muß. Das 
Instructionset ist blöderweise nicht mehr im Datenblatt enthalten.

von Georg M. (g_m)


Lesenswert?

Ralph S. schrieb:
>
1
   puts_ram("\n\r Bonjour le monde");   // funktioniert nicht !


Andererseits funktioniert der Microchip-Beispielcode (TB3216):
1
int main(void)
2
{
3
  USART0_init();
4
 
5
  while(1)
6
  {
7
    USART0_sendString("Hello World!\r\n");
8
    _delay_ms(500);
9
  }
10
}

von S. L. (sldt)


Lesenswert?

Das Programm zeigt auf einem ATmega4809 dasselbe Fehlverhalten; auf 
einem AVR128DB28 hingegen funktioniert es korrekt.

von C-hater (c-hater)


Lesenswert?

S. L. schrieb:
> Das Programm zeigt auf einem ATmega4809 dasselbe Fehlverhalten; auf
> einem AVR128DB28 hingegen funktioniert es korrekt.

Also entweder ein Compiler- oder ein Hardware-Bug.

Das aufzuklären, sollte ziemlich simpel sein: einfach das Listfile 
anschauen der beiden Varianten anschauen. Wenn der Assemblercode(!) lt. 
InstructionsetReference funktionieren müsste, dann ist's ein 
Hardware-Bug. Wenn nicht, ist's ein Compiler-Bug.

So einfach ist das.

von MaWin O. (mawin_original)


Lesenswert?

C-hater schrieb:
> So einfach ist das.

nö. Es ist ziemlich sicher kein Compiler- oder Hardwarebug. Das ist es 
nie.
Es ist eher eine falsche Buildeinstellung.

Falsches Device gewählt oder sowas.

von Wilhelm M. (wimalopaan)


Lesenswert?

Entferne mal alle Ausgaben bis auf die letzten beiden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wie kommen deine Daten vom ELF-File in den Flash?

Was mir auffällt: das lokale Array wird vom Compiler beim Start von 
main() in den Stack kopiert:
1
  char str2[]= "\n\r Hallo uC-Community\n\r";
2
 142:   88 e1           ldi     r24, 0x18       ; 24
3
 144:   ea e9           ldi     r30, 0x9A       ; 154
4
 146:   f1 e8           ldi     r31, 0x81       ; 129
5
 148:   de 01           movw    r26, r28
6
 14a:   11 96           adiw    r26, 0x01       ; 1
7
 14c:   01 90           ld      r0, Z+
8
 14e:   0d 92           st      X+, r0
9
 150:   8a 95           dec     r24
10
 152:   e1 f7           brne    .-8             ; 0x14c <main+0x14>

Die benutzte Adresse ist 0x819A, was der Flash-Adresse 0x19A entspricht, 
die in den Datenspeicher gemappt wird. So weit, so gut.

Im ELF-File gibt es dafür eine Section .rodata, die so aussieht:
1
Contents of section .rodata:
2
 8186 0a0d2042 6f6e6a6f 7572206c 65206d6f  .. Bonjour le mo
3
 8196 6e646500 0a0d2048 616c6c6f 2075432d  nde... Hallo uC-
4
 81a6 436f6d6d 756e6974 790a0d00           Community...
D.h. sie benutzt ebenfalls die gemappten Adressen. Das ist meiner 
Meinung nach nicht korrekt. Diese Daten müssten stattdessen mit in .text 
landen, damit sie beim Flashen mit übertragen werden.

Ich würde das Problem im Linkerscript vermuten …

von Ralph S. (jjflash)


Lesenswert?

Hallo Jörg,

Jörg W. schrieb:
> Im ELF-File gibt es dafür eine Section .rodata, die so aussieht:Contents
> of section .rodata:
>  8186 0a0d2042 6f6e6a6f 7572206c 65206d6f  .. Bonjour le mo
>  8196 6e646500 0a0d2048 616c6c6f 2075432d  nde... Hallo uC-
>  81a6 436f6d6d 756e6974 790a0d00           Community...
> D.h. sie benutzt ebenfalls die gemappten Adressen. Das ist meiner
> Meinung nach nicht korrekt. Diese Daten müssten stattdessen mit in .text
> landen, damit sie beim Flashen mit übertragen werden.
>
> Ich würde das Problem im Linkerscript vermuten …

genau aus dem Grund habe ich das komplette Progrämmchen mit angehängt 
und auch absichtlich nicht eine einzige Datei hinzugelinkt.

Jörg W. schrieb:
> Diese Daten müssten stattdessen mit in .text
> landen, damit sie beim Flashen mit übertragen werden.

Wie stelle ich das an? Das Progrämmchen wurde übersetzt mit:
1
avr-gcc -Wl,--section-start=.text=0x0000 -Os -DF_CPU -mmcu=attiny1604 puts_vers.c -c -o puts_vers.o

Der Linkerlauf sieht so aus:
1
avr-gcc -mmcu=attiny1604 puts_vers.o -o puts_vers.elf -Wl,--section-start=.text=0x0000

Der abschließende Object-Copy:
1
avr-objcopy -j .text -j .data -O ihex puts_vers.elf puts_vers.hex

Das sind jetzt zumindest mal die Stellen, die ich aus meinem Makefile 
extrahiert und in ein Script gegeben habe, um weitere Fehlerquellen 
auszuschließen. Ausprobiert habe ich das nun mittlerweile mit avr-gcc 
7.3.0 und auf einem anderen Rechner jetzt mit avr-gcc 12.1.0

von Rolf M. (rmagnus)


Lesenswert?

Jörg W. schrieb:
> Wie kommen deine Daten vom ELF-File in den Flash?
>
> Was mir auffällt: das lokale Array wird vom Compiler beim Start von
> main() in den Stack kopiert:

Wäre das nicht das zu erwartende Verhalten bei lokalen Arrays?

von Ralph S. (jjflash)


Lesenswert?

Rolf M. schrieb:
> Jörg W. schrieb:
>> Wie kommen deine Daten vom ELF-File in den Flash?
>>
>> Was mir auffällt: das lokale Array wird vom Compiler beim Start von
>> main() in den Stack kopiert:
>
> Wäre das nicht das zu erwartende Verhalten bei lokalen Arrays?

Ich denke auch, dass das zu erwarten war. Also, mache ich das ganze mal 
ohne wirklich das zu wollen:
1
static char str2[]= "\n\r Hallo uC-Community\n\r";

dann wird das von <puts_ram> korrekt angezeigt (weil ja eben nicht auf 
dem Stack sondern statisch feste Adresse).

Aaaaaaber (neue Baustelle), ich hatte ja auch das Problem bei einem 
anderen Anzeigeprogramm, bei dem ich ein uint16_t Array gebraucht habe. 
Hier funktioniert dann:
1
static uint16_t zpotenz[] = { 10000, 1000, 100, 10 };
dummerweise nicht !!!
Bspw. liefert hier ein zahl= zpotenz[1]; irgendeinen Wert, aber 
garantiert nicht 1000.

Die Adressierungen der 0-series (und die Versuche hierzu) bringen mich 
schier um den Verstand.

@Jörg:  Der Grundstamm des Buildprozesses bei mir (weil du ein Problem 
im Linkerscript vermutest) ist für die Standard-AVR sowie für die 
tinyAVR 0-series derselbe (das gleiche Grund-Makefile, gefüttert mit 
anderen Namen der Datei die das main enthält, sowie Angaben über F_CPU 
und eben den verwendeten Chip). Dieses Makefile-Grundgerüst hat in den 
letzten Jahren immer klaglos funktioniert (was nicht heißen soll, das es 
nicht fehlerbehaftet ist)

von Peter D. (peda)


Lesenswert?

Ralph S. schrieb:
> Der Grundstamm des Buildprozesses bei mir (weil du ein Problem
> im Linkerscript vermutest) ist für die Standard-AVR sowie für die
> tinyAVR 0-series derselbe

Nö, muß unterschiedlich sein.
Wie schon gesagt, die tinyAVR 0 können kein LPM.

von Ralph S. (jjflash)


Lesenswert?

Peter D. schrieb:
> Nö, muß unterschiedlich sein.
> Wie schon gesagt, die tinyAVR 0 können kein LPM.

Okay (und das ist ein fragendes okay), was ist an dem oben beschriebenen 
Buildprozess falsch? Ich hatte das extra ohne hinzuzulinkende Dateien 
gemacht um zusätzliche Fehlerquellen zu verhindern. Hier ist also 
momentan nur eine einzig zu kompilierende Datei vorhanden und ein 
einziger Linkeraufruf für eben diese eine *.o Datei.

von Ralph S. (jjflash)


Lesenswert?

( mit Geundstamm meinte ich das Aufrufen des Compilers und Linkers mit 
zusätzlichen Angaben für bspw. defines wie F_CPU und nicht die für 
unterschiedliche Controller notwendige Compiler und Linkerflags ... oder 
gar andere Softwarebibliotheken)

von Martin W. (martinw0)


Lesenswert?

Peter D. schrieb:
> Nö, muß unterschiedlich sein.
> Wie schon gesagt, die tinyAVR 0 können kein LPM.

Das ist auch gut so.
Das Mapping von Flash und Eeprom in den Speicheraum ist sowieso viel 
eleganter. Es ist nicht Schuld der neuen AVRs daß C-Compiler bzw. deren 
Umgebung sich noch nicht auf diese bessere Architektur eingestellt 
haben.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Martin W. schrieb:
> Das Mapping von Flash und Eeprom in den Speicheraum ist sowieso viel
> eleganter.

Für Eleganz kann man sich aber nichts kaufen. Es muß erstmal jemand die 
Änderungen implementieren.
Hat denn schonmal jemand das neueste Michrochip Studio installiert, ob 
es damit geht?

von S. L. (sldt)


Lesenswert?

Ein ATtiny1604 soll kein 'lpm' können - wie ist das gemeint?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ralph S. schrieb:

>> Ich würde das Problem im Linkerscript vermuten …
>
> genau aus dem Grund habe ich das komplette Progrämmchen mit angehängt
> und auch absichtlich nicht eine einzige Datei hinzugelinkt.

Weiß nicht, was du damit meinst. Der Linkerscript ist jedenfalls nicht 
dabei. …

> Der abschließende Object-Copy:
>
1
avr-objcopy -j .text -j .data -O ihex puts_vers.elf 
2
> puts_vers.hex

Damit kannst du dir zumindest schon mal sicher sein, dass .rodata auf 
keinen Fall mit im Flash landet. ;-)

Frage: warum überhaupt erst das objcopy und nicht gleich das ELF-File 
benutzen?

Bei letzterem hängt es allerdings von der AVRDUDE-Version ab, wie das 
behandelt wird. Traditionell wurde auch da nur eine Section (dann 
typisch .text) geflasht. Jüngere Änderungen flashen alle ladbaren 
Sections aus dem ELF-File, damit könnte das sogar so funktionieren, wie 
es jetzt ist.

Muss ich aber auch erstmal verifizieren.

von Martin W. (martinw0)


Lesenswert?

S. L. schrieb:
> Ein ATtiny1604 soll kein 'lpm' können - wie ist das gemeint?

Du hast Recht.
Das kann er immer noch (Flashstart Adresse in diesem Falle 0).

von C-hater (c-hater)


Lesenswert?

Peter D. schrieb:

> Ralph S. schrieb:
>> Der Grundstamm des Buildprozesses bei mir (weil du ein Problem
>> im Linkerscript vermutest) ist für die Standard-AVR sowie für die
>> tinyAVR 0-series derselbe
>
> Nö, muß unterschiedlich sein.
> Wie schon gesagt, die tinyAVR 0 können kein LPM.

Also, es ging lt. OT um den ATtiny1604. Und der kann lt. aktuellem 
Datenblatt (Seite 536) durchaus lpm in allen üblichen Adressierungsarten 
(und sogar einer mehr, wenn man die "klassischen" AVR8 als Bezug nimmt). 
Und die Errata sagen auch nichts Gegenteiliges.

Nun habe ich zwar kein reales Objekt, um das zu überprüfen, aber ich 
würde bis zum Beweis des Gegenteils mal davon ausgehen, dass das 
Datenblatt hier Recht hat, selbst eins von MC...
So ein gravierender Fehler hätte ziemlich sicher bereits irgendwelche 
belastbaren Spuren im Web hinterlassen. So ganz neu ist die Tiny-0-Serie 
ja nun auch wieder nicht. Auch wäre dann sehr spannend, was die 
lpm-Opcodes dann tatsächlich tun...

Anstatt in C rumzupröbeln, sollte man das also erstmal in pure Asm 
überprüfen, notfalls mit manuell lt. InstructionSetReference codierten 
Opcodes. Wenn man so ein Target verfügbar hat, ist das ja nun wirklich 
kinderleicht.

Wenn dabei rauskommt, dass das DB lügt, dann isses halt so. Ich 
bezweifele  aber ernsthaft, dass es wirklich so ist, räume dem aber 
immerhin eine gewisse Wahrscheinlichkeit ein. Es gab ja im AVR-Assembler 
leider schon immer etliche gefakte Memnonics. Es wäre also nicht völlig 
auszuschließen, dass der Assembler die lpm's bei dem entsprechenden 
Target letztlich zu irgendwelchen ld*-Opcodes auflöst (eben um 
lpm-Errata zu verbergen). Das wäre natürlich äußerst fies und völlig 
unentschuldbar.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ob das hier so richtig ist bezweifel ich.
Was macht c im Schleifenkörper? Bspw. Abbruchbedingung?
Könnte Zufallseffekte auslösen.
1
#define puts_rom(str)      (uart_puts_rom(PSTR(str)))
2
void uart_puts_rom(const uint8_t *dataPtr)
3
{
4
  uint8_t c;
5
  for (c=pgm_read_byte(dataPtr); c; ++dataPtr, c=pgm_read_byte(dataPtr))
6
    uart_putchar(c);
7
}

Bei mir sieht das so aus. Grundlage war/ist die UART Lib von Peter 
Fleury.
1
/*************************************************************************
2
Function: putp()
3
Purpose:  transmit string from program memory to UART
4
Input:    program memory string to be transmitted
5
Returns:  none
6
**************************************************************************/
7
#define putString_P(__s)       puts_p(PSTR(__s))
8
  
9
void puts_p(const char *progmem_s )
10
{
11
  volatile char c {0};
12
  
13
  while ( (c = pgm_read_byte(progmem_s++)) )
14
  putChar(c);
15
}

von Georg M. (g_m)


Lesenswert?

S. L. schrieb:
> Das Programm zeigt auf einem ATmega4809 dasselbe Fehlverhalten; auf
> einem AVR128DB28 hingegen funktioniert es korrekt.

Ja, es gibt einige Unterschiede.

Missing Instructions:

ATtiny402: CALL, JMP, ELPM, SPM, SPM Z+, EIJMP, EICALL

ATtiny1604: ELPM, EIJMP, EICALL, SPM, SPM Z

ATmega4809: ELPM, SPM, SPM Z+, EIJMP, EICALL

AVR128DB28: EIJMP, EICALL

https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf


Aber muss man das alles wissen?

von C-hater (c-hater)


Lesenswert?

Georg M. schrieb:

> ATtiny1604: ELPM, EIJMP, EICALL, SPM, SPM Z

Also: Es fehlt definitiv nicht: LPM! Und das ist, worum es hier (wenn 
überhaupt) geht.

Dass nicht jeder AVR8 jede AVR8-Instruktion unterstützt, ist eine 
Tatsache, die so alt ist, wie die AVR8 selber...

von Peter D. (peda)


Lesenswert?

C-hater schrieb:
> Und der kann lt. aktuellem
> Datenblatt (Seite 536) durchaus lpm in allen üblichen Adressierungsarten

Also im DS40002312A (© 2021 Microchip Technology Inc.) ist das 
Instruction Set Summary nicht mehr enthalten.
Nur im vorläufigen DS40002028A (© 2018 Microchip Technology Inc.).

LPM kann er immer noch:
"The entire Flash memory is mapped in the memory space and is accessible 
with normal LD/ST instructions as well as the LPM instruction. For LD/ST 
instructions, the Flash is mapped from address 0x8000. For the LPM 
instruction, the Flash start address is 0x0000."

Es könnte noch ein Compilerfehler mit dem Offset 0x8000 bestehen.

Kein LPM können aber die ATtiny4..10 und deren Offset ist 0x4000.

von S. L. (sldt)


Lesenswert?

> ... Aber muss man das alles wissen?

Als C-Programmierer doch hoffentlich nicht, oder?

Mein erster Beitrag zielte darauf ab, dass das C-Compilat für den zur 
gleichen Gruppe gehörenden ATmega4809 auch den Fehler bringt, während 
für die neueste Gruppe des AVR128DB28 korrekt übersetzt wird.

> Also im DS40002312A (© 2021 Microchip Technology Inc.)
> ist das Instruction Set Summary nicht mehr enthalten.

Aber dort steht der Verweis auf das 'AVR® Instruction Set Manual' mit 
der Gruppe ('CPU version') 'AVRxt'.

von C-hater (c-hater)


Lesenswert?

Peter D. schrieb:

> Also im DS40002312A (© 2021 Microchip Technology Inc.) ist das
> Instruction Set Summary nicht mehr enthalten.

Da ist tatsächlich nur noch ein Link auf die allgemeine ISR enthalten. 
Aber keinerlei Hinweis (explizit oder implizit), dass lpm nicht 
unterstützt werden würde. Wie also kommst du darauf, das mit dem 
Brustton der völligen Überzeugung zu behaupten?

> Nur im vorläufigen DS40002028A (© 2018 Microchip Technology Inc.).

Ja. Das war das, wo ich nachgeschaut habe. Das DB mit dem kleinsten 
Focus. Nur 804 und 1604.

von Peter D. (peda)


Lesenswert?

C-hater schrieb:
> Wie also kommst du darauf, das mit dem
> Brustton der völligen Überzeugung zu behaupten?

Wie hört man denn einen "Brustton" bei einem sachlich geschrieben Text 
heraus?
Ich habs doch korrigiert, siehe mein Zitat aus dem Datenblatt:
Peter D. schrieb:
> LPM kann er immer noch:
> "The entire Flash memory is mapped in the memory space and is accessible
> with normal LD/ST instructions as well as the LPM instruction. For LD/ST
> instructions, the Flash is mapped from address 0x8000. For the LPM
> instruction, the Flash start address is 0x0000."

Warum kann niemand einen Post vollständig lesen und verstehen?

: Bearbeitet durch User
von C-hater (c-hater)


Lesenswert?

Peter D. schrieb:

> Wie hört man denn einen "Brustton" bei einem sachlich geschrieben Text
> heraus?

Also für mich hört sich eine Äußerung wie (Originalzitat):

> Wie schon gesagt, die tinyAVR 0 können kein LPM.

dann doch so an, als wenn da eine gewisse Überzeugung der sachlichen 
Korrektheit dahinter stehen würde...

Und das hat wohl nicht nur mich ordentlich irritiert, wie man an 
weiteren Beiträgen im Thread sehen kann, z.B. hier:

Beitrag "Re: Fehlerhafte Adressierung von lokalen Arrays bei tinyAVR(R) 0-series"

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

S. L. schrieb:
> Mein erster Beitrag zielte darauf ab, dass das C-Compilat für den zur
> gleichen Gruppe gehörenden ATmega4809 auch den Fehler bringt, während
> für die neueste Gruppe des AVR128DB28 korrekt übersetzt wird.

Es wird für beide korrekt übersetzt. Ich habe doch schon dargelegt, dass 
das Problem weiter hinten in der Kette liegt.

Ein AVR mit mehr als 32 KiB Flash kann zwangsweise das Mapping des 
Flashs in den linearen Adressraum, wie es bei den kleinen AVR0 gemacht 
wird, nicht mehr haben. Daher bleibt für den ausschließlich der 
klassische Weg über LPM übrig.

Bei den kleineren nutzt der Compiler das Flash-Mapping aus (was ja Sinn 
hat, wofür ist es sonst da?). Nun muss man nur noch den Rest der 
Toolchain dazu bringen, dass er die Daten auch korrekt in den Chip 
bekommt. Einen Fehler in deiner Toolchain habe ich dir schon gezeigt 
(dem Hexfile fehlen die Initialisierungsdaten). Den Rest schaue ich mir 
nochmal an.

von S. L. (sldt)


Lesenswert?

an Jörg Wunsch:

Also bekanntlich habe ich von C keine Ahnung. Ich schaue hier nur höchst 
interessiert zu.

> Fehler in deiner Toolchain habe ich dir schon gezeigt
Mir haben Sie nichts gezeigt, würde es ohnehin nicht verstehen, das war 
wohl jemand anders.

> Ein AVR mit mehr als 32 KiB Flash ...
Der ATmega4809 hat 48 KiB.

von Martin W. (martinw0)


Lesenswert?

Jörg W. schrieb:
> Ein AVR mit mehr als 32 KiB Flash kann zwangsweise das Mapping des
> Flashs in den linearen Adressraum, wie es bei den kleinen AVR0 gemacht
> wird, nicht mehr haben. Daher bleibt für den ausschließlich der
> klassische Weg über LPM übrig.

Doch, kann er.
Die eingeblendete Page kann konfiguriert werden.

von C-hater (c-hater)


Lesenswert?

S. L. schrieb:

>> Ein AVR mit mehr als 32 KiB Flash ...
> Der ATmega4809 hat 48 KiB.

Das ist sicher so. Nur ging es im Thread nicht um einen Mega4809, 
sondern um einen Tiny1604...

Und der hat halt nur 16kB Flash und ist deshalb durchaus in der Lage, 
diesen Speicherbereich vollständig in den SRAM-Bereich zu mappen.

Und genau das dürfte auch das Problem sein. Die Hardware kann es, der 
Compiler nutzt es...

von C-hater (c-hater)


Lesenswert?

Martin W. schrieb:

> Jörg W. schrieb:
>> Ein AVR mit mehr als 32 KiB Flash kann zwangsweise das Mapping des
>> Flashs in den linearen Adressraum, wie es bei den kleinen AVR0 gemacht
>> wird, nicht mehr haben. Daher bleibt für den ausschließlich der
>> klassische Weg über LPM übrig.
>
> Doch, kann er.
> Die eingeblendete Page kann konfiguriert werden.

Ja, aber gerade hier gibt es einige (immerhin wohldokumentierte) 
Hardware-Bugs. Für das konkrete Problem dieses Threads sind die aber 
vollkommen irrelavant. Der Tiny1604 ist klein genug, um seinen gesamten 
Flash 1:1 im SRAM-Adressraum abzubilden. Ist also gerade nicht von den 
Bugs bei den größeren Teilen betroffen, die das nur pagewise können.

von S. L. (sldt)


Lesenswert?

an C-hater:

Ja, und das passt ja: auch beim ATmega4809 kann alles direkt in den 
SRAM-Bereich gemappt werden; und er bringt denselben Fehler wie der 
ATtiny1604.
  Es war eben nur die Aussage mit den 32 KiB falsch; hinzu kommt der 
Einwand von Martin W., dass per 'Flash Section Mapping' alles möglich 
ist.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jörg W. schrieb:
>> Der abschließende Object-Copy:
>
> avr-objcopy -j .text -j .data -O ihex puts_vers.elf
>
>> puts_vers.hex
>
> Damit kannst du dir zumindest schon mal sicher sein, dass .rodata auf
> keinen Fall mit im Flash landet. ;-)

Als erste Abhilfe, probier mal:
1
avr-objcopy -j .text -j .rodata -j .data -O ihex puts_vers.elf puts_vers.hex

Ob's direkt mit dem ELF-File (ohne objcopy) auch klappt, schau ich mir 
nochmal an.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Martin W. schrieb:
> Die eingeblendete Page kann konfiguriert werden.

Das hilft nur beschränkt, denn dann müsste der Compiler das ja alle 
nasenlang umkonfigurieren. Ich habe mir die Codegenerierung jetzt nicht 
angesehen, gehe aber einfach mal davon aus, dass er für alle MCUs mit 
mehr als 32 KiB, bei denen das simple Mapping ab 0x8000 nicht 
funktioniert, dann schlicht alles wieder mit LPM macht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ralph S. schrieb:
> Für diejenigen, die immer nach dem gesamten Quellcode "schreien" habe
> ich das gesamte "Programm" mit angehängt und extra auf das nötigste
> (hier eine UART-Anbindung) reduziert.

Danke übrigens nochmal, dass du das auf ein minimalistisches, 
compilierbares Beispiel herunter gebrochen hast, das das Problem 
demonstriert. Das hat bei der Suche nach der Ursache gut geholfen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Warum eigentlich so kompliziert:
1
/usr/bin/avr-gcc -DNDEBUG -Os -std=gnu2x -Wall -Wextra -Wconversion -DF_CPU=20000000 -mmcu=attiny1604 bm03.c --output bm03.elf

und
1
avr-objcopy -O ihex -R .eeprom bm03.elf bm03.hex

sollte es doch tun (sorry: der Dateiname ist hier anders).

von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> Danke übrigens nochmal, dass du das auf ein minimalistisches,
> compilierbares Beispiel herunter gebrochen hast, das das Problem
> demonstriert. Das hat bei der Suche nach der Ursache gut geholfen.

Naja: minimal ist das Beispiel keineswegs. Einiges hätte man noch 
weglassen können.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> void puts_p(const char *progmem_s )
> {
>   volatile char c {0};
>
>   while ( (c = pgm_read_byte(progmem_s++)) )
>   putChar(c);
> }

Ist das ein "Angst"-volatile?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> sollte es doch tun

Nur, wenn du nicht auch noch Fuses drin hast.

Ich mag dieses "nimm alles außer" nicht so sehr.

Das Unkomplizierteste wäre eigentlich, wenn AVRDUDE das direkt aus dem 
ELF korrekt handhabt. Muss ich aber noch gucken (nicht heute Abend, habe 
gerade noch anderes vor).

von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> Wilhelm M. schrieb:
>> sollte es doch tun
>
> Nur, wenn du nicht auch noch Fuses drin hast.

Davon war bisher nicht die Rede. Wie war das mit dem MVCE?

> Ich mag dieses "nimm alles außer" nicht so sehr.

War nur ein Vorschlag, Unnötiges wegzulassen.

> Das Unkomplizierteste wäre eigentlich, wenn AVRDUDE das direkt aus dem
> ELF korrekt handhabt.

Kann avrdude mittlerweile einfach via usb/serial UPDI programmieren?

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Kann avrdude mittlerweile einfach via usb/serial UPDI programmieren?

Ich denke schon. Schau doch einfach in den Releasenotes nach. ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Das Unkomplizierteste wäre eigentlich, wenn AVRDUDE das direkt aus dem
> ELF korrekt handhabt.

Tut es, zumindest in Version 7.1 aufwärts. Habe zwar hier keinen dieser 
kleinen ATtinys, aber ich habe mal testhalber das ELF-File auf einen 
AVR128DA48 geflasht, und ich sehe im Flash hinterher alle Strings. Das 
heißt, dass er auch die .rodata ordentlich geschrieben hat.

ps: Kommandozeile:
1
avrdude -c pkobn_updi -p attiny1604 -U puts_vers.elf

("pkobn_updi" wäre jetzt ein Curiosity Nano Board.)

: Bearbeitet durch Moderator
von Ralph S. (jjflash)


Lesenswert?

Jörg W. schrieb:
> Tut es, zumindest in Version 7.1 aufwärts. Habe zwar hier keinen dieser
> kleinen ATtinys, aber ich habe mal testhalber das ELF-File auf einen
> AVR128DA48 geflasht, und ich sehe im Flash hinterher alle Strings. Das
> heißt, dass er auch die .rodata ordentlich geschrieben hat.
>
> ps: Kommandozeile:
> avrdude -c pkobn_updi -p attiny1604 -U puts_vers.elf

Funktioniert auch bei mir mit einer ELF-Datei...

und natürlich höchstpeinlich für mich:

Jörg W. schrieb:
>> Der abschließende Object-Copy:
>>avr-objcopy -j .text -j .data -O ihex puts_vers.elf
>> puts_vers.hex
>
> Damit kannst du dir zumindest schon mal sicher sein, dass .rodata auf
> keinen Fall mit im Flash landet. ;-)

dieses avr-objcopy ist aus einer alten Datei reingerutscht und hätte 
hier gar nicht auftauchen sollen und mir ist auch nicht aufgefallen, 
dass das auf .text und auf .data begrenzt ist. Das wird auch bei 
vorherigen Versuchen das gewesen sein, dass ich beim ersten Setup einer 
12.1 Compilertoolchain mit avr-gcc noch eine richtiges avr-objcopy 
eingerichtet hatte und danach eben ein altes, falsch parametriertes 
avr-objcopy aus einem anderen Projekt genommen hatte.

Jörg W. schrieb:
> Als erste Abhilfe, probier mal:
> avr-objcopy -j .text -j .rodata -j .data -O ihex puts_vers.elf
> puts_vers.hex

Natürlich funktioniert das, aber auch ein:
1
 avr-objcopy -O ihex puts_vers.elf

funktioniert !

Und wie gesagt: Höchst peinlich für mich. Andererseits: wieder etwas 
gelernt (und wieder mal den Fehler wo anderst gesucht als er war .... )
:-) das einzige das noch funktioniert hat war mein "Instinkt" der mir 
sagt: "Ich habe was falsch gemacht"

Jörg W. schrieb:
> Danke übrigens nochmal, dass du das auf ein minimalistisches,
> compilierbares Beispiel herunter gebrochen hast, das das Problem
> demonstriert. Das hat bei der Suche nach der Ursache gut geholfen.

:-) hier lernt man, sein Problem so umfassend es geht zu beschreiben. 
Leider wird dann das Eröffnungsthread bisweilen recht lang.

Ich bedanke mich bei allen Diskutanten (vor allem bei Jörg). Gehabt es 
Euch wohl und eine angenehme Nachtruhe

von Veit D. (devil-elec)


Lesenswert?

Hallo,

könnte mir mal bitte jemand die Abbruchbedingung erklären? Wann wird die 
Schleife beendet? Danke.
1
void uart_puts_rom(const uint8_t *dataPtr)
2
{
3
  uint8_t c;
4
  for (c=pgm_read_byte(dataPtr); c; ++dataPtr, c=pgm_read_byte(dataPtr))
5
    uart_putchar(c);
6
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> könnte mir mal bitte jemand die Abbruchbedingung erklären? Wann wird die
> Schleife beendet? Danke.
>
1
> void uart_puts_rom(const uint8_t *dataPtr)
2
> {
3
>   uint8_t c;
4
>   for (c=pgm_read_byte(dataPtr); c; ++dataPtr, c=pgm_read_byte(dataPtr))
5
>     uart_putchar(c);
6
> }
7
>

c == '\0'

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wilhelm M. schrieb:
> Veit D. schrieb:
>> void puts_p(const char *progmem_s )
>> {
>>   volatile char c {0};
>>
>>   while ( (c = pgm_read_byte(progmem_s++)) )
>>   putChar(c);
>> }
>
> Ist das ein "Angst"-volatile?

Hast Du das dazu gesetzt?

von Ralph S. (jjflash)


Lesenswert?

Eigentlich dachte ich, der Thread hier ist abgeschlossen, da das (mein) 
Problem gelöst und mein Fehler durch Hilfe des Forums gefunden wurde.

Allerdings fühle ich mich irgendwie doch genötigt auf einen 
"Zwischenruf" zu antworten:

Veit D. schrieb:
> Hallo,
>
> könnte mir mal bitte jemand die Abbruchbedingung erklären? Wann wird die
> Schleife beendet? Danke.void uart_puts_rom(const uint8_t *dataPtr)
> {
>   uint8_t c;
>   for (c=pgm_read_byte(dataPtr); c; ++dataPtr, c=pgm_read_byte(dataPtr))
>     uart_putchar(c);
> }

Zum Einen ging es hier nie darum, einen String aus dem Flashrom 
anzuzeigen, denn dieses funktionierte ja und diente nur zur 
Verdeutlichung, dass etwas funktioniert.

Veit D. schrieb:
> Hallo,
>
> ob das hier so richtig ist bezweifel ich.
> Was macht c im Schleifenkörper? Bspw. Abbruchbedingung?
> Könnte Zufallseffekte auslösen.

Die Schleife hier stammt aus meinen Anfängen der AVR-Zeit und habe ich 
nach deren funktionieren nicht mehr geändert und werde es auch nicht tun 
(never change a running system, egal wie trivial etwas ist).

:-) ... grundsätzlich besteht eine For-Schleife aus 3 Teilen:
- Startbedingung
- Abbruchbedingung
- Bedingung(en) die bei einem Schleifenende ausgeführt wird.

Die erste Bedingung der Schleife ist klar: es wird das Zeichen gelesen, 
auf den der Pointer gerade zeigt.

Die Abbruchbedingung ist eigentlich einfach. In C liefert ein Vergleich, 
bspw. ein "if (i> 9) " zu einer 1 oder 0, je nachdem ob eben i> 9 
(liefert eine 1) oder eben nicht > 9 ist (liefert eine 0). Für 
Abbruchbedingungen aber auch der Bedingung (if) ob etwas ausgeführt 
werden soll oder nicht, wird nur die 0 kontrolliert. Das will heißen, 
dass jeglicher Wert > 0 als 1 gewertet wird, selbst dann wenn der Wert 
bspw. 5 oder 120 sein sollte. Es wird also nur etwas gecheckt nach der 
Art und Weise:

==> ist etwas 0, wenn ja dann, wenn nicht, dann...

In meiner Schleife wurde in die Variable c eben ein Zeichen des Strings 
eingelesen. Bei den Strings in C handelt es sich (meistens) um 
sogenannte Ascii-Zero Strings. D.h. das Ende eines Strings wird mit 
einem Char mit dem Wert 0 markiert. Steht dieser Wert nun in der 
Abbruchbedingung der For-Schleife, wird diese nicht weiter ausgeführt.

Die Bedingungen am Schleifenende sollten klar sein, sie bestehen aus 2 
Anweisungen:

- den Zeiger auf das nächste Zeichen setzen ( ++dataPtr, ) und das 
nächste Zeichen zu lesen ( c=pgm_read_byte(dataPtr)

1000 Wege führen nach Rom und es gibt viele Wege, einen AsciiZ String 
anzuzeigen, aber wie gesagt: Never change a running system... und diese 
Funktion hat schon viele male funktioniert.

:-) in diesem Sinne hoffe ich, dass dieser Thread jetzt abgeschlossen 
ist !

Gruß, JJ

von Wilhelm M. (wimalopaan)


Lesenswert?

Ralph S. schrieb:
> Allerdings fühle ich mich irgendwie doch genötigt auf einen
> "Zwischenruf" zu antworten:

War schon längst beantwortet:

Beitrag "Re: Fehlerhafte Adressierung von lokalen Arrays bei tinyAVR(R) 0-series"

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Wilhelm M. schrieb:
> Wilhelm M. schrieb:
>> Veit D. schrieb:
>>> void puts_p(const char *progmem_s )
>>> {
>>>   volatile char c {0};
>>>
>>>   while ( (c = pgm_read_byte(progmem_s++)) )
>>>   putChar(c);
>>> }
>>
>> Ist das ein "Angst"-volatile?
>
> Hast Du das dazu gesetzt?

Die originale Funktion von Peter Fleury lautete so. Vorab. Peter trifft 
hier keine Schuld. Ist für C programmiert wurden und schon viele Jahre 
alt. Nicht  das es jemand falsch versteht.
1
/*************************************************************************
2
Function: uart_puts_p()
3
Purpose:  transmit string from program memory to UART
4
Input:    program memory string to be transmitted
5
Returns:  none
6
**************************************************************************/
7
void uart_puts_p(const char *progmem_s )
8
{
9
    register char c;
10
    
11
    while ( (c = pgm_read_byte(progmem_s++)) ) 
12
      uart_putc(c);
13
14
}/* uart_puts_p */

Da "register" mittlerweile deprecated ist, hatte ich das anscheinend 
damals mit volatile ersetzt und seitdem nie geändert. Das werde ich 
jetzt nachholen und löschen. Danke für den Hinweis. Die Funktion ist 
wenigstens lesbar.  ;-)

von Rolf M. (rmagnus)


Lesenswert?

Veit D. schrieb:
> Da "register" mittlerweile deprecated ist, hatte ich das anscheinend
> damals mit volatile ersetzt und seitdem nie geändert.

volatile ist so ziemlich das Gegenteil von register und im Gegensatz zu 
diesem auch verpflichtend für den Compiler.

von 900ss (900ss)


Lesenswert?

Nein, bitte nicht noch eine Diskussion über volatile auch wenn Wilhelm 
M. da gerne ganze Abhandlungen drüber schreibt.
Hat er in anderen Threads zur genüge und ist hier OT.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Sowas wollte ich damit gar nicht starten. Es ging mir nur darum, dass es 
eine etwas ungewöhnliche Wahl für den Ersatz von register ist.

von 900ss (900ss)


Lesenswert?

Rolf M. schrieb:
> Sowas wollte ich damit gar nicht starten

Du vielleicht nicht :)

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Veit D. schrieb:
>> Da "register" mittlerweile deprecated ist, hatte ich das anscheinend
>> damals mit volatile ersetzt und seitdem nie geändert.
>
> volatile ist so ziemlich das Gegenteil von register und im Gegensatz zu
> diesem auch verpflichtend für den Compiler.

900ss D. schrieb:
> Nein, bitte nicht noch eine Diskussion über volatile auch wenn Wilhelm
> M. da gerne ganze Abhandlungen drüber schreibt.
> Hat er in anderen Threads zur genüge und ist hier OT.

Die Antworten zeigen jedoch - sagen wir mal - einen gewissen 
Nachholbedarf. Ich denke, dass Veit D. das vllt auch noch anderswo 
falschermaßen eingesetzt hat. und nun froh über den Hinweis ist.

von Ralph S. (jjflash)


Lesenswert?

Wilhelm M. schrieb:
> Die Antworten zeigen jedoch - sagen wir mal - einen gewissen
> Nachholbedarf. Ich denke, dass Veit D. das vllt auch noch anderswo
> falschermaßen eingesetzt hat. und nun froh über den Hinweis ist.

Hat jetzt aber mit dem Threadtitel so gar nichts zu tun...

von Wilhelm M. (wimalopaan)


Lesenswert?

Ralph S. schrieb:
> Wilhelm M. schrieb:
>> Die Antworten zeigen jedoch - sagen wir mal - einen gewissen
>> Nachholbedarf. Ich denke, dass Veit D. das vllt auch noch anderswo
>> falschermaßen eingesetzt hat. und nun froh über den Hinweis ist.
>
> Hat jetzt aber mit dem Threadtitel so gar nichts zu tun...

Das mag sein. Und ist bei den meisten Threads hier so.

Auch zu dem Code des TO könnte man ja noch mehr sagen. Die gröbsten 
Schnitzer sollte man auch ansprechen, selbst wenn nicht explizit danach 
gefragt wurde.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich kann nicht mehr sagen wann und warum ich dort volatile verwendet 
hatte. Das ist auch schon paar Jahre her. Mittlerweile denke ich 
volatile & Co verstanden zu haben. Ohne den Hinweis wäre es mir 
vielleicht nie aufgefallen. Danke nochmal.
Das Letzte volatile (volatile uint8_t *) in meiner USART Lib wird für 
Hardwareregisterzugriffe verwendet.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Das Letzte volatile (volatile uint8_t *) in meiner USART Lib wird für
> Hardwareregisterzugriffe verwendet.

Wobei das normalerweise bereits aus den Headerfiles kommen sollte.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

habe nachgeschaut. Bei modernen Controllern wie ATmega4809 steht 
folgendes im Headerfile. Ohne Zeiger!
1
typedef volatile uint8_t  register8_t;
2
typedef volatile uint16_t register16_t;
3
typedef volatile uint32_t register32_t;

In Headerfiles älterer Controller wie ATmega328P oder ATmega2560 sehe 
ich nichts dergleichen, weil das ist was anderes
1
#define _MMIO_BYTE (mem_addr) (*(volatile uint8_t  *)(mem_addr))
2
#define _MMIO_WORD (mem_addr) (*(volatile uint16_t *)(mem_addr))
3
#define _MMIO_DWORD(mem_addr) (*(volatile uint32_t *)(mem_addr))

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Ohne Zeiger!

Das hängt damit zusammen, dass dort die Registerblöcke der einzelnen 
Geräte jeweils in einer Struktur zusammengefasst werden. Wenn es das 
Gerät in mehreren Instanzen gibt, bleibt die struct die gleiche, nur die 
Adresse ändert sich. Der Zeiger wird daher erst dann gebildet (und zeigt 
auf die struct), wenn das Gerät instanziiert wird.

Bei den alten AVRs war das noch nicht durchgehend so, daher wurden alle 
Register eines jeden Geräts alle einzeln benannt (TCCR1A, TCCR3B etc.) 
und dann jeweils über einen solchen (dereferenzierten) Zeiger 
abgebildet.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

jetzt musste ich erstmal nachdenken ...  :-)
Im eigenen Code
1
volatile uint8_t *
ersetzen durch
1
register8_t *
Okay kann ich machen. Da schreibe ich für die ersten Jahre einen 
Kommentar dahinter bis sich das gesetzt hat.   :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Im eigenen Code
>
1
> volatile uint8_t *
2
>
> ersetzen durch
>
1
> register8_t *
2
>

Zeig mal ein Beispiel, wo Du glaubst, dass das notwendig wäre. Könnte 
sein, dass da ein Missverständnis vorliegt.

Normalerweise ist das bei den etwas moderneren AVRs so, das die 
Register, die zu einer internen Peripherie-Komponente gehören, 
zusammenhängend in den Adressbereich eingeblendet werden, so dass man 
sie sinnvoll in einem struct zusammenfassen kann (bei den alten megas 
lagen die teilweise verstreut). Anschließend mapped man diese struct auf 
die Anfangsadresse der Komponente, also man castet die Adresse des 
ersten Registers auf den Typ struct XYZ*. Damit liegen alle 
Strukturkomponenten dann auch an den richtigen Adressen. Dieses 
Verfahren nennt sich structure-mapping.

Beispiel AVR128DA64:
1
typedef struct AC_struct
2
{
3
    register8_t CTRLA;  /* Control A */
4
    register8_t CTRLB;  /* Control B */
5
    register8_t MUXCTRL;  /* Mux Control A */
6
    register8_t reserved_1[2];
7
    register8_t DACREF;  /* DAC Voltage Reference */
8
    register8_t INTCTRL;  /* Interrupt Control */
9
    register8_t STATUS;  /* Status */
10
} AC_t;

und dann findet folgendes Mapping statt:
1
#define AC0                    (*(AC_t *) 0x0680) /* Analog Comparator */
2
#define AC1                    (*(AC_t *) 0x0688) /* Analog Comparator */
3
#define AC2                    (*(AC_t *) 0x0690) /* Analog Comparator */

D.h. hier liegt AC0.CTRLA auf der Adresse 0x0680, AC0.CTRLB auf der 
Adresse 0x0681, usw.

Natürlich ist register_t hier ein volatile-qualified uint8_t, damit der 
Zugriff hierauf auch tatsächlich an der Stelle im Code ausgeführt wird, 
wo er steht, weil er einen Seiteneffekt auslöst, und v.a. keine 
Umsortierung gegenüber anderen volatile-Zugriffen stattfindet. Denn die 
Reihenfolge des Registerzugriffs ist ja wesentlich für die korrekte 
Funktion.

Damit sollte eigentlich alles ok sein.

An welcher Stelle (außer ggf. Objekte, die zwischen ISRs und Rest 
geteilt werden) meinst Du denn sonst noch volatile einsetzen zu müssen?

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

ich hoffe der Auszug reicht aus.
Ich hole mir die Registeradresse bspw. mittels
1
namespace PortmegaAVR0
2
{
3
  // Direction
4
  constexpr register8_t* regVPORTdir(const uint8_t pin) {
5
    using namespace Pins::Addr;
6
    return (register8_t*) (baseAddr[pin].vport + addrOffset.vDir);
7
  }
8
    
9
  // Output Level
10
  constexpr register8_t* regVPORTout(const uint8_t pin) {
11
    using namespace Pins::Addr;
12
    return (register8_t*) (baseAddr[pin].vport + addrOffset.vOut);
13
  }
14
  // ...
15
}

Hier habe ich jetzt schon den Syntax soeben ersetzt. Zwei von vielen 
immer nach gleichen Schema.

Die Adressentabelle ist wie folgt aufgebaut um sie mit obiger Funktion 
zu berechnen. Siehe Anhang.

von Wilhelm M. (wimalopaan)


Lesenswert?

In aller Kürze: bezogen auf den Typ register_t und das volatile darin 
ist das richtig.

Allerdings mal auf die schnelle noch folgende Anmerkungen. Die 
Funktionen können nicht constexpr sein, weil sie ein reinterpret_cast 
enthalten. Von dem constexpr bleibt also nur das implizite inline übrig. 
Diese Funktionen können also nie in einem constexpr-Kontext eingesetzt 
werden. Das merkst Du etwa bei:
1
    constexpr auto a = PortmegaAVR0::regVPORTdir(1);

Weiterhin kannst Du auch mit Referenzen arbeiten, dann es der Anwender 
einfacher:
1
    inline register8_t& regVPORTflag(const uint8_t pin) {
2
        using namespace Pins::Addr;
3
        return *reinterpret_cast<register8_t*>(baseAddr[pin].vport + addrOffset.vFlag);
4
    }

und dann
1
    PortmegaAVR0::regVPORTflag(pin) = 0x55;

Du solltest Dir bewusst sein, dass das recht viel Aufwand produziert, 
falls Du diese Funktionen mal nicht mit einer compile-time Konstanten 
aufrufst, etwa:
1
uint8_t pin;
2
int main() {
3
    *PortmegaAVR0::regVPORTdir(pin) = 0x55;
4
}

Damit wandert die LUT in die .roData section (RAM) und die Berechnung 
findet zur Laufzeit statt. Falls Du das nicht möchtest, so sollte man 
verhindert, dass diese Funktion zur Laufzeit ausgewertet wird. Die macht 
man normalerweise mit consteval, da Du jedoch ein reinterpret_cast 
verwendest, geht das nicht. Es bleibt dann also nur der Weg über eine 
Meta-Funktion.

... just my 2 cents.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

die berechneten Adressen pro Pin werden in Template Klassen verwendet. 
Die Objekte sind Template Klassen mit konstantem initialisierten Pin. 
Eine Pinänderung bzw. Übergabe zur Laufzeit findet nicht statt.
Bsp.
OutputPin <5> userLed;

Anwendung:
userLed.init();
userLed.toggle();
1
namespace PortmegaAVR0
2
{
3
  // Direction
4
  constexpr register8_t* regVPORTdir(const uint8_t pin) {
5
    using namespace Pins::Addr;
6
    return (register8_t*) (baseAddr[pin].vport + addrOffset.vDir);
7
  }
8
    
9
  // Output Level
10
  constexpr register8_t* regVPORTout(const uint8_t pin) {
11
    using namespace Pins::Addr;
12
    return (register8_t*) (baseAddr[pin].vport + addrOffset.vOut);
13
  }
14
  // ...
15
}

Das wird am Ende in den zur Verfügung gestellten Elementfunktionen 
verwendet. Bsp.
1
template<uint8_t pin>
2
class Pin
3
{              
4
  private:
5
    static_assert(pin < Pins::Addr::ANZAHLPINS, "pin number not available for this controller");
6
    
7
// ...
8
// Outputs
9
    void inline __attribute__((always_inline)) init() {
10
      *regVPORTdir(pin) = *regVPORTdir(pin) | getMask(pin);
11
    }
12
    
13
    void inline __attribute__((always_inline)) toggle() {
14
      *regVPORTin(pin) = getMask(pin);
15
    }
16
// ...
17
}

So wie du das beschreibst das nur implizite inline übrig bleibt habe ich 
das noch gar nicht betrachtet. Wenn ich so darüber nachdenke muss ich ja 
nicht von Anfang an einen Zeiger mitschleppen sondern nur die berechnete 
Adresse als reine Zahl. Erst am Ende wenn ich wirklich Registerinhalte 
ändere benötige ich einen Zeiger. Dann könnte man ggf. consteval 
verwenden. Da muss ich einmal in Ruhe nachdenken ob und wie ein Umbau 
möglich ist. Danke für die weitere Anmerkung.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> die berechneten Adressen pro Pin werden in Template Klassen verwendet.
> Die Objekte sind Template Klassen mit konstantem initialisierten Pin.
> Eine Pinänderung bzw. Übergabe zur Laufzeit findet nicht statt.

Das habe ich mir gedacht.

Wenn also das Argument für Deine Funktionen wie regVPORTdir() eine 
Compile-Zeit-Konstante ist, dann wird der Optimizer natürlich den 
Funktionsaufruf ebenfalls zu einem Compile-Zeit konstanten Wert 
überführen und keinen tatsächlichen Funktionsaufruf mehr ausführen zur 
Laufzeit. Alles andere wäre ein ziemlich dummer Compiler.

Diese Funktionen kannst Du aber wegen des reinterpret_cast nicht 
consteval machen.

Wenn Du nun verhindern willst, dass "jemand" diese Funktionen 
tatsächlich zur Laufzeit aufruft, und damit ein tatsächlichen Lookup in 
der Tabelle auslöst, was ja zur Folge hat, das Viel Laufzeit UND RAM 
durch die LUT verbraucht wird, dann kannst Du bspw. den Parameter zu 
einem NTTP (non-type-template-parameter) machen. Dann ist das zwar immer 
noch ein Laufzeitaufruf, allerdings ist der Index in die Tabelle dann 
eben Compile-Zeit konstant. Auch hier muss natürlich eine Phase des 
Optimizers dies Template-Funktion, die dann einen Compile-Zeit 
konstanten Wert zurück gibt, durch diese Konstante selbst ersetzen. Aber 
auch das ist natürlich für den Optimizer super easy.

Wenn Du allerdings beispielsweise auch ohne jede Optimierung besseren 
Code haben möchtest (Debugging), dann solltest Du den Code so 
formulieren, dass der Compiler auch ohne Optimizer erträglichen Code 
produziert.

Dies erreicht man, indem man eben Compile-Zeit-Berechnungen auch 
definitiv zur Compile-Zeit ausführen lässt. Leider ist in Deinem 
Anwendungsfall consteval nicht möglicht wegen reinterpret_cast.

Was bleibt, sind klassische Meta-Funktionen. Mit Meta-Funktionen kann 
man 4 unterschiedliche Arten von Abbildungen machen:

Type -> Type

Type -> Wert

Wert -> Type

Wert -> Wert

Es gibt keine festgelegte Syntax für Meta-Funktion, aber es ist üblich 
sie als Klassentemplates zu schreiben.

Nun, da Du jetzt schon einigen Code hast, denke ich, dass Du diesen 
(besseren) Weg deswegen aber nicht verfolgen wirst.

> Bsp.
> OutputPin <5> userLed;
>
> Anwendung:
> userLed.init();
> userLed.toggle();

Andere Anmerkungen:
Du verwendest das Klassentemplate als Monostate, d.h. dieses Objekt 
userLed hat keinen eigenen Zustand. Auch hier vertraust Du darauf, dass 
der Optimizer dieses Objekt eliminiert. Was auch sicher stattfindet.

Aber auch hier bin ich ein Freund davon, genau zu spezifizieren, was man 
will. Das Monostate-Pattern ist eine Krücke. Besser ist es, die 
Template-Klasse OutputPin<5> uninstantiierbar zu machen und nur static 
Elementfunktionen darin zu haben.

>
>
1
> namespace PortmegaAVR0
2
> {
3
>   // Direction
4
>   constexpr register8_t* regVPORTdir(const uint8_t pin) {
5
>     using namespace Pins::Addr;
6
>     return (register8_t*) (baseAddr[pin].vport + addrOffset.vDir);
7
>   }
8
> 
9
>   // Output Level
10
>   constexpr register8_t* regVPORTout(const uint8_t pin) {
11
>     using namespace Pins::Addr;
12
>     return (register8_t*) (baseAddr[pin].vport + addrOffset.vOut);
13
>   }
14
>   // ...
15
> }
16
>

...

Deine Abbildung [äußerer Pin -> MCU-Register-Adressen] ist eigentlich 
eine Verknüpfung von zwei Abbildungen: f1:[äußerer Pin -> MCU-Pin] und
f2:[MCU-Pin -> MCU-Register]. Die erste ist vom Board abhängig, die 
zweite von der MCU. Um es also etwas universeller zu halten, würde ich 
auch zwei Abbildungen verwenden, damit kannst Du dann wenigstens f2 
universell auch woanders benutzen

> So wie du das beschreibst das nur implizite inline übrig bleibt habe ich
> das noch gar nicht betrachtet. Wenn ich so darüber nachdenke muss ich ja
> nicht von Anfang an einen Zeiger mitschleppen sondern nur die berechnete
> Adresse als reine Zahl. Erst am Ende wenn ich wirklich Registerinhalte
> ändere benötige ich einen Zeiger. Dann könnte man ggf. consteval
> verwenden. Da muss ich einmal in Ruhe nachdenken ob und wie ein Umbau
> möglich ist. Danke für die weitere Anmerkung.

dazu s.o.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

soweit so gut, verstehe fast alles davon. Den Vorschlag zur anderen 
Auftrennung muss ich mir auch überlegen. Da hat ja jeder so seine eigene 
Denkweise. Ich werde mich damit nochmal befassen.
Danke.

von Veit D. (devil-elec)


Lesenswert?

Hallo Wilhelm,

ich habe mir mal par Gedanken gemacht mit Experimenten für die 
Basisfunktionen.
Das wäre die Basis nach alten Prinzip nur etwas umgearbeitet. Könnte man 
bestimmt wieder ein struct daraus bauen, nur dann werden die Zeilen ewig 
lang. Weiß noch nicht recht.
1
#include <iostream>
2
#include <sstream>
3
#include <bitset>
4
5
using namespace std;
6
7
template<typename T> struct BaseAddrVPORT;
8
template<> struct BaseAddrVPORT<uint16_t> {
9
  enum VPORT {
10
    A = 0x0000,
11
    B = 0x0004,
12
    C = 0x0008,
13
    D = 0x000C,
14
    E = 0x0010,
15
    F = 0x0014
16
  };
17
};
18
19
template<typename T> struct BaseAddrPORT;
20
template<> struct BaseAddrPORT<uint16_t> {
21
  enum PORT {
22
    A = 0x0400,
23
    B = 0x0420,
24
    C = 0x0440,
25
    D = 0x0460,
26
    E = 0x0480,
27
    F = 0x04A0
28
  };
29
};
30
31
// Bsp. Pin > Port Zuordnung
32
constexpr uint16_t PinVPortAddr [4] {
33
  BaseAddrVPORT<uint16_t>::VPORT::F,
34
  BaseAddrVPORT<uint16_t>::VPORT::A,
35
  BaseAddrVPORT<uint16_t>::VPORT::D,
36
  BaseAddrVPORT<uint16_t>::VPORT::B,
37
};
38
39
// Bsp. Pin > Port Zuordnung
40
constexpr uint16_t PinPortAddr [4] {
41
  BaseAddrPORT<uint16_t>::PORT::F,
42
  BaseAddrPORT<uint16_t>::PORT::A,
43
  BaseAddrPORT<uint16_t>::PORT::D,
44
  BaseAddrPORT<uint16_t>::PORT::B,
45
};
46
47
consteval uint16_t getPinVportAddr(const uint16_t n) {
48
  return PinVPortAddr[n];
49
}
50
51
constexpr uint8_t bitValence [8] = {1, 2, 4, 8, 16, 32, 64, 128};
52
53
consteval uint8_t getBitMaske(const uint8_t n) {
54
  return bitValence[n];
55
}
56
57
constexpr uint16_t offsetPinCTRL [8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
58
59
consteval uint16_t getOffsetPinCTRL(const uint16_t n) {
60
  return offsetPinCTRL[n];
61
}
62
63
64
int main()
65
{
66
  std::cout << "0x" << std::hex << PinVPortAddr[0] << std::endl;
67
  std::cout << "0x" << std::hex << PinVPortAddr[1] << std::endl;
68
  std::cout << "0x" << std::hex << PinPortAddr[2]  << std::endl;
69
  std::cout << "0x" << std::hex << PinPortAddr[3]  << std::endl;
70
71
  std::cout << "0b" << std::bitset<8>{getBitMaske(0)} << std::endl;
72
  std::cout << "0b" << std::bitset<8>{getBitMaske(3)} << std::endl;
73
  std::cout << "0b" << std::bitset<8>{getBitMaske(7)} << std::endl;
74
75
  return 0;
76
}

Mit mehr Templates bin ich hierbei abgebrochen. Hier gefallen mir die 
getAddr... Funktionen noch nicht. Es gibt kein sauberes return wenn kein 
Vergleich gültig ist.
1
#include <iostream>
2
#include <sstream>
3
#include <bitset>
4
#include <iomanip>      // std::setw
5
#include <cstdint>
6
#include <type_traits>
7
8
using namespace std;
9
10
struct PA { } pa;
11
struct PB { } pb;
12
13
template<typename T> constexpr bool isPortA (const T var) {
14
  [[maybe_unused]] var;
15
  return (is_same_v<T, PA>);
16
}
17
18
template<typename T> constexpr bool isPortB (const T var) {
19
  [[maybe_unused]] var;
20
  return (is_same_v<T, PB>);
21
}
22
23
template<typename T> uint16_t getAddrVPORT (const T port) {
24
  if constexpr (isPortA(port)) return 0x0000;
25
  if constexpr (isPortB(port)) return 0x0008;
26
}
27
28
template<typename T> uint16_t getAddrPORT (const T port) {
29
  if constexpr (isPortA(port)) return 0x0400;
30
  if constexpr (isPortB(port)) return 0x0420;
31
}
32
33
int main()
34
{
35
  std::cout << isPortA(pa) << std::endl;
36
  std::cout << isPortA(pb) << std::endl;
37
  std::cout << isPortB(pa) << std::endl;
38
  std::cout << isPortB(pb) << std::endl;
39
  std::cout << "0x" << std::hex << getAddrVPORT(pa) << std::endl;
40
  std::cout << "0x" << std::hex << getAddrVPORT(pb) << std::endl;
41
  std::cout << "0x" << std::hex << getAddrPORT(pa)  << std::endl;
42
  std::cout << "0x" << std::hex << getAddrPORT(pb)  << std::endl;
43
44
  return 0;
45
}

Desweiteren versuche ich immer selbst komplexe Zusammenhänge möglichst 
einfach zu halten. Ich habe nur leider das Gefühl alles zu 
verkomplizieren. Viel Tipparbeit für wenig Nutzen. Wie schätzt man ab 
was wann Sinn macht? Die wichtigste Frage ist allerdings. Egal wie ich 
zu meiner endgültigen Adresse komme, am Ende muss ich doch wieder einen 
Cast machen. Genau das soll doch am Ende vermieden werden wenn ich dich 
richtig verstanden hatte. Wie macht man das?

Und wegen der Trennung
äußerer Pin -> MCU-Pin
MCU-Pin -> MCU-Register

Ich habe doch eigentlich bisher auch nicht mehr Aufwand. Die 
Registertabelle (LUT) ist Controller spezifisch geschrieben. Und die 
Klassen sind allgemein gehalten und greifen auf die LUT zu. Das heißt 
alles ist für eine Controllerfamilie gültig und nur die LUTs werden 
entsprechend µC der Familie eingebunden. Worin siehst du dabei einen 
Nachteil? Irgendeine Tabelle musst du doch auch sicherlich Controller 
spezifisch immer anpassen und der Rest bleibt gleich?

Ich weiß ehrlich gesagt noch nicht wie ich weitermache bzw. weitermachen 
soll. Ich habe jetzt hier auch sicherlich keine konkreten Fragen 
gestellt, weil ich nicht weiß was ich fragen soll. Es ist zu viel offen 
an Möglichkeiten und ich weiß nicht welcher Weg wirklich Sinn macht. 
Eigentlich wollte ich irgenwann einmal bspw. Pin Objekte erstellen wie
Output <PA, 6> led0;  // Port A Bit 6

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.