Hallo,
Ich frage mich wie z.B. PORTB implementiert ist. Das habe ich bisher
herausgefunden:
Die Addresse des Ports kann man aus dem Datenblatt entnehmen oder aus
der avrlibc. Für den ATmega8 steht sie in der Datei iom8.h und lautet
0x18.
1
#define PORTB _SFR_IO8(0x18)
Das Makro _SFR_IO8 ist in der Datei sfr_defs.h definiert und addiert je
nach verwendeten AVR noch einen Offset hinzu, in meinem Fall beträgt der
Offset 0x20. Danach wird diese Zahl (0x18+0x20) mit Hilfe eines weiteren
Makros _MMIO_BYTE als ein 8 Bit breiter Pointer mit dem Attribut
volatile interpretiert. Da es sich hier um ein Hardware Register handelt
kann der Compilier keine Annahmen für Optimierungen treffen, daher ist
das volatile notwenig.
Der so erzeuge Pointer wird sofort dereferenziert damit damit eine
einfach Zuweisung mit = möglich ist.
Man kann sich das alles zur Compilezeit mit folgenden Code anzeigen
lassen:
Nun will ich mir den erzeugten Assembler Code für diese Zeile Code
genauer ansehen: PORTB |= 0xf0;
Es werden 3 Instructionen erzeugt:
1
in r24,0x18
2
ori r24,lo8(-16)
3
out 0x18,r24
Als erstes wird ein Wert von PORTB geladen, dann die OR Verknüpfung mit
der Konstante 0xf0 durchgeführt (die hier als signed Zahl angezeigt
wird, daher die -16) und zum Schluss wird das Ergebniss wieder nach
PORTB gespeicher. Es wird das Register r24 benutzt um den Wert zwischen
zu speichern und gelesen und geschrieben wird von Addresse 0x18.
Warum wird hier im Assembler Code Addresse 0x18 benutzt und nicht 0x38?
Also 0x18+0x20. Im C Code steht eindeutig 0x18+0x20. Also muss der
Compiler hier doch zusätzliches Wissen einprogrammiert haben um den
Offset für bestimme (oder alle?) Hardware Register wieder abzuziehen.
Weis hier jemand dazu etwas?
Sobald ich wieder etwas Zeit habe, werde ich von den anderen Sachen
berichten die ich herausgefunden habe.
Grüße
Thomas H. schrieb:> Weis hier jemand dazu etwas?
Das Datenblatt deines unbekannten Prozessors.
Wobei es sich wohl um einen classic AVR handeln dürfte. Zu dem wurde im
Netz und auch hier im Forum eigentlich schon alles geschrieben, was es
zuschreiben gibt, und das sogar von jedem…
Mal ein Beispiel:
Beitrag "Adressen verstehen im Datenblatt"
Oliver
Thomas H. schrieb:> Warum wird hier im Assembler Code Addresse 0x18 benutzt und nicht 0x38?
a) in, out, cbi, sbi, sbic und sbis greifen auf I/O Register
anhand ihrer Registernummer zu.
b) Zusätzlich kann man auch über die Adresse/Zeiger 0x38 auf das
Register zugreifen, aber mit anderen Befehlen (die gleichen, die auch
auf RAM zugreifen).
Der Compiler entscheidet automatisch, welche Variante für die jeweilige
Stelle im Quelltext die günstigere ist.
> Also muss der Compiler hier doch zusätzliches Wissen einprogrammiert> haben um den Offset für bestimme (oder alle?) Hardware Register wieder> abzuziehen.
Hat er. Und der Optimizer mischt da auch noch mit.
Oliver S. schrieb:> Das Datenblatt deines unbekannten Prozessors.Thomas H. schrieb:> Für den ATmega8
OUT – Store Register to I/O Location
5.84.1 Description
Stores data from register Rr in the Register File to I/O space.
Operation:
(i) I/O(A) ← Rr
Syntax: Operands: Program Counter:
(i) OUT A,Rr 0 ≤ r ≤ 31, 0 ≤ A ≤ 63 PC ← PC + 1
16-bit Opcode:
1011 1AAr rrrr AAAA
5.84.2 Status Register (SREG) and Boolean Formula
I T H S V N Z C
– – – – – – – –
Example:
clr r16 ; Clear r16
ser r17 ; Set r17
out 0x18,r16 ; Write zeros to Port B
nop ; Wait (do nothing)
out 0x18,r17 ; Write ones to Port B
ST – Store Indirect From Register to Data Space using Index X
5.114.1 Description
Stores one byte indirect from a register to data space. The data space
usually consists of the Register File, I/O
memory, and SRAM, refer to the device data sheet for a detailed
definition of the data space.
The I/O memory space contains 64 addresses for CPU peripheral functions
as Control Regis-
ters, SPI, and other I/O functions. The I/O Memory can be accessed
directly, or as the Data
Space locations following those of the Register File, 0x20 - 0x5F.
Thomas H. schrieb:> Wie ist PORTB implementiert?
In den Headern stehen immer die RAM-Adressen, nicht die I/O-Adressen.
> Warum wird hier im Assembler Code Addresse 0x18 benutzt und nicht 0x38?
Optimierung.
0x38 ist die RAM-Adresse des SFRs, also die Adresse beim Zugriff per
LDS, STS, LD, LDD oder ST oder STD.
0x18 ist die I/O Adresse, also die Adresse die IN, OUT, SBI, CBI, SBIC,
SBIS verwenden. Falls also die Adresse zur Linktime bekannt ist UND im
Adressbereich einer I/O Instruktion liegt, kann eine I/O Instruktion
verwendet werden.
Der I/O Offset ist abhängig vom Core: 0x0 für ATxmega und 0x20 für
andere AVRs.
IN ist besser als z.B. LDS weil kleinerer Code (hängt vom Core ab) und
schneller (hängt auch vom Core ab). Pferdefuß der I/O Instruktionen
ist, dass sie nur einen kleinen Speicherbereich abdecken: I/O-Adressen
0x0..0x3f (IN und OUT) bzw. I/O-Adressen 0x0..0x1f (andere I/O
Instruktionen), wohingegen "normale" Schreib-Lases Operationen einen
16-Bit Adressbereuch haben (Ausnahmen: AVRrc und Operationen wie ELPM.
Außerdem Instruktionen wie RAMPx verwenden, was aber GCC nicht nutzt).
> als ein 8 Bit breiter Pointer
Nitpick: Der Zeiger ist 16 Bits breit. Das Ziel ist ein 8-Bit Wert.
Johann L. schrieb:> Der I/O Offset ist abhängig vom Core: 0x0 für ATxmega und 0x20 für> andere AVRs.>> IN ist besser als z.B. LDS weil kleinerer Code (hängt vom Core ab) und> schneller (hängt auch vom Core ab).
Nicht zu vergessen: Wird nur ein einzelnes Bit gesetzt/gelöscht oder
geprüft, kann per sbi/cbi bzw. sbis/sbic noch weiter optimiert werden.
Für Speicheradressen gibt es nichts vergleichbares.
Als man den AVR entwickelte, war man der Meinung, 64 IO-Adressen würden
ausreichen. Beim ATmega8 hat man auch noch alles rein gequetscht und
dafür Register doppelt belegt (UBRRH/UCSRC).
Beim ATmega88 hat man das aufgegeben und weitere IO-Register in den
SRAM-Bereich gelegt. Die sind dann nur noch über LDS/STS erreichbar.
Hallo
vielen Dank für eure ausführlichen Antworten! Nun verstehe ich es
besser. Und auch das Beispiel aus der avr libc FAQ wird nun klar. Dort
wird gezeigt wie man einen Port als Funktions Parameter benutzen kann.
Einmal als Pointer/Referenz mit volatile und einmal als Makro. Das Makro
benutzt IN/OUT Anweisungen während die Funktion LD/ST verwendet, solange
keine Optimierung mit Inline passiert.
Die Variante mit LD/ST ist etwas ineffizienter wenn ich den ASM Code
richtig lese, denn LD/ST benutzt das Z Register, was ja erst mit dem
richtig Wert belegt werden muss.
Jetzt behaupte ich einfach mal, dass doch in allen Projekten die
Hardware fest steht und wo an welchen Port und Pin sie angeschlossen
wird, noch bevor die Software compiliert wird. Und dass sich der
Hardware Anschluss zur Laufzeit des Programms nicht mehr ändert. Dann
lässt sich doch die Port Nummer als Template Parameter übergeben. Damit
hätte man eine Funktion der man ein Port als Parameter mitgeben kann
ohne auf die Performance eines Makros zu verzichten.
Nur können Template Parameter kein volatile haben, von daher habe ich
die Nummer von PORTB selbst angegeben. Analog zum avr libc Beispiel
sieht der Code nun so aus:
Ich werde versuchen das auszubauen und in einem Testprojekt mal nutzen.
Und ja, natürlich ist es ein 16 Bit Pointer, sonst könnte man ja nicht
1KB an RAM nutzen. Da habe ich mich von der "8 Bit Architektur"
verleiten lassen.
Thomas H. schrieb:> Die Variante mit LD/ST ist etwas ineffizienter wenn ich den ASM Code> richtig lese, denn LD/ST benutzt das Z Register, was ja erst mit dem> richtig Wert belegt werden muss.
Außerdem brauchen sie auf den meisten AVRs 2 Taktzyklen, während IN/OUT
nur einen brauchen.
Thomas H. schrieb:> Ich werde versuchen das auszubauen
Benutze mal die Suchfunktion des Forums, denn ich bin ziemlich sicher,
dass hier eine fertiges Template bereits mindestens einmal
veröffentlicht wurde.
Thomas H. schrieb:> Die Variante mit LD/ST ist etwas ineffizienter wenn ich den ASM Code> richtig lese, denn LD/ST benutzt das Z Register, was ja erst mit dem> richtig Wert belegt werden muss.
Das muss aber nicht so sein. Tatsächlich könnte der Compiler auch
LDS/STS verwenden und damit die Indirektion vermeiden. Diese Optimierung
lohnt auf jeden Fall immer dann, wenn auf nur genau ein SFIOR genau ein
mal zugegriffen werden soll.
> Jetzt behaupte ich einfach mal, dass doch in allen Projekten die> Hardware fest steht und wo an welchen Port und Pin sie angeschlossen> wird, noch bevor die Software compiliert wird.
Nö. Sobald man es mit "pluggable" Hardware zu tun hat, ist das natürlich
eine Milchmädchenrechnung. Dazu kommt dann noch die Problematik, dass
die effizientesten Instruktionen für den Hardwarezugriff auf Ports nur
für genau vier Ports verfügbar sind. "Große" AVR8-MCUs haben aber
teilweise bis zu zehn(??? bin nicht ganz sicher, aber es sind auf jeden
Fall mehr als 6) Ports, die sich allerdings variabel auf sog. VPORTS
(eben die interessanten vier) mappen lassen.
> Dann> lässt sich doch die Port Nummer als Template Parameter übergeben.
Und schon sind wir in der Höllen der kaum lesbaren und praktisch nicht
debugbaren C++-Verwichsungen. Nunja, wer sich unbedingt direkt mit der
Schrotflinte in's Knie schiessen will, der benutzt natürlich sowas...
> sieht der Code nun so aus:
[es folgt überaus abscheckendes C++-Geschwafel, was ich hier nicht
erneut zitieren mag]
> Und ja, natürlich ist es ein 16 Bit Pointer, sonst könnte man ja nicht> 1KB an RAM nutzen. Da habe ich mich von der "8 Bit Architektur"> verleiten lassen.
Wer sich von sowas derart verwirren läßt, ist wohl sowieso nicht der
richtige Mann...
Thomas H. schrieb:> Nur können Template Parameter kein volatile haben, von daher habe ich> die Nummer von PORTB selbst angegeben. Analog zum avr libc Beispiel
1
>template<uint8_tport>
2
>__attribute__((noipa))
3
>voidset_bits_func_template(uint8_tmask)
4
>{
5
>(*(volatileuint8_t*)(port))|=mask;
6
>}
7
>
8
>set_bits_func_template<0x18+0x20>(0xf0);
Sowas will man eben nicht haben, weil man nicht an die Port-Adressen von
avr/io.h kommt. Man muss die Adressen also händisch tippsen oder die
I/O Header so anpassen, dass man die Adressen als Blanke Integer
bekommt.
Das Problem ist, dass (volatile uint8_t*) 0x123 in C++ ein Dynamic Cast
ist, d.h. das Ergebnis des Casts ist für C++ erst zur Laufzeit bekannt.
M.E. ein Bug in der C++ Spec.
Außerdem muss `port` ein uint16_t sein, kein 8-Bit Wert.
Und das `noipa` verstehe ich auch nicht, weil das dazu führt, dass die
Funktion nicht geinlinet werden darf.
Ein weiterer Haken ist, dass obwohl die Port-Adressen natürlich fix
sind, sie u.U. dennoch erst zur Laufzeit bekannt sind. Man bräuchte also
2 Versionen: Eine mit einem Funktionstemplate und eine mit einer
Inline-Funktion oder einem Makro. Und dann kann man auch gleich die 2.
Version für den 1. Fall nehme, weil der Compiler das optimieren kann
falls möglich.
> Das Makro benutzt IN/OUT Anweisungen während die Funktion LD/ST> verwendet, solange keine Optimierung mit Inline passiert.
Ja. Es gibt keine indirektes I/O Addressing, und selbst wenn, müsste
man immer noch die Adresse laden. Zudem bräuchte man einen Named
Address-Space dafür.
Ich sehe echt nicht, was einem hier ein Template bringen soll. Das
macht den Code nicht wirklich lesbarer.
In meinen AVR Projekten verwende ich die bösen Makros, und ich finde,
das das sowohl lederlich als auch komfortabel ist:
Johann L. schrieb:> In meinen AVR Projekten verwende ich die bösen Makros, und ich finde,> das das sowohl lederlich als auch komfortabel ist:
Ich habe damit auch nur ausnahmslos beste Erfahrungen gemacht.
Meine <sbit.h> definiert alle Pins als Bitvariable und in der
"hardware.h" werden beschreibende Namen zugewiesen:
1
/*** Outputs ***/
2
#define LED_GN PORT_A4
3
#define LED_GN_oe DDR_A4
4
#define LED_RD PORT_A5
5
#define LED_RD_oe DDR_A5
6
#define xADC0 PORT_B0
7
#define xADC0_oe DDR_B0
8
#define SCK PORT_B1
9
#define SCK_oe DDR_B1
10
#define MOSI PORT_B2
11
#define MOSI_oe DDR_B2
12
#define xDAC0 PORT_B4
13
#define xDAC0_oe DDR_B4
14
#define xDAC1 PORT_B5
15
#define xDAC1_oe DDR_B5
16
#define FILEN PORT_B6
17
#define FILEN_oe DDR_B6
18
#define AMP_ON PORT_B7 // switch on power for amplifiers
19
#define AMP_ON_oe DDR_B7
20
#define BIASON PORT_D7 // switch enable on for the Bias module
21
#define BIASON_oe DDR_D7
22
#define FANON PORT_E2
23
#define FANON_oe DDR_E2
24
#define xHVON PORT_E7 // switch enable on for HV modules
25
#define xHVON_oe DDR_E7
26
#define RELBIASOFF PORT_E5 // switch relay on to short circuit the Bias module
27
#define RELBIASOFF_oe DDR_E5
28
#define DEGAS PORT_G3 // Emission: Degas on
29
#define DEGAS_oe DDR_G3
30
#define HVPWRON PORT_G4 // switch 12V on for HV modulee
31
#define HVPWRON_oe DDR_G4
32
33
/*** Inputs ***/
34
#define xHVEN_in PIN_E6 // extern HV enable from IFP
35
#define PGOOD_in PIN_E6 // power good from XR76208
36
#define FIL_DETECT_in PIN_D0 // Filament ok
37
#define EXT_ILOCK_in PIN_D1 // Interlock ok
38
39
/*** Bidirectional ***/
40
#define W1 PORT_E3
41
#define W1_oe DDR_E3
42
#define W1_in PIN_E3
Man ist aber nicht auf IO-Pins beschränkt, sondern kann sich nach dem
gleichen Prinzip auch virtuelle Bits definieren und dann im SPS-Stil
programmieren. Diese müssen auch nicht mehr volatile sein, so daß der
Compiler mehrere Bitoperationen auf das selbe Byte zusammen fassen kann.
Peter D. schrieb:> Ich habe damit auch nur ausnahmslos beste Erfahrungen gemacht.> Meine <sbit.h> definiert alle Pins als Bitvariable und in der> "hardware.h" werden beschreibende Namen zugewiesen:
1
>#defineW1PORT_E3
2
>#defineW1_oeDDR_E3
3
>#defineW1_inPIN_E3
Wobei für jeden Portdefinition 2 oder sogar 3 Makros gebraucht werden.
Bei meiner Umsetzung genüg ein einziger Wert :-)
Johann L. schrieb:> Wobei für jeden Portdefinition 2 oder sogar 3 Makros gebraucht werden.
Ein Macro kann immer nur ein Register ansprechen, es sind aber je Pin 3
Register vorhanden.
Vielleicht kann man das elegant mit einem Template oder Klasse lösen,
aber so tief bin ich nicht drin in C++.
Der Offset dieser 3 Register zueinander scheint ja bei allen AVRs
einheitlich zu sein (PINx, DDRx, PORTx).
Das wäre schon fein, wenn man nur W1 definieren müßte und dann
automatisch auch W1.oe, W1.in existieren.
Peter D. schrieb:> Johann L. schrieb:>> Wobei für jeden Portdefinition 2 oder sogar 3 Makros gebraucht werden.>> Ein Macro kann immer nur ein Register ansprechen, es sind aber je Pin 3> Register vorhanden.
Hab ich gelöst, indem PORTB_3 ein Enum ist. Und ein Enum (bzw. dessen
Wert) kann man auf das abbilden, was gerade gebraucht wird.
Die Makros sind nicht wirklich schön, ist aber alles in einen Header
gepackt. Und in den X Jahren, seit ich den verwende, hab ich den nicht
mehr anpacken müssen.
> Vielleicht kann man das elegant mit einem Template oder Klasse lösen,
Scheidet für mich schon deshalb aus, weil das nicht für C funktioniert.
Und wenn man eine gut funktionierende Lösung in C hat, ist es Käse, nur
um der Templates willen Templates zu verwenden. Und schön wären solche
Templates auch nicht.
Johann L. schrieb:> Die Makros sind nicht wirklich schön
Ja, einen Tod muß man wohl sterben.
Entweder man definiert nur ein Symbol, muß dann aber fürderhin
Spezialfunktionen (MAKE_OUT, MAKE_IN, IS_SET) für jeden Zugriff
benutzen.
Oder man definiert alle 3 Symbole, kann dafür aber bei jedem Zugriff die
Standardsyntax für Bitvariablen verwenden.
Peter D. schrieb:> Entweder man definiert nur ein Symbol, muß dann aber fürderhin> Spezialfunktionen (MAKE_OUT, MAKE_IN, IS_SET) für jeden Zugriff> benutzen.
Ist auch nicht mehr Tipparbeit. Und Aktionen wie Toggle will man
ohnehin kapseln:
Auf den alten Devices ist Port ^= Mask nicht atomar, also will man einen
atomic Block drumrum. Auf neuen Devices will man auf PIN = Mask
abbilden.
Will man denn einen Port auf Ausgang und HIGH setzen, oder eine LED
einschalten?
1
power_led_an();
2
power_led_aus();
3
antriebsmotor_stop();
Ich finde: Wenn man schon kapselt, dann soll man auch sprechende Namen
verwenden. Das geht mit ganz normalen Funktionen am einfachsten und am
saubersten. Das zeigt doch schon diese elend lange Diskussion über
raffinierte Makros und deren Nachteile.
Wo es wirklich zeitkritisch ist, da hat man in der Regel eine ganze
Sequenz von Port-Zugriffen (z.B. Serielle Ausgabe per Bit-Banging). Das
lässt sich auch viel besser in einer Funktion unterbringen, als in einer
Sequenz von Makro-Aufrufen. Meinetwegen kann man in C-Funktionen auch
ein paar Zeilen Inline-Assember unterbringen, wenn man es für nötig
hält. Das ist immer noch viel besser lesbar, als universelle raffiniert
verschachtelte Makros.
Na dann zeig mal.
D.h. für jeden Port braucht du 6 Funktionen, Makros, oder was auch
immer?
make_XXX_input();
make_XXX_output();
set_XXX();
clear_XXX();
toggle_XXX();
is_XXX_set();
Johann L. schrieb:> D.h. für jeden Port braucht du 6 Funktionen, Makros, oder was auch> immer?
Wie gesagt will ich nicht auf den Pin eines Ports zugreifen, sondern
etwas bewirken. Danach benenne ich die Funktionen. Und ja, das sind oft
mehrere Funktion pro angeschlossenen Dingsbums. Oder ein C++ Objekt.
Ich will auch etwas bewirken: Ein bestimmtes Pad setzen, löschen,
toggeln, lesen, als Output oder Input betreiben, you name it.
Und die Aktionen werden auf ein bestimmtes Pad angewandt.
Wenn ich zum Beispiel eine LED nicht an Port B3 anschließe sondern
stattdessen an C1, dann wird an GENAU EINER Stelle im Code was geändert
von
1
PORT_LED=PORTB_3
zu
1
PORT_LED=PORTC_1
und nicht an 6 verschiedenen Stellen. Ist in OO doch auch so:
Ein Pad unterstützt verschiedene Funktionen: Setzen, löschen, toggeln,
lesen, als Output oder Input betreiben, you name it.
Also gib es ein pad.set(), pad.clear(), etc. und wenn ich die LED
umklemme muss ich nicht alle 6 Methoden der Pad Klasse ändern. Sowas
wäre doch voll nervig und fehleranfällig.
Johann L. schrieb:> Ich will auch etwas bewirken: Ein bestimmtes Pad setzen, löschen,> toggeln, lesen, als Output oder Input betreiben, you name it.
Nein, das ist in der Regel nicht das, was du bewirken willst, sondern
lediglich, das was nötig ist, um das zu erreichen, was du eigentlich
willst (z.B. dass eine LED aufleuchtet). Das heißt, in deinem Programm
kommst du irgendwo an eine Stelle, an der du möchtest, dass die LED
angeht. Dass dazu irgendwo ein Bit in einem Port-Register gesetzt werden
muss, ist ein Implementationsdetail, das dich an dieser Stelle nicht
interessiert.
> Und die Aktionen werden auf ein bestimmtes Pad angewandt.>> Wenn ich zum Beispiel eine LED nicht an Port B3 anschließe sondern> stattdessen an C1, dann wird an GENAU EINER Stelle im Code was geändert> von> PORT_LED = PORTB_3> zuPORT_LED = PORTC_1>> und nicht an 6 verschiedenen Stellen.
Und wenn du jetzt feststellt, dass du zu wenig Pins hast und die LED
deshalb eine SPI-Porterweiterung anschließt? Wie nennst du deinen
PORT_LED dann? Wenn du stattdessen eine Funktion led_an() hast, musst du
auch bei so einer Änderung nur an genau einer Stelle was anpassen.
Rolf M. schrieb:> Und wenn du jetzt feststellt, dass du zu wenig Pins hast und die LED> deshalb eine SPI-Porterweiterung anschließt?
Du wirst lachen, auch das habe ich mit meinem SBIT Macro schon gemacht.
Eine Bitvariable läßt sich im gesamten 64k SRAM Adreßraum anlegen.
Natürlich wird dann das read-modify-write vom Compiler zu Fuß gemacht.
Der einzige Unterschied, in der Mainloop wird noch das io_update();
aufgerufen, was dann die 74HC595 und 74HC165 Kaskaden setzt bzw. liest.
Rolf M. schrieb:> Johann L. schrieb:>> Wenn ich zum Beispiel eine LED nicht an Port B3 anschließe sondern>> stattdessen an C1, dann wird an GENAU EINER Stelle im Code was geändert>> von>> PORT_LED = PORTB_3>> zuPORT_LED = PORTC_1>>>> und nicht an 6 verschiedenen Stellen.>> Und wenn du jetzt feststellt, dass du zu wenig Pins hast und die LED> deshalb eine SPI-Porterweiterung anschließt?
Genau das wird ebenfalls abgebildet :-) Wenn das Pad in einem
bestimmten Bereich liegt, dann wird erkannt, dass es nicht zu einem
physischen Port gehört sondern zu einem SPI Interface (heißt da SerPa
"Seriell-Parallel" Converter wie 74*595 etc), dann wird auf dem
entsprechenden Puffer für's SPI Interfact operiert
>Sowas will man eben nicht haben, weil man nicht an die Port-Adressen von>avr/io.h kommt.
Das ist richtig. Man sollte als template<...> auch nicht eine uint8_t
PortNummer übergeben, sondern eine voll funktionsfähige class{} PortC
(oder ein struct{} )
Als Beispiel: ports_.h enthält die class PortImplementation{}, die die
Funktionalität enthält. Das Makro AUTO_IO_REG_WRAPPER stellt die
Verbindung zu avr.io her. .
der PortC wird deklariert mit:
1
namespacePrivate{
2
3
AUTO_IO_REG_WRAPPER(PORTC,OutC,uint8_t);
4
AUTO_IO_REG_WRAPPER(DDRC,DirC,uint8_t);
5
AUTO_IO_REG_WRAPPER(PINC,InC,uint8_t);
6
}
7
8
usingPortC=PortImplementation<Private::OutC,
9
Private::DirC,
10
Private::InC,2>;
und steht dann voll funktionsfähig zur Verfügung.
Das Ganze erfordert eine grundsätzliche positive Einstellung zu "header
only" C++ mit mit statischen Funktionen.
Das ist nicht in Prosa und auch nicht in wenigen Worten vermittelbar.
Hallo,
nur kurz zum Verständnis warum ich diesen Thread gestartet habe: Ich
will mein Verständnis der Technik verbessern und nicht die Welt neu
erfinden.
Johann L. schrieb:> Das Problem ist, dass (volatile uint8_t*) 0x123 in C++ ein Dynamic Cast> ist, d.h. das Ergebnis des Casts ist für C++ erst zur Laufzeit bekannt.> M.E. ein Bug in der C++ Spec.
Z.b. das mit dem cast finde ich interessant. Auf den ersten Blick sieht
das nicht wie ein cast aus, der zur Laufzeit erst aufgeführt wird.
Immerhin ist der erzeugte ASM Code identisch zu dem Makro (darum das
noipa um es zu zeigen) und der Code für den Cast selbst habe ich aus der
avr libc.
Ich habe darauf hin den Code auseinander gefummelt um die Bit Operation
getrennt zu haben und den cast selbst constexpr gemacht. Das sieht dann
so aus und gab auch gleich eine Fehlermeldung.
1
constexprauto&addr=*(volatileuint8_t*)port;
2
addr|=mask;
1
error: 'reinterpret_cast' from integer to pointer
Ohne constexpr geht es. Ist es das was du mit Bug in C++ meinst?
Johann L. schrieb:> Ein weiterer Haken ist, dass obwohl die Port-Adressen natürlich fix> sind, sie u.U. dennoch erst zur Laufzeit bekannt sind.
Wie gesagt nehme ich einfach an, dass sich die Port Adressen und die
Information wo welche Hardware angeschlossen ist zur Laufzeit nie
ändert. Damit stehen die Wert schon zur Compilezeit fest. Im Quelltext,
in irgendwelchen Dateien, per Compiler Option u.s.w.. Sonst könnte der
Compiler ja niemals die IN/OUT Anweisungen erzeugen.
Johann L. schrieb:> Außerdem muss `port` ein uint16_t sein, kein 8-Bit Wert.
Und wieder erwischt :) size_t sollte auch fein sein. Vielleicht kann ich
mir das so besser merken.