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 :-)
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.
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:
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.
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)
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
staticinlinevoidsetTestpin(uint8_tval)
2
{
3
registeruint8_ttmp=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!
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
structBits{
2
unsignedbit0:1;
3
unsignedbit1:1;
4
unsignedbit2:1;
5
unsignedbit3:1;
6
unsignedbit4:1;
7
unsignedbit5:1;
8
unsignedbit6:1;
9
unsignedbit7:1;
10
};
11
12
#define BITVAR(x,y) ((volatile struct Bits*)(void*)&x)->bit ## y
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.
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:
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 :
"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
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
voidenable_led(boolon){
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.
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).
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.
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".
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.
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.
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"?
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:
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.
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.
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 :-)
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.
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.
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.
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...
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.
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.
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.
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. ;-)
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.
}
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?
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.
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:
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
intmain(){
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:
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.
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.
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.
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:
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:
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.
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...
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:
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.
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.
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
voidled1_init(void);
2
voidled1_on(void);
3
voidled1_off(void);
In drivers/led.c:
1
voidled1_init(void)
2
{
3
/* hier die initialisierung von LED 1 */
4
}
5
6
voidled1_on(void)
7
{
8
/* hier wird die LED eingeschaltet */
9
}
10
11
voidled1_off(void)
12
{
13
/* hier wird die LED ausgeschaltet */
14
}
Im Hauptprogramm, als Beispiel ein Blinkprogramm:
1
intmain()
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
typedefstruct{
8
volatileuint8_t*port;
9
uint8_tpin;
10
}portpin_t;
11
12
staticinlinevoidset_as_output(portpin_tpp){
13
*(pp.port-1)|=(1<<pp.pin);
14
}
15
16
staticinlinevoidset_as_input(portpin_tpp){
17
*(pp.port-1)&=~(1<<pp.pin);
18
}
19
20
staticinlinevoidset_pin_high(portpin_tpp){
21
*pp.port|=(1<<pp.pin);
22
}
23
24
staticinlinevoidset_pin_low(portpin_tpp){
25
*pp.port&=~(1<<pp.pin);
26
}
27
28
staticinlineboolis_pin_high(portpin_tpp){
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
constportpin_tled_green={&PORTA,0};
7
constportpin_tled_yellow={&PORTA,1};
8
constportpin_tsensor={&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
staticinlinevoidled_green_on(){
6
set_pin_low(led_green);// this LED is driven low side!
7
}
8
9
staticinlinevoidled_green_off(){
10
set_pin_high(led_green);// this LED is driven low side!
11
}
12
13
staticinlineboolis_sensor_signal(){
14
returnis_pin_high(sensor);
15
}
16
17
intmain(){
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:d09asbi0x1a,0;26
3
4c:d19asbi0x1a,1;26
4
4e:d298cbi0x1a,2;26
5
50:ca9bsbis0x19,2;25
6
52:02c0rjmp.+4;0x58<main+0xe>
7
54:d898cbi0x1b,0;27
8
56:fccfrjmp.-8;0x50<main+0x6>
9
58:d89asbi0x1b,0;27
10
5a:facfrjmp.-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
staticinlinevoidled_green_on(){
2
set_pin_low(led_green);// this LED is driven low side!
3
}
schreiben wir jetzt
1
staticinlinevoidled_on(portpin_tpp){
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
staticinlinevoidled_on(portpin_tpp){
6
set_pin_low(pp);// all LEDs are driven low side!
7
}
8
9
staticinlinevoidled_off(portpin_tpp){
10
set_pin_high(pp);// all LEDs are driven low side!
11
}
12
13
staticinlineboolis_sensor_signal(){
14
returnis_pin_high(sensor);
15
}
16
17
intmain(){
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:
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.
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):