Forum: Compiler & IDEs Define Makros in C


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Pet (Gast)


Lesenswert?

Hallo,
habe folgendes Testmakro
#define   A     1
#define   Test(x)    x

Aufruf: Test(A)
Ergebnis : A
Ich hätte aber gerne das 1 rauskommt. Hat jemand eine Idee wie ich das 
Makro änder muss, damit ich 1 erhalte?

von Sebastian V. (sebi_s)


Lesenswert?

Wie testest du das? Da sollte eigentlich die gewünschte 1 als Ergebnis 
rauskommen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Je nach Konstellation muss man einen Zwischenmakro einfügen:
1
#define A 1
2
#define Test_(x) x
3
#define Test(x) Test_(x)

von Pet (Gast)


Lesenswert?

Super, danke jetzt gehts:)

von AVRJohnny (Gast)


Lesenswert?

@Jörg: warum ist das so?

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

AVRJohnny schrieb:
> @Jörg: warum ist das so?

Wegen der Reihenfolge, in der Makros und Makro-Argumente ausgewertet 
werden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

AVRJohnny schrieb:
> warum ist das so?

Ganz bis zu Ende verstanden habe ich das auch nie, um ehrlich zu sein 
;),
mir aber auch nie Mühe gegeben.  Die entsprechenden Beschreibungen im
Standard (Abschnitt 6.10.3) sind nicht so ganz trivial zu begreifen.

von Peter D. (peda)


Lesenswert?

Pet schrieb:
> Hat jemand eine Idee wie ich das
> Makro änder muss, damit ich 1 erhalte?

Das Macro funktioniert doch einwandfrei:
1
00000034 <foo>:
2
#define   A     1
3
#define   Test(x)    x
4
5
uint8_t foo()
6
{
7
  return Test(A);
8
}
9
  34:  81 e0         ldi  r24, 0x01  ; 1
10
  36:  08 95         ret

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

Jörg W. schrieb:
> AVRJohnny schrieb:
>> warum ist das so?
>
> Ganz bis zu Ende verstanden habe ich das auch nie, um ehrlich zu sein
> ;),

Dito.
Ich glaub das hat bis auf Ritchie noch nie irgendjemand komplett 
begriffen.
Das ist eines der grossen Mysterien des Präprozessors. Was mich nur 
wundert ist, wie die Compilerhersteller es immer wieder schaffen, genau 
diesen Effekt zu reproduzieren :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl H. schrieb:
> Ich glaub das hat bis auf Ritchie noch nie irgendjemand komplett
> begriffen.

Und der ist mittlerweile tot …

> Das ist eines der grossen Mysterien des Präprozessors. Was mich nur
> wundert ist, wie die Compilerhersteller es immer wieder schaffen, genau
> diesen Effekt zu reproduzieren :-)

Vielleicht haben sie ja alle seinen Quellcode dafür bekommen. :)

von (prx) A. K. (prx)


Lesenswert?

Karl H. schrieb:
> Das ist eines der grossen Mysterien des Präprozessors. Was mich nur
> wundert ist, wie die Compilerhersteller es immer wieder schaffen, genau
> diesen Effekt zu reproduzieren :-)

Schaffen sie auch nicht. Ich bin vor Jahren im Umfeld von µCs einem 
Compiler begegnet, der bei solchen Fiesemantenten scheiterte.

> Ich glaub das hat bis auf Ritchie noch nie irgendjemand komplett
> begriffen.

Kunststück. Ritche hat halt mal eben einen Präprotz geschrieben und dann 
dessen zufällig entstandenes Verhalten zum Standard erkoren.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Ritche hat halt mal eben einen Präprotz geschrieben und dann dessen
> zufällig entstandenes Verhalten zum Standard erkoren.

Wobei die rekursive Ersetzung ja schon ein nettes Feature ist, das
man hin und wieder mal ganz gut gebrauchen kann.  Immerhin schafft
man es damit (zusammen mit dem token pasting), sowas wie
1
#define LED1 B, 4
2
#define LED2 A, 5

in die passenden Befehle umrubeln zu lassen, mit denen man dann
entsprechend bspw. auf einem AVR auf DDRx und PORTx zugreift.

von Peter D. (peda)


Lesenswert?

Zwischenmacros benötige ich, um:
1. die Argumentprüfung zu umgehen.
2. den ## Operator zu verwenden.

Nur im ersten Durchlauf überprüft der Präprozessor die Anzahl der 
Argumente bzw. verbindet 2 Ausdrücke ohne vorherige Expansion.

von Joschua C. (Gast)


Lesenswert?

1
#include <stdio.h>
2
3
#define A 1
4
#define Test(x) (x)
5
6
int main(){
7
8
  printf("%i\n", Test(A));
9
  return 0;
10
}
1
#include <stdio.h>
2
3
#define A 1
4
#define Test(x) (#x)
5
6
int main(){
7
8
  printf("%s\n", Test(A));
9
  return 0;
10
}

Getestet mit dem gcc. Das erste gibt 1 aus, das zweite gibt A aus. Ob 
die Parameter eines Makros nochmal expandiert werden sollen wird mit dem 
'#' vor dem Parameternamen (bei dessen Verwendung) gesteuert.

von Jens D. (jens) Benutzerseite


Lesenswert?

Jörg W. schrieb:
>
1
> #define LED1 B, 4
2
> #define LED2 A, 5
3
>

Hallo,

wie greife ich denn auf den Port und die Nummer zu?

von mec (Gast)


Lesenswert?

Jörg W. schrieb:
> Wobei die rekursive Ersetzung ja schon ein nettes Feature ist, das
> man hin und wieder mal ganz gut gebrauchen kann.  Immerhin schafft
> man es damit (zusammen mit dem token pasting), sowas wie
> #define LED1 B, 4
> #define LED2 A, 5
>
> in die passenden Befehle umrubeln zu lassen, mit denen man dann
> entsprechend bspw. auf einem AVR auf DDRx und PORTx zugreift.

Ja das ist schon schön. Nur die Makros dahinter sind nicht so schön ;)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jens D. schrieb:
> wie greife ich denn auf den Port und die Nummer zu?

Für LEDs habe ich gerade nichts zur Hand, aber hier das, was ich in
meinem HD44780-Treiber benutze.

project.h definiert, wo das LCD angeschlossen ist:
1
/* HD44780 LCD port connections */
2
#define HD44780_RS C, 6
3
#define HD44780_RW C, 1
4
#define HD44780_E  C, 0
5
/* The data bits have to be in ascending order. */
6
#define HD44780_D4 D, 2

hd44780.c hat dann:
1
#define GLUE(a, b)     a##b
2
3
/* single-bit macros, used for control bits */
4
#define SET_(what, p, m) GLUE(what, p) |= (1 << (m))
5
#define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m))
6
#define GET_(/* PIN, */ p, m) GLUE(PIN, p) & (1 << (m))
7
#define SET(what, x) SET_(what, x)
8
#define CLR(what, x) CLR_(what, x)
9
#define GET(/* PIN, */ x) GET_(x)
10
11
/* nibble macros, used for data path */
12
#define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \
13
                                                ~((1 << (m)) | (1 << ((m) + 1)) | \
14
                                                  (1 << ((m) + 2)) | (1 << ((m) + 3)))) | \
15
                                                ((v) << (m))
16
#define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \
17
                                            (1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m)
18
#define ASSIGN(what, x, v) ASSIGN_(what, x, v)
19
#define READ(what, x) READ_(what, x)

Benutzt werden sie so:
1
static inline uint8_t
2
hd44780_pulse_e(bool readback)
3
{
4
  uint8_t x;
5
6
  SET(PORT, HD44780_E);
7
  /*
8
   * Guarantee at least 500 ns of pulse width.  For high CPU
9
   * frequencies, a delay loop is used.  For lower frequencies, NOPs
10
   * are used, and at or below 1 MHz, the native pulse width will
11
   * already be 1 us or more so no additional delays are needed.
12
   */
13
#if F_CPU > 4000000UL
14
  _delay_us(0.5);
15
#else
16
  /*
17
   * When reading back, we need one additional NOP, as the value read
18
   * back from the input pin is sampled close to the beginning of a
19
   * CPU clock cycle, while the previous edge on the output pin is
20
   * generated towards the end of a CPU clock cycle.
21
   */
22
  if (readback)
23
    __asm__ volatile("nop");
24
25
  if (readback)
26
    x = READ(PIN, HD44780_D4);
27
  else
28
    x = 0;
29
  CLR(PORT, HD44780_E);
30
31
  return x;
32
}
33
34
// …
35
static void
36
hd44780_outnibble(uint8_t n, uint8_t rs)
37
{
38
  CLR(PORT, HD44780_RW);
39
  if (rs)
40
    SET(PORT, HD44780_RS);
41
  else
42
    CLR(PORT, HD44780_RS);
43
  ASSIGN(PORT, HD44780_D4, n);
44
  (void)hd44780_pulse_e(false);
45
}

mec schrieb:
> Nur die Makros dahinter sind nicht so schön ;)

So ist es. ;-)

von Walter T. (nicolas)


Lesenswert?

Hm. Dafür nutze ich mittlerweile inline-Funktionen. Wobei die 
Schreibweise am Ende die Gleiche ist.

von Uhu U. (uhu)


Lesenswert?

A. K. schrieb:
> Kunststück. Ritche hat halt mal eben einen Präprotz geschrieben und dann
> dessen zufällig entstandenes Verhalten zum Standard erkoren.

Nein, das ist kein Zufallsprodukt. Dieses Verhalten hat auch der 
m4-Macroprozessor. Das ermöglicht eine ganze Menge Tricks, ist aber 
etwas unübersichtlich - nur das gilt für den ganzen m4...

So läuft die Macroexpansion ab:

1. Zuerst werden die aktuellen Parameter in der Definition substituiert. 
Dabei werden sie nicht evaluiert.

2. Dann wird die rechte Seite nach weiteren Macros durchsucht. Wenn 
dabei Macros mit Parametern gefunden werden, werden diese Parameter 
zuerst evaluiert und dann folgt die Expansion.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Walter T. schrieb:
> Dafür nutze ich mittlerweile inline-Funktionen.

Und wie machst du in einer Inline-Funktion aus einem
1
B, 4

dann ein
1
   PORTB |= 1 << (4);
?

von Walter T. (nicolas)


Lesenswert?

Jörg W. schrieb:
> Walter T. schrieb:
>> Dafür nutze ich mittlerweile inline-Funktionen.
>
> Und wie machst du in einer Inline-Funktion aus einem
> B, 4
> dann ein
>    PORTB |= 1 << (4);
> ?

OK, jetzt verstehe ich den Sinn dieser Aktion erst. Du willst den Port 
in einer Art "Initialisierung" festlegen, nicht im Header.

Nein, soetwas mache ich tatsächlich nicht. Bei mir sähe das so aus:
1
gpio_SetBit(LCD_EN_GPIO,LCD_EN_Pin);
d.h. ich muß die Port-Zuordnung im Header machen.

von Peter D. (peda)


Lesenswert?

Jens D. schrieb:
> wie greife ich denn auf den Port und die Nummer zu?

Z.B.:
http://www.avrfreaks.net/comment/711155#comment-711155

von W.S. (Gast)


Lesenswert?

mec schrieb:
>> #define LED1 B, 4
>> #define LED2 A, 5
>>
>> in die passenden Befehle umrubeln zu lassen, mit denen man dann
>> entsprechend bspw. auf einem AVR auf DDRx und PORTx zugreift.
>
> Ja das ist schon schön. Nur die Makros dahinter sind nicht so schön ;)

Ach, so etwas findest du schön?
Ich nicht.

Das simple Ersetzen von schnöden Zahlen durch lesbare Bezeichner ist 
völlig in Ordnung, also so etwas
#define joerg 0
#define karl  1
und so weiter.

Aber daraus irgendwelche Kunst-Ungetüme zu machen, ist die 
personifizierte Abscheulichkeit, die im übrigen kein Mensch auf dieser 
Welt wirklich braucht. Wer da meint, er bräuchte ein Makro, um 
PortX,PinY architekturunabhängig zu setzen, hat einfach keinen Überblick 
darüber, an welcher Stelle man in seiner Firmware die Hardware 
abstrahieren sollte.

Jörg W. schrieb:
> Benutzt werden sie so:

Ja, da hast du ein abschreckendes Beispiel aufgezeigt. Eigentlich geht 
es darin nur um das simple Ausgeben eines Nibbles und nachfolgenden 
Hi-Puls an Enable. Aber es ist aufgebauscht ohne Ende - und im Grunde 
ohne jeglichen Sinn.

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Aber es ist aufgebauscht ohne Ende - und im Grunde ohne jeglichen Sinn.

Klar, ein echter W.S., so wie wir ihn kennen und mögen.

von Eric B. (beric)


Lesenswert?

W.S. schrieb:
> Ach, so etwas findest du schön?
> Ich nicht.

De gustibus non disputandum.

> Aber daraus irgendwelche Kunst-Ungetüme zu machen, ist die
> personifizierte Abscheulichkeit, die im übrigen kein Mensch auf dieser
> Welt wirklich braucht.

Doch. Es geht darum Informationen klar und deutlich zu vermitteln und 
gleichzeitig mögliche Fehlerquellen auszuschliessen. Wenn das mit ein 
paar vielleicht wild aussehende Makros machbar ist, gibt es keinen Grund 
(außer MISRA ;-)) das nicht zu tun.

von Bernd K. (prof7bit)


Lesenswert?

Pet schrieb:
> Hallo,
> habe folgendes Testmakro
> #define   A     1
> #define   Test(x)    x
>
> Aufruf: Test(A)
> Ergebnis : A
> Ich hätte aber gerne das 1 rauskommt. Hat jemand eine Idee wie ich das
> Makro änder muss, damit ich 1 erhalte?

Da kommt 1 raus.

Beweis:
1
$ cat test.c 
2
#define   A     1
3
#define   Test(x)    x
4
5
Test(A)
6
7
8
9
$ cpp test.c 
10
# 1 "test.c"
11
# 1 "<built-in>"
12
# 1 "<command-line>"
13
# 1 "/usr/include/stdc-predef.h" 1 3 4
14
# 1 "<command-line>" 2
15
# 1 "test.c"
16
17
18
19
1

von W.S. (Gast)


Lesenswert?

Eric B. schrieb:
> Doch. Es geht darum Informationen klar und deutlich zu vermitteln und
> gleichzeitig mögliche Fehlerquellen auszuschliessen.

Und genau deshalb würdest du dich auf sowas fehlerträchtiges und 
undurchsichtiges wie Makros verlegen? Du hast komische Ansichten.

Ich hab diesen Mist seit langem durch. Regelmäßig sieht man bei 
Programmen, wo extensiv mit Makros gearbeiter wird eine ebenso extensive 
Verwendung von void Pointern, ebensolchen Argumenten bei Funktionen und 
deren Resultaten und Typecasts an fast jeder Stelle. Es ist wie ein 
Dickicht aus Unkraut. Wie jemand da auf "Informationen klar und deutlich 
zu vermitteln" kommt, ist unverständlich. Eher sehe ich dort den Drang 
nach Verallgemeinerung per Makro um der Verallgemeinerung willen.


Jörg W. schrieb:
> Und wie machst du in einer Inline-Funktion aus einem
> B, 4
> dann ein
>    PORTB |= 1 << (4);
> ?

Eben, ein Makro zu schreiben, das aus einem SetPort(b,4) sowas macht wie 
PortB |= 1<<4 trifft ja nur auf solche Architekturen zu, bei denen es 
genau so gemacht werden muß. Schon der allerkleinste Blick über den 
Tellerrand zeigt einem, daß es woanders ganz anders gemacht werden muß 
und folglich so ein Makro eine prächtige Fehlerquelle ist. Portiere das 
mal auf einen LPC oder STM32, dann siehst du was ich meine.

Deshalb: Man macht sowas besser ÜBERHAUPT NICHT.

Stattdessen abstrahiert man die Sache ein kleines Stück weiter oben, 
also nicht das Setzen von Portpins abstrahieren wollen, sondern 
Funktionen schreiben, die echte Zielfunktionen ausführen. Beispiel:

void Motor_Ein (void) { GPIOB_PSOR = 1<<4; }

Sowas kommt ohne irgendwelche Makros aus, ist leicht lesbar und ebenso 
leicht an andere HW anpaßbar. Der Inhalt so einer Funktion ist mit 1 
Anweisung ja durchaus übersichtlich - und wer will, deklariert diese 
Funktion als inline.

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Portiere das mal auf einen LPC oder STM32

Kein Thema, hab's schon auf einen SAMD20 portiert.

von Uhu U. (uhu)


Lesenswert?

W.S. schrieb:
> Und genau deshalb würdest du dich auf sowas fehlerträchtiges und
> undurchsichtiges wie Makros verlegen? Du hast komische Ansichten.

Au Mann, das sind doch alles unverdaut nachgeplapperte Dogmen... Selber 
denken macht schlau, nicht Gehorsamsleistungen und auf Punkte im 
Programmiererhimmel ist gepfiffen.

C-Macros bieten - obwohl sie für Macros ziemlich doof sind - einige 
Möglichkeiten, die man in C anders nicht bekommt. Und ja, man kann 
Unsinn damit machen - aber das kann man so ziemlich mit jedem Werkzeug 
und vor allem, wenn man nicht weiß, was man tut...

von mec (Gast)


Lesenswert?

W.S. schrieb:
> Deshalb: Man macht sowas besser ÜBERHAUPT NICHT.

Wir sind hier im Mikrocontroller Forum, also geht es meist auch um sehr 
eingeschränkte Resourcen, und meist völlig andere Anforderungen, als im 
normalen Programieralltag. Da können Macros und Compiler 
Buildin-Funktionen und anderer "spezieler Kram" schon sehr praktisch 
sein, natürlich muss man noch einige Abstraktionsschichten einbauen, 
wenn man Programmteile Portabel halten will. Man braucht beides, bzw. 
ihr habt beide recht.

Das mit
#define INFO_LED A,2
... weiterer Macrozauber
ist zB. gut dafür, um nur an einer Stelle die Verknüpfung der LED mit 
dem Pin im Code zum haben, und den MCU-Eigenheiten von der Programmlogik 
zu trennen. Sonst müsste man bei einem Pinwechsel z.B. in jeder 
Funktion, welche die LED anspricht
wie z.B. set_info_led(){... SET_PIN(INFO_LED) /* nach belieben Macro 
oder Funktion */  ... } einzeln anpassen.

hab wenig Zeit, hoffe es ist ein wenig verständlich ;)

von W.S. (Gast)


Lesenswert?

mec schrieb:
> Das mit
> #define INFO_LED A,2
> ... weiterer Macrozauber
> ist zB. gut dafür, um nur an einer Stelle die Verknüpfung der LED mit
> dem Pin im Code zum haben,

Nein, es ist eben NICHT gut.

Das Ziel der Sache ist es ja wohl, die Info-LED ein oder aus zu 
schalten. Ob man dazu einen Portpin oder ein grünes Marsmännchen 
braucht, ist dem übergeordneten Programm schnurz (bzw. sollte ihm 
schnurz sein). Also setzt man die Abstraktion nicht bei der Zuordnung zu 
einem Portpin an, sondern auf der darüberliegenden logischen Ebene, die 
damit von der konkreten Hardware abgehoben funktioniert. Etwa so:

inline void InfoLed_ein (void) { .... }
und
inline void InfoLed_aus (void) { .... }

Das ist echte Hardware-Abstraktion und sie ist leserlicher und sauberer 
und besser wartbar und sinnvoller als alle Versuche a la "#define 
INFO_LED A,2".

Und nochwas:
Makros reduzieren NIEMALS den tatsächlichen Ressourcen-Aufwand, sondern 
blähen ihn in vielen Fällen auf, da man damit Code mehrfach erzeugt, der 
in vielen Fällen besser in einem separaten Unterprogramm aufgehoben 
wäre, so daß er mehrfach genutzt werden kann. Deshalb ist das Folgende 
einfach falsch gedacht:

mec schrieb:
> Wir sind hier im Mikrocontroller Forum, also geht es meist auch um sehr
> eingeschränkte Resourcen,...

W.S.

von (prx) A. K. (prx)


Lesenswert?

W.S. schrieb:
> Das ist echte Hardware-Abstraktion und sie ist leserlicher und sauberer
> und besser wartbar und sinnvoller als alle Versuche a la "#define
> INFO_LED A,2".

Vollkommen richtig. Das macht aber solche Makros nicht unnütz.

Solche Makros sind ein Layer von vielen. Es ergibt nur beschränkt Sinn, 
sie über die Grenzen von verschiedenen Mikrocontrollern anzuwenden, da 
die Portstrukturen zu verschieden sind. Aber zur Konfiguration von Code, 
den man zwischen verschiedenen Implementierungen übernimmt, können sie 
schon einen Sinn ergeben.

Wenn man also Code für Text-LCDs so gestaltet, dass man nur ein paar 
Makros anpassen muss, um ihn von vom Port B eines ATmega8 auf den Port A 
eines ATmega644 zu verschieben, dann kann das durchaus sinnvoll sein. 
Nur bleibt das innerhalb dieses LCD-Codes und seiner Konfiguration 
verborgen, der Rest des Programms nutzt dessen Funktionen.

: Bearbeitet durch User
von Falk S. (db8fs)


Lesenswert?

Uhu U. schrieb:
> Au Mann, das sind doch alles unverdaut nachgeplapperte Dogmen... Selber
> denken macht schlau, nicht Gehorsamsleistungen und auf Punkte im
> Programmiererhimmel ist gepfiffen.

So sollte man das nicht sehen. Eine Programmiersprache ist letztlich 
eine textuelle Abstraktion des Gedankengangs des Programmierenden. 
Sprich: sie hat also auch die nicht-funktionale Aufgabe, die 
Kommunikation mit Reviewern oder anderen Dritten zu erleichtern (im 
Zweifelsfall sogar zu sich selber nach n Jahren). Und wenn man 
MISRA-C(++) schreiben darf/muss und typkorrekt eine übelste Kaskade von 
Makros entflechtet, kann da der Spaß irgendwo aufhören.

> C-Macros bieten - obwohl sie für Macros ziemlich doof sind - einige
> Möglichkeiten, die man in C anders nicht bekommt.

Soweit gehe ich gerne mit - als Sprachfeature zur Optimierung in C bzw. 
C99 sehe ich das gerne ein. Auch als Hilfsmittel zur quasi 
"syntaktischen Erweiterung" der Sprache kann es sehr nützlich sein und 
sogar auch gute Dinge ermöglichen (z.B. Unit-Test-Suites). Das aber nur, 
wenn ein hartes Constraint auf die Verwendung der Programmiersprache C 
liegt.

Intuitiv würde ich aber eher dazu neigen, für solche Kaskaden wie oben 
eher C++ und templates zu nutzen. Das ist wenigstens halbwegs typsicher, 
ist aber zugegeben bei falscher oder exzessiver Verwendung auch nicht 
unbedingt besser zu lesen (Stichwort variadic templates C++11).

Vielleicht wäre vielleicht sogar die allerbeste Lösung noch, den zu 
parametrisierenden Code als einzelnes plattformabhängiges Modul 
dazuzulinken. Ob das dann mit C, C++ oder gar Asm erzeugt wird und wie 
darin geschweinst wird, ist dann fast egal. Solange der Rest der Library 
bzw. Binary sauber bleibt.

> Und ja, man kann
> Unsinn damit machen - aber das kann man so ziemlich mit jedem Werkzeug
> und vor allem, wenn man nicht weiß, was man tut...

Es ist halt was Wahres dran:
Ideal ist es, Code zu schreiben, den jemand so mühelos lesen kann, dass 
derjenige den Eindruck bekommt, der Code wäre trivial.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Nein, es ist eben NICHT gut.

Ich mag es einfach zu schreiben und gut lesbar:
1
// hardware.h:
2
#include "sbit.h"
3
#define LED_GREEN    PORT_B7
4
#define LED_GREEN_oe  DDR_B7
5
6
// init.h:
7
  LED_GREEN_oe = 1;
8
9
// code:
10
  LED_GREEN = 1;
11
  LED_GREEN = 0;

W.S. schrieb:
> Ob man dazu einen Portpin oder ein grünes Marsmännchen
> braucht, ist dem übergeordneten Programm schnurz

Mir doch auch.
Ich definiere dazu virtuelle Ports, die z.B. zyklisch per SPI auf nen 
74HC595 ausgegeben werden oder vom 74HC165 eingelesen:
1
#define LED_RED SBIT( vpout0, 1 )
2
3
  volatile uint8_t vpout0;
4
5
  LED_RED = 1;

von Falk S. (db8fs)


Lesenswert?

Peter D. schrieb:
> Ich mag es einfach zu schreiben und gut lesbar:

Ich mag Code, der potentiell keine Seiteneffekte hat. Und das kann man 
bei Makro-Definitionen nicht durchgängig ausschließen. Da stimme ich 
W.S. 100%ig bei der Bildung von HALs und der Minimierung von 
Makro-Verwendungen zu.

>
1
> // hardware.h:
2
> #include "sbit.h"
3
> #define LED_GREEN    PORT_B7
4
> #define LED_GREEN_oe  DDR_B7
5
> 
6
> // init.h:
7
>   LED_GREEN_oe = 1;
8
> 
9
> // code:
10
>   LED_GREEN = 1;
11
>   LED_GREEN = 0;
12
>

Für einfache Sachen bzw. extremste Optimierungen (AtTiny-Niveau) geht 
das sicher in Ordnung. Für alles andere lieber einen gescheiten 
Datenfluss im Programm haben, Kontrollfluss minimieren und hardwarenahe 
Zugriffe in 'ne HAL packen. Die HAL kann ja dann wie oben geschrieben, 
als einzelnes .o dazugelinkt werden, deren externe "C"-Exporte nur 
innerhalb der .o hardwaregekoppelt sind.

Kann man dann auch mal schön in eine CMake-Umgebung reinstricken, die 
dann die Builds automatisiert für alle möglichen Targets generieren kann 
und sogar mit Testcases in 'nem Simulator bzw. QEmu abdecken könnte. 
CMake muss dann nur für das jeweilige Target die richtige HAL dazulinken 
zu lassen. Und linkt man da noch FlexeLint dazu, hat man sogar 'n 
richtiges Framework für MISRA.

von mec (Gast)


Lesenswert?

W.S. schrieb:
> mec schrieb:
>> Das mit
>> #define INFO_LED A,2
>> ... weiterer Macrozauber
>> ist zB. gut dafür, um nur an einer Stelle die Verknüpfung der LED mit
>> dem Pin im Code zum haben,
>
> Nein, es ist eben NICHT gut.
Doch ist es schon, ich will nicht den ganzen Code durchsuchen, um alle 
Abhängikeiten von einen Pin zu finden und zu ändern, wenn ich nur eine 
Pinzuordnung ändere. Ich ändere an einer Stelle meine Pinzuordnung, und 
muss nicht alle Funktionen, welche den Pin verwenden, des HALs danach 
abklappern

> Das Ziel der Sache ist es ja wohl, die Info-LED ein oder aus zu
> schalten. Ob man dazu einen Portpin oder ein grünes Marsmännchen
> braucht, ist dem übergeordneten Programm schnurz (bzw. sollte ihm
> schnurz sein). Also setzt man die Abstraktion nicht bei der Zuordnung zu
> einem Portpin an, sondern auf der darüberliegenden logischen Ebene, die
> damit von der konkreten Hardware abgehoben funktioniert. Etwa so:
>
> inline void InfoLed_ein (void) { .... }
> und
> inline void InfoLed_aus (void) { .... }
>
> Das ist echte Hardware-Abstraktion und sie ist leserlicher und sauberer
> und besser wartbar und sinnvoller als alle Versuche a la "#define
> INFO_LED A,2".

Und du musst jede Funktion anpassen, wenn du mal eine Änderung bei der 
Pinbelegung machst. Dann geht die LED an, aber nicht mehr aus, wenn du 
was vergisst ;)
Was meinst du was das ist:
set_info_led(){... SET_PIN(INFO_LED) /* nach belieben Macro
oder Funktion */  ... }

> Makros reduzieren NIEMALS den tatsächlichen Ressourcen-Aufwand, sondern
> blähen ihn in vielen Fällen auf, da man damit Code mehrfach erzeugt, der
> in vielen Fällen besser in einem separaten Unterprogramm aufgehoben
> wäre, so daß er mehrfach genutzt werden kann.

Das was du da schreibst, mache ich natürlich auch. Das was ich meine ist 
eine Abstraktionsebene darunter einzufügen, was mir bei Hardware 
abhängigen Code Tipparbeit und Fehlermöglichkeiten erspart und einen 
einfacheren Wechsel der MCU ermöglicht, halt auf kosten einer weiteren 
Abstraktionsschicht. Manches Dabei geht nur mit Macros, anderes auch als 
Funktion. Da verwende ich auch Buildin-Funktionen des Compilers, dann 
muss ich mich nicht mehr auf das Optimierungsvermögen des Compilers 
verlassen, sondern weiß dann auch das der Bit-Toogle wirklich nur ein 
Maschinenbefehl ist, ohne Nebenwirkungen. Und auf inline kannst du dich 
auch nicht verlassen, wenn es der Compiler sogar anbieten würde. Kleine 
4 bis 16 Bit energiespar MCUs im unteren MHz Berreich mit sehr 
beschränkten Resourcen sind halt anders handzuhaben, als 32Bit 
Rechenmonster, ohne großartige Echtzeitanforderungen ;)

Was du brauchst ist C++, da kannst du weitestgehend auf Makros 
verzichten, aber C++ mit z.B. PIC16F ist irgendwie Overkill, und ein 
32Bit ARM für eine einfache Batterieschaltung ebenfals ;)
Es gibt selten reines Schwarz und Weiß, meist ist die Welt Grau.

von Uhu U. (uhu)


Lesenswert?

Falk S. schrieb:
> Auch als Hilfsmittel zur quasi
> "syntaktischen Erweiterung" der Sprache kann es sehr nützlich sein

Nein, als "syntaktische Erweiterung" der Sprache sind sie nicht 
geeignet.

Macros sind in C dort angebracht, wo man unübersichtliche Operationen - 
z.B. die Bitpfriemeleien bei der Bedienung von Ports - mit Namen 
versehen und "übersichtlich machen" will.

Normalen Code sollte man eher sparsam damit "schmücken".

Ein Idiom, das ich gerne verwende, wenn immer wiederkehrende 
Codefragmente - z.B. Fallunterscheidungen in einem switch - zu schreiben 
sind, sieht so aus:
1
#define DETECT(x) case (x):
2
switch (v) {
3
   DETECT(a) machwas(); break;
4
   DETECT(a) machwasanderes(); break;
5
   ...
6
   ...
7
}
8
#undef DETECT

Damit wird nicht das ganze Programm mit den Macros kontaminiert und es 
bleibt übersichtlich.

von Bernd K. (prof7bit)


Lesenswert?

Uhu U. schrieb:

> #define DETECT(x) case (x):
> switch (v) {
>    DETECT(a) machwas(); break;
>    DETECT(a) machwasanderes(); break;
>    ...
>    ...
> }
> #undef DETECT

Hä?

Wozu soll denn das gut sein? Das reduziert weder die Komplexität noch 
spart es Schreibarbeit, das erzeugt einfach nur ein WTF??? beim Lesen 
des Codes, da bleibt man dann dran hängen und beginnt völlig 
unnötigerweise drüber nachzudenken welcher ausgeklügelte Mechanismus nun 
das schon wieder sein soll  und fragt sich 10 Sekunden lang ob man zu 
blöd ist zu sehen was der tiefere Sinn davon sein soll, ob einem was 
entgangen ist (man sucht ja immer erstmal die Schuld bei sich selbst 
wenn man etwas nicht sofort versteht), solange bis man sieht dass 
ABSOLUT KEIN Sinn dahinter steckt und das nur ein böser Witz zur 
Code-Obfuskierung ist.

Wenn mir jemand diesen Code vererben würde, dreimal darfst Du raten was 
ich gleich als erstes damit machen würde (unmittelbar folgend auf die 
oben genannten 10 Sekunden, noch bevor ich irgendwas anderes damit mache 
oder weiterlese).

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Ich definiere dazu virtuelle Ports, die z.B. zyklisch per SPI auf nen
> 74HC595 ausgegeben werden oder vom 74HC165 eingelesen:#define LED_RED
> SBIT( vpout0, 1 )
>
>   volatile uint8_t vpout0;
>
>   LED_RED = 1;

Und wo bleibt bei deinem Beispiel die Lesbarkeit? Und wo die eigentliche 
Abstraktion?

Auf der Strecke bleibt sie.

Was muß ich mir unter "LED_RED = 1;" vorstellen, wenn ich mir nach einem 
Jahr die Quelle wieder angucken muß? Ist es ein "LedRed_ein();" oder ein 
"LedRed_aus();" ? Das hängt in der HW ja davon ab, gegen welches Rail 
die LED geht, gelle?

Nein, so ein Stil ist mir viel zu wurschtelig und extrem 
unübersichtlich. Anstatt eine les- und wartbare Abstraktion zu 
schreiben, machst du dir hier nur eine unnütze Verkomplizierung. Da wäre 
es einfacher lesbar, wenn du die direkten Portzugriffe hinschriebest, 
denn die kennt man ja auswendig, sofern man das RefMan gelesen hat.

Naja - und die von dir erwähnten TTL-Chips deuten mir darauf hin, daß du 
unter permanenter Portpin-Knappheit leidest - aber das ist ein anderes 
Thema.



mec schrieb:
> Doch ist es schon, ich will nicht den ganzen Code durchsuchen, um alle
> Abhängikeiten von einen Pin zu finden und zu ändern, wenn ich nur eine
> Pinzuordnung ändere.

Ich verstehe den Sinn deiner Ausführungen nicht. Was bittesehr soll "ich 
will nicht den ganzen Code durchsuchen" bedeuten?

Bei meiner Art, die HW zu abstrahieren, muß ich nur an einer einzigen 
Stelle - nämlich in der Quelle des zugehörigen Treibers - etwas ändern. 
Alle anderen Programmteile greifen lediglich auf "InfoLed_ein()" bzw. 
"InfoLed_aus()" zu und sind damit völlig unabhängig davon, welches Pin 
dafür benutzt wird und wie konkret diese Birne anzusteuern ist. Könnte 
ja auch sein, daß sie (wie bei Peter) an einem Pin eines externen 
Schieberegisters liegt, was bedeuten würde, daß man im RAM einen 
Pufferspeicher halten müßte für alle Bits dieses Schieberegisters und 
nach Ändern eines Bits darin dessen Inhalt seriell herausschieben müßte. 
Bei meinem "InfoLed_ein()" ist das treiberintern alles möglich, ohne daß 
es das übergeordnete Programm interessiert. Bei deinem "#define INFO_LED 
A,2" würde sowas überhaupt nicht gehen, sondern einen noch viel 
unübersichtlicheren Makro erfordern. Merkst du jetzt endlich was?

Bei dem, was du schreibst, willst du eine Art theoretischen Port 
erschaffen, mit dem du dann operieren willst. Aber so ein theoretischer 
Port ist keine funktionelle Sache, hat also keine Abstraktion von 
einem Stück Controller-Hardware hin zu einer Systemfunktionalität, 
sondern will nur alle Portpins aller denkbaren Controller 
generalisieren. Das ist das "SichImKreiseDrehen" auf derselben Ebene.

W.S.

von Eric B. (beric)


Lesenswert?

Bernd K. schrieb:
> Uhu U. schrieb:
>
>> #define DETECT(x) case (x):

   ...

> Wozu soll denn das gut sein? Das reduziert weder die Komplexität noch
> spart es Schreibarbeit, das erzeugt einfach nur ein WTF??? beim Lesen
> des Codes,

http://i.imgur.com/J1svNp7.jpg

Ausserdem widerspricht es genau das, was Uhu selber davor noch schrieb:

> Nein, als "syntaktische Erweiterung" der Sprache sind sie nicht
> geeignet.

von mec (Gast)


Lesenswert?

W.S. schrieb:


> mec schrieb:
>> Doch ist es schon, ich will nicht den ganzen Code durchsuchen, um alle
>> Abhängikeiten von einen Pin zu finden und zu ändern, wenn ich nur eine
>> Pinzuordnung ändere.
>
> Ich verstehe den Sinn deiner Ausführungen nicht. Was bittesehr soll "ich
> will nicht den ganzen Code durchsuchen" bedeuten?
>
> Bei meiner Art, die HW zu abstrahieren, muß ich nur an einer einzigen
> Stelle - nämlich in der Quelle des zugehörigen Treibers - etwas ändern.
> Alle anderen Programmteile greifen lediglich auf "InfoLed_ein()" bzw.
> "InfoLed_aus()" zu und sind damit völlig unabhängig davon, welches Pin
> dafür benutzt wird und wie konkret diese Birne anzusteuern ist. Könnte
> ja auch sein, daß sie (wie bei Peter) an einem Pin eines externen
> Schieberegisters liegt, was bedeuten würde, daß man im RAM einen
> Pufferspeicher halten müßte für alle Bits dieses Schieberegisters und
> nach Ändern eines Bits darin dessen Inhalt seriell herausschieben müßte.
> Bei meinem "InfoLed_ein()" ist das treiberintern alles möglich, ohne daß
> es das übergeordnete Programm interessiert. Bei deinem "#define INFO_LED
> A,2" würde sowas überhaupt nicht gehen, sondern einen noch viel
> unübersichtlicheren Makro erfordern. Merkst du jetzt endlich was?
>
> W.S.

Nochmal zum mitmeiseln, wenn du im folgenden Beispiel, nach deinem 
Ansatz, den Pin 0 von Port A mit der Funktion FU1 ändern willst, musst 
du in jeder Funktion eine Änderung durchführen.

void FU1_SetHigh(void) {  _LATA0 = 1; }

void FU1_SetLow(void) { _LATA0 = 0; }

void FU1_Toggle(void)  { _LATA0 ^= 1; }

void FU1_GetValue(void) { _RA0; }

void FU1_SetDigitalInput(void) { _TRISA0 = 1; }

oder min in drei def-Makros für die drei verschiedenen Register. Wenn 
das nicht Fehleranfällig ist?
zur Info die _XXX Schreibweise ist nur eine Vereinfachung von Microchip
#define _LATA0 LATAbits.LATA0


Mein Ansatz, stark vereinfacht:

#include MEINE_MAKROS
#define FU1 A,0  // Ich muss nur hier eine änderung durchführen

void FU1_SetHigh(void) { SET_PIN(FU1)  }

void FU1_SetLow(void) { CLEAR_PIN(FU1) }

void FU1_Toggle(void)  { TOGGLE_PIN(FU1) }

void FU1_GetValue(void) { GET_PIN(FU1) }

void FU1_SetDigitalInput(void) { SET_TO_DIGI(FU1) }

Diese Makros wie "SET_PIN(FU1)" sind einfach noch eine 
Abstraktionsebene, um die Hardware schnell anpassen zu können, und meine 
ganzen Pin-Definitionen wie "#define FU1 A,0" sind auch zentral in einem 
Header, da muss ich nur 2 Zeichen ändern, und nicht die Ganze Datei 
durchsuchen, wie du, und wenn ich Pins tauschen will, reicht es 4 
Zeichen in einer einzigen Datei zu ändern.
Natürlich geht dieser Ansatz nicht immer, bei einem Schieberegister 
anstadt MCU-Pin würde ich dann vielleicht, je nach Aufwand, mir ein 
weiteres Makro/Funktionenpaket dafür machen (nach möglichkeit 
wiederverwendbar), oder es halt dierekt da machen wo du es auch machst.
Oder ich benutze nur meine Makros, so wie ich lust drauf habe ;)
Wie gesagt, die Welt ist meist Grau.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Das hängt in der HW ja davon ab, gegen welches Rail
> die LED geht, gelle?

Ist auch kein unlösbares Problem. In Altium heißen bei mir low-aktive 
Signale "/Irgendwas" und in C eben "xIrgendwas".

W.S. schrieb:
> Naja - und die von dir erwähnten TTL-Chips deuten mir darauf hin, daß du
> unter permanenter Portpin-Knappheit leidest - aber das ist ein anderes
> Thema.

Nö, an den Portpins liegt es nicht (AT90CAN128).
Ich hab eben oft galvanisch isolierte Schaltungskreise (über ADuM1401).
Oder ich möchte größere Lasten schalten (TPIC6B595).
Oder ich möchte keine riesen Kabelbäume ziehen.

von W.S. (Gast)


Lesenswert?

mec schrieb:
> void FU1_SetHigh(void) {  _LATA0 = 1; }

OK, du willst es nicht verstehen.

Also nochmal: ich will dediziert keine noch so vigilantischen Ausdrücke, 
die sich auf das Setzen von Portpins beziehen, sondern Funktionen, die 
was Tatsächliches bewirken.

Also NICHT:
 void FU1_SetMyPortIrgendwie()

sondern SO:
 void SchalteMotorEin()
 void MacheTüreAuf()
 bool IstNachbarZuhause()

W.S.

von mec (Gast)


Lesenswert?

W.S. schrieb:
> mec schrieb:
>> void FU1_SetHigh(void) {  _LATA0 = 1; }
>
> OK, du willst es nicht verstehen.
>
> Also nochmal: ich will dediziert keine noch so vigilantischen Ausdrücke,
> die sich auf das Setzen von Portpins beziehen, sondern Funktionen, die
> was Tatsächliches bewirken.
>
> Also NICHT:
>  void FU1_SetMyPortIrgendwie()
>
> sondern SO:
>  void SchalteMotorEin()
>  void MacheTüreAuf()
>  bool IstNachbarZuhause()
>
> W.S.

Und wer Programiert dir dann diese Funktionen, wenn du das nicht selbst 
machst?

Ich schrieb ja, das es sehr vereinfacht ausgedrückt ist, nochmal für 
dich ein wenig ausführlicher. Ich mache sowas wie:

void schalte_motor_ein( void )
{
     ... //irgendwelcher Code
     READ_PIN(MOTOR_READY)
     ... //irgendwelcher Code
     SET_PIN(MOTOR_ENABLE)
     ... //irgendwelcher Code
}


void schalte_motor_aus( void )
{
     ... //irgendwelcher Code
     MACHNOCHWAS(MOTOR_IRGENDWAS)
     CLEAR_PIN(MOTOR_ENABLE)
     ... //irgendwelcher Code
}

von mec (Gast)


Lesenswert?

Auf diese Weise wird dann sogar sowas wie "void schalte_motor_ein( void 
);" zum Teil Portabel, also die PCB-Hardware wird von der MCU-Hardware 
getrennt.

von Uhu U. (uhu)


Lesenswert?

Bernd K. schrieb:
> fragt sich 10 Sekunden lang ob man zu
> blöd ist zu sehen was der tiefere Sinn davon sein soll

Die Das Beispiel ist nur zur Illustration gedacht. Mit etwas Nachdenken 
wirst auch du sinnvolle Anwendungsfälle finden ;-)

: Bearbeitet durch User
von mec (Gast)


Lesenswert?

Leider kommen jetzt keine Antworten mehr von W.S. und gleichgesinnten. 
Also muss ich mal direckt nachfragen, ob ihr in dem was ich beschrieben 
habe auch Vorteile, oder doch eher mehr Nachteile sieht, bzw ich etwas 
wichtiges übersehen habe?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Weißt du, am Ende musst du doch selbst wissen, wie du am besten
zurecht kommst.  Jeder muss da irgendwie seinen Weg finden.

Die ursprüngliche Frage drehte sich ja um die Art und Weise der
Ersetzung, und das mit der doppelten Ersetzung zum Erzeugen von
Namen wie PORTB und DDRB war nur als Beispiel gedacht.

Meiner Meinung nach (und so machen wir das auch in größeren Projekten)
hat es sehr wohl Sinn, unterhalb der von W.S. genannten Schicht noch
eine Abstraktionsebene zu haben, damit man beim Wechsel auf eine
(geringfügig) andere Hardware nicht alles neu zimmern muss.  In C
macht sich diese Ebene nun mal am besten mit Makros, denn dann ist
garantiert, dass es eine sehr dünne Abstraktionsschicht wird, die
keinen eigenen Overhead mitbringt.

In C++ könnte man beide durchaus auch in einer Klasse gemeinsam
erledigen.  Ein Beispiel dafür hatte ich hier mal gepostet:

Beitrag "Re: Wie lange Assemblerprogrammierung IC ADC ?"

von mec (Gast)


Lesenswert?

Jörg W. schrieb:
> Weißt du, am Ende musst du doch selbst wissen, wie du am besten
> zurecht kommst.  Jeder muss da irgendwie seinen Weg finden.
>
Ja das stimmt. Und es muss auch das, was einen nicht liegt aber auch 
geht, toleriert werden.


> Meiner Meinung nach (und so machen wir das auch in größeren Projekten)
> hat es sehr wohl Sinn, unterhalb der von W.S. genannten Schicht noch
> eine Abstraktionsebene zu haben, damit man beim Wechsel auf eine
> (geringfügig) andere Hardware nicht alles neu zimmern muss.  In C
> macht sich diese Ebene nun mal am besten mit Makros, denn dann ist
> garantiert, dass es eine sehr dünne Abstraktionsschicht wird, die
> keinen eigenen Overhead mitbringt.

schön zu hören, dann habe ich wahrscheinlich die richtigen Schlüße aus 
meinen noch wenigen MCU-C-Programmen gezogen. :)

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

mec schrieb:
> Jörg W. schrieb:
>> Weißt du, am Ende musst du doch selbst wissen, wie du am besten
>> zurecht kommst.  Jeder muss da irgendwie seinen Weg finden.
>>
> Ja das stimmt. Und es muss auch das, was einen nicht liegt aber auch
> geht, toleriert werden.

Toleriert wird es sowieso. Auch wenn jeder da seine eigene Meinung hat.
Die normative Kraft des Faktischen schlägt jede Theorie.

Letzten Endes legt man sich beispielsweise so einen Satz Makros einmal 
in einem Header File an, verbannt das auf ein Common Directory (auf dem 
man alles mögliche wiederverwendbare Zeugs sammelt) und includiert von 
dort ohne sich jemals wieder grossartig viele Gedanken über die dahinter 
steckende Makro-Magie zu machen. Man verwendet dann nur noch das Zeugs 
in weiterer Folge nachdem man sich einmalig da durchgekämpft und sich 
für eine Variante entschieden hat.

: Bearbeitet durch Moderator
von W.S. (Gast)


Lesenswert?

Karl H. schrieb:
> und includiert von
> dort ohne sich jemals wieder grossartig viele Gedanken über die dahinter
> steckende Makro-Magie zu machen.

..und fällt dann bei relativ geringfügigen Hardwareunterschieden 
grandios auf die Nase, weil selbige nicht im Mindesten in einem alten 
Makro berücksichtig wurden - wie auch?

Also: Makros, die garantiert hardwareunabhängig sind, kann man ja so 
behandeln, aber hier haben wir es die ganze Zeit mit dem Versuch zu tun, 
in Makros Hardwaredetails zu behandeln, wie eben das Setzen von Portpins 
und so. Das war schon immer, ist und wird bis ewig MUMPITZ sein, der 
einem auf lange Sicht mehr Scherereien macht als nützt. Wer es trotzdem 
will und Probleme gern hat und nicht hören will, der möge das tun - 
bittesehr.

Seit dem hartnäckigen NichtBegreifenWollen von mec halte ich mich da 
einfach raus, macht und meint was ihr wollt - Hauptsache in meinem 
nächsten Auto ist möglichst KEIN Code von euch drin.

W.S.

von mec (Gast)


Lesenswert?

W.S. schrieb:

> Also: Makros, die garantiert hardwareunabhängig sind, kann man ja so
> behandeln, aber hier haben wir es die ganze Zeit mit dem Versuch zu tun,
> in Makros Hardwaredetails zu behandeln, wie eben das Setzen von Portpins
> und so. Das war schon immer, ist und wird bis ewig MUMPITZ sein, der
> einem auf lange Sicht mehr Scherereien macht als nützt. Wer es trotzdem
> will und Probleme gern hat und nicht hören will, der möge das tun -
> bittesehr.>
> Seit dem hartnäckigen NichtBegreifenWollen von mec halte ich mich da
> einfach raus, macht und meint was ihr wollt - Hauptsache in meinem
> nächsten Auto ist möglichst KEIN Code von euch drin.

Wie löst du so eine Aufgabe:
Man hat eine Hardware FU1, welche mit folgenden Funktionen angesprochen 
wird.
Normalerweise greifen die Funktionen auf die gleichen Pins und 
Register/RAM_Bereiche zu.
Dann muß die Pinzuordnung oder ein Register/RAM-Bereich geändert werden.
Änderst du dann in jeder Funktion den Code, ist das nicht auch sehr 
Fehleranfällig, oder gehst du ganz anders vor?

void FU1_on(void) { ... }

void FU1_off(void) { ... }

FU1_state FU1_GetState(void) { ... }

void FU1_State1(void) { ... }

void FU1_State2(void) { ... }

void FU1_ToggleState(void)  { ... }

uint16_t FU1_GetValue(void) { ... }

int FU1_Write(uint16_t value) { ... }

...

von Tom (Gast)


Lesenswert?

Auch wenn ich nicht W.S. bin (und nie gedacht hätte, dass ich ihm mal 
bei irgendwas zustimmen würde...):

Die Pins darf man natürlich immer noch hinter Makros verstecken.

fu.h
1
void FU1_on(void);

fu.c
1
#define FU1_PIN 2
2
#define FU1_PORT PORTB
3
4
void FU1_on(void)
5
{
6
    FU1_PORT |= FU1_PIN;
7
}

main.c
1
#include "fu.h"
2
int main(void)
3
{
4
    while(1)
5
    {
6
        FU1_on();
7
    }
8
}


Als alleinige Abstraktionsebene bringen Makros fast nichts und machen 
den Code pseudoportabel.
1
#define FU1_PIN 2
2
#define FU1_PORT PORTB
3
#define FU1_ON FU1_PORTB |= FU1_PIN
4
5
int main(void)
6
{
7
    while(1)
8
    {
9
        FU1_ON;
10
    }
11
}


Aber das Thema hatten wir schon 1000x mit dem gleichen Ergebnis:
Die Abstrahierer-Fraktion hält die Assembler/Makro-Ecke für nicht zu 
höherem Denken fähig und die Assembler/Makro-Ecke hält die Abstrahierer 
für weltfremde Elfenbeinturmarchitekten. Alle bringen 
Mickeymaus-Beispiele mit LEDs, die -- wie man aus der Praxis weiß -- 
stets besonders ressourcensparend oder flexibel eingeschaltet werden 
müssen, und am Ende halten sich alle weiterhin gegenseitig für Idioten.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Tom schrieb:
> Alle bringen Mickeymaus-Beispiele mit LEDs, die -- wie man aus der
> Praxis weiß -- stets besonders ressourcensparend oder flexibel
> eingeschaltet werden müssen

Nun, mein Mickeymouse-Beispiel war immerhin für HD44780. :) Dort ist
es vor allem dafür da, damit man nicht bei jedem neuen Board durch den
Code durchlatschen muss.  Dennoch war die Portierung des gleichen Codes
auf den SAMD2x kein großer Akt, wenngleich natürlich nicht 1:1 (DDR
heißt DIR, PORT heißt OUT, PIN heißt IN, _delay_ms heißt delay_ms –
kann man aber alles mit Suchen&Ersetzen erschlagen).

Ich habe das Ding ja auch nur zitiert, weil es einer der wenigen
Fälle ist, bei denen man die zweistufige Ersetzung des Präprozessors
mal gebrauchen kann.

Ansonsten, auch wenn du's nicht glaubst, für Debug-Pins (zum Tracen
auf LA) ist es uns auch im kommerziellen Umfeld extrem wichtig, dass
es so schnell wie nur möglich geht, denn das Pin-Gewackel soll das
Timing des Codes nicht durcheinander bringen.  Da ist es dann schon
nervig, dass man für ein Pintoggle auf dem SAM4 doppelt so viel Zeit
braucht wie für Pinset/-clear, da man zweimal auf den lahmen IO-Bus
muss (erst Pinstatus einlesen, danach Setzen oder Löschen).

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

Tom schrieb:
> Auch wenn ich nicht W.S. bin (und nie gedacht hätte, dass ich ihm mal
> bei irgendwas zustimmen würde...):
>
> Die Pins darf man natürlich immer noch hinter Makros verstecken.

Darum gehts doch der Makro-Fraktion gar nicht nur.

Worum es der Makrofraktion geht, das ist, das hier zum Bleistift

>
1
> #define FU1_PIN 2
2
> #define FU1_PORT PORTB
3
>

ein #define fehlt. Da fehlt noch ein
1
#define FU1_DDR  DDRB

Das mag jetzt für viele (inklusive mir) kein Ding sein, diese 3 Angaben 
auf gleich zu halten, da aber DDRB unmittelbar mit PORTB zusammenhängt, 
wäre es nützlich, die Angabe 'B' da heraus zu extrahieren und getrennt 
zu haben. Auf Deutsch: Der Compiler/Präprozessor soll sich selber 
ausknobeln, dass wenn ich den Port B meine, das PORT-Register das PORTB 
sein muss und das zugehörige DDR-Register das DDRB sein muss. Was man 
erreichen möchte, das ist, dass dieser vom Programmierer einzuhaltende 
Zusamennhang DDRB-PORTB vom Präprozessor erledigt wird. Muss ich mich 
als Programmierer nicht darum kümmern, dann kann ich da auch keinen 
Fehler machen.

Wie wichtig das für jemanden ist, muss er selbst entscheiden. Ich kann 
gut ohne damit leben.

von Peter D. (peda)


Lesenswert?

Schade ist es schon, daß ein Macro keine weiteren Macros definieren 
kann.
Dann könnte man mit nur einem define Output, Input und Direction 
erschlagen.
So muß ich für jeden Portpin bis zu 3 defines anlegen.

von Walter T. (nicolas)


Lesenswert?

Peter D. schrieb:
> So muß ich für jeden Portpin bis zu 3 defines anlegen.

Oder Dich heftig auf die Optimierungen des Compilers verlassen.

Oder Dich implizit auf bestimmte Adress-Offsets verlassen.

Einen Tod sterben wir eben immer :-)

von W.S. (Gast)


Lesenswert?

Walter T. schrieb:
> Einen Tod sterben wir eben immer :-)

Ach Walter, du hast hier einen Punkt ganz vergessen:

All diese grandiosen Gedankenspielchen von mec, Peter, Jörg und 
KarlHeinz lassen IMMERZU eines außen vor, nämlich daß es sich bai all 
diesen PortB Pin2 Spielchen entweder um einen theoretischen Port eines 
theoretischen µC handelt oder eben immer wieder nur um den begrenzten 
Horizont eines AVR.

Auf allen anderen Architekturen sehen die Port ganz anders aus und 
müssen mit ganz anderen Strategien benutzt werden. Da sind derarige 
Makros wie hier verhandelt komplett kontraproduktiv. Das Einzige was 
wirklich hilft ist die Abstraktion auf funktionaler Ebene - was 
logischermaßen verbunden ist mit dem Editieren des betreffenden 
Treibers, wenn man ihn auf einer anderen Architektur haben will.

Ich kenne das zur Genüge. Tom hat es NOCHMAL klar herausgestellt:

Tom schrieb:
> fu.h
> void FU1_on(void);

Ja, eben. Im Hauptprogramm hat NICHTS zu stehen, was einen auf 
irgendwelche Pins und so festnagelt, selbst fu.h muß frei von sowas sein 
und in der Quelle fu.c stehen die Details, die eben hardware- und 
projektabhängig sind - und die man als einzige editieren muß, wenn man 
das ganze portieren will.

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Auf allen anderen Architekturen sehen die Port ganz anders aus und
> müssen mit ganz anderen Strategien benutzt werden.

Sie sind einem dort aber auch nicht im Wege, wie ich dir bereits auf
den entsprechenden Einwurf am Anfang entgegnet habe.

Ja, wenn man nur mit dem begrenzten Horizont irgendeines ARMs die
Implementierung begonnen hätte, hätte man das nicht benötigt.  Auch
nicht für Xmega.

Aber zurück zum Ursprung, es war nicht danach gefragt, wie man am
besten eine Hardwareabstraktion erledigt, sondern wie das mit diesen
verschachtelten Makros funktioniert (stand nicht explizit in der Frage,
war aber vom Fehlerbild her eigentlich klar).  Wenn dir mein Beispiel 
für
die Anwendung verschachtelter Makros nicht gefällt, steht es dir frei,
ein anderes zu posten.  Alle Diskussionen darum, wie man die Hardware
am besten abstrahiert, gehen jedoch am Thema dieses Threads vorbei.

: Bearbeitet durch Moderator
von Karl H. (kbuchegg) (Moderator)


Lesenswert?

W.S. schrieb:

> Das Einzige was
> wirklich hilft ist die Abstraktion auf funktionaler Ebene

Ich denke nicht, dass das jetzt irgendwer bestritten hätte.

> - was
> logischermaßen verbunden ist mit dem Editieren des betreffenden
> Treibers, wenn man ihn auf einer anderen Architektur haben will.

Ja. Das schliesst aber immer noch nicht aus, dass ich auf einem AVR 
gerne das Problem vermeiden möchte, wenn ich es kann, dass ich den 
Port-Buchstaben an 2 Stellen warten muss.
Auf einer anderen Plattform hab ich andere mögliche Fallen, die anders 
zu behandeln sind. Aber auf einem AVR ist es nun mal genau dieses.
Ob ich das dann nur ein einer Treiberebene einsetze oder nicht, macht 
diesen Wunsch ja deswegen nicht obsolet.

von mec (Gast)


Lesenswert?

W.S. schrieb:
> All diese grandiosen Gedankenspielchen von mec, Peter, Jörg und
> KarlHeinz lassen IMMERZU eines außen vor, nämlich daß es sich bai all
> diesen PortB Pin2 Spielchen entweder um einen theoretischen Port eines
> theoretischen µC handelt oder eben immer wieder nur um den begrenzten
> Horizont eines AVR.

Ich kann dir versichern, das ich noch nie für einen AVR Programiert habe 
und meine Beispiele auch nicht für einen AVR gedacht sind ;)
Außerdem wiederholst du dich ständig, und gehst überhaupt nicht auf die 
Fragen ein, welche man dir stellt. In der zwischenzeit gehe ich davon 
aus, dass du dich bei Änderungen im Code wirklich auf ein gutes "Suchen 
und Ersetzen" der IDE verlässt. ;)

Außerdem denke ich, dass du es noch nicht verstanden hast, was ich/wir 
wollen. Karl Heinz hat es verstanden, und wenn ich ihn richtig 
interpretiere, geht er lieber deinen Weg, warum zählst du ihn dann zur 
Makrofraktion ;)

von Karl Heinz:
"Das mag jetzt für viele (inklusive mir) kein Ding sein, diese 3 Angaben
auf gleich zu halten, da aber DDRB unmittelbar mit PORTB zusammenhängt,
wäre es nützlich, die Angabe 'B' da heraus zu extrahieren und getrennt
zu haben. Auf Deutsch: Der Compiler/Präprozessor soll sich selber
ausknobeln, dass wenn ich den Port B meine, das PORT-Register das PORTB
sein muss und das zugehörige DDR-Register das DDRB sein muss. Was man
erreichen möchte, das ist, dass dieser vom Programmierer einzuhaltende
Zusamennhang DDRB-PORTB vom Präprozessor erledigt wird. Muss ich mich
als Programmierer nicht darum kümmern, dann kann ich da auch keinen
Fehler machen.
Wie wichtig das für jemanden ist, muss er selbst entscheiden. Ich kann
gut ohne damit leben."

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Schade ist es schon, daß ein Macro keine weiteren Macros definieren
> kann.
> Dann könnte man mit nur einem define Output, Input und Direction
> erschlagen.
> So muß ich für jeden Portpin bis zu 3 defines anlegen.

Im IRMP mache ich es mit 2 defines bei AVR:

Zu konfigurieren in irmpconfig.h:
1
#define IRMP_PORT_LETTER                      B
2
#define IRMP_BIT_NUMBER                       6

Und dann in irmp.h:
1
#define _CONCAT(a,b)                          a##b
2
#define CONCAT(a,b)                           _CONCAT(a,b)
3
#define IRMP_PORT                             CONCAT(PORT, IRMP_PORT_LETTER)
4
#define IRMP_DDR                              CONCAT(DDR, IRMP_PORT_LETTER)
5
#define IRMP_PIN                              CONCAT(PIN, IRMP_PORT_LETTER)
6
#define IRMP_BIT                              IRMP_BIT_NUMBER

Ähnliche (hardwareabhängige) Makros gibt es dann noch für XMega, STM32, 
STM8, ESP8266, PIC und alle anderen µCs, wo IRMP drauf läuft.

Wenn es beim Preprocessor noch die Möglichkeiten gäbe, Werte nicht nur 
zu konkatenieren, sondern sie auch aufzudröseln, könnte man einfach 
schreiben:
1
#define MY_PORT_PIN                           B6

Aber das wäre zu schön, um wahr zu sein.

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Im Hauptprogramm hat NICHTS zu stehen, was einen auf
> irgendwelche Pins und so festnagelt

Das ist Quatsch mit Soße, darum ging es hier doch gar nicht.

Ich mache das doch auch bei größeren Programmen, daß im Hauptprogramm 
keinerlei Low-Level Zugriffe stehen. Z.B. die 6 Pins des HD44780 greift 
auschließlich der LCD-Treiber zu und das Hauptprogramm ruft nur 
LCD-Funktionen auf usw.

Man sollte immer Treiberschicht und Applikationsschicht soweit wie 
möglich trennen. Aber das hat überhaupt nicht das geringste damit zu 
tun, ob ich Pins als Macro definiere oder nicht.

von Le X. (lex_91)


Lesenswert?

W.S. schrieb:
> Das Einzige was
> wirklich hilft ist die Abstraktion auf funktionaler Ebene

Oder eben eine Zweischichtige Abstraktion. Dann sollte hier doch jeder 
zufrieden sein.

Einmal wird auf unterster Ebene die konkrete Hardware 
(=Pin-Setz-Mechanismus) abstrahiert, darüber die Funktion.

Das könnte dann z.B. so aussehen:
1
void Enable_Led(void)
2
{
3
    i2c_write(LED_CHANNEL)
4
    pin_set(LED_ENABLE_CHANNEL)
5
}

Dann hat man auch gleich erschlagen wenn die LED an einem Port-Expander 
hängt und man vielleicht noch explizit einen Ausgangstreiber einschalten 
muss.

Die Low-Level-Driver für I2C und Port müssen an die Controllerhardware 
angepasst werden, die Funktionale Abstraktion Enable_Led() muss ans 
Board-Layout angepasst werden.
Die API der Low-Level-Driver ist im Idealfall immer gleich, unabhängig 
vom Prozessor.

Dank LTO und Konsorten sollte auch kaum Overhead anfallen.
Lediglich Pin-Setzen mittels Funktion ist auf der konkreten Architektur 
"AVR" immer bisl pfriemelig, wenn der Compiler nicht ersehen kann dass 
sbi/cbi benutzt werden kann.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Wenn es beim Preprocessor noch die Möglichkeiten gäbe, Werte nicht nur
> zu konkatenieren, sondern sie auch aufzudröseln, könnte man einfach
> schreiben:

> #define MY_PORT_PIN                           B6

Yep, das wäre manchmal hübsch.  Aber naja, mit
1
#define MY_PORT_PIN    B,6

kann man durchaus auch leben.

Wobei, mit ein bisschen Mimik kann man sicher aus B6 ein "B6" machen,
und das wieder kann man mit "B6"[0] und "B6"[1] zugreifen. ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Liest sich als herrsche hier keine klare Trennung zwischen 
Portierbarkeit (auf eine andere Hardware) und Rekonfigurierbarkeit (z.B: 
vergleichbare Hardware mit anderer Verdrahtung).

Die meisten Vorschläge haben offenbar nur eine einfache 
Rekonfigurierbarkeit als Ziel.  Makros zum Beispiel, die auf Bitfelder 
abbilden, sind inhärent nicht-portabel.  Auch Adresse(n) von Port(s) zu 
übergeben ist nicht portabel da es bestimmte Implikationen macht, wie 
entsprechende Port-SFRs zu bedienen sind.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Tja, Johann, vielleicht hast du ja noch ein schönes Beispiel, wo man
geschachtelte Makro-Ersetzungen sinnvoll brauchen kann. ;-)

Darum ging's ja in diese Thread, nicht um irgendwelche Methode, wie man
einen HAL aufsetzt.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Wobei, mit ein bisschen Mimik kann man sicher aus B6 ein "B6" machen,

Das müsste gehen, Stichwort "Stringification".

> und das wieder kann man mit "B6"[0] und "B6"[1] zugreifen. ;-)

Ok, dann hast Du die Character 'B' und '6'. Bei der '6' könnte man noch 
'0' abziehen, um auf das Bit zu schließen. Aber wie man PORT und 'B' 
konkatenieren soll, ist mir schleierhaft.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Aber wie man PORT und 'B' konkatenieren soll, ist mir schleierhaft.

Geht deshalb nicht, weil es zwar eine "Stringification" im Preprocessor 
gibt, aber keine "Destringification".

Aber hier der ultimative, absolut dreckige Trick, wie es doch geht:
1
#include <avr/io.h>
2
3
#define IRMP_PORT_PIN                   C6
4
5
#define STRINGIFY(s) TO_STRING(s)
6
#define TO_STRING(s) #s
7
8
#define IRMP_PORT                       (*(&PORTB + 3 * (STRINGIFY(IRMP_PORT_PIN)[0]-'B')))
9
#define IRMP_DDR                        (*(&DDRB + 3 * (STRINGIFY(IRMP_PORT_PIN)[0]-'B')))
10
#define IRMP_PIN                        (*(&PINB + 3 * (STRINGIFY(IRMP_PORT_PIN)[0]-'B')))
11
#define IRMP_BIT_NUMBER                 (STRINGIFY(IRMP_PORT_PIN)[1]-'0')
12
13
int main ()
14
{
15
    volatile uint8_t v;
16
17
    v = PINC & (1<<6);
18
    v = IRMP_PIN & (1<<IRMP_BIT_NUMBER);
19
}

Da es bei einigen AVRs keinen PORTA gibt, benutze ich für 
Offset-Berechnungen einfach PORTB. Klappt trotzdem auch mit
1
#define IRMP_PORT_PIN                   A6

auf einem ATmega162, also auch mit negativen Offsets.

Ausschnitt aus der lss-Datei - kompiliert für ATmega328:
1
v = PINC & (1<<6);
2
  8a:  86 b1         in  r24, 0x06  ; 6
3
  8c:  80 74         andi  r24, 0x40  ; 64
4
  8e:  89 83         std  Y+1, r24  ; 0x01
5
6
v = IRMP_PIN & (1<<IRMP_BIT_NUMBER);
7
  90:  86 b1         in  r24, 0x06  ; 6
8
  92:  80 74         andi  r24, 0x40  ; 64
9
  94:  89 83         std  Y+1, r24  ; 0x01

Beide Zuweisungen führen zu exakt demselben Code.

Aber wie gesagt: Schmutziger gehts nicht :-)

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Aber wie gesagt: Schmutziger gehts nicht :-)

Yep, an sowas in der Art dachte ich dabei, war nur zu faul, es erst
noch auszuprobieren.  Normalerweise würde ich's auch nicht so machen,
und bezüglich des eigentlichen Thread-Themas (geschachtelte Expansion
von Makros) hilft es auch nicht viel weiter.

von Konrad S. (maybee)


Lesenswert?

Frank M. schrieb:
> Aber wie gesagt: Schmutziger gehts nicht :-)

Ja, nun, was soll's, nur weil sich der Herr MISRA im Grabe umdreht? ;-)

Du hast immerhin eine sehr interessante Lösungsmöglichkeit aufgezeigt.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Konrad S. schrieb:
> Ja, nun, was soll's, nur weil sich der Herr MISRA im Grabe umdreht? ;-)

Mit dem Herrn MISRA hat es weniger zu tun. Das schmutzige ist das 
Ausnutzen der Tatsache, dass die Register PORTx, DDRx, PINx in einem AVR 
jeweils mit Abstand 3 aufsteigend angeordnet sind.

Ich hätte selber eine Lösung bevorzugt, die aus C6 die Konstanten PORTC, 
DDRC, PINC erzeugt hätte. Dann wäre das sauber. Das geht aber nicht mit 
dem Standard-C-Preprocessor wegen fehlender "De-Stringification".

von Konrad S. (maybee)


Lesenswert?

Frank M. schrieb:
> Das schmutzige ist das
> Ausnutzen der Tatsache, dass die Register PORTx, DDRx, PINx in einem AVR
> jeweils mit Abstand 3 aufsteigend angeordnet sind.

Spezifische Eigenschaften eines bestimmten Prozessors bzw. einer 
bestimmten Plattform auszunutzen um genau dafür die Hardwarezugriffe zu 
schreiben finde ich nicht schmutzig. Es erfordert bezüglich Portabilität 
natürlich schon erhebliche Aufmerksamkeit, selbst innerhalb der AVRs. 
Das Speicher-Layout der SFRs ist schließlich keine zugesicherte 
Eigenschaft der AVRs. Die ATxmegas unterscheiden sich ja auch schon 
recht deutlich von den ATmegas.

von W.S. (Gast)


Lesenswert?

Konrad S. schrieb:
> Spezifische Eigenschaften eines bestimmten Prozessors bzw. einer
> bestimmten Plattform auszunutzen um genau dafür die Hardwarezugriffe zu
> schreiben finde ich nicht schmutzig.

Ich schon. Und zwar SEHR.

Und genau deshalb hab ich hier die ganze Zeit davon abgeraten. Es ist 
in wirklich JEDEM Falle besser, schlichtweg die direkten 
Hardwarezugriffe so zu schreiben, wie man es nach Lesen des RefManuals 
tun würde - ohne Makros.
OK, manche Sachen müssen sein, wie z.B. sowas:

#define GPIOB_PSOR (*((volatile dword *) 0x400FF044))

aber darüber hinaus wird es nur fischig, schlecht lesbar und auch nicht 
tatsächlich BESSER - und damit schlecht wartbar, bis hin zu sowas:

Karl H. schrieb:
> Letzten Endes legt man sich beispielsweise so einen Satz Makros einmal
> in einem Header File an, verbannt das auf ein Common Directory (auf dem
> man alles mögliche wiederverwendbare Zeugs sammelt) und includiert von
> dort ohne sich jemals wieder grossartig viele Gedanken über die dahinter
> steckende Makro-Magie zu machen.

Allein bei dem Gedanken an sowas krieg ich nen Schreikrampf. Ja, es ist 
wohl der Wunsch dahinter, ähem der Wunsch nach Ergebnis ohne sich 
"grossartig viele Gedanken" machen zu müssen.

Aber wenn ich die letzten Beiträge von mec lese, merke ich, daß einige 
hier rein garnix verstehen WOLLEN. Da ist wohl jeder Rat vergebens. Nee, 
es ist nicht Herr oder Frau Misra, sondern einfach nur der Gedanke an 
KISS. Keep it small and simple. Das ist mMn der einzige Grundsatz, der 
auf lange Sicht wirklich Bestand hat.


le x. schrieb:
> Das könnte dann z.B. so aussehen:
> void Enable_Led(void)
> {
>     i2c_write(LED_CHANNEL)
>     pin_set(LED_ENABLE_CHANNEL)
> }

Besser:
1
void DoMyAction (void) // war mal Enable_Led (void)
2
{ bool r;
3
  GPIOB_PSOR = 1<<7; // aus
4
  r = i2c_write(LED_ADDR,ActionCode); // mach mir mein ding..
5
  if (r) GPIOB_PCOR = 1<<7; // ein wenn ACK
6
}

Und warum so und nicht wie oben? Weil pin_set(LED_ENABLE_CHANNEL) als 
Funktion mit konstantem Argument auftritt und an dieser Stelle eine 
völlig unnütze Komplizierung darstellt - insbesondere wenn man es wie 
Karlheinz macht. Dann wird sogar ne Bugschleuder draus.

Leute, denkt lieber daran, was ihr eurer Liebsten zu Weihnachten 
schenken wollt anstatt hier der Makro-Onanie das Wort zu reden...

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Da ist wohl jeder Rat vergebens.

Nochmal, der Thread ging um Makros und deren geschachtelte Ersetzungen.

Wenn du dafür ein Beispiel jenseits der genannten hast, dann bring' es,
ansonsten mach' bitte deinen eigenen Thread auf, um über
Hardwareabstraktion zu philosophieren.

von Bernd K. (prof7bit)


Lesenswert?

Ich bin mittlerweile dazu übergegangen das wie folgt zu lösen:

In der aller untersten Ebene meiner Abstraktion habe ich für jeden 
CPU-Typ eine Definition eines struct das einen Pin auf der jeweiligen 
Hardware vollständig beschreibt und einen Satz von inline-Funktionen die 
darauf operieren, z.B.:
1
struct pin {
2
    // hier alle notwendigen Felder
3
    // die einen Pin identifizieren,
4
    // also Pointer auf alle notwendigen
5
    // Register, zum Beispiel bei einem
6
    // Kinetis sieht das so aus:
7
    FGPIO_Type* const FPTx;
8
    PORT_Type* const PORTx;
9
    const u8 number;
10
}
11
12
static inline void GPIO_set_high(const struct pin pin) {
13
    // hier ist der Code implementiert
14
    // der auf dieser Hardware den Pin auf
15
    // high setzt, zum Beispiel bei Kinetis:
16
    pin.FPTx->PSOR = (1 << pin.number);
17
}
18
19
static inline void GPIO_set_low(const struct pin pin) {
20
  // und so weiter...
21
  [...]


Der Vorteil ist nun ich kann die Pins auch innerhalb meiner 
Treiberschichten ganz transparent und typsicher als Variablen 
herumreichen, zum Beispiel kann ich meinem SPI-Teiber einfach beim 
initialisieren den CS-Pin als Variable übergeben. Dieser dann wiederum 
bedient sich der obigen Funktionen wenn er am Chip-Select wackeln will. 
Dabei ist es ihm völlig egal wie das struct aussieht und was die 
GPIO-Funktion eigentlich genau macht.

Darüber lege ich dann eine weitere dünne Schicht die so Sachen hat wie
1
void led_pwr_on() {
2
    GPIO_set_low(LED_PWR);
3
}
4
5
void led_pwr_off() {
6
    GPIO_set_high(LED_PWR);
7
}

oder auch (vereinfachtes Beispiel)
1
static struct pin chip_select;
2
3
void spi_init(pin cs, foo bar, whatever) {
4
    chip_select = cs;
5
6
    // und anderer code
7
}
8
9
void spi_select() {
10
    GPIO_set_low(chip_select);
11
}
12
13
void spi_unselect() {
14
    GPIO_set_high(chip_select);
15
}

Mit -Os und -flto und der Constant-Propagation wird das übrigens alles 
ganz hervorragend geinlined und zusammengeschrumpft auf wenige inline 
ASM-Befehle pro Funktinsaufruf.

Bis hierhin ist alles plain C.

Das einzige was nun verbleibt ist das Erstellen der pin-Konstanten.
1
static const struct pin HMT_CS   = {...whatever..};
2
static const struct pin HMT_SCK  = {...whatever..};
3
static const struct pin HMT_MISO = {...whatever..};
4
static const struct pin HMT_MOSI = {...whatever..};
5
static const struct pin HMT_INT  = {...whatever..};
6
static const struct pin LED_PWR  = {...whatever..};
7
// und so weiter

Und genau hier an dieser Stelle wo viel repetitive Schreibarbeit 
angesagt ist bediene ich mich der uralten fast schon in Vergessenheit 
geratenen Technik der X-Macros. Anstatt all die vielen const struct da 
oben von Hand hinzuschreiben erzeuge ich diesen Code mit X-Macros aus 
simplen kurzen knackigen Listen ohne viel redundantes Geschreibsel:

(in einer separaten config datei)
1
#define INPUTS              \
2
    PIN(HMT_INT,    A,8)    \
3
4
#define OUTPUTS_HIGH        \
5
    PIN(HMT_CS,     A,5)    \
6
    PIN(LED_PWR,    B,6)    \
7
8
#define ALT_3               \
9
    PIN(HMT_SCK,    B,0)    \
10
    PIN(HMT_MISO,   A,6)    \
11
    PIN(HMT_MOSI,   A,7)    \

Darauf lass ich nun ein X-Makro los:
1
#define PIN(name, port, number) static const struct pin name = {FPT##port, PORT##port, number};
2
INPUTS                  
3
OUTPUTS_HIGH            
4
ALT_3                   
5
#undef PIN
Und schon hab ich meine Konstanten da stehen, sauber als reinen C-Code.

Und weils so schön ist erzeuge ich auch gleich mein GPIO_init() 
ebenfalls mit einer Handvoll X-Macros, das kann ich machen da ich in 
weiser Voraussicht die pins bereits in Listen einsortiert habe die ich 
dann jeweils geeignet initialisieren kann.

Den Code dazu schreibe ich jetzt nicht hin, das kann der geneigte Leser 
sich anhand des Gesagten nun einfach selbst erarbeiten, das ist nur noch 
Fleißarbeit.

Somit schlage ich nun also 2 Fliegen mit einer Klappe: Es ist einfach zu 
warten (wenig Schreibarbeit), es ist extrem flexibel und kann auf 
praktisch jede beliebige Hardware portiert werden und es wird im Code 
kein einziges Makro aufgerufen, keine üblen Makro-Hacks, kein 
Macro-Verschachtelungen mit Stringify oder Komma-Splitten oder anderen 
Vergewaltigungen, das ganze API ist nach außen hin plain C, inclusive 
der eigentlichen Pin-Definitionen die als echte structs sogar typsicher 
in Funktionsargumenten verwendet und als Variablen gespeichert werden 
können.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> In der aller untersten Ebene meiner Abstraktion

Das ist nicht das Thema dieses Threads, Leute!

Schaut euch doch mal den Beitrag des TE an.

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:
> Das ist nicht das Thema dieses Threads, Leute!
>
> Schaut euch doch mal den Beitrag des TE an.

Sorry, hab Deinen Einwand übersehen, hat sich zeitlich überschnitten. 
Aber bitte nicht löschen, es hat eine halbe Stunde gekostet das 
auszuformulieren.

Deshalb hab ich auch den Betreff geändert um einen Seitenthread zu 
erzeugen.

Zum Thema: Ich dachte das sei bereits gelöst nach den ersten paar 
Antworten: Der Code des Threaderstellers funktioniert nachweislich genau 
so wie er ihn gepostet hat und wie er es sich gewünscht hat, es ist 
unklar wie er darauf kommt es läge ein Problem vor, daher war ich der 
Meinung dieses Thema wäre bereits seit Wochen abgeschlossen und wir 
könnten zum gemütlichen Teil übergehen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> Aber bitte nicht löschen, es hat eine halbe Stunde gekostet das
> auszuformulieren.

Wie fändest du es: wenn ich ihn lösche, bekommst du deinen Beitrag
ja per Mail zurück, und könntest damit einen neuen Thread eröffnen.
Wär' das OK?

Man kommt sonst einfach vom Hundertsten ins Tausendste.

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:
> Wie fändest du es: wenn ich ihn lösche, bekommst du deinen Beitrag
> ja per Mail zurück, und könntest damit einen neuen Thread eröffnen.
> Wär' das OK?

Du könntest ihn auch stehen lassen und wir lassen Gras drüber wachsen, 
immerhin berührt er einen Aspekt der Makros der bislang nicht genannt 
wurde und komplementiert somit Deinen eigenen Beitrag den Du in 
Beitrag "Re: Define Makros in C" geschrieben hast 
und in dem Du praktisch das selbe Problem auf andere Weise löst? Was 
unterscheidet Deinen Beitrag von meinem in dieser Hinsicht?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> Was unterscheidet Deinen Beitrag von meinem in dieser Hinsicht?

Dass meiner ein Beispiel dafür war, wofür man die doppelte Ersetzung
der Makros mal benötigen könnte.  Er war nicht dafür gedacht, nun
unbedingt als Empfehlung für eine bestimmte Art von Hardwareabstraktion
zu dienen.  (Mein Fehler war es, die diesbezügliche Diskussion, die
W.S. dann losgetreten hat, nicht gleich mit Verweis auf das Thema des
Threads abzuwürgen.)

Wie gesagt, im Sinne dieses Threads wäre ich viel stärker daran
interessiert, weitere Beispiele für solche doppelten Makroersetzungen
gezeigt zu bekommen.  Ja, das Problem des TE ist gelöst, aber wenn den
Thread mal jemand findet, kann er zumindest dafür dann Anregungen
entnehmen.

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:
> Wie gesagt, im Sinne dieses Threads wäre ich viel stärker daran
> interessiert, weitere Beispiele für solche doppelten Makroersetzungen

Nun, im weitesten Sinne kann man mein gezeigtes X-Macro ebenfalls als 
doppelte Ersetzung betrachten.
1
#define LISTE \
2
  X(foo, 42) \
3
  X(bar, 23) 
4
5
#define X(a,b) \
6
  const baz_t a = b;
7
8
LISTE
9
10
#undef X

Zuerst wird LISTE ersetzt durch X(foo, 42) X(bar, 23) und im nächsten 
Schritt wird X(a,b) ersetzt für jedes Listenelement.

Und im weiteren Verlauf meines Postings (zugegeben mit viel Vorwort und 
Nachwort und Umschweif, aber nur um den Abstraktionsfundamentalisten 
gleich von vornherein den Wind aus den Segeln zu nehmen) hab ich halt 
mal eine praktische Anwendung dafür aufgezeigt im selben Themenbereich 
der schon angesprochen wurde, nämlich die Erzeugung von vielen const 
struct für viele Pins mit der simplen Notation Buchstabe, Pin-Nummer.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Wie gesagt, im Sinne dieses Threads wäre ich viel stärker daran
> interessiert, weitere Beispiele für solche doppelten Makroersetzungen
> gezeigt zu bekommen.

Diese waren auch hier notwendig:

Frank M. schrieb:
> #define STRINGIFY(s) TO_STRING(s)
> #define TO_STRING(s) #s

Uff. Gerade noch die Kurve gekriegt :-)

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Nochmal, der Thread ging um Makros und deren geschachtelte Ersetzungen.
>
> Wenn du dafür ein Beispiel jenseits der genannten hast, dann bring' es,

Oh ja, genau DAS habe ich bereits geschrieben, nämlich: LASST SOWAS 
BESSER KOMPLETT BLEIBEN. Genau das - und das meine ich sehr sehr 
ernst.

Du und viele andee hier haben die unselige Mentalität, einen beim 
Beackern seines Feldes festgefahrenen TO in seiner zumeist falschen 
Furche zu belassen und ihm allenfalls Ratschläge zu geben, wie er in 
derselben Furche ein paar Schritte weiterkommt. Dabei übersiehst du mit 
Regelmäßigkeit das Sinnvollste, nämlich so einem TO zu sagen "Laß es, 
geh in eine andere Furche, dort geht's problemlos weiter bis zu deinem 
eigentlichen Ziel. Wir hatten das neulich schon mal (mit Yalu). Also 
ruf dir den Ansatz des TO nochmal ins Gedächtnis:

Pet schrieb:
> habe folgendes Testmakro
> #define   A     1
> #define   Test(x)    x
>
> Aufruf: Test(A)
> Ergebnis : A
> Ich hätte aber gerne das 1 rauskommt. Hat jemand eine Idee wie ich das
> Makro änder muss, damit ich 1 erhalte?

Er hätte gern, daß 1 rauskommt. Ah ja. Warum will er mit dem Kopf durch 
die Wand? Ist das etwa sinnvoll? Nein, natürlich nicht. Hätte er sich 
nicht damit aufgehalten, sondern sich seinem eigentlichen Ziel 
zugewandt, wäre er längst weiter.

Zum Schluß kommt Bernd mit seinem Beitrag, der ihn ne halbe Stunde zum 
Formulieren gekostet hat - eine halbe Stunde!!, um zu erklären zu 
versuchen, wie er sich sowas ausbaldowert hat. Hat er jemals daran 
gedacht, wie lange jemand anderes (oder er selbst nach 2..3 Jahren) in 
seine Quelle reingucken muß wie das berüchtigte Schwein ins Uhrwerk, bis 
er kapiert hat, was und wie hier eigentlich abgehen soll? Ganz gewiß 
nicht. Sowas ist Programmierer-Akrobatik oder Husarenstückchen, je 
nachdem wie man's benennen will. Aber es mach über alles die ganze 
Quelle nicht besser, sondern schlechter. Gründe gegen sowas sind genug 
genannt, man braucht beim Programmieren keine kühnen Helden, sondern 
solides biederes Handwerk.

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> LASST SOWAS BESSER KOMPLETT BLEIBEN.

Warum brüllst du denn so herum?

Wenn du dieses Feature nicht brauchst, dann lass es einfach.  Du
musst sie ja nicht nehmen, aber du musst hier nicht den Moby jetzt
spielen und dann auch noch jeden anderen davon überzeugen wollen,
dass das alles Mist wäre, nur weil es dir nicht in deinen Kram passt.

von Bernd K. (prof7bit)


Lesenswert?

W.S. schrieb:
> Zum Schluß kommt Bernd mit seinem Beitrag, der ihn ne halbe Stunde zum
> Formulieren gekostet hat - eine halbe Stunde!!

28 Minuten davon entfielen auf ordentliche Strukturierung und 
Formatierung, die Prüfung von Rechtschreibung und Grammatik und nicht 
zuletzt den Einbau von prophylaktischen Abwehrklauseln die Leuten wie 
Dir die Argumente entkräften sollten noch bevor sie sie überhaupt 
vorgebracht haben.

Leider hast Du sie nicht gelesen und kritisierst blind. Das ist schade.

Die Kurzfassung ist übrigens in meiner Antwort auf Jörg, 2 Postings 
weiter.

Das ist übrigens ein guter Eignungstest fürs Vorstellungsgespräch: Wenn 
einer kommt der sagt er kann fließend C seit zig Jahren macht ihm keiner 
was vor und dann mit nem X-Makro der simpelsten Sorte überfordert ist 
(um eine Ecke denken, Scripte die C-Code generieren aus Datentabellen, 
Teufelszeug!) dann kann man an der Stelle gleich zur Verabschiedung 
übergehen, denn das würde nur in Tränen enden.

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Warum brüllst du denn so herum?

Ganz einfach: damit es durch die mit Fleiß zugehaltenen Ohren dennoch 
durchkommt. Ich hab's gefühlte 100x geschrieben und du hast dir ebenso 
gefühlte 100x die Ohren zugehalten, um's nicht hören zu müssen. Also 
lassen wir's bleiben - es sei denn, irgend jemand schreibt in diesen 
Thread etwas derart abstruses, daß es geradegerückt werden muß.

W.S.

von Konrad S. (maybee)


Lesenswert?

"W.S., schreib hundert mal an die Tafel: 'Makros sind Teufelszeug!'. Und 
dann ab in die Ecke mir dir!"

Das war ihm wohl eine Lehre. ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

W.S. schrieb:
> Ganz einfach: damit es durch die mit Fleiß zugehaltenen Ohren dennoch
> durchkommt. Ich hab's gefühlte 100x geschrieben [...]

Na weil ihr aneinander vorbei redet bzw. schreibt.

von mec (Gast)


Lesenswert?

W.S. schrieb:
> Jörg W. schrieb:
>> Warum brüllst du denn so herum?
>
> Ganz einfach: damit es durch die mit Fleiß zugehaltenen Ohren dennoch
> durchkommt. Ich hab's gefühlte 100x geschrieben und du hast dir ebenso
> gefühlte 100x die Ohren zugehalten, um's nicht hören zu müssen. Also
> lassen wir's bleiben - es sei denn, irgend jemand schreibt in diesen
> Thread etwas derart abstruses, daß es geradegerückt werden muß.
>
> W.S.

Du hälst dir die Ohren auch zu, außer dass man es so nicht machen soll, 
kommt von dir nichts, und Fragen an dich wie du es besser machen willst 
weichst du aus.
In welcher Branche arbeitest du?

Ich hab mir jetzt noch einigen profesionellen Code von einem MCU 
Hersteller angesehen, und da wird alles mögliche gemacht, mit und ohne 
Makros, wahrscheinlich einfach nach vorliebe des jeweiligen 
Programmierers.

von W.S. (Gast)


Lesenswert?

mec schrieb:
> Du hälst dir die Ohren auch zu, außer man es so nicht machen soll,
> kommt von dir nichts, und Fragen an dich wie du es besser machen willst
> weichst du aus.

zum 101. mal: erstens weiche ich nicht aus (ich mach das fast NIE, auch 
wenn ich dadurch gezwungen bin, andere abzuwatschen), zweitens sag ich 
nochmal: Es besser zu machen bedeutet in den allermeisten Fällen es 
bleibenzulassen, also etwa so wie du zitiert hast: "man es so nicht 
machen soll". Reicht das denn noch immer nicht aus?

Guck mal in einen anderen grad hier aufgekommenen Thread:
"Beitrag "ChibiOS 3.0.4 STM32F4 Discovery Port";

Jaja, wieder mal. Es geht nicht, weil der TO wahrscheinlich in seiner 
tollen IDE mal wieder was vergessen oder nicht verstanden hat und weil 
in irgendwelchen ach so allgemein gehaltenen *.h von irgend einer 
CMSIS-Geschmacksvariante der ganze Quatsch zwar deklariert ist, selbiges 
jedoch mit gefühlten 100 verschiedenen #ifdef je nach konkreter Hardware 
verklausuliert ist - letzteres deshalb, weil so ein bescheuerter Makro 
eben doch nicht so allgemein gehalten sein kann.

Solche Beispiele findest du hier zuhauf. Ein ganz besonders übles hatte 
ich vor einigen Jahren: den USB-Treiber von Nuvoton für deren NUC120. 
Wenn du mal richtig abkotzen willst, dann guck dir den an. Ein wahres 
Meisterwerk chinesischer Makro-Künstler. Es ist ein Sammelsurium von 
geschachtelten Makros und void Pointern. So richtig Klasse, wenn man 
nach irgendwelchen Bugs suchen muß!

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Es ist ein Sammelsurium von geschachtelten Makros und void Pointern.

Niemand, absolut niemand würde sowas hier das Wort reden.  (Hab' ich
auch schon gesehen.)

Aber ein Beispiel für die miserable Anwendung eines Werkzeugs
rechtfertigt es nicht, das Werkzeug selbst zu verdammen.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.