Forum: Compiler & IDEs Wie ist PORTB implementiert?


von Thomas H. (thux)


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

von Oliver S. (oliverso)


Lesenswert?

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

von Nemopuk (nemopuk)


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.

: Bearbeitet durch User
von Michael B. (laberkopp)


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.

von Johann L. (gjlayde) Benutzerseite


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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


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.

von Peter D. (peda)


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.

von Thomas H. (thux)


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.

von Rolf M. (rmagnus)


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.

Außerdem brauchen sie auf den meisten AVRs 2 Taktzyklen, während IN/OUT 
nur einen brauchen.

von Nemopuk (nemopuk)


Lesenswert?

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.

von Ob S. (Firma: 1984now) (observer)


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

von Johann L. (gjlayde) Benutzerseite


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);

von Peter D. (peda)


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.

von Johann L. (gjlayde) Benutzerseite


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 :-)

von Peter D. (peda)


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.

von Johann L. (gjlayde) Benutzerseite


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.

von Peter D. (peda)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


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.

von Nemopuk (nemopuk)


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.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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();

: Bearbeitet durch User
von Nemopuk (nemopuk)


Lesenswert?

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.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


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.

von Rolf M. (rmagnus)


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.

von Peter D. (peda)


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.

von Johann L. (gjlayde) Benutzerseite


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

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Genau das wird ebenfalls abgebildet :-)

Verlinke doch mal Deine geheime "port-macros.h", damit wir das auch 
sehen können.

von A. B. (Firma: ab) (bus_eng)


Angehängte Dateien:

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.

: Bearbeitet durch User
von Thomas H. (thux)


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.

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