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:
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
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.
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.
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.
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.
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
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 …
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:
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
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?
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:
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:
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)
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.
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.
( 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)
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.
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?
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:>
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.
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).
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.
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?
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...
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?
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"
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.
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.
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.
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...
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.
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.
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:
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.
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.
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.
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?
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).
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?
Wilhelm M. schrieb:> Kann avrdude mittlerweile einfach via usb/serial UPDI programmieren?
Ich denke schon. Schau doch einfach in den Releasenotes nach. ;-)
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:
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
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?
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
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.
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. ;-)
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.
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.
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.
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...
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.
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.
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.
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.
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
typedefstructAC_struct
2
{
3
register8_tCTRLA;/* Control A */
4
register8_tCTRLB;/* Control B */
5
register8_tMUXCTRL;/* Mux Control A */
6
register8_treserved_1[2];
7
register8_tDACREF;/* DAC Voltage Reference */
8
register8_tINTCTRL;/* Interrupt Control */
9
register8_tSTATUS;/* 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?
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.
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
constexprautoa=PortmegaAVR0::regVPORTdir(1);
Weiterhin kannst Du auch mit Referenzen arbeiten, dann es der Anwender
einfacher:
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_tpin;
2
intmain(){
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.
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();
Das wird am Ende in den zur Verfügung gestellten Elementfunktionen
verwendet. Bsp.
1
template<uint8_tpin>
2
classPin
3
{
4
private:
5
static_assert(pin<Pins::Addr::ANZAHLPINS,"pin number not available for this controller");
6
7
// ...
8
// Outputs
9
voidinline__attribute__((always_inline))init(){
10
*regVPORTdir(pin)=*regVPORTdir(pin)|getMask(pin);
11
}
12
13
voidinline__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.
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.
>>
...
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.
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.
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.
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.
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