Guten Tag!
Ich habe wieder nun eine Frage:
wie kann ich Code besser optimieren?
Jetzt habe ich eine Aufgabe: Kommunikation über SPI. Dabei sollte
SPI-Mode zuvor umgeschaltet werden und danach zurück gemacht. Das
wiederholt sich in mehreren Funktionen, deshalb möchte ich das gerne als
inline Funktion oder als Macro schreiben.
Erste Verwirklichung mit Funktion arbeitet zwar, braucht aber 2 bytes in
RAM und call-ret Verluste:
1
staticu8temp_SPCR,temp_SPSR;
2
3
voidds3234_spi_sichern(void){
4
temp_SPCR=SPCR;
5
temp_SPSR=SPSR;
6
spi_master_setmode(1,3,0);// F_CPU/4 Mode 3
7
}
8
9
voidds3234_spi_herstellen(void){
10
SPCR=temp_SPCR;
11
SPSR=temp_SPSR;
12
}
13
14
voidds3234_zeit_lesen(void){// Stunden, Minuten und Sekunden (BCD)
15
16
spi_intern_outadr(UHR_ADR);
17
ds3234_spi_sichern();
18
spi_intern_start();
19
spi_master_transmit(0);
20
for(u8i=0;i<7;i++){
21
ds3234_zeitpuffer[i]=spi_master_transmit(0);
22
}
23
24
spi_intern_stop();
25
ds3234_spi_herstellen();
26
}
Zweite Variante bedient sich mit lokalen Variablen, dann sollte ich aber
in jeder Funktion zwei Variablen erklären und immer wieder "per Hand"
zwei SPI-Register sichern und wiederherstellen:
1
voidds3234_zeit_lesen(void){// Stunden, Minuten und Sekunden (BCD)
2
3
u8temp_SPCR,temp_SPSR;
4
5
spi_intern_outadr(UHR_ADR);
6
7
// ds3234_spi_sichern();
8
temp_SPCR=SPCR;
9
temp_SPSR=SPSR;
10
spi_master_setmode(1,3,0);// F_CPU/4 Mode 3
11
12
spi_intern_start();
13
spi_master_transmit(0);
14
for(u8i=0;i<7;i++){
15
ds3234_zeitpuffer[i]=spi_master_transmit(0);
16
}
17
18
spi_intern_stop();
19
20
// ds3234_spi_herstellen();
21
SPCR=temp_SPCR;
22
SPSR=temp_SPSR;
23
}
Meine Versuche, diese Operationen als Macro oder inline Funktion
aufzuschreiben, scheiterten bisher, da ich auch Variablendeklaration in
Macro oder Funktion integrieren möchte.
Die Frage: wie könnte ich das schöner und kompakter schreiben?
Vielen Dank im voraus.
P.S.
meine Versuchsplatine ist so aufgebaut, daß alle interne Geräte über SPI
intern verbunden sind. Deshalb Begriffe wie spi_intern_outadr(UHR_ADR);
u.Ä. Ziel war, möglichst mehr Pins von AT Mega1284P frei für externe
Erweiterungen zu lassen (besetzt sind somit nur 4x JTAG-Pins, 3
Intern-Adresse-Pins und ~CS-Pin.). Nun schreibe ich Treiber für interne
Geräte (Grafische, symbolische, digitale und LED-Säule-Anzeige, Uhr,
RAM, Tastatur, Drehgeber, 3x WS2812B und SD-Card). Das ist sozusagen
mein eigenes "Arduino", für gleiche Zwecke (Spielerei mit Code) gedacht.
Nur zum Unterschied von Arduino sind C-Sprache und JTAG-Debug
vorausgesetzt, Akzent auf serielle externe Verbindungen gemacht und
viele Basisgeräte in Hauptplatine integriert.
Rolf M. schrieb:> Das gibt's in C nicht.
Oh, dass es C sein muss war im letzten Satz versteckt. Naja, das ist
eine perfekte Gelegenheit im makefile "gcc" durch "g++" zu ersetzen und
das sauber und effizient mit C++ zu machen.
Stefanus F. schrieb:> Ich glaube, du übertreibst es da mit Optimierung. Ist es wirklich nötig?
Ziel dieser Platte ist, zu lernen. Je mehr ich lerne, umso besser.
Natürlich negative Ergebnis ist auch eine Lehre.
Dr. Sommer schrieb:> Das geht nur mit C++... Also mit g++ statt gcc kompilieren
Ich benutze die Bequemlichkeiten von AVR Studio 4.19. Wie könnte ich
dort auf C++ umschalten?
Es gibt in GCC eine Möglichkeit mit BLOCK und NOBLOCK usw, in einer etwa
ähnlicher Situation ohne per hand geschriebenen "temp_SREG" zu gehen.
Könnte ich eine ähnliche Lösung benutzen?
Dr. Sommer schrieb:> Vermutlich die Source-Datei von ".c" nach ".cpp" umbenennen.
Der Fehler kommt nicht in *.c sondern in *.h. Ich fürchte, es sind viel
größere Umstellungen notwendig.
Ja, ich habe's versucht...
Nur File alleine aus *.c in *.cpp umnennen reicht nicht: Compiler will
-std=gnu99 nicht. Wenn alle *.c in *.cpp, dann akzeptiert Compiler
__flash nicht. Und schimpft weiter noch...
Maxim B. schrieb:> Der Fehler kommt nicht in *.c sondern in *.h. Ich fürchte, es sind viel> größere Umstellungen notwendig.
So wird’s wohl sein. C++ und Templates und alles weiter bringt da aber
auch keinerlei Vorteile, denn wenn du den Inhalt von zwei Registern
zwischenspeichern musst, dann kostet das CPU-Cycles und RAM, egal, in
welcher Programmiersprache.
Aber ganz ehrlich, was soll das alles?
Optimierungen auf diesem Level, bei denen es um ein paar CPU-Cycles mehr
oder weniger geht, macht man, wenn überhaupt, in zeitkritischen ISRs,
und da halt auch nur, wenn man muß.
Bei dir geht das im Rauschen unter.
Da wird es in der Gesamtarchitektur wesentlich größere
Optimierungsmögöichkeiten geben.
Oliver
Maxim B. schrieb:> Der Fehler kommt nicht in *.c sondern in *.h. Ich fürchte, es sind viel> größere Umstellungen notwendig.
Ja, aber die .h wird von der .c inkludiert, welche mit gcc kompiliert
wird. Wenn du sie nach .cpp umbenennst, wird sie mit g++ kompiliert.
Maxim B. schrieb:> Compiler will> -std=gnu99 nicht
Das musst du dann nach -std=gnu17 ändern. gnu99 ist halt C.
Maxim B. schrieb:> Wenn alle *.c in *.cpp, dann akzeptiert Compiler> __flash nicht.
Achja, __flash kann der AVR-G++ nicht. Da muss man das klassische
PROGMEM nutzen.
Alles klar...
Ich habe gedacht, ich kann das etwa schöner ausschreiben...
Bequemlichkeit, __flash zu schreiben und danach alles wie für RAM
schreiben - das möchte ich nicht aufgeben.
Negative Antwort ist auch eine Antwort.
Ich möchte noch genau ankucken, wie es mit BLOCK / NOBLOCK gemacht
wurde.
Oliver S. schrieb:> Optimierungen auf diesem Level, bei denen es um ein paar CPU-Cycles mehr> oder weniger geht, macht man, wenn überhaupt, in zeitkritischen ISRs,> und da halt auch nur, wenn man muß.
Hier geht es mir nicht um Zeitoptimierung, sondern um besser und
verständlicher aussehende Text.
Was aber Zeit betrifft, hier habe ich alles Mögliche schon gemacht.
Grafische Anzeige habe ich über MCP23S17. Das bedeutet: um ein Byte in
Anzeige zu schreiben, sollten 12 bytes per SPI übertragen werden. Um
einen Punkt zu lesen, verändern und wieder aufzuschreiben, sind 24 bytes
notwendig. Hier muß man schon etwas überlegen beim Optimieren auch für
die Zeit... Aber MCP23S17 bringt Vorteil, für die Anzeige nur eine
SPI-Adresse verwenden zu dürfen...
Das Kompilat mit -Os entspricht genau dem des zweiten Beispiels des TE.
Im Gegensatz zu diesem ist für das Sichern und Wiederherstellen der
SPI-Register aber nur eine einzige Coodezeile (SPI_SAVE:) erforderlich.
mod = 1 => sichern
mod = 0 => herstellen
Durch die STATIC Deklaration bleibt der Wert von temp_SPCR ,
temp_SPSR auch nach Verlassen der Funktion erhalten. Die beiden
Variablen sind außerhalb der Funktion nicht sichtbar.
Wenn der Compiler dafür sorgt, dass Abschlussbehandlungen bei Verlassen
des Blocks oder der Funktion automatisch durchgeführt werden, kann es
der Programmierer nicht mehr vergessen.
GEKU schrieb:> Durch die STATIC Deklaration bleibt der Wert von temp_SPCR ,> temp_SPSR auch nach Verlassen der Funktion erhalten.
Vielen Dank,
aber gerade das möchte ich vermeiden: 2 bytes RAM und je 8 Takte für
Zugang zu RAM zu verschwenden.
Wenn du 2 Bytes RAM sparen willst, musst du zwei Register verwenden. Da
diese aber kostbares Gut sind, würde ich eher dem Compiler vertrauen,
die Register sinnvoll einzusetzen.
Du hast weiter oben geschrieben, dass gut lesbarer Code dein hauptziel
ist. Ich lese aber heraus, dass du immer noch unnötige Mikro-Optimierung
betreibst.
Yalu X. schrieb:> struct spi_regs {> uint8_t spcr, spsr;> };>> static void spi_restore(struct spi_regs *regs) {> SPCR = regs->spcr;> SPSR = regs->spsr;> }>> #define SPI_SAVE struct spi_regs spi_regs> __attribute__((cleanup(spi_restore))) = { SPCR, SPSR };
Vielen, vielen Dank!!!
Das ist die Lösung, die ich gesucht habe!!!
So habe ich heute wieder etwas gelernt!
Oliver S. schrieb:> warum musst du die Registerinhalte überhaupt> sichern?
Diese Platine ist wie eine Spielkiste gedacht. Es gibt Steckdosen für
externe Erweiterungen: eine mit 34 Kontakten und zusätzlich separat für
USART0, USART1, I2C und auch SPI.
Schon mit internen Geräten kommt ein Problem: während fast alles
problemlos in Mode 0 und F_CPU/2 Geschwindigkeit arbeitet, braucht
DS3234 F_CPU/4 und Mode 3 (F_CPU = 16 MHz). Da durch externe Steckdosen
in der Zukunft auch verschiedene Geräte geschaltet werden, die nicht
alle bestimmt mit Mode 0 und 8 MHz arbeiten, so ist notwendig bei
Uhr-Abfrage nicht einfach F_CPU/2 und Mode 0 zu schalten, sondern alles
wie vor Uhr-Abfrage, es kann beliebige Mode und beliebige
Geschwindigkeit sein.
Andere Möglichkeit besteht darin, das für jedes Gerät neben Adresse auch
SPI-Mode und Geschwindigkeit eingestellt werden. Ich werde später sehen,
welche Variante optimal ist. Aber die Möglichkeit von Yalu ist sehr
interessant und ich werde sie bestimmt bei Gelegenheit nutzen.
Jetzt probiere ich verschiedene Lösungen aus, auch
schaltungstechnischen.
Als Beispiel: LCD DIP203G-4 arbeitet mit 3,3 Volt, während die Platine
mit 5 Volt auskommt. Als Pegelwandler und gleichzeitig als seriell >
parallel - Converter hat sich 74VHC595 gut gezeigt.
Noch ein Beispiel: WS2812B. Aus dem Datenblatt ist nicht ganz klar,
welche Pegel in Ruhestand gebraucht wird. Experimente haben gezeigt:
eigentlich sollte das "low" sein. Wenn aber "high", dann sollte man
Reset zweimal machen: vor- und nach Übertragung. Dann arbeiten WS2812B
wie gedacht.
Vieles kann man nur per Probe klären.
Stefanus F. schrieb:> Ich lese aber heraus, dass du immer noch unnötige Mikro-Optimierung> betreibst.
Das ist keine unnötige Optimierung.
Eingebaute Geräte sind vor allem für Debug gedacht. Sie sollten so wenig
Ressourcen nehmen wie nur möglich.
> Wenn du 2 Bytes RAM sparen willst, musst du zwei Register verwenden. Da> diese aber kostbares Gut sind, würde ich eher dem Compiler vertrauen,> die Register sinnvoll einzusetzen.
Wie Disassembler zeigt, macht die Variante von Yalu gerade das. D.h.
schön geschriebene Register-Variablen. Was mir im Moment noch fehlt, ist
klare Verständnis, wie das arbeitet. :) Aber das arbeitet.
Wozu so umständlich.
Wenn Du Bauteile hast, die verschiedene SPI-Mode benötigen, dann setze
ihn einfach vor jedem Zugriff neu.
Und wenns schnell gehen soll, dann nimm eine UART im SPI Mode. Die
können ohne Pause zwischen 2 Bytes senden.
Maxim B. schrieb:> Wie Disassembler zeigt, macht die Variante von Yalu gerade das. D.h.> schön geschriebene Register-Variablen.
Naja, eher hässlicher GCC-Spezifischer unportabler Hack. Die
C++-Varianten (auch die von Zombie) sind portabel und funktionieren auch
auf anderen Plattformen genau so. Nur leider kann der g++ halt kein
__flash, weil das auch wieder so ein AVR-Hack ist...
Maxim B. schrieb:>>>> Das ist keine unnötige Optimierung.> Eingebaute Geräte sind vor allem für Debug gedacht. Sie sollten so wenig> Ressourcen nehmen wie nur möglich.
Du solltest dir konkret überlegen, worauf du optimieren möchtest und ob
es dir den Aufwand wert ist. Zwei Beispiele:
Eine Funktion kann inline sein oder auch nicht. Je nachdem benötigt sie
mehr CPU oder Programmspeicher. Soetwas würde ich aber dem Compiler
überlassen, damit man es je nach Bedarf ändern kann.
Bei den Variablen für die Sicherung der Register kann man Register,
Stack oder Statischen Speicher nehmen. Ich würde in diesem Fall den
Stack bevorzugen, weil ich dann den Speicher später für andere Variablen
benutzen kann. Beim statischen Speicher ist das nicht der Fall. Globale
Variablen können außerdem bei Rekursion zu unerwartetem Verhalten
führen.
Maxim B. schrieb:> Das ist die Lösung, die ich gesucht habe!!!
Echt jetzt?
Du sparst durch kryptischen Code 2 Byte von 16kB ein, d.h. riesige
0,01%, das nenne ich mal Effizienz.
Maxim B. schrieb:> Vielen Dank,> aber gerade das möchte ich vermeiden: 2 bytes RAM und je 8 Takte für> Zugang zu RAM zu verschwenden.Yalu X. schrieb:> struct spi_regs {> uint8_t spcr, spsr;> };>> static void spi_restore(struct spi_regs *regs) {> SPCR = regs->spcr;> SPSR = regs->spsr;> }>> #define SPI_SAVE struct spi_regs spi_regs> __attribute__((cleanup(spi_restore))) = { SPCR, SPSR };Maxim B. schrieb:> Vielen, vielen Dank!!!> Das ist die Lösung, die ich gesucht habe!!!
Hä? Jetzt hast du zwei Variablen durch eine Struktur ersetzt, die zwei
Variablen enthält. Dazu ein Makro, dass die Komplexität verbirgt aber
nicht weg macht. Tolle Wurst.
Peter D. schrieb:> Und wenns schnell gehen soll, dann nimm eine UART im SPI Mode. Die> können ohne Pause zwischen 2 Bytes senden.
USARTs sind bei mir für externe Gebrauch reserviert.
Eigentlich wollte ich zuerst gar keine eigene Platte machen und als
Herzstück Arduino 2560 benutzen. Aber wie durch böse Wille haben alle
vier (!) USARTs dort keine XCK-Leitungen nach außen geführt!
Dann habe ich eigene Platte entworfen. Ich habe AT Mega in DIP genommen
nur um sie leichter wechseln zu können, wie ihre 10 000 Programmierungen
erschöpft werden. Sie ist für 1284P gerechnet, jetzt aber bringe ich
alles mit billigeren 324PA :) in Lauf.
Für mich sind gerade beide USARTs interessant, da ich u.A. z.B.
MIDI-Code empfagen, überarbeien und weiter schicken möchte. Früher habe
ich schon eine einfachere Platine dafür gemacht, mit eingebautem
MIDI-Interface, und ein bißchen experimentiert. Jetzt ist das als
externe Modul gemacht.
Ich möchte auch Arbeit mir SD-Karte und Filesysem lernen, deshalb
Micro-SD-Steckdose eingebaut (mit Pegelwandler). Auch I2C möchte ich
besser verstehen, besonders Arbeit als Slave.
SPI betrachte ich für externe Zwecke eher als eine Zusatzmöglichkeit,
die hilft z.B. wenn Hauptplatine als Slave arbeiten sollte (was USART
nicht kann). Deshalb gibt es zusätzliche Absicherung: wenn auf ~SS in
der Steckdose "0", ist Zugang zu internen SPI-Geräten nicht möglich.
Entscheidend für den Projekt war für mich, daß ich im Januar eine
chinesische Kopie von JTAGICE mk2 gekauft habe, die gut mit AVR Studio
4.19 arbeitet. Sehr hilfreiche Werkzeug, ich möchte dessen Möglichkeiten
nun benutzen in vollem Maß.
Stefanus F. schrieb:> Dazu ein Makro, dass die Komplexität verbirgt aber> nicht weg macht. Tolle Wurst.
Einmal Macro und hundertmal Nutze.
Statt am Anfang jeder Funktion zwei Variablen zu deklarieren, dann zwei
Register speichern und später wieder herstellen - hier ist das nur eine
Zeile. Das finde ich besser. Die Zeile schreiben - und nicht mehr
denken, was dort alles passiert...
Peter D. schrieb:> Echt jetzt?> Du sparst durch kryptischen Code 2 Byte von 16kB ein, d.h. riesige> 0,01%, das nenne ich mal Effizienz.
Hier ist für mich die Möglichkeit selbst von Wert, die ich früher nicht
kannte.
Was vielleicht dadurch leidende C-Kompatibilität betrifft: die sollte
sowieso für WS2812B geopfert werden, da dort nur Inline-Assembler hilft.
Maxim B. schrieb:> Die Zeile schreiben - und nicht mehr> denken, was dort alles passiert...
Das ist der Knackpunkt. Aus den Augen aus dem Sinn ist zwar verlockend,
birgt jedoch auch die Gefahr, es falsch zu verwenden.
Ich will das Konstrukt nicht ablehnen, sondern darauf hinweisen. Wenn
man weiß, was man tut, ist alles in Ordnung.
Nicht nur 2 bytes RAM, sondern auch 4x2 Takte für lesen und schreiben.
In Disassembler sieht man: beide SPI-Register werden in r24 und r25
gelesen. Am Ende der Funktion wird aus r24 und r25 zurück in
SPI-Register geschrieben. Einzige Unterschied: bei static-Variante
werden sie noch in RAM geschrieben und danach aus RAM gelesen. Und zwar
bei jedem Zugriff an Uhr!
Stefanus F. schrieb:> Ich will das Konstrukt nicht ablehnen, sondern darauf hinweisen. Wenn> man weiß, was man tut, ist alles in Ordnung.
Das wird ja wie Vorteil von C präsentiert: man schreibt einmal eine
Funktion, und danach interessiert man sich nie mehr, was drin steht.
Wichtig bleibt nur, was kommt und was man zurück bekommt. Die Küche
bleibt in sich geschlossen.
Diese Lösung ist in diesem Sinn gut gedacht.
Maxim B. schrieb:> Das wird ja wie Vorteil von C präsentiert:
Nur dass das halt nicht C sondern eine GCC-Erweiterung ist... Der
Nachteil von C ist, dass C so etwas nicht vernünftig abstrahieren kann.
C++ schon!
Ich versuche, Funktionen so zu schreiben, daß sie möglichst wenig
Fallgruben enthalten. Daher setze ich grundsätzlich den SPI Mode neu,
wenn ein anderes Device angesprochen wird.
Darauf zu vertrauen, daß alle anderen diese Register sichern, ist
riskant. Es reicht, wenn ein einziger das vergißt und Dein Code fällt
Dir auf die Füße.
Sie haben Recht.
Vielleicht mache ich so. Das ist wirklich sicherer. Schließlich kostet
das nicht viel, 3-4 Takte und 6 bytes Flash pro Funktion.
Z.Z. habe ich noch Problem mit LCD: zuerst habe ich vermutet, Problem
liegt in schlechten Kontakten. Jetzt denke ich aber, das liegt bei Init.
Ich habe Init so gemacht wie immer für LCD in 4 bit Mode. Hier ist aber
DIP-203 mit SSD1803, und Init soll wahrscheinlich etwas anders gehen.
Leider steht in Datenblann gerade für 4 bit Mode etwas verwirrendes...
Maxim B. schrieb:> Nicht nur 2 bytes RAM, sondern auch 4x2 Takte für lesen und schreiben.> In Disassembler sieht man: beide SPI-Register werden in r24 und r25> gelesen. Am Ende der Funktion wird aus r24 und r25 zurück in> SPI-Register geschrieben. Einzige Unterschied: bei static-Variante> werden sie noch in RAM geschrieben und danach aus RAM gelesen. Und zwar> bei jedem Zugriff an Uhr!
Klar. Weil du die Variablen d*mlicherweise als static deklariert hast.
Hättest du einfach lokale Variablen genommen (so wie es das Makro auch
macht), dann hätte der Compiler genauso die Freiheit gehabt, die
Variablen in Registern zu halten. Die "Optimierung" besteht einfach
darin, den Unsinn zu lassen, den du vorher gemacht hast. Tolle Wurst!
Ich finde übrigens auch die Orgie von Funktionsaufrufen für jeden
KleinscheiXX weder übersichtlich(er) noch schön(er). Du hast genau diese
eine Funktion, die den Status des SPI sichern muß. Warum schreibst du
das nicht einfach direkt rein?
Dr. Sommer schrieb:> Maxim B. schrieb:>> Wenn alle *.c in *.cpp, dann akzeptiert Compiler>> __flash nicht.>> Achja, __flash kann der AVR-G++ nicht. Da muss man das klassische> PROGMEM nutzen.
Müsste "constexpr" nicht denselben Effekt haben? Da der Compiler die
Variable nicht in den RAM legen darf, wird sie ja dann wohl im Flash
abgespeichert?
C++17 schrieb:> Müsste "constexpr" nicht denselben Effekt haben? Da der Compiler die> Variable nicht in den RAM legen darf, wird sie ja dann wohl im Flash> abgespeichert?
Leider nicht so wirklich, weil ja für den Zugriff extra Funktionen nötig
sind. constexpr hat mit LPM erstmal nix zu tun...
Peter D. schrieb:> Ich versuche, Funktionen so zu schreiben, daß sie möglichst wenig> Fallgruben enthalten. Daher setze ich grundsätzlich den SPI Mode neu,> wenn ein anderes Device angesprochen wird.> Darauf zu vertrauen, daß alle anderen diese Register sichern, ist> riskant. Es reicht, wenn ein einziger das vergißt und Dein Code fällt> Dir auf die Füße.
Das ist m.E. die einzige richtige Antwort auf die Frage des TO: einen
externen Code vom internen Zustand eines Objektes abhängig zu machen,
ist immer
problematisch.
Natürlich ist die meiste interne Peripherie zustandsbehaftet, aber der
Anwender
einer Abstraktion derselben sollte gerade ja davon i.a. befreit werden
(am besten) oder mindestens dazu gezwungen werden, sich darüber Gedanken
zu machen.
Damit man in diesem Beispiel (SPI) nicht vergisst, irgendwelche Modi vor
einer
Übertragung zu konfigurieren, sollte man den Anwender durch eine
geeignete
Schnittstelle dazu zwingen, entsprechende Argumente anzugeben.
Maxim B. schrieb:> Vielen, vielen Dank!!!> Das ist die Lösung, die ich gesucht habe!!!Yalu X. schrieb:> Das Kompilat mit -Os entspricht genau dem des zweiten Beispiels des TE.
Klasse...
Oliver
Wilhelm M. schrieb:> Das ist m.E. die einzige richtige Antwort auf die Frage des TO: einen> externen Code vom internen Zustand eines Objektes abhängig zu machen,> ist immer> problematisch.
Danke euch beiden!
Ich habe inzwischen das Programm umgestellt:
1
#define SD_ADR 0
2
#define ZIF_ADR 1
3
#define LED_ADR 2
4
#define GF_ADR 3
5
#define LCD_ADR 4
6
#define KNOPF_ADR 4
7
#define BAR_ADR 5
8
#define RAM_ADR 6
9
#define UHR_ADR 7
10
11
#define SPI_INTERN_E_PORT PORTC
12
#define SPI_INTERN_E_DDR DDRC
13
#define SPI_INTERN_E PC6
14
15
#define SPI_INTERN_A0_PORT PORTA
16
#define SPI_INTERN_A0_DDR DDRA
17
#define SPI_INTERN_A0 PA6
18
#define SPI_INTERN_A1_PORT PORTA
19
#define SPI_INTERN_A1_DDR DDRA
20
#define SPI_INTERN_A1 PA7
21
#define SPI_INTERN_A2_PORT PORTC
22
#define SPI_INTERN_A2_DDR DDRC
23
#define SPI_INTERN_A2 PC7
24
25
// CS-Adresse fuer interne SPI
26
staticinlinevoidspi_intern_outadr(u8adr){
27
28
if(adr&(1<<0)){
29
SPI_INTERN_A0_PORT|=(1<<SPI_INTERN_A0);
30
}else{
31
SPI_INTERN_A0_PORT&=~(1<<SPI_INTERN_A0);
32
}
33
if(adr&(1<<1)){
34
SPI_INTERN_A1_PORT|=(1<<SPI_INTERN_A1);
35
}else{
36
SPI_INTERN_A1_PORT&=~(1<<SPI_INTERN_A1);
37
}
38
if(adr&(1<<2)){
39
SPI_INTERN_A2_PORT|=(1<<SPI_INTERN_A2);
40
}else{
41
SPI_INTERN_A2_PORT&=~(1<<SPI_INTERN_A2);
42
}
43
}
44
45
staticinlinevoidspi_intern_start(void){
46
SPI_INTERN_E_PORT&=~(1<<SPI_INTERN_E);
47
}
48
49
staticinlinevoidspi_intern_stop(void){
50
SPI_INTERN_E_PORT|=(1<<SPI_INTERN_E);
51
}
52
53
/****************************/
54
55
voidspi_master_setmode(u8geschw,u8mode,u8order){
56
// wie spi_init(), aber ohne spi_master_transmit() und ohne spi_portmasterinit();
57
58
switch(mode){
59
case0:// mode 0
60
SPCR=(1<<SPE)|(1<<MSTR);
61
break;
62
case1:// mode 1
63
SPCR=(1<<SPE)|(1<<MSTR)|(1<<CPHA);
64
break;
65
case2:// mode 2
66
SPCR=(1<<SPE)|(1<<MSTR)|(1<<CPOL);
67
break;
68
case3:// mode 3
69
SPCR=(1<<SPE)|(1<<MSTR)|(1<<CPOL)|(1<<CPHA);
70
break;
71
72
default:// mode 0
73
SPCR=(1<<SPE)|(1<<MSTR);
74
}
75
76
switch(geschw){
77
case0:// fosc/2
78
SPSR=1;
79
break;
80
case1:// fosc/4
81
SPSR=0;
82
break;
83
case2:// fosc/8
84
SPCR|=(1<<SPR0);
85
SPSR=1;
86
break;
87
case3:// fosc/16
88
SPCR|=(1<<SPR0);
89
SPSR=0;
90
break;
91
case4:// fosc/32
92
SPCR|=(1<<SPR1);
93
SPSR=1;
94
break;
95
case5:// fosc/64
96
SPCR|=(1<<SPR1);
97
SPSR=0;
98
break;
99
case6:// fosc/64
100
SPCR|=(1<<SPR1)|(1<<SPR0);
101
SPSR=1;
102
break;
103
case7:// fosc/128
104
SPCR|=(1<<SPR1)|(1<<SPR0);
105
SPSR=0;
106
break;
107
default:// fosc/2
108
SPSR=1;
109
}
110
111
if(order){
112
SPCR|=(1<<DORD);
113
}
114
}
115
116
/****************************/
117
staticinlinevoidds3234_adrmode(void){
118
spi_master_setmode(1,3,0);// F_CPU/4 Mode 3
119
spi_intern_outadr(UHR_ADR);
120
}
121
122
voidds3234_zeit_lesen(void){// Stunden, Minuten und Sekunden (BCD)
Und so weiter. Das Compilat wurde sogar nicht größer, da der Compiler
"spi_master_setmode" bis einfachen ldi + out optimiert.
"spi_intern_outadr" und "spi_intern_start", "spi_intern_stop" werden bis
sbi-cbi optimiert.
Ursprünglich wollte ich hier Konstruktionen wie
machen, aber das bringt nicht immer gut optimierte Code. Deshalb lasse
ich diese Funktionen zwar, aber nur für externen Gebrauch. Sie sind ja
inline und werden keinen Speicherplatz vergeuden, falls nicht benutzt.
Maxim B. schrieb:>> Danke euch beiden!
Gerne.
Aber wenn ich so etwas lese
> spi_master_setmode(1,3,0); // F_CPU/4 Mode 3
dann schrillen bei mir die Alarmglocken: in einer mehrstelligen Funktion
eine Parameterliste mit gleichen Datentypen zu verwenden, ist unlesbar.
Deswegen brauchtest Du einen Kommentar, der hier aber auch nicht viel
hilft.
Nimm wenigstens enum-types dafür (geht in C nicht besser).
Maxim B. schrieb:> Die Frage: wie könnte ich das schöner und kompakter schreiben?> Vielen Dank im voraus.
Indem du die DS3234 Funktionen komplett frei machst von Kenntnisse über
deine SPI infrastruktur. Die ds3234-Funktionen sollen dann so einfach
wie etwa so aussehen:
1
voidds3234_recv_time(){
2
spi_recv(SPI_TIME_ADRESSE,ds3234_buffer,size);
3
4
// daten aus 'buffer' dekodieren
5
}
Das ganze gedöns mit Register speichen und wiederherstellen ist nicht
Aufgabe des SD3234's sondern Aufgabe des SPI-treibers.
ist dann notwendig, wenn das Gerät mit einer willkürlichen Adresse
auftauschen will. Das führt oft aber zu langen Berechnungen, das kann
der Compiler nicht immer bis cbi-sbi optimieren.
Eine Uhr braucht man in System nur einmal. Deshalb habe ich Kompromiß
gemacht.
Ich möchte, daß alle eingebauten Geräte so wenig wie möglich Leistung
von Controller verschwenden.
Was Lesen und Schreiben in Puffer betrifft, das wird noch gemacht. Auch
möchte ich alle Zahlenkonversionen in einem separaten von allen Geräten
Ort machen. Das kommt noch. Im Moment geht es mir um die eingebauten
Geräte selbst und Überprüfung von Lotstellen, damit ich mit Garantie
wußte, daß eine Fehlfunktion durch Programm und nicht durch Lotfehler
verursacht wird. Eine von Hand geätzte und gebohrte Platte, ohne
Prototypen... Die erste Platte von meinem neuen zweiseitigen
UV-Belichter gemacht.
Maxim B. schrieb:> Bis enum bin ich noch nicht gekommen
Enum funktioniert ähnlich wie Defines, bloß daß ohne Angabe eines Wertes
der Wert automatisch aufwärts zählt, beginnend mit 0.
Maxim B. schrieb:> Bis enum bin ich noch nicht gekommen :) Ich muß darüber zuerst lesen...> Ich lerne erst.
Das ist halt auch so eine Sache mit der Reihenfolge. Wer noch nicht mal
die grundlegenden Sprachfeatures kennt, sollte nicht mit irgendwelchen
unsinnigen Optimierungen liebäugeln.
Da wird das Pferd wieder mal von Hinten aufgezäumt.
Cyblord -. schrieb:> Da wird das Pferd wieder mal von Hinten aufgezäumt.
Mit Erhöhung der Lesbarkeit durch sprechende Symbole kann man nie früh
genug anfangen. Ob man dafür Enums oder Defines verwendet, ist egal.
Magische Nummern fallen einem später selber auf die Füße und Kommentare
können veraltet sein.
Daß bei geschickter Zuweisung der Symbole das switch/case wegfällt, ist
nur ein Nebeneffekt.
Peter D. schrieb:> Cyblord -. schrieb:>> Da wird das Pferd wieder mal von Hinten aufgezäumt.>> Mit Erhöhung der Lesbarkeit durch sprechende Symbole kann man nie früh> genug anfangen. Ob man dafür Enums oder Defines verwendet, ist egal.
So richtig verstanden hast du meinen Einwurf ja nun nicht. Ich habe
keineswegs Enums kritisiert.
Cyblord -. schrieb:> So richtig verstanden hast du meinen Einwurf ja nun nicht. Ich habe> keineswegs Enums kritisiert.
Ich habe aber verstanden. Du bist der Meinung, Programmieren sei nur für
Profi. OK. Ich respektiere das. Und mache weiter :)
Maxim B. schrieb:> Ich habe aber verstanden. Du bist der Meinung, Programmieren sei nur für> Profi. OK. Ich respektiere das. Und mache weiter :)
Unglaublich. Wo habe ich das geschrieben?
Also nochmal meine Aussage, da anscheinend schwer verständlich:
Erst mal die GRUNDLAGEN einer Sprache lernen, dann erst die kranken
sinnlosen Hacks und Optimierungen. Nicht andersrum. Jetzt klar?
Cyblord -. schrieb:> Jetzt klar?
Du irrst!
Auch wenn du recht hast, irrst du!
Und so ist es hier.
Nicht logisch?
Doch.
Woher soll jemand wissen, wo vorne und hinten ist, richtig und falsch,
wichtig oder unwichtig, wenn er/sie/es offensichtlich noch ganz am
Anfang steht.
Du leistest dir Urteile vom hohen Ross.
Du irrst in der Annahme, dass deine Ansichten (egal, ob richtig, oder
nicht) für jeden Anfänger maßgeblich sind.
Bedenke:
Das Lernen ist ein Prozess.
Jahre, wird es dauern.
Peter D. schrieb:> Die Kommentare kann man sich sparen, wenn man sprechende Symbole> benutzt:
Vielen, vielen Dank!
Ich habe gleich versucht, alles arbeitet gut.
Heute habe ich wieder etwas gelernt!
Cyblord -. schrieb:> Erst mal die GRUNDLAGEN einer Sprache lernen, dann erst die kranken> sinnlosen Hacks und Optimierungen. Nicht andersrum. Jetzt klar?
Ich habe andere Meinung. Eine Sprache lernt man, indem man sie benutzt.
Yalu X. schrieb:> Beim GCC ist RAII mittels des Attributs "cleanup" auch in C möglich:
Auch wenn ich jetzt schließlich anders entschieden habe: diese
Möglichkeit kennen zu lernen ist für mich sehr von Wert. Ich habe diesen
Beispiel in meiner Liste von Lösungen gespeichert (zusammen mit
-gstrict-dwarf, " -Wl,-u,vfprintf -lprintf_flt -lm" für sprintf für
float und anderen Lösungen).
Vielen Dank!
Peter D. schrieb:> Die Kommentare kann man sich sparen, wenn man sprechende Symbole> benutzt:#include <avr\io.h>>> typedef enum> {> MOD_0 = 1<<SPE | 1<<MSTR,> MOD_1 = 1<<SPE | 1<<MSTR | 1<<CPHA,> MOD_2 = 1<<SPE | 1<<MSTR | 1<<CPOL,> MOD_3 = 1<<SPE | 1<<MSTR | 1<<CPOL | 1<<CPHA,> } spimod_t;
Das hat mir sehr gut gefallen!
Nun habe ich mir nach dem gleichen Prinzip eine Tontabelle erspart:
Dr. Sommer schrieb:> Nur leider kann der g++ halt kein __flash, weil das auch wieder> so ein AVR-Hack ist...
Named Address Spaces sind kein Hack sonder in der ISO/IEC 18037
spezifiziert. __flash folgt dieser Spezifikation.
Bislang hat es aber noch niemand erforderlich gefunden, Named Address
Spaces auch ins C++-Frontend des GCC einzubauen.
Und WG21 wird Named Address Spaces auch nicht in C++ aufnehmen, weil
sich WG21 wohl schon an anderen Qualifiern (volatile) die Finger
verbrannt hat:
https://lists.gnu.org/archive/html/avr-gcc-list/2012-05/msg00044.html
Johann L. schrieb:> Named Address Spaces sind kein Hack sonder in der ISO/IEC 18037> spezifiziert. __flash folgt dieser Spezifikation.
Also ein spezifizierter Hack ;-)
Johann L. schrieb:> Und WG21 wird Named Address Spaces auch nicht in C++ aufnehmen,
Ja, das würde einiges stark verkomplizieren. Es gibt jetzt schon 12
Varianten, wie man eine Member Funktion anhand des Typs von "this"
überladen kann (const, volatile, const volatile, &, &&, const &, ... ).
Das noch mit der Anzahl an Namespaces multiplizieren wäre dann nicht
mehr so schön.
Das vorgeschlagene
1
std::flash<char[]>str="literal";
wäre da schöner. Bloß wie definiert man dann eigene Klassen welche im
Flash liegen können... ? eigene Spezialisierungen von std::flash für
eigene Klassen erlauben?
Maxim B. schrieb:> Nun habe ich mir nach dem gleichen Prinzip eine Tontabelle erspart:
Hm, das kann man bestimmt noch aufhübschen wenn du die Anforderungen
konkretisiert...