mikrocontroller.net

Forum: Compiler & IDEs Prozent rechnung mit AVR


Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo noch mall, ich habe schon wieder ein problem, und zwar:

...
uint16_t pwm1;
uint16_t adcw1;
uint16_t adcw2;
...
if (adcw1 >= 800) && (adcw1 <= 900) ) {

    adcw2= adcw1-800;
    pwm1 = ((adcw2 / 100)*12024); //pwm1 = x% von adcw2
    pwm_setting[1]=pwm1;
    pwm_update();
    
    }

das Prolem ist, dass adcw2 die richtigen Werte zugewiesen bekommt, aber 
die berechnung von pwm1 in die hose geht.

Für eure hilfe danke ich im vorraus.

P.S: Ich glaube, ich brauche nicht zu sagen, dass ich so gut wie keine 
ahnung von programieren habe. :)

MFG

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> pwm1 = ((adcw2 / 100)*12024); //pwm1 = x% von adcw2
Mal 12024? Bist Du sicher?

Abgesehen davon: Ich hoffe, Du weißt, dass eine Integer-Division erstens 
immer eine ganze Zahl als Ergebnis liefert und dass sie zweitens nicht 
rundet...

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Rudolf Bremer (Gast)

Die Berechnung wird mit Integer durchgeführt. D.h. alle Nachkommastellen 
sind für die Katz. 800/100 = 8, 899/8 =8 etc.

Siehe Festkommaartihmetik.

So gehts besser. Die Rechung erfolgt in 32 Bit, ausserdem erst 
Multiplikation, dann Division.

    pwm1 = (uint32_t)adcw2 * 12024 / 100; //pwm1 = x% von adcw2

MfG
Falk

Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ups, nein, natürlich ist 1024 gemeint :) Aha, ich wuste es aber nicht, 
die Nachkommastellen sind egall, so genau will ich es nicht haben. Und 
wie könte ich das am besten machen?

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Falk Brunner wrote:
>
>     pwm1 = (uint32_t)adcw2 * 12024 / 100; //pwm1 = x% von adcw2
> 
Hmmm, also ich ganz persönlich würde da ein paar Klammern spendieren.
   pwm1 = ((uint32_t)adcw2 * 12024) / 100; //pwm1 = x% von adcw2
Sieht doch besser aus, gelle? Und der Compiler kommt auch nicht auf die 
Idee, 12024/100 als Konstante zu berechnen.

Aber ich denke nach wie vor, dass der OP eher 1024 meint und nicht 
12024...

Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
da war Falk schneller...werde es gleich testen

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Johannes M. (johnny-m)

>Sieht doch besser aus, gelle? Und der Compiler kommt auch nicht auf die
>Idee, 12024/100 als Konstante zu berechnen.

Das darf er nicht, und demzufolge tut er es auch nicht. Weil ne Variable 
im Spiel ist.

@ Rudolf Bremer (Gast)

>die Nachkommastellen sind egall, so genau will ich es nicht haben. Und

Schon, aber in deinen Zwischenergebnissen werden sie weggeschmissen!
-> massiver Rundungsfehler.

MfG
Falk

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Machst Du ne 10-Bit-PWM? Dann überleg mal, was passiert, wenn Du in 
adcw2 100% vorgibst... Ich weiß jetzt zwar nicht, wie Du die Werte 
bearbeitest, bevor Du sie an irgendein Compare-Register weitergibst, 
aber das dürfte zu einem interessanten Nebeneffekt führen. Kleiner Tipp: 
Ein 10-Bit-Wert kann maximal 1023 sein...

Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ALso es ist 1024 gemeint, nicht 12024, 8 bit PWM. Es handelt sich um die 
Software PWM von Falk Brunner: 
http://www.mikrocontroller.net/articles/Soft-PWM#I...
Ich frage mich gerade selber warum ich 1024 benutze und nicht 255?!? Wie 
sollte denn die "Formel" aussehen, um die PWM mit werte 0-100, also mit 
Prozente zu steuern?

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Rudolf Bremer (Gast)

>Ich frage mich gerade selber warum ich 1024 benutze und nicht 255?!? Wie

Weil dein ADC 10 Bit Werte ausspuckt?

>sollte denn die "Formel" aussehen, um die PWM mit werte 0-100, also mit
>Prozente zu steuern?

Man kann auch mit 0..255 prozentual steuern. Halt /255 statt /100 
rechnen.

In deinem Beispiel geht das einfach mit
pwm1 = adcw2/4;

Aber warum einfach, wenns auch umständlich geht ;-)

MFG
Falk

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Falk:
So wie ich das verstanden habe (siehe OP) möchte er den Wertebereich von 
800...900 auf 0...100% abbilden (warum auch immer). Dementsprechend 
steht in adcw2 eine Zahl von 0 bis 100.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Johannes M. (johnny-m)

>So wie ich das verstanden habe (siehe OP) möchte er den Wertebereich von
>800...900 auf 0...100% abbilden (warum auch immer). Dementsprechend
>steht in adcw2 eine Zahl von 0 bis 100.

Und wozu dann die Multiplikation/Division?

MfG
Falk

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Falk Brunner wrote:
> Und wozu dann die Multiplikation/Division?
Um von einem Prozentwert (0...100) auf einen Wert für die PWM (0...1023) 
zu kommen. Oder wie würdest Du einen Wert aus einem bestimmten Bereich 
auf einen anderen Wertebereich skalieren? Ist im Prinzip nichts anderes 
als ne Multiplikation mit 10,24 (wobei 10,23 richtiger wäre...).

Dass er nur eine 8-Bit-PWM hat und dementsprechend die PWM-Werte nur 
0...255 werden können, hat er ja mittlerweile gemerkt.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ahhhh, klar. Manchmal ist die Leitung lang . . . ;-)

Autor: Random ... (thorstendb) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Am besten ist es, ganz auf divisionen zu verzichten. Als einer der 
ersten µCs habe die ARM Cortex-M3 ne Divisionseinheit drin, bzw. nen 
Befehl, der dies in einer Hand voll Takten realisiert.

Shifts sind da viel effektiver, da sie schneller abgearbeitet werden 
können (bei ARM z.B. brauchen alle shifts (0...31) nur einen Takt 
braucht). Wie schnell der AVR das schafft, weiss ich nicht, aber 
sicherlich schneller als ne Division.

"In deinem Beispiel geht das einfach mit"
pwm1 = adcw2 >> 2;

Bleibt man in seiner software auf werten zur basis 2 (also z.B. 0...1023 
adc, 0...255 pwm etc) kann man sehr effektiv mit shifts arbeiten.

Für ein User Interface kann man dann ja die prozente einbauen ...


Greetz,
/r.


Hab noch was nettes:
void led(char value);

led_out(1<<(i=++i<8?i:0));

led_out((0xff>>(0x08-adc/0x7f))&0xff);

led() nimmt 8bit werte entgegen und shiftet die an pos. 8 eines 32Bit 
GPIOs. Was machen die funktion in den klammern? ;-)
Bei der zweiten ist absichtlich kein (>>7) drin, da ich sonst nicht an 
den Endbereich rankomme (siehe beitrag oben) für die Anzeige. adc sind 
10 bit.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Random ... wrote:
>
> "In deinem Beispiel geht das einfach mit"
>
> pwm1 = adcw2 >> 2;
> 
>

Du kannst davon ausgehen, dass die Compilerbauer auch keine
Vollidioten sind und eine Division durch 4 durch einen Shift
ersetzen, so er denn auf der Zielhardware schneller abläuft
als eine Division.

> Bleibt man in seiner software auf werten zur basis 2 (also z.B. 0...1023
> adc, 0...255 pwm etc) kann man sehr effektiv mit shifts arbeiten.

Oh, man kann auch mit anderen Werten und Operationen sehr effektiv
arbeiten. Zb. kann der gcc für eine Reihe von Multiplikationen
sehr effektiv ein paar Shifts und Additionen einsetzen um sich
eine Multiplikation zu ersparen. zb. eine Multiplikation mit 3
oder mit 10 oder ...

Wie gesagt: Compilerbauer sind keine Vollidioten und kennen meistens
den Prozessor sehr viel besser als die meisten Anwendungsprogrammierer.

Für den Programmierer aber gilt:
Schreib den Code so, wie er am klarsten ist. Diese Low-Level
Pfriemeleien überlasse dem Compiler. Der kann das nämlich besser.

> Hab noch was nettes:
> led_out(1<<(i=++i<8?i:0));

undefined bahaviour und damit illegales C

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Random:
Man kann alles übertreiben! Eine Division durch eine Zweierpotenz hat 
der Compiler automatisch durch entsprechende Alternativen zu ersetzen, 
oder er taugt nichts. Der Anwender sollte sich nur im Klaren sein, dass 
eine Division durch 2^n (bzw. ein %2^n) i.d.R. wesentlich einfacher 
durch andere Operationen ersetzt werden kann und dass er deshalb 
versucht, durch geschickte Kombinationen mit Multiplikationen dafür 
sorgt, dass es möglichst mit /2^n geht.

Aber es geht nunmal nicht immer und dementsprechend kann man Divisionen 
durch andere Zahlen nicht immer vermeiden. Deshalb sollte man erstens 
solche Divisionen nicht generell verteufeln und außerdem nicht mit 
praktisch nicht mehr lesbarem Code rumprotzen, den man auch so schreiben 
kann, dass man ihn ohne Papier und Stift entziffern kann.

Der Compiler ist u.a. dazu da, dem Programmierer zumindest einen Teil 
der Drecksarbeit abzunehmen.

Autor: random (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
oha, das gab haue duck

>> Hab noch was nettes:
>> led_out(1<<(i=++i<8?i:0));

>undefined bahaviour und damit illegales C

warum?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
random wrote:
> oha, das gab haue *duck*
>
>>> Hab noch was nettes:
>>> led_out(1<<(i=++i<8?i:0));
>
>>undefined bahaviour und damit illegales C
>
> warum?

Weil du zwischen 2 Sequence Points (in diesem Fall ist ein
Sequence Point: von einem ; zum nächsten ;) nur einmal
schreibend auf eine Variable zugegriffen werden darf.
In dem Moment in dem in einer Expression links vom =
eine Variable vorkommt, die rechts noch mal mit einem ++
steht -> undefined behaviour.

Aus dem selben Grund:

  printf( "%d %d", ++i, ++i );    <-- undefined behaviour
  i = ++i;                        <-- undefined bahaviour
  foo( ++i, ++i );                <-- undefined behaviour

das sind so die häufigsten Fälle.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Frage. Erkennen die Compiler sowas und schmeissen wenigstens ne Warnung?

Das ist einer der Gründe, warum ich mich für C nicht wirklich begeistern 
kann. "Aktive" Anweisungen in Abfragen und Funktionsaufrufen. Lang lebe 
Pascal! Naja, eher R.I.P. :-(

MFG
Falk

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Falk Brunner wrote:
> Frage. Erkennen die Compiler sowas und schmeissen wenigstens ne Warnung?

Ich kenn keinen.
Wohl auch deshalb nicht, weil sowas nicht in allen Fällen
detektierbar ist. Ich denke da an Übergabe in Funktionen
hinein, in der dann 2 verschiedene Argumente letztendlich
bei ein und derselben Variablen landen.

Grob skizziert

void foo( int* a, int* b )
{
  *a = (*b)++;
}

void bar()
{
  int i;
  foo( &i, &i );
}

Autor: Random ... (thorstendb) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
es gibt aber doch eine reihenfolge, in der operatoren abgearbeitet 
werden. Und in ASM werden doch eh einzelne operationen draus, je nach 
architektur.

> printf( "%d %d", ++i, ++i );    <-- undefined behaviour
> foo( ++i, ++i );                <-- undefined behaviour

Bei den beiden ist es klar, weil glaub ich nciht festgelegt ist, welcher 
der beiden parameter zuerst übergeben wird. So was macht man ja auch 
nicht :-)

Für mein Beispiel sollte aber klar geregelt sein, dass erst das ++i 
erfolgt (i++ hat natürlich eine andere wirkung), und danach die andere 
Zuweisung, egal, wie man es dreht.

> led_out(1<<(i=++i<8?i:0));

also:
i++;
if(i>8) i=0;
led_out(i);

auch die entscheidung, ob i<8 wird vorher getroffen, da ++i ja 
ausdrücklich sagt, dass i erst erhöht wird, bevor es gelesen wird für 
weitere auswertung...

Ist zwar jetzt OT, aber es interessiert mich trotzdem :-)

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Random ... wrote:
> es gibt aber doch eine reihenfolge, in der operatoren abgearbeitet
> werden.

Das hat nichts damit zu tun, in welcher Reihenfolge die
Operatoren dann tatsächlich ausgeführt werden.

Bei einem ++ gibt es nur eine Zusage:
Das Inkrement wird irgendwann vor dem nächsten Sequence Point
durchgeführt. Irgendwann. Das kann auch die allerletzte Aktion
vor dem ';' (dem nächsten Sequence Point) sein.
Der Compiler hat alle Freiheiten die Aktion so umzustellen,
wie ihm das in den Kram passt. Der eigentliche Inkrement ist
ein Nebeneffekt und von dem wird nur gefordert, dass er
beim nächsten Sequence Point abgeschlossen sein muss.

> Und in ASM werden doch eh einzelne operationen draus, je nach
> architektur.

Spielt keine Rolle. C definiert sich nicht dadurch welche
Assembler Befehle da raus kommen.

>
>> printf( "%d %d", ++i, ++i );    <-- undefined behaviour
>> foo( ++i, ++i );                <-- undefined behaviour
>
> Bei den beiden ist es klar, weil glaub ich nciht festgelegt ist, welcher
> der beiden parameter zuerst übergeben wird.

Im C Jargon heist das:
Die Reihenfolge der Auswertung von Funktionsargumenten ist
undefiniert. Mit der Übergabe hat das erst mal nichts zu tun.
Vor dem eigentlichen Funktionsaufruf sitzt ein Sequence Point.
Wenn die Funktion dann aufgerufen wird, müssen alle Nebeneffekte
abgeschlossen sein.

> So was macht man ja auch
> nicht :-)

:-) Geh mal nach comp.lang.c-c++
Da kommt sowas öfter vor als man denkt.

>
> Für mein Beispiel sollte aber klar geregelt sein, dass erst das ++i
> erfolgt (i++ hat natürlich eine andere wirkung), und danach die andere
> Zuweisung, egal, wie man es dreht.

Ist es nicht.

> i++;
> if(i>8) i=0;
> led_out(i);

Du verwechselst Operatorenreihung mit Auswertereihenfolge.
An ersterem kann der Compiler nichts drehen, die ist definiert.
Aber an letzterem kann der Compiler rumdrehen wie er lustig
ist. Wenn der Compiler beschliest, den inkrement erst ganz
zum Schluss zu machen (weil ihm dann die Register besser in
den Kram passen) dann ist das zulässig. Auch sagt kein Mensch,
dass nach dem Inkrement die Variable i sofort zurückgeschrieben
werden muss. Das Ergebnis von i++ (also der neue, erhöhte Wert)
kann auch nach dem led_out(i) an die Variable i im Speicher
zugewiesen werden. Oder irgendwo dazwischen.

Es ist daher völlig zulässig, dass Folgendes passiert.

  i habe den Wert 8

  i++            i wird erhöht, das Zwischenergebnis, 9,
                 wird in einem Register gehalten und der
                 Compiler verschiebt den Update der Variablen
                 nach hinten

  if( i >  8 ) i = 0       i, bzw. das gecachte Zwischenergebnis 9
                           ist größer als 8, daher wird i zu 0.
                           Der Compiler erfüllt auch gleich seine
                           Pflicht und schreibt die 0 in den
                           Speicher zurück.

  led_out(i)         0 wird ausgegeben

                und jetzt ist noch die Rückspeicherung des Inkrements,
                nämlich der 9 ausständig. Also macht der Compiler das
                mal.

                Neuer Wert für i: 9

Autsch!

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moral von der Geschichte:
Nicht zuviel in ein Statement packen. Das führt sehr schnell
zu unliebsamen Effekten.
Auf eine Variable nur einmel schreibend zugreifen. Wenn das
geschieht, darf das dann auch nur der einzige Zugriff auf die
Variable sein.

Selbst ein
  j = i + i++;
hat schon undefiniertes Verhalten.

Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jetzt bin ich etwas durcheinander....Ich bin nich soooo helle in Mathe 
und in Programieren schon garnicht.
Was ich machen will ist: An ein ADC ist ein NTC angeschlossen, die Werte 
die  ich benötige sind zw. 100 und 175. Also, müßten die Werte 0-75 
0-100% representieren und, diese wieder rumm 0-255. Jetzt ist es etwas 
anderes, ich benutze den ADC in 8 bit ( (1<<ADLAR) )also müsste das 
ganze etwas einfacher sein, aber ich komme nicht drauf.

MFG

Autor: Michael Wilhelm (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Messwert = Messwert - 100;  // Offset weg
Messwert = Messwert * 1.34; // 75 ergibt nun 100
Messwert = Messwert * 2.55; // Maximalwert ist nun 255

so in etwa?

MW

Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, so in etwa, was für Variablen soll ich den nnehmen, int16? oder 
reichen int8 (nee oder)?

Autor: Michael Wilhelm (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
unsigned char

MW

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Aber du musst da nicht mit Gleitkomma Operationen rumschmeissen.

Das ganze ist eine Variante der in der Schule beliebten
Apfel-Rechnerei:
10 Äpfel kosten 12 Mark, wieviel kosten 7 Äpfel?

   10 ....... 12
    7 .......  x
  ---------------

( Zahl über x, mal Stumpf durch Spitz )

       12 * 7
  x = --------
         10

Der klassische Dreisatz


Den ADC Wert im Bereich von 100 bis 175 hast du durch die
Subtraktion von 100 schon mal in den Bereich 0 bis 75 gebracht.

Also:
Ein Eingangswert von 75 soll einem Ausgangswert von 255 entsprechen.
Welcher Ausgangswert ergibt sich daher für einen Eingangswert
von ADC


   75   .....   255
  ADC   .....    x
 -------------------

        255 * ADC
   x = -----------
           75

Autor: Rudolf Bremer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Genau, das haut jetzt alles hinn. Ich bedanke mich an alle. Bist zum 
nächsten mall :)

MFG

Autor: Michael Wilhelm (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Karl-Heinz,
nur zur Information:
meine Lösung 724 Takte
deine Lösung 234 Takte

MW

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Michael Wilhelm wrote:
> nur zur Information:
> meine Lösung 724 Takte
...und die Einbindung der kompletten float-Library (vorausgesetzt, die 
wird im restlichen Programm nirgends gebraucht), also u.U. ein paar KiB 
mehr Code...

Außerdem geht das ganze nicht wie Du schreibst in unsigned char ...

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.