Forum: Mikrocontroller und Digitale Elektronik Lokale Variable statt static, aber optimiert. Wie?


von Maxim B. (max182)


Angehängte Dateien:

Lesenswert?

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
static u8 temp_SPCR, temp_SPSR;
2
3
void ds3234_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
void ds3234_spi_herstellen(void){
10
  SPCR = temp_SPCR;
11
  SPSR = temp_SPSR;
12
}
13
14
void ds3234_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(u8 i=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
void ds3234_zeit_lesen(void){  // Stunden, Minuten und Sekunden (BCD)
2
3
u8 temp_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(u8 i=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.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

z.B. mit einer Funktion die den "inneren" Code als Funktional übernimmt 
und einem Lambda:
1
template <typename F>
2
inline void ds3234_spi (F&& f) {
3
  uint8_t temp_SPCR = SPCR;
4
  uint8_t temp_SPSR = SPSR;
5
6
  static_cast<F&&> (f) ();
7
8
  SPCR = temp_SPCR;
9
  SPSR = temp_SPSR;
10
}
11
12
void ds3234_zeit_lesen() {  // Stunden, Minuten und Sekunden (BCD)
13
  spi_intern_outadr(UHR_ADR);
14
  ds3234_spi ([&] () {
15
    spi_intern_start();
16
    spi_master_transmit(0);
17
    for(u8 i=0;i<7;i++){
18
      ds3234_zeitpuffer[i] = spi_master_transmit(0);
19
    }
20
  });
21
}

Dank "inline" wird das alles zu einem Block.

von Stefan F. (Gast)


Lesenswert?

Ich glaube, du übertreibst es da mit Optimierung. Ist es wirklich nötig?

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> z.B. mit einer Funktion die den "inneren" Code als Funktional übernimmt
> und einem Lambda:

Das gibt's in C nicht.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

Vielen Dank!

etwas klappt aber noch nicht.
In h. habe ich geschrieben:
1
template <typename F>
2
inline void ds3234_spi (F&& f) {
3
  uint8_t temp_SPCR = SPCR;
4
  uint8_t temp_SPSR = SPSR;
5
6
  static_cast<F&&> (f) ();
7
8
  SPCR = temp_SPCR;
9
  SPSR = temp_SPSR;
10
}
Compiler schimpft:
../ds3234.h:25:10: error: expected '=', ',', ';', 'asm' or 
'__attribute__' before '<' token
 template <typename F>

Was habe ich falsch gemacht?

von Dr. Sommer (Gast)


Lesenswert?

Maxim B. schrieb:
> Was habe ich falsch gemacht?

Das geht nur mit C++... Also mit g++ statt gcc kompilieren

von Maxim B. (max182)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

Maxim B. schrieb:
> Wie könnte ich
> dort auf C++ umschalten?

Vermutlich die Source-Datei von ".c" nach ".cpp" umbenennen.

von Maxim B. (max182)


Lesenswert?

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...

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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...

: Bearbeitet durch User
von Zombie (Gast)


Lesenswert?

Für mich wäre das ein klarer Fall für RAII in C++.
1
struct SPIModeSichern {
2
    SPIModeSichern() noexcept : temp_SPCR(SPCR), temp_SPSR(SPSR) { }
3
    ~SPIModeSichern() noexcept {
4
        SPCR = temp_SPCR;
5
        SPSR = temp_SPSR;
6
    }
7
8
    private:
9
    uint8_t temp_SPCR = SPCR;
10
    uint8_t temp_SPSR = SPSR;
11
}
12
13
void ds3234_zeit_lesen() {  // Stunden, Minuten und Sekunden (BCD)
14
  spi_intern_outadr(UHR_ADR);
15
  SPIModeSichern old_spi_mode;
16
  spi_master_setmode(1,3,0);  // F_CPU/4 Mode 3
17
  spi_intern_start();
18
  spi_master_transmit(0);
19
  for(u8 i=0;i<7;i++){
20
      ds3234_zeitpuffer[i] = spi_master_transmit(0);
21
  }
22
}

von Yalu X. (yalu) (Moderator)


Lesenswert?

Zombie schrieb:
> Für mich wäre das ein klarer Fall für RAII in C++.

Beim GCC ist RAII mittels des Attributs "cleanup" auch in C möglich:

1
struct spi_regs {
2
  uint8_t spcr, spsr;
3
};                       
4
5
static void spi_restore(struct spi_regs *regs) {
6
  SPCR = regs->spcr;
7
  SPSR = regs->spsr;
8
}                                                                
9
10
#define SPI_SAVE struct spi_regs spi_regs __attribute__((cleanup(spi_restore))) = { SPCR, SPSR };
11
12
void ds3234_zeit_lesen(void) {
13
  spi_intern_outadr(UHR_ADR);
14
  SPI_SAVE;
15
  spi_master_setmode(1, 3, 0);
16
  spi_intern_start();
17
  spi_master_transmit(0);
18
  for(uint8_t i=0; i<7; i++)
19
    ds3234_zeitpuffer[i] = spi_master_transmit(0);
20
  spi_intern_stop();
21
}

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.

: Bearbeitet durch Moderator
von GEKU (Gast)


Lesenswert?

Warum nicht so:
1
inline void ds3234_spi_sichern(int mod){
2
  static u8 temp_SPCR, temp_SPSR;
3
4
  if (mod) {
5
    temp_SPCR = SPCR;
6
    temp_SPSR = SPSR;
7
    spi_master_setmode(1,3,0);  // F_CPU/4 Mode 3
8
  } else {
9
    SPCR = temp_SPCR;
10
    SPSR = temp_SPSR;
11
  } 
12
}

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

Mal ganz blöd gefragt: warum musst du die Registerinhalte überhaupt 
sichern?

Oliver

von Maxim B. (max182)


Lesenswert?

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!

von Maxim B. (max182)


Lesenswert?

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.

: Bearbeitet durch User
von Maxim B. (max182)


Lesenswert?

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.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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...

von M.K. B. (mkbit)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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ß.

von Maxim B. (max182)


Lesenswert?

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.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

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!

von Peter D. (peda)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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...

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

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?

von C++17 (Gast)


Lesenswert?

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?

von Maxim B. (max182)


Lesenswert?

Ich denke, ich bleibe lieber bei C.

von Dr. Sommer (Gast)


Lesenswert?

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...

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Maxim B. (max182)


Lesenswert?

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
static inline void spi_intern_outadr(u8 adr){
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
static inline void spi_intern_start(void){
46
  SPI_INTERN_E_PORT &= ~(1<<SPI_INTERN_E);
47
}
48
49
static inline void spi_intern_stop(void){
50
  SPI_INTERN_E_PORT |= (1<<SPI_INTERN_E);
51
}
52
53
/****************************/
54
55
void spi_master_setmode(u8 geschw,u8 mode,u8 order){
56
//  wie spi_init(), aber ohne spi_master_transmit() und ohne spi_portmasterinit();
57
58
  switch(mode){
59
    case 0:  // mode 0
60
      SPCR = (1<<SPE)|(1<<MSTR);
61
      break;
62
    case 1:  // mode 1
63
      SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPHA);
64
      break;
65
    case 2:  // mode 2
66
      SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPOL);
67
      break;
68
    case 3:  // 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
    case 0:  // fosc/2
78
      SPSR = 1;
79
      break;
80
    case 1:  // fosc/4
81
      SPSR = 0;
82
      break;
83
    case 2:  // fosc/8
84
      SPCR |= (1<<SPR0);
85
      SPSR = 1;
86
      break;
87
    case 3:  // fosc/16
88
      SPCR |= (1<<SPR0);
89
      SPSR = 0;
90
      break;
91
    case 4:  // fosc/32
92
      SPCR |= (1<<SPR1);
93
      SPSR = 1;
94
      break;
95
    case 5:  // fosc/64
96
      SPCR |= (1<<SPR1);
97
      SPSR = 0;
98
      break;
99
    case 6:  // fosc/64
100
      SPCR |= (1<<SPR1)|(1<<SPR0);
101
      SPSR = 1;
102
      break;
103
    case 7:  // 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
static inline void ds3234_adrmode(void){
118
  spi_master_setmode(1,3,0);  // F_CPU/4 Mode 3
119
  spi_intern_outadr(UHR_ADR);
120
}
121
122
void ds3234_zeit_lesen(void){  // Stunden, Minuten und Sekunden (BCD)
123
124
  ds3234_adrmode();
125
  
126
  spi_intern_start();
127
  /* u.s.w. */
128
}
129
130
/****************************/
131
static inline void graph_128x64_adrmode(void){
132
  spi_master_setmode(0,0,0);  // F_CPU/2 Mode 0
133
  spi_intern_outadr(GF_ADR);
134
}
135
136
void graph_128x64_writedata(u8 data, u8 strob_e){  // strob_e b0= E1, b1 = E2
137
  graph_128x64_adrmode();
138
  graph_128x64_write_servport(GRAPH_128X64_DI);  // D/I = 1 RW = 0
139
  /* u.s.w. */
140
}

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
1
static inline void spi_write_byte(volatile u8 *csreg,u8 csbit,u8 data)
2
{  
3
  *csreg &= ~(1<<csbit);
4
  spi_master_transmit(data);
5
  *csreg |= (1<<csbit);  
6
}
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.

: Bearbeitet durch User
Beitrag #5951896 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

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).

von Maxim B. (max182)


Lesenswert?

Bis enum bin ich noch nicht gekommen :) Ich muß darüber zuerst lesen... 
Ich lerne erst.

von Eric B. (beric)


Lesenswert?

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
void ds3234_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.

von Maxim B. (max182)


Lesenswert?

Ja, ich bin einverstanden. Aber...
So etwas wie
1
static inline void spi_write_byte(volatile u8 *csreg,u8 csbit,u8 data)
2
{  
3
  *csreg &= ~(1<<csbit);
4
  spi_master_transmit(data);
5
  *csreg |= (1<<csbit);  
6
}
 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.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Maxim B. schrieb:
> spi_master_setmode(1,3,0);  // F_CPU/4 Mode 3

Die Kommentare kann man sich sparen, wenn man sprechende Symbole 
benutzt:
1
#include <avr\io.h>
2
3
typedef enum
4
{
5
  MOD_0 = 1<<SPE | 1<<MSTR,
6
  MOD_1 = 1<<SPE | 1<<MSTR | 1<<CPHA,
7
  MOD_2 = 1<<SPE | 1<<MSTR | 1<<CPOL,
8
  MOD_3 = 1<<SPE | 1<<MSTR | 1<<CPOL | 1<<CPHA,
9
} spimod_t;
10
11
typedef enum
12
{
13
  DIV_4,
14
  DIV_2,
15
  DIV_16,
16
  DIV_8,
17
  DIV_64,
18
  DIV_32,
19
  DIV_128,
20
} spidiv_t;
21
22
typedef enum
23
{
24
  MSB_FIRST,
25
  LSB_FIRST  = 1<<DORD,
26
} spiord_t;
27
28
static inline void spi_master_setmode(spimod_t mode, spidiv_t geschw, spiord_t order)
29
{
30
  SPCR = mode | order | geschw>>1;
31
  SPSR = geschw & 1;
32
}
33
34
void test(void)
35
{
36
  spi_master_setmode(MOD_3, DIV_128, MSB_FIRST);
37
  spi_master_setmode(MOD_0, DIV_2, LSB_FIRST);
38
  spi_master_setmode(MOD_1, DIV_4, LSB_FIRST);
39
}


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.

von Stefan F. (Gast)


Lesenswert?

Peter D. schrieb:
> beginnend mit 0.

Kann man sich darauf verlassen, dass es immer mit 0 anfängt?

von Peter D. (peda)


Lesenswert?

Stefanus F. schrieb:
> Kann man sich darauf verlassen, dass es immer mit 0 anfängt?

Ja, wenn keine anderslautende Zuweisung erfolgt.
1
typedef enum
2
{
3
  BIN_0,
4
  BIN_1,
5
  BIN_2,
6
  BIN_7 = 7,
7
  BIN_8,
8
} blub_t;

von Cyblord -. (cyblord)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> #include <avr\io.h>

Backslashes sind hier zu vermeiden, da betriebssystemabhängig.

Korrekt (und portabel) ist:
1
#include <avr/io.h>

von Peter D. (peda)


Lesenswert?

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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 :)

von Cyblord -. (cyblord)


Lesenswert?

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?

von Einer K. (Gast)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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!

von Maxim B. (max182)


Lesenswert?

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.

: Bearbeitet durch User
von Maxim B. (max182)


Lesenswert?

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!

von Maxim B. (max182)


Lesenswert?

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:
1
#define A1_TONHOEHE 440.0
2
3
#define EXP_KC     0.5946036
4
#define EXP_KCs   0.6299605
5
#define EXP_KD    0.6674199
6
#define EXP_KDs    0.7071068
7
#define EXP_KE    0.7491535
8
#define EXP_KF    0.7937005
9
#define EXP_KFs    0.8408964
10
#define EXP_KG    0.8908987
11
#define EXP_KGs    0.9438743
12
#define EXP_KA    1.0
13
#define EXP_KB    1.0594631
14
#define EXP_KH    1.1224620
15
16
#define OKTAVE_M  1.0
17
#define OKTAVE_1  2.0
18
#define OKTAVE_2  4.0
19
#define OKTAVE_3  8.0
20
#define OKTAVE_4  16.0
21
#define OKTAVE_5  32.0
22
#define OKTAVE_6  64.0
23
#define OKTAVE_7  128.0
24
25
typedef enum
26
{
27
  c_m  =   (u16)((F_CPU / (EXP_KC * A1_TONHOEHE * OKTAVE_M)) - 0.5),
28
  cs_m  =  (u16)((F_CPU / (EXP_KCs * A1_TONHOEHE * OKTAVE_M)) - 0.5),
29
  d_m  =  (u16)((F_CPU / (EXP_KD * A1_TONHOEHE * OKTAVE_M)) - 0.5),
30
  ds_m  =  (u16)((F_CPU / (EXP_KDs * A1_TONHOEHE * OKTAVE_M)) - 0.5),
31
  e_m  =  (u16)((F_CPU / (EXP_KE * A1_TONHOEHE * OKTAVE_M)) - 0.5),
32
  f_m  =  (u16)((F_CPU / (EXP_KF * A1_TONHOEHE * OKTAVE_M)) - 0.5),
33
  fs_m  =  (u16)((F_CPU / (EXP_KFs * A1_TONHOEHE * OKTAVE_M)) - 0.5),
34
  g_m  =  (u16)((F_CPU / (EXP_KG * A1_TONHOEHE * OKTAVE_M)) - 0.5),
35
  gs_m  =  (u16)((F_CPU / (EXP_KGs * A1_TONHOEHE * OKTAVE_M)) - 0.5),
36
  a_m  =  (u16)((F_CPU / (EXP_KA * A1_TONHOEHE * OKTAVE_M)) - 0.5),
37
  b_m  =  (u16)((F_CPU / (EXP_KB * A1_TONHOEHE * OKTAVE_M)) - 0.5),
38
  h_m  =  (u16)((F_CPU / (EXP_KH * A1_TONHOEHE * OKTAVE_M)) - 0.5),
39
  c_1  =   (u16)((F_CPU / (EXP_KC * A1_TONHOEHE * OKTAVE_1)) - 0.5),
40
  cs_1  =  (u16)((F_CPU / (EXP_KCs * A1_TONHOEHE * OKTAVE_1)) - 0.5),
41
  /* und so weiter */
42
  h_6  =   (u16)((F_CPU / (EXP_KH * A1_TONHOEHE * OKTAVE_6)) - 0.5),
43
  c_7  =   (u16)((F_CPU / (EXP_KC * A1_TONHOEHE * OKTAVE_7)) - 0.5),
44
} zwuk_note_t;
45
46
static inline void zwuk_ton_ein(zwuk_note_t note){
47
  OCR1A = note;
48
  TCCR1B |= (1<<CS10);
49
}
Hier wird gar keine Tabelle gemacht. Der Compiler bringt die Zahlen 
direkt über r24 und r25, und nur die wirklich gebraucht werden.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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...

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.