Fehlerhafte Adressierung von lokalen Arrays bei tinyAVR(R) 0-series

OP #7378270
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

Angehängte Dateien:
#7378343
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.

#7378347
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.

#7378458
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.

Moderator Persönliche Seite #7378593
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 …

OP #7378732
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

OP #7378786
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)

OP #7378975
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.

Moderator Persönliche Seite #7379023
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.

#7379035
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.

#7379060
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
}
#7379064
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?

#7379072
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.

#7379079
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'.

#7379081
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.

#7379100
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 im Beitrag #7379072:

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?

#7379126
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"

Moderator Persönliche Seite #7379165
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.

#7379169
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.

#7379198
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...

#7379201
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.

#7379204
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.

Moderator Persönliche Seite #7379228
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.

Moderator Persönliche Seite #7379229
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.

Moderator Persönliche Seite #7379236
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.

#7379256
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?

Moderator Persönliche Seite #7379291
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.)

OP #7379313
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

OP #7379923
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

#7380006
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. ;-)

#7380222
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.

#7380243
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.

#7380506
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.

#7380709
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))
Moderator Persönliche Seite #7380721
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.

#7380763
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?

#7380769
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.

Angehängte Dateien:
#7380826
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.

#7381148
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.

#7381377
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.

#7390583
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

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