Hallo *,
ich habe ein Makro für die Bitmanipulation:
struct bits {
unsigned char b0:1;
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char b5:1;
unsigned char b6:1;
unsigned char b7:1;
unsigned char b8:1;
unsigned char b9:1;
unsigned char b10:1;
unsigned char b11:1;
unsigned char b12:1;
unsigned char b13:1;
unsigned char b14:1;
unsigned char b15:1;
} __attribute__((_packed_));
#define BIT( port, pin ) ((*(volatile struct bits*)&port).b##pin)
Beim Atmega ist das kein Problem, da z.B. PORTB direkt das
Ausgangsregister ist. Beim STM32 ist funktioniert es, wenn ich es so
angebe:
#define LED_gruen BIT( GPIOD->ODR, 12 )
Hier ist ja das ODR nur eine Element von GPIOx. Ich würde im Define,
z.B. der LED, aber lieber nur "GPIOx" und den Operand "->" mit dem
Element "ODR" gerne im Makro hinzufügen. Gibt es da eine Möglichkeit?
C. W. schrieb:> #define LED_gruen BIT( GPIOD->ODR, 12 )
Was willst du eigentlich?
Möglichst unlesbaren Code erzeugen?
Und wie müßte man schreiben, damit die LED rot oder gelb wird?
Das Ganze ist eine Bit-Verhunzung ohne wirklichen Nutzeffekt. Laß sowas
einfach bleiben und befasse dich lieber mit dem effektiven Benutzen von
Peripheriebits bei ARM und Cortex-Architekturen. Hier gibt es Register
zum Setzen von Bits und andere zum Löschen von Bits. Das möglichst
perfide Formulieren von Bits ist hingegen einfach nicht hilfreich.
W.S.
W.S. schrieb:> Was willst du eigentlich?> Möglichst unlesbaren Code erzeugen?
Ich hole mal Popcorn. Nicht, daß Du Unrecht hättest. Aber der Post
dürfte wenig dabei helfen, Einsicht zu erzeugen.
@TO:
Das Define ist wirklich perfekt dazu, Verwirrung zu erzeugen. Mach
einfach zwei Defines:
1
#define LED_GPIO GPIOD
2
#define LED_Pin (1U<<PD12)
und schreib Dir Befehle, mit denen Du die oberen und unteren Bits des
BSSR-Registers manipulieren kannst. Ungefähr so:
1
/* Einzelnes oder mehrere Bits eines Ports setzen
2
*
3
* GPIOx
4
* Pin: Bitmaske */
5
staticinline__attribute__((always_inline))void
6
SetBit(GPIO_TypeDef*constGPIOx,constuint16_tPin)
7
{
8
GPIOx->BSRR=Pin;
9
}
10
11
12
13
/* Einzelnes oder mehrere Bits eines Ports loeschen
Dann würde ich die Makro aber nicht mehr BIT nennen sondern GPIO_OUT_BIT
oder ähnliches, weil man nicht mehr auf beliebige bits zugreifen kann,
nur mehr auf die bits von port->ODR.
Bau Dir ne Abstraktion aus vielen einzelnen statischen
Inlinefunktionen für jeden Pin einzeln die es Dir erlaubt später im Code
einfach zum Beispiel sowas zu schreiben:
1
LED_GRUEN_high();
2
LED_BLAU_low();
3
LED_ROT_low();
Praktischerweise kann man die alte x-Makro Technik (Google das mal)
verwenden um den Preprozessor automatisch dutzende von statischen
inline-Funktionen erzeugen zu lassen, für jeden GPIO-Pin jeweils
welche zum setzen, löschen, lesen, konfigurieren als Eingang, Ausgang,
etc.
Ich habe leider kein Beispiel für den STM32 da aber zum Beispiel bei
einem Kinetis sieht das dann zum Beispiel so aus:
(oben trägst Du die gewünschten Namen und Pinzuordnungen ein und der
Präprozessor erzeugt für die unten verwendeten beispielhaften drei Pins
insgesamt 21 statische inline-Funktionen (7 für jeden) plus eine
init-Funktion)
1
/*
2
* gpio.h
3
*
4
* Created on: 14.05.2015
5
* Author: bernd
6
*/
7
8
#ifndef GPIO_H_
9
#define GPIO_H_
10
11
#include<MKL05Z4.h>
12
#include<stdbool.h>
13
14
15
#define DIGITAL_IN_LIST \
16
17
18
#define DIGITAL_OUT_LIST \
19
PIN(LED_RED, B,8) \
20
PIN(LED_GREEN, B,9) \
21
PIN(LED_BLUE, B,10) \
22
23
#define MUX_ANALOG_LIST \
24
25
26
#define MUX_2_LIST \
27
28
29
#define MUX_3_LIST \
30
31
32
33
/**
34
* Some convenience macros, used in the implementations below
(vorsicht, das ist eine sehr frühe Variante, wahrscheinlich noch nicht
vollständig oder fehlerhaft, die aktuelle hab ich grad nicht zur Hand)
Wenn Du die Hardware änderst dann muss nur noch dieses eine Headerfile
geändert oder getauscht werden, die Tatsache daß ich das auch schon bei
AVR genau so gemacht habe hat mir schon so manches Portieren erheblich
vereinfacht.
Bernd K. schrieb:> Bau Dir ne Abstraktion aus vielen einzelnen statischen> Inlinefunktionen für jeden Pin einzeln die es Dir erlaubt später im Code> einfach zum Beispiel sowas zu schreiben:> LED_GRUEN_high();> LED_BLAU_low();> LED_ROT_low();
OK, ich habe jetzt eine Stunde gewartet, daß jemand hervorgesprungen
kommt und "April April!" schreibt.
[c] schrieb:> Die gesuchte Lösung ist:#define BIT( port, pin ) ((*(volatile struct> bits*)&port->ODR).b##pin)
Wobei hier das Beschreiben eines Pins alles andere als atomar ist. Über
ODR ergibt das eine read-modify-write-Operation.
Atomare Pinfunktionen ergeben sich durch das Beschreiben von BSRR zum
Setzen und Löschen - das Register ist für genau diesen Zweck da.
Wenn die Art von Schreiboperation:
> VermutlichLED_gruen = 1;> und> LED_gruen = 0;
wirklich eine echte Forderung ist, lautet mein Rat: Wechsel auf C++ und
schreibe die entsprechenden Setter und Getter.
C. W. schrieb:> Also doch eher so:>> #define LED_gruen_on GPIOD->BSRRL = GPIO_Pin_12> #define LED_orange_on GPIOD->BSRRL = GPIO_Pin_13> #define LED_rot_on GPIOD->BSRRL = GPIO_Pin_14> #define LED_blau_on GPIOD->BSRRL = GPIO_Pin_15>> #define LED_gruen_off GPIOD->BSRRH = GPIO_Pin_12> #define LED_orange_off GPIOD->BSRRH = GPIO_Pin_13> #define LED_rot_off GPIOD->BSRRH = GPIO_Pin_14> #define LED_blau_off GPIOD->BSRRH = GPIO_Pin_15>> ?
Kommt der Sache schon näher, ist aber irrsinniger Schreibaufwand und
fehlerträchtig. Alternativen:
(0) Akzeptiere den Schreibaufwand und mache es so
(1) Ein separates Skript (Python, perl, whatever) das alle diese Makros
einmalig (oder wann immer sich die Hardware ändert) aus einer simplen
einfach strukturierten Pin-Zuordnungsliste erzeugt
(2) Wie (1) aber als Scriptsprache nimmst Du den C-Preprozessor selbst,
allerdings kann der keine Makros erzeugen sondern nur reinen C-Code,
also erzeugst Du dann halt eben Inlinefunktionen die das selbe tun wie
obige Makros, das wäre meine Lösung die ich oben gepostet habe.
(3) Riskiere den eventuellen Verlust von ein paar Taktzyklen und mache
universelle Funktionen die Port und Pin als Funktionsargument nehmen,
wenn Du es geschickt anstellst und etwas Glück hast optimiert der
Compiler das per Constant-Propagation auch noch soweit daß da zur
Laufzeit ebenfalls kein Overhead mehr bleibt (-> ausprobieren, mit
avr-gcc gehts definitiv, mit arm-gcc hab ichs noch nicht versucht).
(4) Selbes Prinzip wie (3) nur mit Makros statt Funktionen, da muss man
aber etwas in die Trickkiste greifen um zwei Argumente (Pin und Port)
als eines zu übergeben, geht aber meist auch ganz gut.
(5) C++ und Templates würde wahrscheinlich auch elegant funktionieren,
das wäre dann sinngemäß wie (2) allerdings diesmal nicht über den
Preprozessor sondern direkt unter Verwendung der vorhandenen (C++)
Sprachfeatures.
Such Dir eins aus.
Bernd K. schrieb:> Walter T. schrieb:>> OK, ich habe jetzt eine Stunde gewartet, daß jemand hervorgesprungen>> kommt und "April April!" schreibt.>> Lol, warum?
Weil hier mit viel Aufwand eine Abstraktionsebene eingezogen würde, die
eigentlich gar nicht benötigt wird.
Direkte GPIO-Pinoperationen sind bei meinen Projekten zu schätzungsweise
90% in irgendwelchen Peripherie-Treibern versteckt. Dort ist eine
Abstraktion auf dem Level:
1
SetBit(ADC_CS_GPIO,ADC_CS_Pin);
als komfortabler, gut lesbarer Einzeiler anzusehen. Eine Zwischenschicht
à la:
1
ADC_CS_HIGH();
macht das Ganze nicht besser lesbarer, bedeutet aber pro Pin wieder
mindestens zwei neue Makros. Sprich: Auf Peripherie-Treiber-Ebene ist
diese Makro-Zwischenschicht unnötig.
Es bleiben also die übrigen ca. 10%. Davon sind wiederum die meisten
Debug-Ausgaben, die später auskommentiert werden, in der folgenden Form:
1
SetBit(LED_SPARE0_GPIO,LED_SPARE0_Pin);// DEBUG
2
doSomeWeirdAndLongCalculation();
3
ClearBit(LED_SPARE0_GPIO,LED_SPARE0_Pin);// DEBUG
Für diese wenigen Stellen, die ohnehin nicht lange im Quelltext
verbleiben, würde ich auch keine Extra-Makro-Sammlung implementieren
wollen.
Es verbleiben damit winzig wenige Fälle, wo ich auf
"Anwendungsprogramm-Ebene" wirklich Pins behandeln will.
LEDs gehören allerdings nicht dazu. Bei LEDs bietet es sich an, alle
LEDs in einer gemeinsamen led_refresh()- oder led_display()-Routine zu
steuern, die wiederum einfach die Darstellung der internen
Zustandsmaschine übernimmt. So lassen sich auch LED-Zustände wie "blink"
komfortabel implementieren.
Nebenbei:
Was will ich eigentlich mit Pin-Bezeichnern wie "LED_GRUEN"? Selbst bei
meinen Hobbyprojekten gehe ich mittlerweile davon aus, daß sich die
LED-Farbe im Laufe des Projekts noch ändern kann. Insbesondere bei
Projekten, die auch nachgebaut werden. Sinnvolle Pin-Bezeichner wären:
"LED_DATA", "LED_CONNECT", "LED_RDY", "LED_BUSY".
Walter T. schrieb:> Nebenbei:> Was will ich eigentlich mit Pin-Bezeichnern wie "LED_GRUEN"? Selbst bei> meinen Hobbyprojekten gehe ich mittlerweile davon aus, daß sich die> LED-Farbe im Laufe des Projekts noch ändern kann. Insbesondere bei> Projekten, die auch nachgebaut werden. Sinnvolle Pin-Bezeichner wären:> "LED_DATA", "LED_CONNECT", "LED_RDY", "LED_BUSY".
Vielleicht baut er ja eine (Modelleisenbahn-)ampel. Dann ist LED_GRUEN
usw schon sinnvoll ;-)
Walter T. schrieb:> bedeutet aber pro Pin wieder> mindestens zwei neue Makros. Sprich: Auf Peripherie-Treiber-Ebene ist> diese Makro-Zwischenschicht unnötig.
SetBit(ADC_CS_GPIO,ADC_CS_Pin);
Ist auch schon eine Abtraktion, nur daß Du hier pro Pin doppelt so viele
Makros definieren musst wie ich. Warum strebst Du nicht stattdessen
etwas an wie
SetBit(ADC_CS);
Das wäre erheblich sauberer und abstrahiert auch die willkürlichge
Annahme daß ein Pin stets durch ein Tupel aus Port und Nummer bestimt
ist. Was ist wenn manche Pins zusätzlich noch eine dritte Angabe
brauchen, z.B. eine Busadresse? Oder sie sind durch IP-Adressen und
Gerätenamen adressiert? Was dann? Schreibst Du dann alle Deine Treiber
um?
Bernd K. schrieb:> nur daß Du hier pro Pin doppelt so viele> Makros definieren musst wie ich.
Hm. Ich zähle bei mir zwei (xx_GPIO und xx_Pin) und bei Dir zwei (xx_on,
xx_off).
Nur daß ich mit meinen zwei Makros
a) die Pins auch initialisieren kann und
b) für die Definition des I/O-Mappings für jede Plattform nur Angaben
brauche, die auch im Schaltplan zu finden sind (PORT+Pin). Ich finde
diese Form der Abstraktion sauberer als mit Pin+Register (BSSR) die
Definition des I/O-Mappings (Hardware/Datenblatt) mit der
Implementierung (Programming manual) zu vermischen.
Bernd K. schrieb:> Was ist wenn manche Pins zusätzlich noch eine dritte Angabe> brauchen, z.B. eine Busadresse?
Warum sollte ein Pin eine Busadresse benötigen? Durch Port+Pin ist der
Pin eindeutig festgelegt.
Bernd K. schrieb:> Bau Dir ne Abstraktion
OT: Hmmmm..... eigentlich ist das eine ziemliche Schweinerei, die Makros
so zu verwenden. Blickt später kein Fremder mehr durch.
Aber ich mach auch einiges so. :-) Das vereinfacht einige Dinge so
dermaßen, dass selbst meine Kollegen, die erst einmal Schnappatmung
bekamen, als sie das sahen, es jetzt auch verwenden. :-)
Ansonsten bin ich auch eher für klaren, gut lesbaren Code. Dazu gehört
das definitiv nicht.
Walter T. schrieb:> Hm. Ich zähle bei mir zwei (xx_GPIO und xx_Pin) und bei Dir zwei (xx_on,> xx_off).>
Sprechen wir über den selben code? Ich glaube eher nicht, denn:
Ich zähle bei mir genau eine Definition pro Pin:
1
PIN(LED_RED,B,8) \
2
PIN(LED_GREEN,B,9) \
3
PIN(LED_BLUE,B,10)\
> Nur daß ich mit meinen zwei Makros> a) die Pins auch initialisieren kann und
Wenn Du mal weiter runter scrollen magst dann siehst Du die automatisch
generierte gpio_init() die alle Pins automatisch initialisiert (samt
Pin-Muxing entsprechend der Kategorie in die ich den Pin oben
einsortiert habe) und andere auf der jeweiligen Hardware nötigen
Initialisierungen (z.B. hier das clock gating Register im SIM)
> b) für die Definition des I/O-Mappings für jede Plattform nur Angaben> brauche, die auch im Schaltplan zu finden sind (PORT+Pin).
Ich finde die obigen Angaben (z.B. hier B8, B9, B10) immer bei mir im
Schaltplan, denn das sind die logischen Pin-Namen die auch im Datenblatt
und konsequent auch im Schaltplansymbol verwendet werden.
Bernd K. schrieb:> [...] dann siehst Du die automatisch> generierte gpio_init() die alle Pins automatisch initialisiert [...]
OK, so langsam verstehe ich den Zweck Deiner Präprozessororgien. Da bin
ich wirklich froh, daß meine Projekte bisher alle überschaubar genug
waren, auf soetwas verzichten zu können. Ich bin ja der letzte, der
etwas gegen Overengineering hat, solange er es nicht selbst machen muß
:-)