Kann man folgenden Code platzsparender schreiben

Gast #4337643
Lesenswert?

Hallo,

Bei einer Schaltung ist jetzt durch ein Update der Speicher des 
verwendeten Attiny2313 zu klein geworden. Bei der Suche nach Code der 
viel Speicher benötigt bin ich bei folgenden zwei Codeausschnitten 
fündig geworden.
1
#define DDS_CLK 125000000UL
2
...
3
uint64_t tuningword;
4
tuningword = (frequency * 4294967296) / DDS_CLK;
5
...

Hier wird das 32-bit Tuning-Word für einen 9850 DDS-Chip berechnet.

1
uint32_t GetFreqency(uint8_t values[])
2
{
3
  uint32_t retval;
4

5
  retval  = values[0];
6
  retval += values[1] * 10;
7
  retval += values[2] * 100;
8
  retval += values[3] * 1000;
9
  retval += values[4] * 10000;
10
  retval += values[5] * 100000;
11
  retval += values[6] * 1000000;
12
  retval += values[7] * 10000000;
13

14
  return retval;
15
}

Hier wird der Wert der an der 8-stelligen 7-Segment-Anzeige angezeigt 
wird berechnet.

Ich zerbrich mir jetzt schon länger den Kopf darüber aber ich finde 
keinen Weg diese Code-Ausschnitte effizienter zu schreiben.
Gast #4337648
Lesenswert?

MN schrieb:
> uint32_t GetFreqency(uint8_t values[])

wo wird denn die Funktion aufgerufen? Wo wird das Array gefüllt?

Kannst du den Kompletten Code zeigen, meist lässt sich da besser 
erkennen wo man etwas optimieren kann. Es macht wenig sinn eine Funktion 
zu verbessern wenn der Ansatz schon merkwürdig ist.

Hier kommt man da Array merkwürdig vor.
#4337650
Lesenswert?

MN schrieb:

> Hier wird das 32-bit Tuning-Word für einen 9850 DDS-Chip berechnet.
>
Horner Regel
1
uint32_t GetFreqency(uint8_t values[])
2
{
3
  uint32_t retval = 0;
4
  uint8_t i;
5

6
  for( i = 0; i < 8; i++ )
7
    retval = retval * 10 + values[7-i];
8

9
  return retval;
10
}

Die Richtung der Schleife könnte man noch umdrehen, dann fällt der 
'unschöne' Ausdruck 7-i in der Inidzierung weg.

Aber ob das dein Platzproblem signifikant entlastet - da habe ich so 
meine Zweifel.
Auf der anderen Seite: wenn du nur ein paar Bytes mehr brauchst ...
#4337653
Lesenswert?

als pseudoschleife:
i=7
Ergebnis=0
{
Ergebnis=zahl[i]+Ergebnis*10
i=i-1

}solange bis i=0

und wenn möglich könnte man sogar den Input direkt in der schleife 
abholen.
das würde dann sogar das speichern der einzelstellen sparen.

i=7
Ergebnis=0
{
Ergebnis=lies_stelle_n_ein(i)+Ergebnis*10
i=i-1

}solange bis i=0
sg
(Firma: CIA) #4337655
Lesenswert?

Du übergibst der Routine die auf das 7Seg. ausgibt also einen 32-Bit 
Wert. Was macht die Funktion die das ganze auf dem 7Seg. ausgibt mit dem 
Wert...? Was ich damit sagen will Du wandelst einzelne Ziffern eines 
numerischen Wertes in einen solchen um. Zur Ausgabe auf einer 7Seg. 
Anzeige muss das ganze meist wieder Rückgängig gemacht werden. Da lässt 
sich vielleicht was optimieren. Wieviele Bytes musst Du denn sparen? 
Meist hilft es teile des Programs in Assembler zu programmieren, wenn es 
nicht um Speicher im KB Bereich geht.
Gast #4337671
Lesenswert?

Welcher Speicher ist zu knapp? RAM bei der Ausführung oder Flash für den 
Programmspeicher?

Hast du schon mal mit den Optimier-Optionen des Compilers gespielt? Ich 
vermute mal der ist eher in der Lage das zu reduzieren als irgendwelche 
Programmiertricks.
Wobei mir ehrlich gesagt nicht wirklich klar ist, weshalb gerade diese 
Funktion und das Array viel Platz wegnehmen sollten, da steht doch kaum 
etwas da? Womit hast du herausgefunden, dass gerade diese Code-Teile 
viel Platz benötigen?
Moderator Persönliche Seite #4337681
Lesenswert?

Jorge schrieb:
> Wobei mir ehrlich gesagt nicht wirklich klar ist, weshalb gerade diese
> Funktion und das Array viel Platz wegnehmen sollten, da steht doch kaum
> etwas da?

32-bit-Multiplikation und -Division.

Welche Compilerversion ist das?  Meiner Erinnerung nach hat sich da
in den aktuellen Versionen einiges getan.

Einen ATtiny2313 hätte ich allerdings für diesen 32-bit-Kram wohl
auch eher nicht benutzt.  Ggf. kannst du ja noch einen „Upgrade“ auf
einen ATtiny4313 in Erwägung ziehen, um paar Reserven zu bekommen.
#4337690
Lesenswert?

be s. schrieb:

> Ich kenne den verwendeten Controller nicht, aber wenn dieser keinen
> Hardwaremultiplizierer besitzt und die ganzen verschiedenen 32 Bit
> Multiplikationen in Software durchgeführt werden müssen, kann ich mir
> schon vorstellen, dass der ROM knapp wird.

Na ja. So wild ist das dann auch wieder nicht.
Die 32 * 32 Bit Multiplikation wird ja nicht direkt an dieser Stelle 
gemacht, sondern dafür wird eine Funktion aufgerufen.

D.h. in der ursprünglichen Fassung werden da 8 Funktionsaufrufe stehen 
sowie entsprechende Register Lade Anweisungen um die verschiedenen 
Konstanten auf den Weg zu bringen.

Das kann man natürlich etwas zusammenstreichen, aber um die 
Multiplikationsfunktion kommt man nicht herum. D.h. im wesentlichen kann 
man 7 Funktionsaufrufe samt Registervorbelegung einsparen. Ist natürlich 
eine Einsparung, aber die Welt wird es auch nicht sein.
Gast #4337691
Lesenswert?

be s. schrieb:
> Ich kenne den verwendeten Controller nicht, aber wenn dieser keinen
> Hardwaremultiplizierer besitzt und die ganzen verschiedenen
> Multiplikationen in Software durchgeführt werden müssen, kann ich mir
> schon vorstellen, dass der ROM knapp wird.

wobei man sagen muss, das die Multiplikationen für jeden Datentype nur 
einmal im ROM ist. Wenn er an einer andere stelle noch eine 
Multiplikationen verwendet, bringt es überhaupt nichts diese Funktion zu 
optimieren.
Gast #4337694
Lesenswert?

Jörg W. schrieb:
> Ich würde für sowas (bzw. nehme) wenigstens einen ATmega, der hat
> einen Hardware-Multiplizierer, was deutlich Code spart.

oder das Problem gleich anders Lösen. Kann mir nicht so richtig 
vorstellen für was man so ein Array braucht. Wenn dort wenigsten ASCII 
drin stehen würde, könnte ich es verstehen. Aber wo werden denn die 
Ziffern ein Zahl binär übertragen?
Gast #4337703
Lesenswert?

MN schrieb:
> #define DDS_CLK 125000000UL
> ...
> uint64_t tuningword;
> tuningword = (frequency * 4294967296) / DDS_CLK;
> ...

Wenn man den konstanten ausdruck 4294967296) / DDS_CLK so umrechnet, 
dass eine 2er Potenz im Nenner steht, könnte man sich den Platz für die 
Divisionsroutine im Flash sparen.
#4337713
Lesenswert?

Peter II schrieb:
> Jörg W. schrieb:
>> Ist ja nicht nur das Array, sondern eben auch die Berechnung des
>> tuning words für die DDS.
>
> aber wieso braucht man dort das Dezimalsystem? (habe noch nie mit einem
> DDS gearbeitet)

Keine Ahnung.
Ich schätze mal das wird ein Überbleibsel aus der Benutzersteuerung 
sein. 8 Drehgeber - für jede Stelle eine. Oder irgend so etwas.
Gast #4337733
Lesenswert?

Hallo vielen Dank schon mal an alle. Ich komme mit dem Lesen fast nicht 
mehr hinterher. Der Code passt jetzt gerade so in den Controller ist 
aber noch nicht ganz fertig.

Peter II schrieb:
> Kannst du den Kompletten Code zeigen, meist lässt sich da besser
> erkennen wo man etwas optimieren kann. Es macht wenig sinn eine Funktion
> zu verbessern wenn der Ansatz schon merkwürdig ist.

Ja kann ich selbstverständlich. In der Zip-Datei im Anhang sollten alle 
Code-Dateien enthalten sein. Der Code ist noch etwas wirr und 
unformatiert da ich gerade ständig irgendwo etwas ändere. Ich hoffe man 
kann ihn trotzdem einigermaßen entziffern.

Karl H. schrieb:
> Horner Regel

Vielen Dank. Das hat schon mal viel gebracht! Wenn ich jetzt den Code 
sehe könnte ich mir in den Hintern beißen. Darauf hätte ich auch selber 
kommen können.

Thomas H. schrieb:
> Du übergibst der Routine die auf das 7Seg. ausgibt also einen 32-Bit
> Wert. Was macht die Funktion die das ganze auf dem 7Seg. ausgibt mit dem
> Wert...? Was ich damit sagen will Du wandelst einzelne Ziffern eines
> numerischen Wertes in einen solchen um. Zur Ausgabe auf einer 7Seg.
> Anzeige muss das ganze meist wieder Rückgängig gemacht werden. Da lässt
> sich vielleicht was optimieren.

Nein, ich übergebe der Funktion die auf die 7Segment-Anzeige schreibt 
die Stellen schon einzeln. Ich verwende einen MAX7219 als 
Display-Treiber. Den 32bit-Wert brauche ich zur Berechnung des 
Tuning-Wortes für den AD9850.
Mit der Hilfe von Karl H. passt der Code jetzt gerade so in den Chip 
(text=1908). Ich muss allerdings noch eine Wertebereich-Abfrage 
einfügen.

Jorge schrieb:
> Hast du schon mal mit den Optimier-Optionen des Compilers gespielt? Ich
> vermute mal der ist eher in der Lage das zu reduzieren als irgendwelche
> Programmiertricks.

Optimierer-Option ist leider bereits auf -Os.
Angehängte Dateien:
Gast #4337763
Lesenswert?

MN schrieb:
> Hallo vielen Dank schon mal an alle.

Fällt mir gerade so nebenbei ein:

Der DDS AD9850 braucht die Bits "umgekehrt" hineingeschaufelt
gegenüber der Wertigkeitsfolge wie sie die üblichen AVR
SPI Maschinen erzeugen.

Dies, falls jemand darauf hereinfallen sollte ....
Man braucht einen Bit Swapper.
#4337767
Lesenswert?

Schau mer mal
1
void Send(uint16_t data)
2
{
3
  uint16_t tmp;
4

5
  PORT_DISPLAY &= ~(1<<DISP_CS);
6

7
  for(int i=15; i>=0; i--)
8
  {
9
    tmp = data & (1<<i);
10

11
    PORT_DISPLAY &= ~(1<<DISP_CLK);
12

13
    if(tmp > 0)
14
      PORT_DISPLAY |= (1<<DISP_DATA);
15
    else
16
      PORT_DISPLAY &= ~(1<<DISP_DATA);
17

18
    PORT_DISPLAY |= (1<<DISP_CLK);
19
  }
20

21
  PORT_DISPLAY &= ~(1<<DISP_CLK);
22

23
  // CS pulse
24
  PORT_DISPLAY |= (1<<DISP_CS);
25
  PORT_DISPLAY &= ~(1<<DISP_CS);
26
}

die Variable temp wird dir der Compiler sowieso rauswerfen. In 
Wirklichkeit braucht die keiner. Du könntest die entsprechende 
Auswertung auch hier
1
    if(tmp > 0)
ganz einfach einsetzen. Worum gehts an dieser Stelle tatsächlich? Es 
geht darum, ob 1 Bit auf 1 oder auf 0 stand. Ob dieses temp größer als 0 
ist, interessiert eigentlich nicht. ungleich 0 reicht und dann kann man 
das so schreiben
1
    if( data & (1<<i) )
2
      PORT_DISPLAY |= (1<<DISP_DATA);
3
    else
4
      PORT_DISPLAY &= ~(1<<DISP_DATA);

aber eigentlich willst du auf einem AVR einen Ausdruck der Form '1<<i' 
nicht haben, denn den muss der Compiler in Form einer Schleife auflösen.

eine adequate Umsetzung, die ohne so einen variablen Shift auskommt, 
lautet
1
void Send(uint16_t data)
2
{
3
  uint16_t mask = 0x8000;
4

5
  PORT_DISPLAY &= ~(1<<DISP_CS);
6

7
  for(uint8_t i = 0; i < 16; i++)
8
  {
9
    PORT_DISPLAY &= ~(1<<DISP_CLK);
10

11
    if( data & mask )
12
      PORT_DISPLAY |= (1<<DISP_DATA);
13
    else
14
      PORT_DISPLAY &= ~(1<<DISP_DATA);
15

16
    PORT_DISPLAY |= (1<<DISP_CLK);
17

18
                mask >>= 1;
19
  }
20

21
  PORT_DISPLAY &= ~(1<<DISP_CLK);
22

23
  // CS pulse
24
  PORT_DISPLAY |= (1<<DISP_CS);
25
  PORT_DISPLAY &= ~(1<<DISP_CS);
26
}


Achte auch auf Datentypen! Du schmeisst da zb bei deinem i mit einem int 
um dich, obwohl du den Wertebereich überhaupt nicht brauchst. Das alles 
kostet. Nicht viel, aber ein paar Bytes sind es auch.
Man könnte auch darüber nachdenken, sich eine raustakt-Funktion für 
einen 8 Bit Wert zu schreiben und die dann 2 mal aufzurufen. Das würde 
die ganzen 16 Bit OPerationen runterbrechen. Müsste man ausprobieren, 
was dann tatsächlich kürzer ist.

(Das war jetzt nur die allererste Funktion, auf die ich nach öffnen des 
Zip Files im ersten C-File gestossen bin)
Moderator Persönliche Seite #4337769
Lesenswert?

Profi Frickler schrieb:

> Der DDS AD9850 braucht die Bits "umgekehrt" hineingeschaufelt
> gegenüber der Wertigkeitsfolge wie sie die üblichen AVR
> SPI Maschinen erzeugen.

So?

Ich habe das hier:
1
static void
2
fq_ud(uint32_t fq)
3
{
4
  union
5
  {
6
    uint32_t l;
7
    uint8_t b[5];
8
  }
9
  u;
10
  uint8_t i;
11

12
  u.l = fq;
13
  /*
14
   * W32..W39: no phase angle, no power-down, control bits = 0
15
   */
16
  u.b[4] = 0;
17

18
  for (i = 0; i < 5; i++)
19
    {
20
      SPDR = u.b[i];
21
      while ((SPSR & _BV(SPIF)) == 0)
22
        /* wait */;
23
    }
24

25
  /* Update now. */
26
  PORTC |= _BV(1);
27
  PORTC &= ~_BV(1);
28
}

… und das funktioniert.  Nachgemessen. ;-)

Hier ist die Initialisierungsroutine:
1
static void
2
init_ad9850(void)
3
{
4
  /*
5
   * Disable SPI, and reset the DDS circuitry.
6
   */
7
  SPCR = 0;
8
  PORTC |= _BV(0);
9
  PORTC &= ~_BV(0);
10

11
  /*
12
   * After reset, initiate one W_CLK pulse followed by one FQ_UD pulse
13
   * to activate the serial configuration control word.
14
   */
15
  PORTB |= _BV(7);
16
  PORTB &= ~_BV(7);
17
  PORTC |= _BV(1);
18
  PORTC &= ~_BV(1);
19

20
  /*
21
   * Now, we're ready to hand over the data transmission to SPI.
22
   */
23
  SPCR = _BV(SPE) | _BV(DORD) | _BV(MSTR);
24
  SPSR = _BV(SPI2X);
25
}

Kann es sein, dass dir das DORD-Bit entgangen ist?
#4337776
Lesenswert?

1
ISR(TIM_COMP_vect)
2
{
3
  buttonDebounceISR();
4
}

das willst du nicht tun!

Du willst nicht aus einer ISR heraus eine Funktion aufrufen!
Damit zwingst du den Compiler zu einer Registersicherorgie. Die kostet 
Laufzeit (was noch verschmerzbar wäre) aber sie kostet natürlich auch 
Code!

Zieh die ISR runter zur Funktion und bette den Funktionsinhalt direkt in 
die ISR ein.
#4337782
Lesenswert?

Send ( ) ist mir auch aufgefallen.
Diese Version hier
1
void Send(uint16_t data)
2
{
3
  uint8_t i;
4

5
  PORT_DISPLAY &= ~(1<<DISP_CS);
6

7
  for(i=15; i>=0; i--)
8
  {
9
    PORT_DISPLAY &= ~(1<<DISP_CLK);
10

11
    if(data & 0x8000)
12
      PORT_DISPLAY |= (1<<DISP_DATA);
13
    else
14
      PORT_DISPLAY &= ~(1<<DISP_DATA);
15

16
    PORT_DISPLAY |= (1<<DISP_CLK);
17
    data <<= 1;
18
  }
19

20
  PORT_DISPLAY &= ~(1<<DISP_CLK);
21

22
  // CS pulse
23
  PORT_DISPLAY |= (1<<DISP_CS);
24
  PORT_DISPLAY &= ~(1<<DISP_CS);
25
}
spart 42 Code Bytes ggü deiner.
#4337783
Lesenswert?

Peter II schrieb:
> wobei man sagen muss, das die Multiplikationen für jeden Datentype nur
> einmal im ROM ist.

Korrekt. Ich sehe hier jedoch zwei Multiplikationsroutinen:

16 Bit signed:
1
values[1] * 10;
2
values[2] * 100;
3
values[3] * 1000;
4
values[4] * 10000;
32 Bit signed:
1
values[5] * 100000;
2
values[6] * 1000000;
3
values[7] * 10000000;

Insbesondere diese Zeile
1
values[4] * 10000;
kann aufgrund des Wertebereichs zum Problem werden. Diese Multiplikation 
müsste mit 32 Bit ausgeführt werden.

Nachtrag: Ich nehme an, dass im Array nur Werte von 0 bis 9 gespeichert 
sind, ansonsten müssten andere Multiplikationen auch als 32 Bit 
durchgeführt werden.
#4337801
Lesenswert?

Hier
1
void ddsSetFrequency(uint32_t frequency)
2
{
3
  uint64_t tuningword;
4
  uint8_t msg[5];// = {0x2D, 0x3E, 0x05, 0x00, 0x00}; //ca. 10kHz
5
  uint8_t tmp;
6
  int i, j;
7

8
  tuningword = (frequency * 4294967296) / DDS_CLK;
9

10
  msg[0] = (uint8_t)(tuningword & 0xFF);
11
  msg[1] = (uint8_t)( (tuningword>>8 ) & 0xFF);
12
  msg[2] = (uint8_t)( (tuningword>>16 ) & 0xFF);
13
  msg[3] = (uint8_t)( (tuningword>>24 ) & 0xFF);
14
  msg[4] = 0x00;
15

16
  for(j=0; j<5; j++)
17
  {
18
    for(i=0; i<8; i++)
19
    {
20
      tmp = (msg[j] & (1<<i));
21

22
      PORTD &= ~(1<<PIN_WCLK);
23

24
      if (tmp > 0)
25
        PORTD |= ((tmp >> i) << PIN_DATA);
26
      else
27
        PORTD &= ~(1<<PIN_DATA);
28

29
      PORTD |= (1<<PIN_WCLK);
30
    }
31
  }
32
  PORTD &= ~(1<<PIN_WCLK);
33
  PORTD |= (1<<PIN_FQUP);
34
  PORTD &= ~(1<<PIN_FQUP);
35
}

dasselbe Spielchen noch einmal.

Ich denke, es würde auch etwas bringen, wenn man sich eine eigene 8 Bit 
Ausgabefunktion schreibt und die dann direkt mit dem jeweils 
zurechtgeschobenen Byte aufruft. Das Array braucht man in Wirklichkeit 
nämlich gar nicht
1
void dddsSetFrequencyByte( uint8_t byte )
2
{
3
  uint8_t i;
4

5
  for(i = 0; i < 8; i++)
6
  {
7
    PORTD &= ~(1<<PIN_WCLK);
8

9
    if( byte & 0x01 )
10
      PORTD |= 1<<PIN_DATA;
11
    else
12
      PORTD &= ~( 1<<PIN_DATA );
13

14
    PORTD |= (1<<PIN_WCLK);
15

16
    byte >>= 1;
17
  }
18
}
19

20
void ddsSetFrequency(uint32_t frequency)
21
{
22
  uint64_t tuningword;
23

24
  tuningword = (frequency * 4294967296) / DDS_CLK;
25

26
  dddsSetFrequencyByte( tuningword & 0xFF );
27
  dddsSetFrequencyByte( ( tuningword >> 8 ) & 0xFF );
28
  dddsSetFrequencyByte( ( tuningword >> 16 ) & 0xFF );
29
  dddsSetFrequencyByte( ( tuningword >> 24 ) & 0xFF );
30
  dddsSetFrequencyByte( 0x00 );
31

32
  PORTD &= ~(1<<PIN_WCLK);
33
  PORTD |= (1<<PIN_FQUP);
34
  PORTD &= ~(1<<PIN_FQUP);
35
}

aber der Löwenanteil wird wahrscheinlich tatsächlich für die 64Bit bzw. 
32Bit Arithmetik drauf gehen.
Gast #4337832
Lesenswert?

Der Code passt mittlerweile in den Controller und macht auch schon das, 
was er soll. Vielen Dank an dieser Stelle noch einmal für die Hilfe.

Karl H. schrieb:
> ISR(TIM_COMP_vect)
> {
>   buttonDebounceISR();
> }
>
> das willst du nicht tun!
>
> Du willst nicht aus einer ISR heraus eine Funktion aufrufen!
> Damit zwingst du den Compiler zu einer Registersicherorgie. Die kostet
> Laufzeit (was noch verschmerzbar wäre) aber sie kostet natürlich auch
> Code!
>
> Zieh die ISR runter zur Funktion und bette den Funktionsinhalt direkt in
> die ISR ein.

Okey, das sehe ich ein.

Dieses komische Konstrukt kommt daher, dass man in der Timer-ISR neben 
dem entprellen oft andere Aufgaben mit erledigen kann. Dann habe ich 
zwei Möglichkeiten:

1) die ISR() steht in der button.c und enthält Code, der eigentlich gar 
nicht zum Taster gehört. Die Datei button.c würde dann in jedem Projekt 
anders aussehen was ja nicht der Sinn der Sache ist.
2) die ISR() steht z.B. in main.c und der Code der jetzt in 
buttonDebounceISR() steht muss kopiert werden. Auch irgendwie eine 
unschöne Arbeitsweise.

Könnte man denn die angesprochene "Registersicherorgie" nicht vermeiden, 
wenn man die Funktion buttonDebounceISR() mit "inline" definieren würde?
#4337842
Lesenswert?

MN schrieb:

> Könnte man denn die angesprochene "Registersicherorgie" nicht vermeiden,
> wenn man die Funktion buttonDebounceISR() mit "inline" definieren würde?

Im Zweifelsfall: ausprobieren und im Listing File nachsehen ob und was 
sich verändert hat. In deinem Fall könnte man auch sagen: Wird das 
Hex-File kleiner, dann hat es was gebracht - auch wenn die nackten 
Zahlen interpretiert werden wollen.
Gast #4337854
Lesenswert?

Karl H. schrieb:
> Im Zweifelsfall: ausprobieren und im Listing File nachsehen ob und was
> sich verändert hat. In deinem Fall könnte man auch sagen: Wird das
> Hex-File kleiner, dann hat es was gebracht - auch wenn die nackten
> Zahlen interpretiert werden wollen.

Das werde ich bei Gelegenheit machen. Heute bin ich schon mal sehr 
zufrieden, dass die neue Codeversion für den DDS läuft.

danke

PS: Ich bin mal wieder entsetzt auf wie viele Dinge ich beim 
programmieren zu wenig achte. Das kommt wohl daher das ich die µC 
ansonsten eher überdimensioniere.
Gast #4337987
Lesenswert?

Uwe S. schrieb:
> dann bist Du kein Mathematiker

Für soviel reicht es dass ich sagen kann dass der Ausdruck
in der Klammer

Uwe S. schrieb:
> (34 +1/2 -1/7 +1/385 -1/528.613)

konstant ist und damit nur einmal berechnet werden muss.
Du würdest es aber jedesmal neu berechnen wenn du nicht Glück
hast und der Compiler das sowieso wegoptimiert.

In deiner Formel kommt noch "erschwerend " hinzu dass eine
Floating Point Zahl vorkommt die den Compiler zwingt
in FP zu rechnen. Sehr ungünstig ..... für die Erzeugung
von minimalem Code in einem AVR .....
#4337995
Lesenswert?

Hallo,

Um zu erfahren mit wie vielen Bits man rechnen muss, macht man eine 
Abschätzung.

Es gilt mit FCLK = 125MHz:
a) k = 2^32 /FCLK ~ 34,360 < 35
b) 0 <= f <= FCLK/3 ~ 41,667MHz

Anm.: Mit FCLK/3 hat man noch 3 Stützstellen bei der Sinusgenerierung.

Wir wählen für unsere Rechnung als obere Grenzfrequenz f_max = 40MHz.

Es folgt:
FTW = f *2^32 /FCLK = f *(k -Fehlerterm) = f *k - f *Fehlerterm <= f *k

mit k = 35 aus a) und f_max = 40MHz können wir die maximale Anzahl Bits 
für unsere Rechnung herleiten:

max. Bit = log2(40 *10⁶ *35) = log2(1.4 *10³ *10⁶)
max. Bit = log2(1.4 *10⁹) = 30,38 Bit < 31 Bit

Also können wir bei alle Rechnungen mit ganzen 32 Bit Zahlen (0<=N<2³²) 
durchführen ohne einen Überlauf erwarten zu müssen.
#4338000
Lesenswert?

Frickelfritze schrieb:

Sorry, für das Missverständnis:

528.613 = 528613 und es wurde ein Tausendertrennzeichen eingefügt.

> Uwe S. schrieb:
>> dann bist Du kein Mathematiker
>
> Für soviel reicht es dass ich sagen kann dass der Ausdruck
> in der Klammer
>
> Uwe S. schrieb:
>> (34 +1/2 -1/7 +1/385 -1/528.613)
>
> konstant ist und damit nur einmal berechnet werden muss.
> Du würdest es aber jedesmal neu berechnen wenn du nicht Glück
> hast und der Compiler das sowieso wegoptimiert.
>
> In deiner Formel kommt noch "erschwerend " hinzu dass eine
> Floating Point Zahl vorkommt die den Compiler zwingt
> in FP zu rechnen. Sehr ungünstig ..... für die Erzeugung
> von minimalem Code in einem AVR .....
Gast #4338006
Lesenswert?

Uwe S. schrieb:
> Also können wir bei alle Rechnungen mit ganzen 32 Bit Zahlen (0<=N<2³²)
> durchführen ohne einen Überlauf erwarten zu müssen.

In deiner Formel hast du das aber nicht verinnerlicht, das
mit den ganzen Zahlen ... und der Compiler weiss auch nichts davon.
#4338008
Lesenswert?

Hallo Chris,

ich sehe schon, diese Art von Berechnung ist nicht vielen geläufig.
Natürlich löst man die Klammer auf.

Es gilt ja weiterhin laut Datenblatt des AD9850:

FTW = f * k mit k = 2³²/125MHz

und k ~ 34,3597 ~ (34 +1/2 -1/7 +1/385 -1/528613)

FTW ~ 34*f +f/2 -f/7 +f/385 -f/528613

chris schrieb:
> Uwe S. schrieb:
>> FTW = (34 +1/2 -1/7 +1/385 -1/528.613) * f ; f in Hz.
>
> 1/2 = 0
> 1/7 = 0
> 1/385 = 0
>
> integer Division...

Ich kann auch behaupten und beweisen, schon für die DDS AD9912, AD9851, 
AD9851/AD9850 und AD9834 Funktionen für die Berechnung des FTW 
geschrieben zu haben.
#4338234
Lesenswert?

Karl H. schrieb:
> ISR(TIM_COMP_vect)
> {
>   buttonDebounceISR();
> }
>
> das willst du nicht tun!
>
> Du willst nicht aus einer ISR heraus eine Funktion aufrufen!

Sorge dafür, dass die Funktion inlined wird (eventuell per LTO), dann 
ist das kein Problem. Den Ansatz, in der ISR mehrere Funktionen 
aufzurufen, ist jedenfalls grundsätzlich nicht schlecht; das Ganze 
hässlich machen kann der Compiler selbst.

Uwe S. schrieb:
> hier ist noch ein anderer Ansatz für die Berechnung des FTW eines AD8950
> mit FCLK = 125MHz konstant !.
>
> FTW = (34 +1/2 -1/7 +1/385 -1/528.613) * f ; f in Hz.
>
> D.h. man benötigt 1 Multiplikation, 4 Divisionen, 2 Additionen und 2
> Subtraktionen

Das ist großer Unfug. Du ersetzt jetzt nämlich eine Laufzeitoperation 
("Konstante einsetzen") durch 9x Arithmetik, plus Registerbenutzung und 
so weiter. Die Division alleine wird so viel Code erzeugen, dass der 
Flash wieder platzt; die Rechenzeit ignoriere ich dabei mal gepflegt.

Zumal ich nicht verstehe, warum du 2^32/FCLK unbedingt zur Laufzeit 
ausrechnen willst. Das kann der Compiler auch selbst. Dann bleibt da 
nämlich "FTW = f * k" stehen, und das ist eine Laufzeitmultiplikation.
#4338236
Lesenswert?

S. R. schrieb:
> Zumal ich nicht verstehe, warum du 2^32/FCLK unbedingt zur Laufzeit
> ausrechnen willst. Das kann der Compiler auch selbst. Dann bleibt da
> nämlich "FTW = f * k" stehen, und das ist eine Laufzeitmultiplikation.

Weil
1
(frequency * 4294967296) / 125000000UL;
nicht immer das gleiche Ergebnis wie
1
frequency * (4294967296 / 125000000UL);
liefert.

Die Idee von Uwe ist, die 64 Bit Operationen durch 32 Bit Operationen zu 
ersetzen.
#4338237
Lesenswert?

Hallo S. R.,

an wen ist der Kommentar gerichtet ?

S. R. schrieb:
> Uwe S. schrieb:
>> hier ist noch ein anderer Ansatz für die Berechnung des FTW eines AD8950
>> mit FCLK = 125MHz konstant !.
>>
>> FTW = (34 +1/2 -1/7 +1/385 -1/528.613) * f ; f in Hz.
>>
>> D.h. man benötigt 1 Multiplikation, 4 Divisionen, 2 Additionen und 2
>> Subtraktionen
>
> Das ist großer Unfug. Du ersetzt jetzt nämlich eine Laufzeitoperation
> ("Konstante einsetzen") durch 9x Arithmetik, plus Registerbenutzung und
> so weiter. Die Division alleine wird so viel Code erzeugen, dass der
> Flash wieder platzt; die Rechenzeit ignoriere ich dabei mal gepflegt.
>
> Zumal ich nicht verstehe, warum du 2^32/FCLK unbedingt zur Laufzeit
> ausrechnen willst. Das kann der Compiler auch selbst. Dann bleibt da
> nämlich "FTW = f * k" stehen, und das ist eine Laufzeitmultiplikation.

Wenn ich gemeint bin, dann zeige bitte genau, wie Du die Berechnung des 
FTW ohne Nutzung der 64Bit Arithmetik machen möchtest.

Bei mir belegt die Funktion uint32_t ftw_ad9850(uint32_t freq); 380Byte 
Code im Flash.
Gast #4338477
Lesenswert?

Uwe S. schrieb:
> Bei mir belegt die Funktion uint32_t ftw_ad9850(uint32_t freq); 380Byte
> Code im Flash.

Ich habe es nachvollzogen. Die Rechnung mit Approximation und mehrfacher
Division in uint32_t ist deutlich kompakter als die Rechnung der
Multiplikation und Division mit long long (uint64_t). Grund ist
hauptsächlich dass bei der 64 bit Rechnung grössere Divisionsroutinen
aus der AVR Lib (ich nenne es mal so) mit eingebunden werden müssen.
Moderator Persönliche Seite #4338512
Lesenswert?

Frickelfritze schrieb:
> Ich habe es nachvollzogen.

Mit welcher Compilerversion?  (Hatte ich oben den TE schon mal gefragt.)

Ich habe so in Erinnerung, dass Johann da in letzter Zeit mal was
getan hatte an den Bibliotheksroutinen.

Ggf. stellst du ja einfach deinen (compilierfähigen) Code mal allen
zur Verfügung, damit das jeder selbst testen kann?
Moderator Persönliche Seite #4338628
Lesenswert?

avion23 schrieb:
> Ich habe letztens mit -flto herum gespielt und konnte dadurch 16kb
> sparen.

Das Problem ist, dass der zu WinAVR 4.18 ursprünglich mal gehörende 
avr-gcc von 2010 gar kein -flto kann.

Nimmt der TO eine aktuellere Toolchain, wird sich sein Problem nach ein 
paar Handgriffen wahrscheinlich schon in Luft auflösen.

Hier ist beschrieben, wie das auch mit WinAVr 4.18 möglich ist:

    Beitrag "Re: WinAVR 2010 / AVR-GCC und Atmega48PA"

Eine aktuelle Toolchain (z.B. 4.7.2 oder höher) bekommt man hier:

    http://sourceforge.net/projects/mobilechessboar/files/avr-gcc%20snapshots%20%28Win32%29/
#4338982
Lesenswert?

Hallo Miteinander,

hier ist noch ein weiterer Ansatz, aber von der Codegröße schlechter, 
für die Berechnung des FTW für einen AD9850 mit Fclk = 125MHz.
Diesen "findet" man unter Betrachtung der Wertigkeiten der 
Nachkommastellen von: k = 2³²/125MHz ~ 34,359738368
1
#include <avr/io.h>
2
#include <stdint.h>
3
#include <stdlib.h>
4

5
// Hilfsmacro
6
#define FTW_TERM(mul,div)  ((uint32_t)(mul) * freq) / (uint32_t)(div)
7

8
// Prototype
9
uint32_t ftw_ad9850( uint32_t freq );
10

11

12
int main()
13
{
14
  volatile uint32_t ftw;
15

16
  ftw = ftw_ad9850( 10000000 );
17
  while (1) { }
18
}
19

20

21
uint32_t ftw_ad9850( uint32_t freq )
22
{
23
// ftw = f * 34.359738368
24
return ((uint32_t)34 *freq) +
25
  FTW_TERM(3,10) +
26
  FTW_TERM(5,100) +
27
  FTW_TERM(9,1000) +
28
  FTW_TERM(7,10000) +
29
  FTW_TERM(3,100000) +
30
  FTW_TERM(8,1000000) +
31
  FTW_TERM(3,10000000) +
32
  FTW_TERM(6,100000000) +
33
  FTW_TERM(8,1000000000) ;
34
}
Gast #4340222
Lesenswert?

Jörg W. schrieb:
> Ggf. stellst du ja einfach deinen (compilierfähigen) Code mal allen
> zur Verfügung, damit das jeder selbst testen kann?

Hier mal meine Verifikation ..... your mileage may vary.
Vielleicht findet jemand noch Fehler oder Verbesserungsmöglichkeiten.

Einfach wechselweise  die beiden  #if 0  und  #if 1  togglen um
beide Codeversionen zu erzeugen. Bin gespannt was bei neueren
Compiler-Versionen rauskommt.

Interessant wäre noch (für Binärcode-Kenner) warum die 64 Bit Version
zusätzlich viel RAM braucht.

1
/********************************************************/
2
/***  DDS9850_calc.c                                   **/
3
/***  DDS Word berechnen aus Frequenz                  **/
4
/********************************************************/
5

6
#include "avr/io.h"
7

8
/*******************************************************/
9
/*  Dummy Set Routine                                  */
10
/*  fuer den Fall dass der Compiler wegoptimieren will */
11
/*******************************************************/
12
void Set_DDS (uint32_t val)
13
{
14
  PORTB = val;
15
  PORTB = val >>  8;
16
  PORTB = val >> 16;
17
  PORTB = val >> 24;
18

19
} /* ---- Set_DDS ---- */
20

21

22
#if 0
23
/*******************************************************/
24
/*  Berechnung mit 64 bit Multiplikation / Division    */
25
/*******************************************************/
26
uint32_t Calc_FTW (uint32_t freq)
27
{
28
  const uint64_t  cSysClock = 125000000;
29
  uint32_t  dds_word;
30

31
  dds_word = (uint32_t) ( ((long long)freq * (long long)(0x100000000ll)) / cSysClock);
32

33
  return dds_word;
34

35
} /* ---- Calc_FTW ---- */
36
#endif
37

38

39
#if 1
40
/*******************************************************/
41
/*  Approximation mit mehreren 32 bit Divisionen       */
42
/*******************************************************/
43
uint32_t Calc_FTW (uint32_t freq)
44
{
45
  /*  FTW = f * k -->  k = 2³²/125MHz                 */
46
  /*  k ~ 34,3597 ~ (34 + 1/2 - 1/7 + 1/385 - 1/528613)  */
47
  /*      34,35973836   */
48
  /*  FTW ~ f*34 + f/2 - f/7 + f/385 - f/528613   */
49

50
  uint32_t  dds_word;
51

52
  dds_word = (freq*34) + (freq/2) - (freq/7) + (freq/385) - (freq/528613);
53

54
  return dds_word;
55

56
} /* ---- Calc_FTW ---- */
57
#endif
58

59

60
/***********************/
61
/*      main           */
62
/***********************/
63
int main (void)
64
{
65
  uint32_t  ftw_val;
66
  uint32_t  freq_dds ;
67

68
  freq_dds = 32123765;
69
  ftw_val = Calc_FTW (freq_dds);
70
  Set_DDS (ftw_val);
71

72
  return 0;
73

74
}  /* ---- main ---- */
75

76
/* ----------------------------------------- */
77
/* ------ End of file DDS9850_calc.c  ------ */
78
/* ----------------------------------------- */
Moderator Persönliche Seite #4340730
Lesenswert?

Frickelfritze schrieb:
> Ich hatte nicht den Anspruch, einen Beweis anzutreten, das wird mir
> von dir einfach so angedichtet.

Sorry, ich wollte dir nichts "andichten".

Danke für den Code.  Hab's mal in #ifdef USE_64 / #else umgeschrieben,
damit kann man das direkt auf der Kommandozeile testen, ohne die Datei
nochmal zu editieren.

Hier das Ergebnis für einen GCC 4.8.3 (auch schon nicht mehr der
Jüngste):
1
$ avr-gcc -Os -mmcu=attiny2313 -o foo.elf foo.c
2
$ avr-size foo.elf
3
   text    data     bss     dec     hex filename
4
    598       0       0     598     256 foo.elf
5
$ avr-gcc -Os -mmcu=attiny2313 -DUSE_64 -o foo.elf foo.c
6
$ avr-size foo.elf
7
   text    data     bss     dec     hex filename
8
    380       0       0     380     17c foo.elf

Hier ist die 64-bit-Rechnung also deutlich platzsparender als der
Workaround.

Frickelfritze schrieb:
> Interessant wäre noch (für Binärcode-Kenner) warum die 64 Bit Version
> zusätzlich viel RAM braucht.

Das müsstest du an deinem Compilat untersuchen.  Ich erinnere mich
dunkel, dass da mal irgendwas war, wo die libgcc ein 256 Byte großes
Array angelegt hat, um irgendwelche Bitsucherei zu beschleunigen.
Möglicherweise ist das ja bei dir der Fall.
Gast #4340894
Lesenswert?

.... hmmmm ... schon interessant, aber ich habe nicht die Kenntnis
und Erfahrung um aus dem Binary schlau zu werden.

Ich poste hiermit mal die für die obige Source erzeugten *.lss und *.map
files für die 64 bit Version. Vielleicht kann sich jemand einen Reim
daraus machen. Es fällt auf dass viel Code dazugelinkt wird, warum auch
immer. Und es scheinen "pauschal" 256 Byte RAM allokiert zu werden.
Aus der Menge an Variablen nicht zu erklären ....

Wie bereits angedeuted ist das mit Studio 4.18 erzeugt worden. Ist
ja aber in den Files auch ersichtlich.
Angehängte Dateien:
Moderator Persönliche Seite #4340939
Lesenswert?

Posten des ELF-Files wäre hilfreicher gewesen: Disassemblieren kann es
sich jeder selbt, aber man könnte dann auch schnell einen Blick auf die
Symboltabelle werfen.

Ja, der „Störenfried“ ist dieser da:
1
 .data          0x00800100      0x100 c:/winavr-20100110/bin/../lib/gcc/avr/4.3.3/avr4\libgcc.a(_clz.o)
2
                0x00800100                __clz_tab

Wenn du mal nach AVR-GCC __clz_tab gugelst, findest du das Problem
seinerzeit sicher hinreichend diskutiert.

Aber vielleicht wären ja solche Artefakte (zusammen mit der ja offenbar
insgesamt deutlich mieseren Optimierung) endlich mal ein Anlass für
einen Upgrade der Toolchain?  Du kannst ja auch eine aktuelle Toolchain
unter das olle Studio schieben, allerdings musst du dann dem Compiler
mit -gstrict-dwarf sagen, dass er keine erweiterten Debug-Infos erzeugt,
denn diese lassen den handgefrickelten DWARF-Parser dieses alten Studios
abstürzen (nicht, weil der Compiler buggy ist, sondern weil der Parser
nicht nach dem DWARF-Standard geschrieben worden war, sondern offenbar
per trial&error auf das maßgeschneidert, was der damalige Compiler so
rausgeworfen hat).

> Nachtrag: und für einen Mega88 übersetzt ... ob das eine Rolle spielt?

Ja, denn der hat einen Hardwaremultiplizierer, den der vom TE
genutzte ATtiny2313 nicht hat.  Damit würde das Compilat also dann
nochmal größer werden.
Gast #4341097
Lesenswert?

Danke für die Hinweise.

Jörg W. schrieb:
> Aber vielleicht wären ja solche Artefakte (zusammen mit der ja offenbar
> insgesamt deutlich mieseren Optimierung) endlich mal ein Anlass für
> einen Upgrade der Toolchain?

Ja, damit hadere ich schon die ganze Zeit, die Lösung mit aktuellerem
GCC und Studio 4.18 wäre eine gute Alternative um das überladene,
resourcenfressende Studio 7.xx zu umgehen. Zumal die Durchsichtigkeit,
Übersichtlichkeit eines Projekts in der neuen Umgebung nicht besser
wird. Mal sehen was ich mir dabei noch für Nebeneffekte einhandele.

Übrigens der Unterschied zwischen Mega88 und Tiny2313 in der Grösse
des Codes ist nur minimal....  abgesehen davon dass die 4K nicht in
einen Tiny2313 passen würden ....

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren