Forum: Mikrocontroller und Digitale Elektronik Arithmetik in C


von Michael (Gast)


Angehängte Dateien:

Lesenswert?

Hi,
in meine C-Programm müssen ein paar Gleichungen berechnet werden.
Siehe Anhang.

Wie mache ich das am effektivsten? Mein 8-bit-µC Tiny26 hat nur 2k
Programmspeicher welcher fast voll ist. Die oben gezeigten Rechnungen
funktionieren einwandfrei. Gibt es eine Möglichkeit auf die
Cast-Konvertierung auf unsigned long int (u32) zu verzichten?

Grüße,
Michael

von peter dannegger (Gast)


Lesenswert?

"Gibt es eine Möglichkeit auf die Cast-Konvertierung auf unsigned long
int (u32) zu verzichten?"

Das weißt nur Du, da nur Du den Maximalwert für speed kennst.

Ansonsten könnte es sich lohnen, die Divisonen auf 2-er Potenzen
umzustellen (128, 1024).


Peter

von Michael (Gast)


Lesenswert?

Die Variable speed ist 8-Bit groß und kann Werte zwischen 0 und 255
annehmen.

"Ansonsten könnte es sich lohnen, die Divisonen auf 2-er Potenzen
umzustellen (128, 1024)."

Meinst du damit z.B. nicht durch 1000 zu teilen, sondern durch 1024 per
9mal nach rechts schieben? Zähler muss natürlich angepasst werden.

Michael

von Malte _. (malte) Benutzerseite


Lesenswert?

>Meinst du damit z.B. nicht durch 1000 zu teilen, sondern durch 1024
per
>9mal nach rechts schieben?
Wird er meinen.

Falls die Rechnengenauigkeit reicht, könntest du eventuell in
Get_Param() *48/1000 durch /21 ersetzt werden, wäre dann also
~47,6/1000. In diesem Fall könnte die komplette Berechnung mit 16 Bit
auskommen. Ansonsten müsste generell bei Get_Param() der hintere und
bei Get_No_Load_Current() der vordere Teil mit (u16) auskommen. Ob dies
aber was bringt weiß ich nicht, eventuell kommt der Compiler selbst
schon drauf.
Als letztes könnte ein Blick auf den erzeugten Assemblercode noch
hinweise auf mögliche Optimierungen bringen.

PS: Irgendwie sind 2KB Flash fast immer zu wenig :-D

von Peter D. (peda)


Lesenswert?

"PS: Irgendwie sind 2KB Flash fast immer zu wenig :-D"


Volle Zustimmung, zumindest beim AVR-GCC.

Deshalb nehme ich eigentlich immer den ATMega8, der kann sogar schnell
multiplizieren.

Ich weiß jetzt nicht, wie das bei den kommerziellen Compilern ist.


Durch den 16Bit-Befehlssatz und RISC ist die Kodeeffizienz ja von
vornherein nicht so gut, wie auf CICS mit 8Bit-Befehlen, z.B. 8051.

Da habe ich in 2kB (AT89C2051) viel mehr machen können und oft auch
floating point gemacht. Hab dann aber auch einen kommerziellen Compiler
genommen (Keil).


Peter

von crazy horse (Gast)


Lesenswert?

bei 8bit Eingangsgrösse und 8bit Ausgangsgrösse könnte auch eine Tabelle
eine sinnvolle Lösung sein, von Excel berechnen lassen. Macht je 256
Byte Tabellengrösse pro Funktion. Schneller als die Berechnung ist es
auf jeden Fall und würde deutlich weniger Speicher brauchen.

von Michael (Gast)


Lesenswert?

"Falls die Rechnengenauigkeit reicht, könntest du eventuell in
Get_Param() *48/1000 durch /21 ersetzt werden, wäre dann also
~47,6/1000. In diesem Fall könnte die komplette Berechnung mit 16 Bit
auskommen."

Respekt! Sehr guter Vorschlag! Stolze 68Byte hat das gebracht! :-)

"PS: Irgendwie sind 2KB Flash fast immer zu wenig :-D"
Ich hatte das gleiche Programm als Prototyp in Assembler in einem
Tiny15 mit 1KB Speicher. Der war auch Rand voll...

@crazy horse: So hab ichs mit m Tiny15 gemacht. Da hab ich die
Parameter (sind Regelparameter) im E² abgelegt. Eine Tabelle im Flash
wäre sicher besser. Ich dachte eigentlich eine Gleichung braucht
weniger Programmspeicher als eine Tabelle? Wenn ich die Gleichung mal
auskommentier, dann sind 50Byte weniger belegt. Eine Tabelle müsste mit
256 Werten auch 256 Byte belegen, oder?

So, jetzt hab ich noch 500Byte. Das sind 200 mehr als vor euren Tipps.
Vielen Dank! Hoffentlich reicht das aus...

Michael

von peter dannegger (Gast)


Lesenswert?

@Michael

Anstatt wahllos rumzuoptimieren, geht doch rein pragmatisch vor.

Du wirst wohl nicht umhin kommen, Dir erstmal das Assemblerlisting
anzusehen und aufzuschreiben, wieviel Code die einzelnen Funktionen
Deines Programms belegen.

Und dann suchst Du Dir die Teile raus, die auch wirklich viel Code
erzeugen.

Es macht nämlich überhaupt keinen Sinn, an irgendwas rumzuoptimieren,
was nur 1% Einfluß hat.


Peter

von Michael (Gast)


Lesenswert?

Das hört sich nach viel Arbeit an g.

Gibts eigentlich ein kleines Tool, welches mir die Größe eines
Programmteils anzeigt? Für Windows gibts z.B. Treesize welches die
Größe von Ordner ausgibt.

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

[avr-]size müsste das können.
Ich habe nur leider keine Ahnung mit welchen Parametern...

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

Kommando zurück:

 $ avr-nm -t d -S <xyz.[a|elf|o]>

Zeigt alle Symbole und die Zugehörige größe Dezimal an.

von Malte _. (malte) Benutzerseite


Lesenswert?

>Gibts eigentlich ein kleines Tool, welches mir die Größe eines
>Programmteils anzeigt?
Erzeuge das Assemblerlisting mit make dateiname.s
Dann kannst du dir ansehen wie groß jede einzelne Funktion wird. Aber
Achtung:
1. ist die Größenangabe in Word und nicht Byte (also mal zwei nehmen)
2. Unterprogramme z.B. von der avr-libc werden nicht berücksichtigt.

von ope (Gast)


Lesenswert?

ich schätze, Du kommst nicht drum'rum Dir anzusehen, welchen Asm Code
Dir Dein Compiler erzeugt, wenn Du richtig optimieren möchtes. Wie
Peter aber schon andeutete: kennst Du die 10-90 Regel? 10% Code
erzeugen 90% Ausführungszeit (Lokalität). Du solltest also wissen, wo
es sich lohnt. Ich kann allerdings nicht sagen, ob der avr-gcc
profiling unterstützt, was eine konkrete Möglichkeit mit gprof wäre.

Du kannst ihm aber etwas auf die Sprünge helfen, indem Du es dem
Compiler etwas einfacher machst:
1
u8 Get_No_Load_Current(void)
2
{
3
  u32 inl = 237;  // kein copy constructor
4
  inl *= speed;
5
  inl /= 100;
6
  ....
7
}

Je nach Wertebereich Deiner Vars kommst Du so auch ohne unit32_t aus,
was alles etwas weniger bytes/register benötigt (also worst-case
Rechnung machen). Wie ich sehe, kannst Du auch speed ausklammern, es
wird hier 3x multipliziert, d.h. klever per Hand umformen, Konstanten
zusammenfassen. Da Du C machst, kannst Du auch vieles dem Präprozessor
machen lassen - gerade die Konstanten Mult.ausdrücke:
#define C1 (4711 * 2)
wird von ihm ausgerechnet - brutal geht auch:
const uint8_t c1 = 4711 * 2; // ggf. static machen
benötigt aber wohl 2 Byte mehr Speicher.

Viele Grüße
Olaf

von Michael (Gast)


Lesenswert?

Oha, jetzt wirds interessant...

Zuerst einmal, ich verwende den IAR Kick-Start-Compiler welcher beim
STK500 mitgeliefert wird.

Das Tool [avr-]size kommt mit gcc mit oder lässt sich als "Plugin"
nachinstallieren? ODer ist es ein eigenständiges Programm? Der IAR sagt
mir den Speicherbedarf für jede *.c Datei, welche er kompiliert. Das ist
schonmal hilfreich. Denn die Datei, in welcher die Gleichungen sind ist
nach main() am größten.

Ich denk, dass ich trotzdem nicht um den Assemblercode herum komme.
Sicher auch ganz lehrreich. Die 10-90 Regel kenn ich als 20-80 Regel.

Vielen Dank für eure tolle Tipps!!!
Grüße,
Michael

von Unbekannter (Gast)


Lesenswert?

Naja, 20/80 oder 10/90, ist ja fast das gleiche...

Der entscheidende Punkt ist: In einem kleinem Teil des Systems werden
die meisten Resourcen benutzt.
Ob das jetzt bei einem Programm Speicher oder Zeit ist, bei einem
elektrischen Gerät der Energieverbrauch oder bei einem mechanischem
Gerät die Fehlerquelle, oder bei einem anderem technischen Gerät die
Kosten, spielt keine Rolle.

Fast immer gibt es in einem System irgendwelche signifikaten
Konzentrationen von irgendetwas.

von ope (Gast)


Lesenswert?

>Ich denk, dass ich trotzdem nicht um den Assemblercode herum komme.
>Sicher auch ganz lehrreich. Die 10-90 Regel kenn ich als 20-80 Regel.

Es lohnt sich wirklich, nicht unbedingt das konkrete verstehen, aber
man kann ungefähr erkennen, was der Compiler macht (besonders
interessant bei C++ expression und meta templates lib zB. tvmet,
blitz++).

Die 10-90 oder 20-80 oder 80-20 bzw. 90-10 Regel - da ist sich die
Literatur nicht so ganz einig (Ich behaupte mal ganz kess, es sind
8,96% zu 91,04% ;-) aber die Kernaussage bleibt ja erhalten wie bereits
hier gesagt.

Was auch helfen kann, u.U. besser als den Compiler auf min. Size achten
zu lassen (switch -s): inlining; d.h. Du musst wissen, wo es sich lohnt
(hier hilft auch der asm code). Persöhnlich (ich kann nur vom gcc
sprechen), versaut die -s Option mehr als sie nutzt. Oft schreibt man
functions nur wegen der Lesbarkeit des sources und der Compiler nimmt
das etwas zu ernst. Der C99 Standard gibt noch das keyword inline her,
es ist allerdings ein Hinweis an den CC es doch bitte zu inlinen, ob
er es macht, ist 'ne andere Sache. Die neueren (>3.1) gcc kann man
dagegen mit  __attribute ((always_inline)) dahin treten. Aber nicht
übertreiben, sonst wird der code zu gross wiederum ;-)

Auch generell mit -O2 zu kompilieren, was ja auf 32bit OS Standard ist,
hilft bei komplexen Sourcen für den uC nichts, da man dann ständig an
den Hardwaregrenzen hängen bleibt. Wie gesagt, wissen wo es sich
lohnt.

Also, einfach mal winavr installieren. Wie ich gerade sehe, ist gprof
anscheinend nicht für den AVR portiert, aber gcov - der kann auch beim
optimieren bzgl. der 90-10/80-20 Regel helfen (mal googeln).

Viele Grüße
Olaf

von Werner Hoch (Gast)


Lesenswert?

Du kannst für jede rechenoperation einen anderen Scalierungsfaktor
verwenden. Dadurch kannst du auch mit 16bit-Operationen eine hohe
Genauigkeit erzielen.

inl= 237/100 * s - 14/1000 *s^2 - 36

erstmal umformen damit das s^2 verschwindet:

inl= (237/100 - 14/1000*s)*s - 36

Die Berechnung enthält drei Faktoren, wird mit 16 bit gerechnet, so
darf jedes einzelergebnis die 32000 Marke nicht überschreiten:

fakt1=(1<<13)*237/100; /* Berechnung durch den User */
fakt1=19415;

fakt2=(1<<13)*14/1000; /* Berechnung durch den User */
fakt2=114;

die Berechnung sieht also so aus:

inl=((((fakt1-fakt2*s)>>8)*s)>>5)-36

Ich hoffe ich hab mich nicht verrechnet.
Selber hab ich diese Methode bei für die skalierung eines SHT11
verwendet. Der Fehler lag bei ca. 0,6 LSB
_____________________
uint16_t
sht11_humidity_scale(uint16_t value) {
  /* scaling factors as defined in the datasheet with factor 100 */
  /*-4.0 * 100*/
#define SHT11_C1 -400
  /*0.0405  100  (1<<28)*/
#define SHT11_C2  1087163597
  /*-0.0000028  100  (1<<30)*/
#define SHT11_C3 -300648
  /* humidity = SHT11_C1 + value * ( SHT11_C2 + SHT11_C3 * value); */
  /* I will calc (humidity * 100) here, the same scale that temperature
has. */
  /* every step is calculated in the best possible scale */
  /* we calculate in long -> we have 31 bits + sign */
  /* value is 12 bit long */
  return (int32_t)
    (((((SHT11_C3 * value
   >>2)               /* rescale 30 -> 28 */
  + SHT11_C2)
       >> 11)               /* rescale 28 -> 17 */
      * value
      + ((int32_t)1<<16))   /* round: add (1<<16) */
     >> 17)                 /* rescale 17 -> 0 */
    + SHT11_C1;
}
__________________

von Michael (Gast)


Lesenswert?

Sehr gute Idee mit dem Ausklammern! Simpel aber wirkungsvoll.
Nochmals vielen Dank an alle! Ihr habt  mir sehr geholfen und mir ein
bisschen von der Platzangst genommen:-)

Michael

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.