Forum: Mikrocontroller und Digitale Elektronik Dumme Frage zum "C" mit AVR's


von Christian J. (Gast)


Lesenswert?

Hallo,

aus der PIC Welt kommen mit CCS Compiler rudere ich nun mit den AVR's 
herum.

Im CCS Compiler war das Definieren der Pins im Header so

#define TESTPIN = PORTB.4
#define LED     = PORTB.5

Fortan wurde mit

TESTPIN = <value>;

immer dieser Pin angesprochen. Der PIc kann einzelne Bits in Registern 
setzen und braucht dazu kein Read/Modify/Write und auch kein PORTB = 
PORTB | (1 << Bit)

#define TESTPIN  PB1
#define TESTPIN2 PB2

PORTB |= (1 << TESTPIN);   //PB1 High
PORTB &= ~(1 << TESTPIN2); //PB2 Low

Gefällt mir nicht, da im Code nochmal bei PORTB definiert sein muss zu 
welchem Port der Pin gehört.

Wie macht man das mit dem AVR in C nun? Ich lese da einiges dass in der 
avrlibc einiges vordefiniert ist, es ein _BV Makro gibt usw.  auch 
Funktione wie loop_until_bit_is_set(WARTEPIN, WARTEBIT); aber wie macht 
man das was oben beim PIC so schoen einfach ist mit AVR und dem GCC?

Sorry.... aber jeder Anfang ist schwer :-)

von S. R. (svenska)


Lesenswert?

Christian J. schrieb:
> Der PIc kann einzelne Bits in Registern setzen und braucht dazu kein
> Read/Modify/Write und auch kein PORTB = PORTB | (1 << Bit)

Das kann der AVR auch.
Der gcc ist helle genug, dass er keine RMW-Zyklen dafür macht, sondern 
direkt die entsprechenden Bits setzt/löscht.

> Wie macht man das mit dem AVR in C nun? Ich lese da einiges dass in der
> avrlibc einiges vordefiniert ist, es ein _BV Makro gibt usw.

Man macht es genau so, wie du es schon beschrieben hast. Wenn du es 
anders haben willst, musst du dir deine eigene Abstrahierungsschicht 
dafür bauen.

Aber ich persönlich finde "LEDPORT |= (1 << LEDPIN)" nicht wirklich 
umständlich. Ist bei CMSIS auch nicht anders. Die STM32-Bibliothek ist 
da deutlich unangenehmer.

von Leo B. (luigi)


Lesenswert?

Also zunächst eine oft nicht bekannte Funktion. Viele AVRs, soweit ich 
weiß alle ATmega, können Port-Bits zumindest Toggeln ohne 
Read-Modify-Write. Einfach das entsprechende Bit im PINx-Register mit 
"1" beschreiben. Setzen und Löschen geht afaik allerdings wirklich nur 
mit Read-Modify-Write.

Und um deinen Code schöner zu gestalten gibt es viele Mittel und Wege:
1
static inline void setTestpin( void )
2
{
3
    PORTB |= (1 << PB1);    //PB1 High
4
}
5
static inline void resetTestpin( void )
6
{
7
    PORTB &= ~(1 << PB2);   //PB2 Low
8
}
9
static inline void toggleTestpin( void )
10
{
11
    PINB = (1 << PB2);      //PB2 Low
12
}
13
// alternativ:
14
static inline void setTestpin( const uint8_t val )
15
{
16
    register uint8_t tmp = PORTB;
17
    if( val != 0 )
18
        tmp |= (1 << PB1);    //PB1 High
19
    else
20
        tmp &= ~(1 << PB2);   //PB2 Low
21
    PORTB = tmp;
22
}

oder auch sehr verbreitet:
1
#define TEST_PORT    PORTB
2
#define TEST_PIN     PB1
3
// optional
4
#define TEST_BIT     (1<<TEST_PIN)
5
6
[...]
7
8
TEST_PORT &= ~(1<<TEST_PIN);
9
TEST_PORT |= TEST_BIT;

ebenfalls möglich aber Option 1 ist mMn besser:
1
#define setTestpin()    TEST_PORT |= (1<<TEST_PIN)
2
#define resetTestpin()    TEST_PORT &= ~(1<<TEST_PIN)
3
4
[...]
5
6
setTestpin();
7
resetTestpin();

und es gibt zig weitere Wege, aber die Nummer 1 ist für meine Begriffe 
die sauberste Lösung.

: Bearbeitet durch User
von Max H. (hartl192)


Lesenswert?

Christian J. schrieb:
> aber wie macht
> man das was oben beim PIC so schoen einfach ist mit AVR und dem GCC?
Da ATMEL das soweit ich weiß nicht implementiert hat, könntest du dir 
das hier mal ansehen: Beitrag "AVR-Register als Bitfields"

BTW: Ich finde das mit den Bitfeldern auch viel komfortabler.

: Bearbeitet durch User
von erik s. (Gast)


Lesenswert?

Ich mache es ganz gerne ähnlich wie folgend dargestellt, das steigert 
für mich die Lesbarkeit deutlich und vereinfacht ggf. auch die 
Portierung. Aber Macros sind immer mit Vorsicht zu genießen und nicht 
eben debugfreundlich!

Macros
1
#include <avr/io.h>
2
3
#define DDR(x)      (*(uint8_t*)(&(x)-1))
4
#define PINR(x)      (*(uint8_t*)(&(x)-2))
5
6
#define SET( pin_name )    pin_name ## PORT |= _BV(pin_name)
7
#define RESET( pin_name )  pin_name ## PORT &= ~_BV(pin_name)
8
9
10
#define CONFIG_OUTPUT( pin_name )  DDR( pin_name ## PORT ) |= _BV(pin_name) 
11
#define CONFIG_INPUT( pin_name )  DDR( pin_name ## PORT ) &= ~_BV(pin_name) 
12
#define GET_PIN_LEVEL( pin_name )  ( 0u < ( PINR( pin_name ## PORT ) & _BV(pin_name) ) ? 1u : 0u )
13
14
#define TEST    PB1
15
#define TESTPORT  PORTB
16
17
#define TEST2    PB2
18
#define TEST2PORT  PORTB
19
20
21
22
23
int main(void)
24
{
25
  uint8_t pin_level = 0u;
26
  CONFIG_OUTPUT( TEST );
27
  CONFIG_OUTPUT( TEST2 );
28
  
29
    while(1)
30
    {
31
        SET( TEST );  
32
    pin_level = GET_PIN_LEVEL( TEST );
33
        RESET( TEST2 );
34
    SET( TEST2 );   
35
    RESET( TEST );   
36
    }
37
}

von Leo B. (luigi)


Lesenswert?

Oh und warum ich das mit dem Pin-Register erwähne:
Wenn du Interrupts verwendest und auf race-conditions stößt, kann 
folgendes helfen ohne interrupts zu Unterbinden:
1
static inline void setTestpin( uint8_t val )
2
{
3
    register uint8_t tmp = PORTB & (1 << TESTPIN);
4
    if( val != 0 )
5
        val = (1 << TESTPIN);
6
    if( ( tmp ^ val ) != 0 )
7
        PINB = (1 << PB1);
8
}
Damit sind zumindest die einzelnen Bits auf einem Port unabhängig und 
können nicht durch race conditions "kaputt gehen".
Greifst du natürlich auf einen Pin sowohl im Interrupt, als auch im 
Hauptprogramm zu, ist das Problem damit noch nicht gelöst!

von Daniel A. (daniel-a)


Lesenswert?

Christian J. schrieb:
> Im CCS Compiler war das Definieren der Pins im Header so
>
> #define TESTPIN = PORTB.4
> #define LED     = PORTB.5
>
> Fortan wurde mit
>
> TESTPIN = <value>;
>
> immer dieser Pin angesprochen.

Das glaube ich dir nicht. Da würde ja dan sowas stehen:
1
 = PORTB.4 = <value>;
Das gleichheitszeichen gehört da einfach nicht hin.

> PORTB &= ~(1 << TESTPIN2); //PB2 Low
> Gefällt mir nicht, da im Code nochmal bei PORTB definiert sein muss zu
> welchem Port der Pin gehört.
>
> Wie macht man das mit dem AVR in C nun?

Denk dir einfach was aus, wenn dir die üblichen wege nicht gefallen.

Wie wär sowas: (ungetestet)
1
struct Bits {
2
  unsigned bit0 : 1;
3
  unsigned bit1 : 1;
4
  unsigned bit2 : 1;
5
  unsigned bit3 : 1;
6
  unsigned bit4 : 1;
7
  unsigned bit5 : 1;
8
  unsigned bit6 : 1;
9
  unsigned bit7 : 1;
10
};
11
12
#define BITVAR(x,y) ((volatile struct Bits*)(void*)&x)->bit ## y
13
#define LED(x) BITVAR(x##B,5)
Und dann sowas:
1
LED(DDR) = 1;
2
LED(PORT) = 1;

von Erik S. (erik_s)


Lesenswert?

So, ich habe mein Passwort rausgefunden...

Daniel A. schrieb:
> Denk dir einfach was aus, wenn dir die üblichen wege nicht gefallen.

Ja, das ist das sowohl schön als auch - besonders wenn man gerade den 
Einstieg wagt - verwirrend.

Leo B. schrieb
> und es gibt zig weitere Wege, aber die Nummer 1 ist für meine Begriffe

Deine "Nummer1" ist sicherlich die ordentlichste und schönste Variante, 
mir aber manchmal zu viel Schreiberei, bis alle Pins versorgt sind.

: Bearbeitet durch User
von Max H. (hartl192)


Lesenswert?

Erik S. schrieb:
> eine "Nummer1" ist sicherlich die ordentlichste und schönste Variante
Für jeden Pin 4 Funktionen schreiben??
Ich finde das hier am schönsten 
Beitrag "AVR-Register als Bitfields"
Ist auch schön logisch zu bedienen:
  Pin auf '1' setzen:
1
  BF_PORTB.portb3 = 1;
  Pin auf '0' setzen:
1
  BF_PORTB.portb3 = 0;
  Pin toggeln:
1
  BF_PORTB.portb3 ^= 1;
  Pin auf '1' abfragen:
1
  if(BF_PINB.pinb3)
  Pin auf '0' abfragen:
1
  if(!BF_PINB.pinb3)
Wie man es auch mit 'normalen' Variablen macht.

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Christian J. schrieb:
> #define TESTPIN  PB1
> #define TESTPIN2 PB2
>
> PORTB |= (1 << TESTPIN);   //PB1 High
> PORTB &= ~(1 << TESTPIN2); //PB2 Low
>
> Gefällt mir nicht, da im Code nochmal bei PORTB definiert sein muss zu
> welchem Port der Pin gehört.

Das ist ein Irrtum. Genauso gut geht:
1
#define TESTPIN  1
2
#define TESTPIN2 2
3
 
4
PORTB |= (1 << TESTPIN);   //PB1 High
5
PORTB &= ~(1 << TESTPIN2); //PB2 Low
Schau dir die defines von PB1 und PB2 mal an. Da steht ganz simpel :
1
// aus io8535.h
2
/* Data Register, Port B */
3
#define    PB7      7
4
#define    PB6      6
5
#define    PB5      5
6
#define    PB4      4
7
#define    PB3      3
8
#define    PB2      2
9
#define    PB1      1
10
#define    PB0      0

von Simpel (Gast)


Lesenswert?

"Der PIc kann einzelne Bits in Registern
setzen und braucht dazu kein Read/Modify/Write und auch kein PORTB =
PORTB | (1 << Bit)"

Das können die AVR auch:

AVR BIT AND BIT-TEST INSTRUCTIONS

SBI P,b Set Bit in I/O Register   I/O(P,b) --> 1  Flags:None  Clocks:2
CBI P,b Clear Bit in I/O Register I/O(P,b) <-- 0  Flags:None  Clocks:2

von Nase (Gast)


Lesenswert?

Max H. schrieb:
> Ist auch schön logisch zu bedienen:
Und außerdem schön unportabel.

Christian J. schrieb:
> Gefällt mir nicht, da im Code nochmal bei PORTB definiert sein muss zu
> welchem Port der Pin gehört.
Steht dir ja frei, es schön zu finden oder nicht.

Sehr viel sinnvoller als diese schwachsinnigen Bit-Definitionen oder der 
Murks mit den Bitfeldern wäre es, einfach eine Funktion zu schreiben.

Sowas wie
1
#define LED PORTB.2
2
LED = 1;
ist semantisch Blödsinn. Du willst nämlich nicht PORTB.2 auf High 
setzen, sondern du willst eine LED einschalten. Also verpack das in 
eine Funktion, die semantisch genau das tut:
1
void enable_led(bool on) {
2
  if (on)
3
    PORTB |= ...;
4
  else
5
    PORTB &= ..;
6
}

Das hat viele Vorteile:
. Du brauchst nicht mehr nachzudenken, ob die LED nun high- oder 
low-aktiv verdrahtet ist.
. Du brauchst nur an einer zentralen Stelle eine Änderung vorzunehmen, 
falls sich das mal ändert.
. Du brauchst nur an einer zentralen Stelle eine Änderung vorzunehmen, 
falls die LED irgendwann mal nicht mehr an einem Pin hängt, der sich so 
einfach beschreiben lässt.

Das Argument mit Overhead (Laufzeit wie Code) zählt heute nicht mehr, da 
jeder ernstzunehmende Compiler diese Funktion inlinen wird.

von Hans (Gast)


Lesenswert?

Nase schrieb:
>
> Das Argument mit Overhead (Laufzeit wie Code) zählt heute nicht mehr, da
> jeder ernstzunehmende Compiler diese Funktion inlinen wird.

Sehe ich genau so. Allerdings muss die Funktion innerhalb der gleichen 
Übersetzungseinheit definiert sein, d.h. sinnvollerweise in einem 
Headerfile mit "static". Sonst wird sie nicht geinlint, außer bei einem 
Compiler mit aktivierter Link Time Optimization (LTO).

von (prx) A. K. (prx)


Lesenswert?

Leo B. schrieb:
> Viele AVRs, soweit ich
> weiß alle ATmega, können Port-Bits zumindest Toggeln ohne
> Read-Modify-Write.

Nicht alle. Die nach wie vor populären Klassiker Mega8/16/32/128 haben 
das noch nicht drauf. Erst danach kam diese Feature.

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

Moin,

danke für dde Antworten, die ich mir gleich nochmal genau durchlesen 
werde. Es geht also nicht :-( Nämlich dass die Defintion der Funktion 
ekines Pins inklusive Port und Bit NUR im Header steht. Sondern man muss 
im Programm auch immer wieder nachschauen zu welchem Port der Pin 
gehört. Das ist natürlich unschön.

Und ja, doch im CCS ist das wirklich so! Grad nachgeschaut bei einem 
meiner vielen Projakte.

#bit LED6   = PORTB.2

LED6 = 1;

Genauso und war immer so aber der CCS hält sich auch nicht an Standards, 
enthält jede Menge spezielle Befehle und Syntax für den PIC. Importieren 
von Code ist nicht grad einfach und Exportieren gar nicht drin. Auch 
kein Ziel der Entwickler sondern eine Lösung nur für PIC. Ist man 
allerdings warm damit geworden ist es super damit zu arbeiten. Linker 
und Linkerscript gibt es auch keine, alles schon "drin".

von Christian J. (Gast)


Lesenswert?

Matthias Sch. schrieb:

> Das ist ein Irrtum. Genauso gut geht:#define TESTPIN  1
> #define TESTPIN2 2
>
> PORTB |= (1 << TESTPIN);   //PB1 High
> PORTB &= ~(1 << TESTPIN2); //PB2 Low
> Schau dir die defines von PB1 und PB2 mal an. Da steht ganz simpel :

Da steht aber das B und im Code PORTB drin. Lötest du nun einen 
Anschluss um an einen anderen Port kannst du das ganze Programm absuchen 
danach wo dieser Pin auftaucht, es in PORTA ändern und musst nicht nur 
im Header einmal etwas ändern. Bei den 22 Modulen die mein PIC Programm 
hat nicht grad schön.

von Max H. (hartl192)


Lesenswert?

Christian J. schrieb:
> Es geht also nicht :-(
Doch: Beitrag "Re: Dumme Frage zum "C" mit AVR's"
Du musst nur noch eine
1
#define LED5 BF_PORTB.portb5   // Oder welcher Pin auch immer
machen und kannst dann in deinem Quellcode LED5 = 1; oder LED5 = 0; 
schreiben.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Christian J. schrieb:
> Gefällt mir nicht, da im Code nochmal bei PORTB definiert sein muss zu
> welchem Port der Pin gehört.

Dafür ist es aber ISO-C-konform. Sowas wie PORTB.5 ist in Standard-C 
nicht erlaubt. Das ist eine Compiler-spezifische Erweiterung. Aus diesem 
Grund gefällt mir die Variante, die du für gcc beschreibst, besser. Sie 
erfordert keine proprietären Erweiterungen. Der vom Compiler daraus 
generierte Code ist trotzdem genauso effizient.

von Christian J. (Gast)


Lesenswert?

Max H. schrieb:
> Du musst nur noch eine#define LED5 BF_PORTB.portb5   // Oder welcher Pin
> auch immermachen und kannst dann in deinem Quellcode LED5 = 1; oder LED5
> = 0;
> schreiben.

Kannst Du das mal an einem Beispiel angeben?

"BF_PORTB" was not defined in this scope" heisst es beim Kompileren mit 
der Arduino IDE, in die ich die AVR Register eingebunden habe.

Was ist "BF_PORTB" und was "portb5"?

von Max H. (hartl192)


Lesenswert?

Christian J. schrieb:
> Was ist "BF_PORTB" und was "portb5"?
Beitrag "AVR-Register als Bitfields"

von Christian J. (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Dafür ist es aber ISO-C-konform. Sowas wie PORTB.5 ist in Standard-C
> nicht erlaubt. Das ist eine Compiler-spezifische Erweiterung. Aus diesem
> Grund gefällt mir die Variante, die du für gcc beschreibst, besser. Sie
> erfordert keine proprietären Erweiterungen. Der vom Compiler daraus
> generierte Code ist trotzdem genauso effizient.

Ja, es ist portierbar, ich mag das auch lieber. Wer mit CCS arbeitet, 
der sehr weit verbreitet ist muss damit leben, dass das für PIC ist und 
niemand sonst.
Memory Mapping usw. ist alles voreingestellt, fest an die Typen gebunden 
in einem Device Editor, wo man festlegt wo was ist, wieviele Timer, AD 
usw.  Aber die Ideologie ist auch eine andere, nämlich "Arduino Style" 
was ja auch inkompatibel ist aber eben sehr leicht zu verwenden und der 
Code ist sehr effzient:
1
....................     set_tris_a(0b11101111); 
2
0340:  MOVLW  EF
3
0342:  MOVWF  F92
4
....................     set_tris_b(0b11000000); 
5
0344:  MOVLW  C0
6
0346:  MOVWF  F93
7
....................     set_tris_c(0b00011111); 
8
0348:  MOVLW  1F
9
034A:  MOVWF  F94
10
....................  
11
....................     // Timer & AD Wandler 
12
....................     SETUP_ADC(ADC_CLOCK_INTERNAL); 
13
034C:  MOVF   FC0,W
14
034E:  ANDLW  C0
15
0350:  IORLW  07
16
0352:  MOVWF  FC0
17
0354:  BSF    FC0.7
18
0356:  BSF    FC2.0
19
....................     setup_adc_ports(AN0);            // AN1 als AD Port 
20
0358:  MOVF   FC1,W
21
035A:  ANDLW  C0
22
035C:  IORLW  0E
23
035E:  MOVWF  FC1
24
....................     set_adc_channel(0); 
25
0360:  MOVLW  00
26
0362:  MOVWF  01
27
0364:  MOVF   FC2,W
28
0366:  ANDLW  C3
29
0368:  IORWF  01,W
30
036A:  MOVWF  FC2

von Max H. (hartl192)


Lesenswert?

Christian J. schrieb:
> Aber die Ideologie ist auch eine andere, nämlich "Arduino Style"
> was ja auch inkompatibel ist aber eben sehr leicht zu verwenden.
Weil Arduino C++ ist.

von Nase (Gast)


Lesenswert?

Christian J. schrieb:
> Lötest du nun einen
> Anschluss um an einen anderen Port kannst du das ganze Programm absuchen
> danach wo dieser Pin auftaucht, es in PORTA ändern und musst nicht nur
> im Header einmal etwas ändern.

Stell dir nur mal vor, der Pin wird nachher mal invertiert oder sowas.

von Christian J. (Gast)


Lesenswert?

Max H. schrieb:
> Christian J. schrieb:
>> Was ist "BF_PORTB" und was "portb5"?
> Beitrag "AVR-Register als Bitfields"

Danke! Für Atmega88. Ich habe den 32PU, der vielleicht anders ist.

Ich verwende AVR's bisher nur in Chipform (also ohne die blauen Boards) 
mit der Arduino IDE, wobei ich aber nur deren Kompilier+Flashen Funktion 
über den UsBasp nutze, sonst aber weitestgehends die 
Registerdefinitionen des uC eingebunden habe, so dass ich Hardware 
selbst benutzen kann und nur bei Bedarf die Arduino Libs nutzen kann. 
PIC hingegen komplett auf der Registerebene ohne jegliche Libs für allen 
möglichen Sensor Kram. Die beiden Hersteller tun sich nichts, mit jeder 
Sorte kann man alles im Hausgebrauch erledigen.

Ok, danke nochmals, was ich wissen wollte weiss ich nun :-)

von Christian J. (Gast)


Lesenswert?

Max H. schrieb:
> Weil Arduino C++ ist.

Nur die Libraries und das auch aus gutem Grund, da viel bessere 
Kapselung möglich und die Instanziierung der Objekte sehr einfach ist. 
Ich brauch 3 Timer Tasks, ok.... erzeuge ich 3 gleiche Timer Objekte der 
gleichen Klasse und bin fertig. Habe da aber noch keinen mit iostream 
usw arbeiten sehen in den Beispielen.

von Christian J. (Gast)


Lesenswert?

S. R. schrieb:
> Man macht es genau so, wie du es schon beschrieben hast. Wenn du es
> anders haben willst, musst du dir deine eigene Abstrahierungsschicht
> dafür bauen.

Uaaaaahh.... diese Makro und Struct Gräber :-(

> Aber ich persönlich finde "LEDPORT |= (1 << LEDPIN)" nicht wirklich
> umständlich. Ist bei CMSIS auch nicht anders. Die STM32-Bibliothek ist
> da deutlich unangenehmer.

Ähm.... der STM32 hat für einen einzigen Pin auch 10.000 register und 
Alternate Funktionen und bevor man den blöden Pin verwenden kann muss er 
erst Takt haben, an einen Bus gekoppelt werden, OC oder HIGH, LOW usw. 
usw. usw Und ohne CMSIS und StdPeriphLibs wäre man 2 Stunden damit zu 
gange bis die LED leuchtet oder das Boad schon in der Tonne wäre, weil 
der Newbie die Nerven blank hat.

von Max H. (hartl192)


Lesenswert?

Christian J. schrieb:
> Habe da aber noch keinen mit iostream
> usw arbeiten sehen in den Beispielen.
Printf und scanf hab ich jetzt auch noch nie auf einem µC gesehen, hat 
ein µC überhaupt einen Standard input stream und Standard output stream?
C++ Strings, die man dann auch z.B. mit '+' aneinanderreihen kann hab 
ich schon ein paar Mal gesehen.

: Bearbeitet durch User
von Leo B. (luigi)


Lesenswert?

Simpel schrieb:
> Das können die AVR auch:
>
> AVR BIT AND BIT-TEST INSTRUCTIONS
>
> SBI P,b Set Bit in I/O Register   I/O(P,b) --> 1  Flags:None  Clocks:2
> CBI P,b Clear Bit in I/O Register I/O(P,b) <-- 0  Flags:None  Clocks:2

Stimmt, ganz vergessen... Nur ist der gcc klug genug, das so zu 
optimieren?
Ich glaube zur Sicherheit gegen race-conditions würde ich da eine 
Funtion oder Makro mit einem inline-asm-Befehl schnitzen...

von (prx) A. K. (prx)


Lesenswert?

Max H. schrieb:
> Printf und scanf hab ich jetzt auch noch nie auf einem µC gesehen,

printf ist recht verbreitet, scanf weniger.

> ein µC überhaupt einen Standard input stream und Standard output stream?

Wieso nicht? Letztlich wird dabei z.B. eine fest benamte aber nicht 
vordefinierte Funktion aufgerufen, die ein Zeichen auf eine 
Schnittstelle deiner Wahl ausgibt. Für diese Funktion sorgst du.

von Lurchi (Gast)


Lesenswert?

Die Optimierung mit den CBI und SBI Befehlen nutzt GCC am AVR schon - 
zumindest wenn einzelne Bits gesetzt oder gelöscht werden. Man muss aber 
auch da Aufpassen: nicht alle Register sind per CBI / SBI ansprechbar - 
die IO Ports sind aber mit dabei, bis auf einige große Chips mit sehr 
vielen IO Ports.

Ob GCC SBI/CBI mitlerweile auch bei Zugriff auf 2 Bits nutzt weiß ich 
nicht - ältere Versionen haben es jedenfalls nicht getan.

Der 2. Punkt wo man aufpassen muss ist, dass bei den älteren AVRs auch 
die SBI/CBI Befehle nicht wirklich Atomar sind - wenn die Hardware im 
fachen Zeitpunkt ein anderes Bit in dem Register ändert gibt es ggf. 
Probleme. Bei den neueren AVR (etwa ab Mega88 /Mega324 , also so ähnlich 
wie die Funktion Toogle per schreiben nach PIN) ist die Funktion dann 
atomar.

von Rolf M. (rmagnus)


Lesenswert?

Leo B. schrieb:
>> AVR BIT AND BIT-TEST INSTRUCTIONS
>>
>> SBI P,b Set Bit in I/O Register   I/O(P,b) --> 1  Flags:None  Clocks:2
>> CBI P,b Clear Bit in I/O Register I/O(P,b) <-- 0  Flags:None  Clocks:2
>
> Stimmt, ganz vergessen... Nur ist der gcc klug genug, das so zu
> optimieren?

Ja.

Lurchi schrieb:
> Die Optimierung mit den CBI und SBI Befehlen nutzt GCC am AVR schon -
> zumindest wenn einzelne Bits gesetzt oder gelöscht werden. Man muss aber
> auch da Aufpassen: nicht alle Register sind per CBI / SBI ansprechbar -
> die IO Ports sind aber mit dabei, bis auf einige große Chips mit sehr
> vielen IO Ports.

Nun, da wo es der Prozessor hardwaremäßig nicht hergibt, kann gcc 
SBI/CBI natürlich nicht nutzen. Das hat aber nichts mit der C-Syntax zu 
tun.

> Ob GCC SBI/CBI mitlerweile auch bei Zugriff auf 2 Bits nutzt weiß ich
> nicht - ältere Versionen haben es jedenfalls nicht getan.

Nun, dann wäre ja der Zugriff auch nicht atomar, und zwar in zweifachem 
Sinn. Er könnte durch einen Interrupt unterbrochen werden, und dazu 
würden sich auch noch die Bits im Register nicht gleichzeitig, sondern 
in zwei Schritten nacheinander ändern. Das könnte dann zu unerwünschten 
bzw. schädlichen Zwischenzuständen führen. Eine großartige Ersparnis hat 
man letzendlich auch nicht mehr gegenüber einem "klassischen" 
read-modify-write.

von (prx) A. K. (prx)


Lesenswert?

Rolf Magnus schrieb:
> Nun, dann wäre ja der Zugriff auch nicht atomar, und zwar in zweifachem
> Sinn.

Ich habe Zweifel, ob die dabei entstehenden mehrfachen Zugriffe noch mit 
der Definition von "volatile" verträglich sind.

Allerdings sind die SBI/CBI Optimierungen schon jetzt nicht mehr auf 
allen AVRs äquivalent zu R-M-W Operationen (unabhängig vom Zeitverhalten 
der Hardware), dürften von Compilern also streng genommen überhaupt 
nicht verwendet werden. ;-)

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

Leo B. schrieb:

> Und um deinen Code schöner zu gestalten gibt es viele Mittel und Wege:

Gibt es auch einen Weg, um zb das hier zu ersetzen, was ich mit der 
Arduino IDE derzeit nutze:

const uint8_t LED[MAX_NUM_LED+1] = {18, 15, 14, 12, 11, 8, 7, 6, 5, 4, 
3, 2, 1, 22};

eine reihe LED an beliebigen Pins, die einfach mit

pin_out(LED[0....14],HIGH)

pin_out ist eigene digitalWrite, ohne die Plausibiltätsprüfungen, da 
schneller. Arduinjo macht das durch eine Pinmap, die im pinout.h liegt, 
eine Lookuptable m Flash, wo jedem Portbit eine Nummer zuugeordnet wird.

ansteuerst? Das Schöne ist ja der Array mit den Pins, völlig egal wie 
die liegen, einfach die Nummern rein und schon lassen sich die Pins 
"mathematisch" berechnen.

Lauflicht:

for (int i=0;i< MAX;i++) {
   pin_out(LED[i],HIGH;
   delay(100);
   pin_out(LED[i],LOW
   usw.
}

von Daniel A. (daniel-a)


Lesenswert?

1
 // ungetestet
2
static volatile unsigned char*const ports[] = {&PORTA,&PORTB};
3
static const unsigned char portPinMap[] = {
4
  0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
5
};
6
static_assert(sizeof(ports)/sizeof(*ports)==sizeof(portPinMap)/sizeof(*portPinMap)/8,"error: portPinMap must match number of ports");
7
#define setPin(x,y) ( x[portPinMap[y]>>3] |= 1 << ( portPinMap[y] & 7 ) )
8
static const unsigned PIN_COUNT = sizeof(portPinMap)/sizeof(*portPinMap);
9
10
setPin(ports,3);

von Christian J. (Gast)


Lesenswert?

Daniel A. schrieb:
> // ungetestet

Selbst ausgedacht? Cool.... müsste mit PORTC und D dann ähnlich gehen. 
Nur kompiliert das nicht, setport wird angemeckert.

invalid operands of types volatile unsigned char and int to binry 
operator.

Übergabe eines Zeigers auf zwei Register mit einem Array dahinter?

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Christian J. schrieb:
> Wie macht man das mit dem AVR in C nun?

Eigentlich genauso. Leider hat es Atmel aber versäumt, die nötigen 
Defines mitzuliefern. Man muß sie selber schreiben:

Beitrag "Re: Einzelne Bits mit WinAvr"

Ich hab mir dann noch einen Header für alle Portpins geschrieben (im 
Anhang), dann spart man sich die Schreibarbeit.
1
#include "sbit.h"
2
3
#define LED0      PORT_C7
4
#define LED0_oe   DDR_C7
5
#define TASTE1_pu PORT_D3
6
#define TASTE1_in PIN_D3
7
8
int main()
9
{
10
  LED0_oe = 1;   // output enable
11
  TASTE1_pu = 1; // pullup on
12
13
  for(;;){
14
    LED0 = !TASTE1_in;
15
  }
16
}

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Christian J. schrieb:
> Nur kompiliert das nicht, setport wird angemeckert.
>
> invalid operands of types volatile unsigned char and int to binry
> operator.
>
> Übergabe eines Zeigers auf zwei Register mit einem Array dahinter?

Ups, da hab ich eine dereferenzierung vergessen. Dashier kommpiliert:
1
#include <avr/io.h>
2
#include <util/delay.h>
3
4
#define setPinTrue(x,y) ( *x[portPinMap[y]>>3] |= 1 << ( portPinMap[y] & 7 ) )
5
#define setPinFalse(x,y) ( *x[portPinMap[y]>>3] &= ~( 1 << ( portPinMap[y] & 7 ) ) )
6
#define setPin(x,y,z) ( (z) ? setPinTrue(x,y) : setPinFalse(x,y) )
7
8
static volatile unsigned char*const ports[] = {&PORTA,&PORTB};
9
static const unsigned char portPinMap[] = {
10
  0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
11
};
12
static const unsigned PIN_COUNT = sizeof(portPinMap)/sizeof(*portPinMap);
13
14
int main(){
15
  while(1){
16
    for (int i=0;i<PIN_COUNT;i++) {
17
      setPin(ports,i,1);
18
      _delay_ms(1000);
19
      setPin(ports,i,0);
20
    }
21
  }
22
  return 0;
23
}

von Bernd K. (prof7bit)


Lesenswert?

Ich hab auch noch eine Variante:

Das braucht etwas Hirnakrobatik um nachzuvollziehen warum das 
funktioniert...
1
#define _SET(type,name,bit)    type##name |= _BV(bit)
2
#define _CLR(type,name,bit)    type##name &= ~_BV(bit)
3
#define _GET(type,name,bit)    (type##name & _BV(bit))
4
#define SET_AS_OUTPUT(pin)     _SET(DDR,pin)
5
#define SET_AS_INPUT(pin)      _CLR(DDR,pin)
6
#define SET_HIGH(pin)          _SET(PORT,pin)
7
#define SET_LOW(pin)           _CLR(PORT,pin)
8
#define TOGGLE(pin)            _SET(PIN,pin)
9
#define IS_HIGH(pin)           _GET(PIN,pin)


Aber es funktioniert dank der Art und Weise wie die Textersetzungen beim 
Auflösen der Makros durchgeführt werden und jetzt kann man bequem jeden 
Pin in nur je einer einzigen Zeile definieren und hat auch Makros um 
sie zu setzen und zu löschen:

1
#define LED_OP         D,6
2
#define LED_PUMP       D,3
3
#define PUMP           D,5
4
#define SENS           D,4
5
6
int main() {
7
    SET_AS_OUTPUT(LED_OP);
8
    SET_AS_OUTPUT(LED_PUMP);
9
    SET_AS_OUTPUT(PUMP);
10
11
    SET_LOW(PUMP);
12
    SET_HIGH(LED_PUMP);
13
    SET_LOW(LED_OP);
14
15
    SET_AS_INPUT(SENS);
16
    SET_HIGH(SENS);        // Pullup!
17
18
    while(1) {
19
        if (!IS_HIGH(SENS)) {
20
            SET_HIGH(PUMP);
21
            SET_LOW(LED_PUMP);
22
23
            [...]

und jetzt noch eine hauchdünne Abstraktion drüber so daß der Code sich 
liest wie Prosa und damit man nur noch an einer einzigen Stelle drüber 
nachdenken muss daß die LEDs low-side geschaltet werden und wie der 
Sensor angeschlossen ist:
1
#define LED_OP         D,6
2
#define LED_PUMP       D,3
3
#define PUMP           D,5
4
#define SENS           D,4
5
6
static inline bool is_empty() {
7
    return !IS_HIGH(SENS);
8
}
9
10
static inline void pump_on() {
11
    SET_HIGH(PUMP);
12
    SET_LOW(LED_PUMP);
13
}
14
15
static inline void pump_off() {
16
    SET_LOW(PUMP);
17
    SET_HIGH(LED_PUMP);
18
}
19
20
int main() {
21
    SET_AS_OUTPUT(LED_OP);
22
    SET_AS_OUTPUT(LED_PUMP);
23
    SET_AS_OUTPUT(PUMP);
24
25
    SET_LOW(PUMP);
26
    SET_HIGH(LED_PUMP);
27
    SET_LOW(LED_OP);
28
29
    SET_AS_INPUT(SENS);
30
    SET_HIGH(SENS);        // Pullup!
31
32
    while(1) {
33
        if (is_empty()) {
34
            pump_on();
35
36
            [...]

von Christian J. (Gast)


Lesenswert?

Daniel A. schrieb:
> Ups, da hab ich eine dereferenzierung vergessen. Dashier kommpiliert

Besten Dank, gleich als Snippet abgelegt :-)

von Nase (Gast)


Lesenswert?

Warum tut man sich so eine Pfriemelei an?!

Eine kleine Funktion kostet weder Laufzeit noch Speicher, ist prima 
wartbar, portabel und leicht durchschaubar. Warum also so einen 
Müllhaufen aus Makros, vage definierten Bitfeldern und magischen 
Konstanten quer durchs Projekt schleifen?

Aber wers braucht, naja bitte.

von Christian J. (Gast)


Lesenswert?

Nase schrieb:
> Warum tut man sich so eine Pfriemelei an?!

w31l 35 c00l 4u5513h7 und 3l173 v0m l4m3r un73r5ch31d37!

von Daniel A. (daniel-a)


Lesenswert?

Christian J. schrieb:
> w31l 35 c00l 4u5513h7 und 3l173 v0m l4m3r un73r5ch31d37!
Ich wähle mal die richtige character encoding aus:
  wEIl ES cOOl AuSSIEhT und ElITe von lAmEr unTErSchEIdET!
  weil es cool aussieht und Elite von Lamer unterscheidet!

1) Was ist ein Lamer?
2) der Lamer, von dem Lamer, vom Lamer, aber nicht "von Lamer"
3) kann "Elite" ohne ein "die" davor vorkommen?

Nase schrieb:
> Warum tut man sich so eine Pfriemelei an?!

0) Ich bin der meinung, dass es wegen der besseren Abstraktion, 
portablität und lesbarkeit ist.

von Christian J. (Gast)


Lesenswert?

Daniel A. schrieb:

> 0) Ich bin der meinung, dass es wegen der besseren Abstraktion,
> portablität und lesbarkeit ist.

1337!

Korrekt und weil es guter Stil ist die Hardware von der Software zu 
trennen. Wächst die Software rächt sich jede Information, die mehrfach 
vorhanden ist aber durch einen einzigen Ausdruck ersetzt werden könnte. 
Und es gehört auch keine Zahl in ein Programm, wobei man hier auch 
overacten kann und es dadurch noch schlimmer wird. Ich habe Programme in 
der Industrie gesehen, wo man sich ernsthaft fragt wie derjenige an 
seinen Job gekommen ist.

von Hans (Gast)


Lesenswert?

Christian J. schrieb:
> Korrekt und weil es guter Stil ist die Hardware von der Software zu
> trennen. Wächst die Software rächt sich jede Information, die mehrfach
> vorhanden ist aber durch einen einzigen Ausdruck ersetzt werden könnte.

Man sollte allerdings realistisch bleiben, wie viel Anteil das reine 
Setzen von Pins auf Low oder High in einem realen Programm hat.

Meiner Erfahrung nach macht das nur einen winzigen Teil der 
Gesamtfunktionalität aus. Ein ebenfalls kleiner Teil bedient die anderen 
IO-Interfaces (Timer, UART, SPI, I2C etc.). Wie diese Dinge programmiert 
werden, ist abhängig vom Mikrocontroller. Also kommen sie in Funktionen 
mit einer möglichst controllerunabhängigen Schnittstelle, aber einer 
controllerabhängigen Implementierung.

Der Hauptteil des Programms besteht aus Abstraktionen für die 
Komponenten, die sich außerhalb des Mikrocontrollers befinden und wie 
sie durch das Gesamtprogramm miteinander interagieren. Wenn man das 
Programm auf einem anderen Controller laufen lassen möchte, muss man 
eben die controllerspezifischen Teile neu implementieren.

Jetzt noch zu versuchen, innerhalb der controllerspezifischen Teile eine 
gemeinsame Abstraktion für Port- und Pin-Register zu finden, lohnt sich 
imo nicht. Entweder ziehe ich auf eine neue Controllerplattform um, dann 
ändern sich mit Sicherheit ohnehin die Namen und Nummern für Ports und 
Pins. Oder ich bleibe bei der Controllerfamilie und muss nur Port- oder 
Pinnummern ändern. Dann tuns auch controllerspezifisches Makros à la:
1
#define POWER_LED_PORT  PORTB
2
#define POWER_LED_PIN   5
3
#define STATUS_LED_PORT PORTC
4
#define STATUS_LED_PIN  1

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Max H. schrieb:
>> Habe da aber noch keinen mit iostream
>> usw arbeiten sehen in den Beispielen.
> Printf und scanf hab ich jetzt auch noch nie auf einem µC gesehen, hat
> ein µC überhaupt einen Standard input stream und Standard output stream?

Schaut euch mal das grosse Beispiel zur avrlib in der HTML Doku an. Da 
wird zumindest stdout initialisiert und genutzt.

Christian J. schrieb:
>> Das ist ein Irrtum. Genauso gut geht:#define TESTPIN  1
>> #define TESTPIN2 2
>>
>> PORTB |= (1 << TESTPIN);   //PB1 High
>> PORTB &= ~(1 << TESTPIN2); //PB2 Low
>> Schau dir die defines von PB1 und PB2 mal an. Da steht ganz simpel :
>
> Da steht aber das B und im Code PORTB drin. Lötest du nun einen
> Anschluss um an einen anderen Port kannst du das ganze Programm absuchen
> danach wo dieser Pin auftaucht, es in PORTA ändern und musst nicht nur
> im Header einmal etwas ändern.

So würde ich persönlich das ja auch nicht schreiben, es ging hier nur um 
den nicht vorhandenen Unterschied zwischen PB1 und 1.
Ich definiere meine Ports einmal etwa so:
1
// AVR
2
#define USER_PORT PORTB
3
// STM32
4
#define USER_PORT GPIOB

: Bearbeitet durch User
von Hans (Gast)


Lesenswert?

Ergänzung zu meinem Post: Da sollte noch das DDR-Register dazu:
1
#define POWER_LED_PORT  PORTB
2
#define POWER_LED_DDR   DDRB
3
#define POWER_LED_PIN   5
4
#define STATUS_LED_PORT PORTC
5
#define STATUS_LED_DDR  DDRC
6
#define STATUS_LED_PIN  1
Ja, wenn man Port B auf A ändern will, muss man an zwei Stellen etwas 
ändern, also PORTB zu PORTA und DDRB zu DDRA. Gemessen daran, wie oft 
das realistischerweise der Fall ist und wie viel Arbeit es verursacht, 
bin ich der Meinung, es lohnt sich nicht, noch eine weitere 
Abstraktionsschicht drumrum zu basteln.

von Christian J. (Gast)


Lesenswert?

Hans schrieb:

> Gemessen daran, wie oft
> das realistischerweise der Fall ist und wie viel Arbeit es verursacht,
> bin ich der Meinung, es lohnt sich nicht, noch eine weitere
> Abstraktionsschicht drumrum zu basteln.

Das mag für diese 8 Bitter ja gelten, die oft nur wenig Zeilen Code 
enthalten, um irgend ein Spielzeug quaken zu lassen. Software ist ein 
Geistesprodukt, so wie ein Gesetz oder eine Norm der IEC oder CENELEC 
(mein eigentliches Wirkungsfeld). Ich kann Dir nur flüstern welches 
Drama wir mal in der Firma hatten (Pleite 2004) als Leute in Rente 
gingen und der Code Salat den sie produziert hatten entwirrt werden 
musste! Still und ohne jedes Review haben sie Automaten programmiert und 
die ganze Produktion mit ihrem "Kot" infiziert. Die jungen neuen 
Techniker kapitulierten vor den Zehntausenden Zeilen unkommentierten 
oder schlecht strukturierten Spaghetti Code! Es gibt sowas wie ein 
ISO/OSI Modell und es ist sogar normiert (ISO 29119), wie Software 
auszusehen hat, es gibt den MISRA Standard usw.

Warum also nicht dran halten, auch zu Hause? Ok, bei mir ist das "drin" 
aber wie ich jeden Tag sehe ist da noch viel Erziehzungsarbeit zu 
leisten beim Nachwuchs...

von Bernd K. (prof7bit)


Lesenswert?

Hans schrieb:
> Ja, wenn man Port B auf A ändern will, muss man an zwei Stellen etwas
> ändern, also PORTB zu PORTA und DDRB zu DDRA.

Oder Du definierst es so wie ich oben beschrieben habe:
1
#define POWER_LED      B,5
2
#define STATUS_LED     C,1

Dann ist es nur noch eine einzige Zeile pro Pin.

von Nase (Gast)


Lesenswert?

Daniel A. schrieb:
> 0) Ich bin der meinung, dass es wegen der besseren Abstraktion,
> portablität und lesbarkeit ist.

Christian J. schrieb:
> Korrekt und weil es guter Stil ist die Hardware von der Software zu
> trennen.

Jaja, und deshalb frickelt man mit Bitfeldern herum, baut Makromonster 
und so weiter, anstatt eine kleine Funktion in den Header zu inlinen?

Das, was hier mit Bitfeldern und Makros propagiert wird, ist doch 
geradezu das Gegenteil von Stil und Abstraktion.

von Bernd K. (prof7bit)


Lesenswert?

Nase schrieb:
> Jaja, und deshalb frickelt man mit Bitfeldern herum, baut Makromonster
> und so weiter, anstatt eine kleine Funktion in den Header zu inlinen?
>
> Das, was hier mit Bitfeldern und Makros propagiert wird, ist doch
> geradezu das Gegenteil von Stil und Abstraktion.

Zeig doch mal wie Dein code aussieht, dann sehen wir wer sich das Leben 
einfacher macht und wessen code

  * In der Anwendung einfacher/schöner zu lesen ist
  * weniger Runtime-Overhead erzeugt
  * Bei Hardwareänderungen einfacher zu portieren¹ ist
  * Weniger Header-"monster" mit sich rumschleppt.

Es ist wie überall ein Kompromiss. Meine persönlichen Prioritäten liegen 
so:

  1. Einfach zu lesen/verstehen, selbsterklärend
  2. Einfach zu pflegen oder portieren¹
  3. So wenig wie möglich Runtime-Bloat, idealer Weise gar keinen!
  4. Makro-Monster ist vollkommen egal², Hauptsache 1 bis 3
_____
¹) portieren: wieviele Zeilen muss ich ändern wenn ich z.B. statt PB5 
den PA3 nehme, welche Teile muss ich ändern wenn ich irgendwann von AVR 
komplett auf einen ARM umsteige.

²) es ändert sich ja nie! Es muss nur einmal verstanden werden, bei nur 
9 Zeilen Makro-"Monster" um das komplette GPIO aller ATTinies und aller 
ATMegas ein für allemal zu abstrahieren ist das noch überschaubar, YMMV.

: Bearbeitet durch User
von S. R. (svenska)


Lesenswert?

Christian J. schrieb:
> S. R. schrieb:
>> Man macht es genau so, wie du es schon beschrieben hast. Wenn du es
>> anders haben willst, musst du dir deine eigene Abstrahierungsschicht
>> dafür bauen.
>
> Uaaaaahh.... diese Makro und Struct Gräber :-(

Und ich frage mich, wozu du deinen AVR-C-Code unbedingt so 
vermakroisieren musst, dass er am Ende aussieht, wie dein nicht 
standardkonformer PIC-Code.

Bernd K. schrieb:
> Zeig doch mal wie Dein code aussieht, dann sehen wir wer sich das Leben
> einfacher macht und wessen code

In drivers/led.h:
1
void led1_init(void);
2
void led1_on  (void);
3
void led1_off (void);

In drivers/led.c:
1
void led1_init(void)
2
{
3
  /* hier die initialisierung von LED 1 */
4
}
5
6
void led1_on(void)
7
{
8
  /* hier wird die LED eingeschaltet */
9
}
10
11
void led1_off(void)
12
{
13
  /* hier wird die LED ausgeschaltet */
14
}

Im Hauptprogramm, als Beispiel ein Blinkprogramm:
1
int main()
2
{
3
  led1_init();
4
  while(1) {
5
    led1_on();
6
    /* warteschleife */
7
    led1_off();
8
    /* warteschleife */
9
  }
10
}

>   * In der Anwendung einfacher/schöner zu lesen ist
  Das Hauptprogramm weiß, dass es um eine LED geht. Das ist wichtig.
  Wie sie angeschlossen ist, ist egal.

>   * weniger Runtime-Overhead erzeugt
  Ein guter Compiler wird das inlinen, ansonsten ist das ein Call
  zusätzlich. Wenn dir das zu viel Aufwand ist, hast du die CPU
  entweder schlecht gewählt, oder ein so übles Timing, dass du
  eigentlich schon über Assembler nachdenken solltest.

>   * Bei Hardwareänderungen einfacher zu portieren¹ ist
  Alles, was mit der LED zu tun hat, befindet sich in einem
  getrennten Modul, und garantiert nur dort muss man etwas
  ändern, wenn man die LED umlötet.

>   * Weniger Header-"monster" mit sich rumschleppt.
  Für jeden Treiber brauchst du sowieso API, also auch eine Headerdatei.

>   1. Einfach zu lesen/verstehen, selbsterklärend
  [x] Ja.

>   2. Einfach zu pflegen oder portieren¹
  [x] Ja.

>   3. So wenig wie möglich Runtime-Bloat, idealer Weise gar keinen!
  [x] Wahrscheinlich Ja.

>   4. Makro-Monster ist vollkommen egal², Hauptsache 1 bis 3
  [ ] Nicht nötig.

Witzigerweise setzt du Les- und Verstehbarkeit ganz oben an, erlaubst 
aber gleichzeitig unlesbare und schwer verständliche Makro-Monster als 
Sonderfall.

Nachtrag: Dieser Ansatz funktioniert auch auf einem STM32 oder SAM3 noch 
genauso, was deine Makros nicht tun. Die sind AVR-spezifisch, weil sie 
auf AVR-Ports zugeschnitten sind, und lassen sich für andere Typen nicht 
besonders gut nachbauen.

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

Apropos Makro Monster.... echt cool für den Arduino, die 
digitalWriteFast Funktion, also Direktzugriff auf die Ports bei 
Beibehaltung der Numerierung.
Die ultimative Lösung für viele AVR.
1
if !defined(digitalPinToPortReg)
2
#if !(defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) )
3
4
5
// Standard Arduino Pins
6
#define digitalPinToPortReg(P) \
7
(((P) >= 0 && (P) <= 7) ? &PORTD : (((P) >= 8 && (P) <= 13) ? &PORTB : &PORTC))
8
#define digitalPinToDDRReg(P) \
9
(((P) >= 0 && (P) <= 7) ? &DDRD : (((P) >= 8 && (P) <= 13) ? &DDRB : &DDRC))
10
#define digitalPinToPINReg(P) \
11
(((P) >= 0 && (P) <= 7) ? &PIND : (((P) >= 8 && (P) <= 13) ? &PINB : &PINC))
12
#define digitalPinToBit(P) \
13
(((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14))
14
15
#if defined(__AVR_ATmega8__)
16
17
// 3 PWM
18
#define digitalPinToTimer(P) \
19
(((P) ==  9 || (P) == 10) ? &TCCR1A : (((P) == 11) ? &TCCR2 : 0))
20
#define digitalPinToTimerBit(P) \
21
(((P) ==  9) ? COM1A1 : (((P) == 10) ? COM1B1 : COM21))
22
#else
23
24
// 6 PWM
25
#define digitalPinToTimer(P) \
26
(((P) ==  6 || (P) ==  5) ? &TCCR0A : \
27
        (((P) ==  9 || (P) == 10) ? &TCCR1A : \
28
        (((P) == 11 || (P) ==  3) ? &TCCR2A : 0)))
29
#define digitalPinToTimerBit(P) \
30
(((P) ==  6) ? COM0A1 : (((P) ==  5) ? COM0B1 : \
31
        (((P) ==  9) ? COM1A1 : (((P) == 10) ? COM1B1 : \
32
        (((P) == 11) ? COM2A1 : COM2B1)))))
33
#endif
34
35
#else
36
// Arduino Mega Pins
37
#define digitalPinToPortReg(P) \
38
(((P) >= 22 && (P) <= 29) ? &PORTA : \
39
        ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PORTB : \
40
        (((P) >= 30 && (P) <= 37) ? &PORTC : \
41
        ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PORTD : \
42
        ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PORTE : \
43
        (((P) >= 54 && (P) <= 61) ? &PORTF : \
44
        ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PORTG : \
45
        ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PORTH : \
46
        (((P) == 14 || (P) == 15) ? &PORTJ : \
47
        (((P) >= 62 && (P) <= 69) ? &PORTK : &PORTL))))))))))
48
49
#define digitalPinToDDRReg(P) \
50
(((P) >= 22 && (P) <= 29) ? &DDRA : \
51
        ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &DDRB : \
52
        (((P) >= 30 && (P) <= 37) ? &DDRC : \
53
        ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &DDRD : \
54
        ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &DDRE : \
55
        (((P) >= 54 && (P) <= 61) ? &DDRF : \
56
        ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &DDRG : \
57
        ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &DDRH : \
58
        (((P) == 14 || (P) == 15) ? &DDRJ : \
59
        (((P) >= 62 && (P) <= 69) ? &DDRK : &DDRL))))))))))
60
61
#define digitalPinToPINReg(P) \
62
(((P) >= 22 && (P) <= 29) ? &PINA : \
63
        ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PINB : \
64
        (((P) >= 30 && (P) <= 37) ? &PINC : \
65
        ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PIND : \
66
        ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PINE : \
67
        (((P) >= 54 && (P) <= 61) ? &PINF : \
68
        ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PING : \
69
        ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PINH : \
70
        (((P) == 14 || (P) == 15) ? &PINJ : \
71
        (((P) >= 62 && (P) <= 69) ? &PINK : &PINL))))))))))
72
73
#define digitalPinToBit(P) \
74
(((P) >=  7 && (P) <=  9) ? (P) - 3 : \
75
        (((P) >= 10 && (P) <= 13) ? (P) - 6 : \
76
        (((P) >= 22 && (P) <= 29) ? (P) - 22 : \
77
        (((P) >= 30 && (P) <= 37) ? 37 - (P) : \
78
        (((P) >= 39 && (P) <= 41) ? 41 - (P) : \
79
        (((P) >= 42 && (P) <= 49) ? 49 - (P) : \
80
        (((P) >= 50 && (P) <= 53) ? 53 - (P) : \
81
        (((P) >= 54 && (P) <= 61) ? (P) - 54 : \
82
        (((P) >= 62 && (P) <= 69) ? (P) - 62 : \
83
        (((P) == 0 || (P) == 15 || (P) == 17 || (P) == 21) ? 0 : \
84
        (((P) == 1 || (P) == 14 || (P) == 16 || (P) == 20) ? 1 : \
85
        (((P) == 19) ? 2 : \
86
        (((P) == 5 || (P) == 6 || (P) == 18) ? 3 : \
87
        (((P) == 2) ? 4 : \
88
        (((P) == 3 || (P) == 4) ? 5 : 7)))))))))))))))
89
90
// 15 PWM
91
#define digitalPinToTimer(P) \
92
(((P) == 13 || (P) ==  4) ? &TCCR0A : \
93
        (((P) == 11 || (P) == 12) ? &TCCR1A : \
94
        (((P) == 10 || (P) ==  9) ? &TCCR2A : \
95
        (((P) ==  5 || (P) ==  2 || (P) ==  3) ? &TCCR3A : \
96
        (((P) ==  6 || (P) ==  7 || (P) ==  8) ? &TCCR4A : \
97
        (((P) == 46 || (P) == 45 || (P) == 44) ? &TCCR5A : 0))))))
98
#define digitalPinToTimerBit(P) \
99
(((P) == 13) ? COM0A1 : (((P) ==  4) ? COM0B1 : \
100
        (((P) == 11) ? COM1A1 : (((P) == 12) ? COM1B1 : \
101
        (((P) == 10) ? COM2A1 : (((P) ==  9) ? COM2B1 : \
102
        (((P) ==  5) ? COM3A1 : (((P) ==  2) ? COM3B1 : (((P) ==  3) ? COM3C1 : \
103
        (((P) ==  6) ? COM4A1 : (((P) ==  7) ? COM4B1 : (((P) ==  8) ? COM4C1 : \
104
        (((P) == 46) ? COM5A1 : (((P) == 45) ? COM5B1 : COM5C1))))))))))))))
105
106
#endif
107
#endif
108
109
#if !defined(digitalWriteFast)
110
#define digitalWriteFast(P, V) \
111
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
112
                if (digitalPinToTimer(P)) \
113
                        bitClear(*digitalPinToTimer(P), digitalPinToTimerBit(P)); \
114
                bitWrite(*digitalPinToPortReg(P), digitalPinToBit(P), (V)); \
115
        } else { \
116
                digitalWrite((P), (V)); \
117
        }
118
#endif
119
120
#if !defined(pinModeFast)
121
#define pinModeFast(P, V) \
122
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
123
                bitWrite(*digitalPinToDDRReg(P), digitalPinToBit(P), (V)); \
124
        } else {  \
125
                pinMode((P), (V)); \
126
        } 
127
#endif
128
129
#if !defined(digitalReadFast)
130
#define digitalReadFast(P) ( (int) __digitalReadFast__((P)) )
131
#define __digitalReadFast__(P ) \
132
(__builtin_constant_p(P) ) ? ( \
133
                digitalPinToTimer(P) ? ( \
134
                       bitClear(*digitalPinToTimer(P), digitalPinToTimerBit(P)) ,  \
135
                             bitRead(*digitalPinToPINReg(P), digitalPinToBit(P))) : \
136
                  bitRead(*digitalPinToPINReg(P), digitalPinToBit(P)))  : \
137
                digitalRead((P))
138
#endif
139
140
#if !defined(digitalWriteFast2)
141
#define digitalWriteFast2(P, V) \
142
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
143
                bitWrite(*digitalPinToPortReg(P), digitalPinToBit(P), (V)); \
144
        } else { \
145
                digitalWrite((P), (V)); \
146
        }
147
#endif
148
149
#if !defined(pinModeFast2)
150
#define pinModeFast2(P, V) \
151
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
152
                if (digitalPinToTimer(P)) \
153
                        bitClear(*digitalPinToTimer(P), digitalPinToTimerBit(P)); \
154
                bitWrite(*digitalPinToDDRReg(P), digitalPinToBit(P), (V)); \
155
        } else {  \
156
                pinMode((P), (V)); \
157
        } 
158
#endif
159
160
#if !defined(digitalReadFast2)
161
#define digitalReadFast2(P) ( (int) __digitalReadFast2__((P)) )
162
#define __digitalReadFast2__(P ) \
163
(__builtin_constant_p(P) ) ? ( \
164
                ( bitRead(*digitalPinToPINReg(P), digitalPinToBit(P))) ) : \
165
                digitalRead((P))
166
#endif

von Nase (Gast)


Lesenswert?

Bernd K. schrieb:
> Bei Hardwareänderungen einfacher zu portieren¹ ist

Allein das dürfte ein KO-Kriterium für jeden Makroverhau sein.

von Bernd K. (prof7bit)


Lesenswert?

Nase schrieb:
> Bernd K. schrieb:
>> Bei Hardwareänderungen einfacher zu portieren¹ ist
>
> Allein das dürfte ein KO-Kriterium für jeden Makroverhau sein.

Nein.

einen Satz von

SET_AS_OPUTPUT(x)
SET_HIGH(x)
SET_LOW(x)

Kann ich mir für jeden Controller schreiben.

Der Vorteil bei solchen Makros gegenüber einer separaten inline Funktion 
für einzelnen jeden Pin ist der daß ich das SET_HIGH(x) Makro nur einmal 
anpassen muss und der Preprozessor erzeugt dann bei Bedarf den inline 
code für jeden pin den ich will, jedoch bei 42 verschiedenen 
selbstgeschriebenen inline-Funktionen (für jeden einzelnen pin eine 
eigene) muss ich alle 42 dieser Funktionen in gleicher Weise anpassen, 
eine ungeheuerliche Redundanz und Codeduplikation.

Oder ich führe eine Abstraktion die das zur Laufzeit macht aber das 
kostet mich eben Laufzeit.

Ich bleib lieber bei meinen Makros, die vereinen beide Vorteile bei 
Vermeidung beider Nachteile.

von Nase (Gast)


Lesenswert?

Bernd K. schrieb:
> SET_AS_OPUTPUT(x)
> SET_HIGH(x)
> SET_LOW(x)
>
> Kann ich mir für jeden Controller schreiben.

Und was machst du, wenn die LED später nicht mehr nach Masse geschaltet 
ist (SET_HIGH = LED an) sondern nach Plus? Genau, an 100 Stellen im Code 
etwas umbauen.

Und wenn die LED später mal an einem Schieberegister hängt?

Deine Makros bieten effektiv Null Abstraktion, weil ich ja doch wieder 
überall wissen muss, ob ich HIGH-aktiv oder LOW-aktiv bin usw. Außerdem 
sind sie nur so lange 'kostenlos', wie das dahinter tatsächlich 
irgendwie in ein paar wenige Instruktionen kompiliert.

Aber naja. Perlen -> Säue, Kopf -> Wand oder was auch immer.

von Nase (Gast)


Lesenswert?

Bernd K. schrieb:
> jedoch bei 42 verschiedenen
> selbstgeschriebenen inline-Funktionen (für jeden einzelnen pin eine
> eigene) muss ich alle 42 dieser Funktionen in gleicher Weise anpassen,
> eine ungeheuerliche Redundanz und Codeduplikation.
Klar, wenn du die Funktionen derart beknackt aufziehst, dann ja.

von Bernd K. (prof7bit)


Lesenswert?

Nase schrieb:
> Und was machst du, wenn die LED später nicht mehr nach Masse geschaltet
> ist (SET_HIGH = LED an) sondern nach Plus? Genau, an 100 Stellen im Code
> etwas umbauen.

Nein.

Wenn Du mal nach oben blättern magst, ich hab inline-Funktionen led_on() 
und led_off() dafür vorgesehen.

von Bernd K. (prof7bit)


Lesenswert?

Nase schrieb:
> Klar, wenn du die Funktionen derart beknackt aufziehst, dann ja.

Dann zeig doch mal wie Du die aufziehen würdest. Ich bin schon sehr 
gespannt.

von Nase (Gast)


Lesenswert?

Bernd K. schrieb:
> Wenn Du mal nach oben blättern magst, ich hab inline-Funktionen led_on()
> und led_off() dafür vorgesehen.
Mag ich, ich denke du meinst pump_on() und pump_off().

Dann verstehe ich zugegebenermaßen nicht, weshalb du noch den 
Makro-Blödsinn zusätzlich machst. Aber das mag nun wirklich 
Geschmackssache sein.

Die Makros sind ohnehin nur so lange haltbar, wie es sich tatsächlich um 
Pins im SFR handelt.

von Bernd K. (prof7bit)


Lesenswert?

Nase schrieb:
> Dann verstehe ich zugegebenermaßen nicht, weshalb du noch den
> Makro-Blödsinn zusätzlich machst. Aber das mag nun wirklich
> Geschmackssache sein.

Weil die makros zur Compilezeit den Code erzeugen der den Portpin 
schaltet, im Falle von AVR wäre das ein sbi oder cbi mit 
einkompiliertem Immediate. Das bekommst Du so nicht hin mit einer 
reinen C Funktion, die würde nämlich zur Laufzeit ausgeführt, ein Makro 
wird bereits zur Compilezeit ausgewertet.

: Bearbeitet durch User
von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Bernd K. schrieb:
> Das bekommst Du so nicht hin mit einer
> reinen C Funktion, die würde nämlich zur Laufzeit ausgeführt, ein Makro
> wird bereits zur Compilezeit ausgewertet.

Das stimmt nicht ganz, das (Asm-)Ergebnis einer inline-Funktion und 
eines Makros ist idR identisch.

Allerdings ist Inlinen über C-File-Grenzen hinweg etwas mühsam, man 
müsste die Funktion fast doppelt definieren, bzw. auf jeden Fall im 
Header-File, das ist unschön.

Allerdings lindert LTO dieses Manko.

Gernerell halte ich das LED-Beispiel für nicht zielführend, weil zu 
einfach. Timer sind hier immer wieder eine herausforderung, speziell 
wenn diese mehrfach für verschiedene Dinge verwendet werden. Da kommt 
jede saubere Kapselung und Abstraktion an ihre Grenzen.

von Bernd K. (prof7bit)


Lesenswert?

Michael Reinelt schrieb:
> Das stimmt nicht ganz, das (Asm-)Ergebnis einer inline-Funktion und
> eines Makros ist idR identisch.

idr == wenn man Glück hat.

Der gcc ist gut, kein Zweifel, aber er hat doch seine Grenzen. Betrachte 
diesen Code:

gpio.h
1
#ifndef GPIO_H_
2
#define GPIO_H_
3
4
#include <stdint.h>
5
#include <stdbool.h>
6
7
typedef struct {
8
    volatile uint8_t* port;
9
    uint8_t pin;
10
} portpin_t;
11
12
static inline void set_as_output(portpin_t pp) {
13
    *(pp.port - 1) |= (1 << pp.pin);
14
}
15
16
static inline void set_as_input(portpin_t pp) {
17
    *(pp.port - 1) &= ~(1 << pp.pin);
18
}
19
20
static inline void set_pin_high(portpin_t pp) {
21
    *pp.port |= (1 << pp.pin);
22
}
23
24
static inline void set_pin_low(portpin_t pp) {
25
    *pp.port &= ~(1 << pp.pin);
26
}
27
28
static inline bool is_pin_high(portpin_t pp) {
29
    return *(pp.port - 2) & (1 << pp.pin);
30
}
31
32
#endif /* GPIO_H_ */

pin_config.h
1
#ifndef PIN_CONFIG_H_
2
#define PIN_CONFIG_H_
3
4
#include "gpio.h"
5
6
const portpin_t led_green = {&PORTA, 0};
7
const portpin_t led_yellow = {&PORTA, 1};
8
const portpin_t sensor = {&PORTA, 2};
9
10
#endif /* PIN_CONFIG_H_ */

main.c
1
#include <avr/io.h>
2
#include "pin_config.h"
3
#include "gpio.h"
4
5
static inline void led_green_on() {
6
    set_pin_low(led_green);             // this LED is driven low side!
7
}
8
9
static inline void led_green_off() {
10
    set_pin_high(led_green);            // this LED is driven low side!
11
}
12
13
static inline bool is_sensor_signal() {
14
    return is_pin_high(sensor);
15
}
16
17
int main() {
18
    set_as_output(led_green);
19
    set_as_output(led_yellow);
20
    set_as_input(sensor);
21
22
    while (1) {
23
        if (is_sensor_signal()) {
24
            led_green_on();
25
        } else {
26
            led_green_off();
27
        }
28
    }
29
}


Bis hierhin ist die Welt noch in Ordnung, so tief kann der gcc noch 
optimieren daß er das ganze Rechnen und Shiften noch zur Compilezeit 
auflöst:
1
0000004a <main>:
2
  4a:  d0 9a         sbi  0x1a, 0  ; 26
3
  4c:  d1 9a         sbi  0x1a, 1  ; 26
4
  4e:  d2 98         cbi  0x1a, 2  ; 26
5
  50:  ca 9b         sbis  0x19, 2  ; 25
6
  52:  02 c0         rjmp  .+4        ; 0x58 <main+0xe>
7
  54:  d8 98         cbi  0x1b, 0  ; 27
8
  56:  fc cf         rjmp  .-8        ; 0x50 <main+0x6>
9
  58:  d8 9a         sbi  0x1b, 0  ; 27
10
  5a:  fa cf         rjmp  .-12       ; 0x50 <main+0x6>

Soweit so gut. Eigentlich schon recht beeindruckend was der Compiler 
hier leistet, aber leider doch zu früh gefreut. Nur eine einzige 
Abstraktionsschicht mehr und es bricht zusammen wie ein Kartenhaus. 
Nehmen wir an wir haben mehrere LED und wir wollen nur an einer Stelle 
definieren wie man eine LED einschaltet, anstatt also für jede LED eine 
eigene on und eine eigene off-Funktion zu schreiben machen wir eine die 
für alle LEDs gilt:

also statt
1
static inline void led_green_on() {
2
    set_pin_low(led_green);       // this LED is driven low side!
3
}
schreiben wir jetzt
1
static inline void led_on(portpin_t pp) {
2
    set_pin_low(pp);             // all LEDs are driven low side!
3
}

also der ganze code nochmal:
1
#include <avr/io.h>
2
#include "pin_config.h"
3
#include "gpio.h"
4
5
static inline void led_on(portpin_t pp) {
6
    set_pin_low(pp);             // all LEDs are driven low side!
7
}
8
9
static inline void led_off(portpin_t pp) {
10
    set_pin_high(pp);            // all LEDs are driven low side!
11
}
12
13
static inline bool is_sensor_signal() {
14
    return is_pin_high(sensor);
15
}
16
17
int main() {
18
    set_as_output(led_green);
19
    set_as_output(led_yellow);
20
    set_as_input(sensor);
21
22
    while (1) {
23
        if (is_sensor_signal()) {
24
            led_on(led_green);
25
        } else {
26
            led_off(led_green);
27
        }
28
    }
29
}

und siehe da, jetzt sind wir an die Grenze gestoßen:
1
0000004a <main>:
2
  4a:  d0 9a         sbi  0x1a, 0  ; 26
3
  4c:  d1 9a         sbi  0x1a, 1  ; 26
4
  4e:  d2 98         cbi  0x1a, 2  ; 26
5
  50:  40 91 66 00   lds  r20, 0x0066
6
  54:  30 91 67 00   lds  r19, 0x0067
7
  58:  81 e0         ldi  r24, 0x01  ; 1
8
  5a:  90 e0         ldi  r25, 0x00  ; 0
9
  5c:  00 90 68 00   lds  r0, 0x0068
10
  60:  02 c0         rjmp  .+4        ; 0x66 <main+0x1c>
11
  62:  88 0f         add  r24, r24
12
  64:  99 1f         adc  r25, r25
13
  66:  0a 94         dec  r0
14
  68:  e2 f7         brpl  .-8        ; 0x62 <main+0x18>
15
  6a:  58 2f         mov  r21, r24
16
  6c:  50 95         com  r21
17
  6e:  e4 2f         mov  r30, r20
18
  70:  f3 2f         mov  r31, r19
19
  72:  ca 9b         sbis  0x19, 2  ; 25
20
  74:  03 c0         rjmp  .+6        ; 0x7c <main+0x32>
21
  76:  20 81         ld  r18, Z
22
  78:  25 23         and  r18, r21
23
  7a:  02 c0         rjmp  .+4        ; 0x80 <main+0x36>
24
  7c:  20 81         ld  r18, Z
25
  7e:  28 2b         or  r18, r24
26
  80:  20 83         st  Z, r18
27
  82:  f5 cf         rjmp  .-22       ; 0x6e <main+0x24>

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

Christian J. schrieb:
> #define TESTPIN = PORTB.4
> #define LED     = PORTB.5

Ganz ganz sicher nicht!

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Bernd K. schrieb:
> und siehe da, jetzt sind wir an die Grenze gestoßen:

Naja, bei mir nicht:
1
00000096 <main>:
2
  96:   20 9a           sbi     0x04, 0 ; 4
3
  98:   21 9a           sbi     0x04, 1 ; 4
4
  9a:   22 98           cbi     0x04, 2 ; 4
5
  9c:   1a 9b           sbis    0x03, 2 ; 3
6
  9e:   03 c0           rjmp    .+6             ; 0xa6 <main+0x10>
7
  a0:   28 98           cbi     0x05, 0 ; 5
8
  a2:   1a 99           sbic    0x03, 2 ; 3
9
  a4:   fd cf           rjmp    .-6             ; 0xa0 <main+0xa>
10
  a6:   28 9a           sbi     0x05, 0 ; 5
11
  a8:   f9 cf           rjmp    .-14            ; 0x9c <main+0x6>

(ich musste übrigens auf PORTB ändern, damits für ATmega328 kompiliert)

Du hast aber nicht ganz unrecht: bei sehr vielen indirektionen mag er 
irgendwann aliasing nciht mehr bis zum ende durchdenken. Man kann ihm 
aber mit -O2 oder -fno-strict-aliasing auf die Sprünge helfen.

von Bernd K. (prof7bit)


Lesenswert?

Einen hab ich noch:
1
/*
2
 * gpio.h
3
 *
4
 *  Created on: 25.04.2015
5
 *      Author: bernd
6
 */
7
8
#ifndef GPIO_H_
9
#define GPIO_H_
10
11
#include <avr/io.h>
12
#include <stdbool.h>
13
14
#define PIN_ASSIGNMENT_LIST \
15
    x(LED_RED  , D, 1) \
16
    x(LED_GREEN, D, 2) \
17
    x(LED_BLUE,  D, 3) \
18
    x(SENSOR,    D, 4) \
19
20
#define OUTPUT_LIST \
21
    x(LED_RED) \
22
    x(LED_GREEN) \
23
    x(LED_BLUE) \
24
25
#define PULLUP_LIST \
26
    x(SENSOR) \
27
28
29
30
#define x(name, p, n) static inline void name##_set_as_output(){DDR##p |= (1 << n);}
31
PIN_ASSIGNMENT_LIST
32
#undef x
33
34
#define x(name, p, n) static inline void name##_set_as_input(){DDR##p &= ~(1 << n);}
35
PIN_ASSIGNMENT_LIST
36
#undef x
37
38
#define x(name, p, n) static inline void name##_set_high(){PORT##p |= (1 << n);}
39
PIN_ASSIGNMENT_LIST
40
#undef x
41
42
#define x(name, p, n) static inline void name##_set_low(){PORT##p &= ~(1 << n);}
43
PIN_ASSIGNMENT_LIST
44
#undef x
45
46
#define x(name, p, n) static inline bool name##_is_high(){return PIN##p & (1 << n);}
47
PIN_ASSIGNMENT_LIST
48
#undef x
49
50
#define x(name, p, n) static inline bool name##_is_low(){return !(PIN##p & (1 << n));}
51
PIN_ASSIGNMENT_LIST
52
#undef x
53
54
static inline void gpio_init() {
55
    #define x(name) name##_set_as_output();
56
    OUTPUT_LIST
57
    #undef x
58
    #define x(name) name##_set_high();
59
    PULLUP_LIST
60
    #undef x
61
}
62
63
#endif /* GPIO_H_ */

Für alle die keine Makromonster mögen, das obige ist sauber und portabel 
und kommt völlig ohne schwarze Magie aus, es erzeugt echte statische 
Inline-Funktionen. Und es erzeugt sogar eine init() Funktion die man nur 
noch aufrufen muss um alle GPIO-Pins zu konfigurieren.

Folgender Code würde da rauskommen zwischen Präprozessor und Compiler 
(zur Veranschaulichung hab ichs mal durch avr-cpp jagt und hinterher 
wieder manuell formatiert):
1
#include <avr/io.h>
2
#include <stdbool.h>
3
4
static inline void LED_RED_set_as_output() {
5
    DDRD |= (1 << 1);
6
}
7
8
static inline void LED_GREEN_set_as_output() {
9
    DDRD |= (1 << 2);
10
}
11
12
static inline void LED_BLUE_set_as_output() {
13
    DDRD |= (1 << 3);
14
}
15
16
static inline void SENSOR_set_as_output() {
17
    DDRD |= (1 << 4);
18
}
19
20
static inline void LED_RED_set_as_input() {
21
    DDRD &= ~(1 << 1);
22
}
23
24
static inline void LED_GREEN_set_as_input() {
25
    DDRD &= ~(1 << 2);
26
}
27
28
static inline void LED_BLUE_set_as_input() {
29
    DDRD &= ~(1 << 3);
30
}
31
32
static inline void SENSOR_set_as_input() {
33
    DDRD &= ~(1 << 4);
34
}
35
36
static inline void LED_RED_set_high() {
37
    PORTD |= (1 << 1);
38
}
39
40
static inline void LED_GREEN_set_high() {
41
    PORTD |= (1 << 2);
42
}
43
44
static inline void LED_BLUE_set_high() {
45
    PORTD |= (1 << 3);
46
}
47
48
static inline void SENSOR_set_high() {
49
    PORTD |= (1 << 4);
50
}
51
52
static inline void LED_RED_set_low() {
53
    PORTD &= ~(1 << 1);
54
}
55
56
static inline void LED_GREEN_set_low() {
57
    PORTD &= ~(1 << 2);
58
}
59
60
static inline void LED_BLUE_set_low() {
61
    PORTD &= ~(1 << 3);
62
}
63
64
static inline void SENSOR_set_low() {
65
    PORTD &= ~(1 << 4);
66
}
67
68
static inline _Bool LED_RED_is_high() {
69
    return PIND & (1 << 1);
70
}
71
72
static inline _Bool LED_GREEN_is_high() {
73
    return PIND & (1 << 2);
74
}
75
76
static inline _Bool LED_BLUE_is_high() {
77
    return PIND & (1 << 3);
78
}
79
80
static inline _Bool SENSOR_is_high() {
81
    return PIND & (1 << 4);
82
}
83
84
static inline _Bool LED_RED_is_low() {
85
    return !(PIND & (1 << 1));
86
}
87
88
static inline _Bool LED_GREEN_is_low() {
89
    return !(PIND & (1 << 2));
90
}
91
92
static inline _Bool LED_BLUE_is_low() {
93
    return !(PIND & (1 << 3));
94
}
95
96
static inline _Bool SENSOR_is_low() {
97
    return !(PIND & (1 << 4));
98
}
99
100
static inline void gpio_init() {
101
    LED_RED_set_as_output();
102
    LED_GREEN_set_as_output();
103
    LED_BLUE_set_as_output();
104
    SENSOR_set_high();
105
}

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.