Wie ist PORTB implementiert?

OP #7910624
Lesenswert?

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:

1
#define STR(x) #x
2
#define XSTR(x) STR(x)
3
#pragma message "PORTB " XSTR(PORTB)
1
note: '#pragma message: PORTB (*(volatile uint8_t *)((0x18) + 0x20))'

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

#7910634
Lesenswert?

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.

#7910643
Lesenswert?

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.

Persönliche Seite #7910708
Lesenswert?

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.

#7911970
Lesenswert?

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.

#7911980
Lesenswert?

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.

OP #7914904
Lesenswert?

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:

1
template<uint8_t port>
2
__attribute__((noipa))
3
void set_bits_func_template(uint8_t mask) {
4
    (*(volatile uint8_t *)(port)) |= mask;
5
}
6

7
...
8

9
set_bits_func_template<0x18+0x20>(0xf0);
1
void set_bits_func_template<(unsigned char)56>(unsigned char):
2
        in r25,0x18
3
        or r25,r24
4
        out 0x18,r25
5
        ret

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.

(Firma: 1984now) #7915288
Lesenswert?

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

Persönliche Seite #7915606
Lesenswert?

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_t port>
2
> __attribute__((noipa))
3
> void set_bits_func_template(uint8_t mask)
4
> {
5
>     (*(volatile uint8_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:

1
// In ports.h
2

3
#include "port-macros.h"
4

5
#define PORT_LED PORT_B3
6

7
// In main.c
8

9
#include "ports.h"
10

11
MAKE_OUT (PORT_LED);
12
SET (PORT_LED);
13
CLR (PORT_LED);
14
if (IS_SET (PORT_LED))
15
    TOGGLE (PORT_LED);
#7915660
Lesenswert?

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.

Persönliche Seite #7915831
Lesenswert?

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
> #define W1              PORT_E3
2
> #define W1_oe            DDR_E3
3
> #define W1_in            PIN_E3

Wobei für jeden Portdefinition 2 oder sogar 3 Makros gebraucht werden. Bei meiner Umsetzung genüg ein einziger Wert :-)

#7916004
Lesenswert?

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.

Persönliche Seite #7916202
Lesenswert?

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.

Persönliche Seite #7916340
Lesenswert?

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.

#7916352
Lesenswert?

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.

Persönliche Seite #7916412
Lesenswert?

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.

#7916577
Lesenswert?

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.

#7916677
Lesenswert?

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.

Persönliche Seite #7916678
Lesenswert?

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

(Firma: ab) #7917245
Lesenswert?

Johann L. (gjlayde) schrieb

1
 template<uint8_t port>
2
 __attribute__((noipa))
3
 void set_bits_func_template(uint8_t mask) {(*(volatile uint8_t *)(port)) |= mask; }
4
 
5
 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.

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
  namespace Private {
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
  using PortC = 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.

Angehängte Dateien:
OP #7917437
Lesenswert?

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
constexpr auto& addr = *(volatile uint8_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.

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