Forum: Mikrocontroller und Digitale Elektronik [t2313] 16bit Wertebereich auf 8bit skalieren


von J. W. (jw-lighting)


Lesenswert?

Hallo,

ansich sollte das nicht weiter kompliziert sein - trotzdem komme ich 
grad nicht weiter.

Ich messe über die Dauer einer C-Entladung einen Potiwert.
Die Entladung wird in der Timer1-OVF-ISR des ATtiny2313 gestartet.
Sinkt die Spannung am C unter die 0.25V externe Referenz des AC löse ich 
damit ein Capture Event für den Timer1 aus.

In der Capture-ISR habe ich den Wert aus ICR1 ausgelesen und mir als PWM 
ausgeben lassen. Aus der Pulsbreite für linken und rechten Potianschlag 
habe ich errechnet, dass der Wertebereich den ICR1 annimmt zwischen 
10500 und 35400 (mit etwas Luft) liegen muss.
Diese Grenzen möchte ich nun auf den Bereich 0-255 (also einen uint8_t) 
skalieren.

Dazu habe ich mir überlegt, zunächst den unteren Grenzwert (10500) 
abzuziehen. Die dann verbleibenden Werte zw. 0 und 24900 teile ich dann 
durch einen Faktor x, den ich wie folgt berechnet habe:

24900 / x = 255
24900 = 255 * x
24900 / 255 = x
x = 97,64 ~= 98
1
// ..
2
3
volatile uint8_t icr_8_value = 0;
4
volatile uint16_t icr_value = 0;
5
6
// ...
7
8
ISR(TIMER1_CAPT_vect){ // triggered via ACO change, used for timenet
9
  
10
  // Catch value
11
  icr_value = ICR1;
12
  
13
  icr_8_value = (uint8_t) ( (icr_value - 10500) / 98 );
14
  
15
}

Rauskommen tuen in 4 Stufen abgestufte Werte, also nicht das gewünschte 
Ergebnis. Das ich den tiny2313 mit der Division schon ziemlich fordere 
und vermutlich genau da das Problem liegt ist mir bewusst. Das sollte 
auch noch optimiert werden.

Ist mein grundsätzlicher Einsatz zur Werteskalierung richtig? Was kann 
ich verbessern, um auf fliessende 8-bit Werte zu kommen? Kurz: Wo ist 
mein Fehler?

LG :)

von Alex S. (thor368)


Lesenswert?

> Ist mein grundsätzlicher Einsatz zur Werteskalierung richtig? Was kann
> ich verbessern, um auf fliessende 8-bit Werte zu kommen? Kurz: Wo ist
> mein Fehler?
Ja, grundsetzlich schon. In der Praxis sieht das ganze aber wie du schon 
richtig erkannt hast noch mal anders aus.

Um das definitiv sagen zu können fehlen Infos:
Was ist der Prozessortakt? Wie oft tritt timer1capt auf?

Du wirst in interrupts Abstand von Divisionen nehmen müssen. Das dauert 
ewig. Da du einen tiny hast, welcher wiederum keine Multiplikatoreinheit 
besitzt und du 16bit Zahlen multiplizieren musst wird schon das allein 
sehr lange dauern.
Das einzige, das sehr schnell geht, sind 2er Potenzen bzw 
Multiplikationen und Divisionen mit dem Faktor 2. Das lässt sich einfach 
durch rechts und links schieben realisieren. Es ist also ein bisschen 
Hinrschmalz gefordert.

Ich habe mal ein bisschen gerechnet und die beste Näherung ist folgende:

y = (x shr 2)*5 shr 7

Thor

von J. W. (jw-lighting)


Lesenswert?

Hallo Alex,

vielen Dank für deine Hilfe.

> Um das definitiv sagen zu können fehlen Infos:
> Was ist der Prozessortakt? Wie oft tritt timer1capt auf?
Takt kommt von einem 8Mhz Quarz. Das Capture Event tritt mit ungefähr 
nach je ca. 4ms auf. Während man am Poti dreht weicht das dann ein wenig 
ab.
Messen tu ich grundsätzlich mit 250Hz


> Du wirst in interrupts Abstand von Divisionen nehmen müssen. Das dauert
> ewig. Da du einen tiny hast, welcher wiederum keine Multiplikatoreinheit
> besitzt und du 16bit Zahlen multiplizieren musst wird schon das allein
> sehr lange dauern.
Damit habe ich bereits gerechnet. Division und Multiplikation wollte ich 
daher ja auch vermeiden. Mit 2er Potenzen kommt man bei dem Wertebereich 
leider nicht weit. Ich hätte aber auch kein Problem, den Wert in der ISR 
nur auszulesen und in der Mainschleife umzurechnen.

> Das einzige, das sehr schnell geht, sind 2er Potenzen bzw
> Multiplikationen und Divisionen mit dem Faktor 2. Das lässt sich einfach
> durch rechts und links schieben realisieren. Es ist also ein bisschen
> Hinrschmalz gefordert.
Habe ich auch schon probiert, aber auch nicht mit besserem Ergebniss.

> y = (x shr 2)*5 shr 7
y ist 8bit, x ist 16bit? Ich probiers mal aus. ;)

Edit:
1
icr_8_value = (uint8_t) (((icr_value >> 2) * 5) >> 7);
Bring 5 Abstufungen.

Nochmals danke, und LG :)

von Alex S. (thor368)


Lesenswert?

> Messen tu ich grundsätzlich mit 250Hz
Mhhhh, dann hast du für die Aufgabe 32000 Takte Zeit. Das reicht auf 
jeden Fall. Bist du dir sicher, dass der interrupt auch sicher nicht 
mehrmals/öfter auftritt?
Ansosnten liegt das Problem nicht in der Rechenzeit. Wie sieht es denn 
mit deiner stack Ausnutzung aus?

Thor

von Bernd (Gast)


Lesenswert?

y = (x shr 2)*5 shr 7

icr_8_value = (uint8_t) ((icr_value >> 2) * 5) >> 7
liefert 128 für 10500
und     345 für 35400

8 Bit ???

zieht man noch 128 ab, gibts 0...217. OK.


Einfacher, aber kaum ungenauer:

icr_8_value = (uint8_t) ((icr_value >> 7) - 82);

liefert 0 für 10500
und   194 für 35400

von Alex S. (thor368)


Lesenswert?

Grrks, habe natürlich die Subtraktion vergessen. facepalm
Also:

y = ((x - 10500) shr 2)*5 shr 7

Thor

von J. W. (jw-lighting)


Lesenswert?

Hmm. Habe den Wertebereich gerade nochmal gecheckt (Werte abfragen und 
LED toggeln...). Anscheinend habe ich mich verrechnet: Schient eher 
zwischen >0 und <550 zu liegen. Weshalb, dass kann ich gerade noch nicht 
nach vollziehen.
Ich hatte die Pulsbreiten vom Oszi abgelesen die 65.535 mal DutyCycle 
(<1) genommen und gehofft so auf den richtigen Wert zu kommmen.....
Kanns dran liegen, dass ich den Timer auch noch fürs PWM nutze?

Momentan scheint ein shr 3 gut zu passen, damit bleibe ich leider in 
einem recht tiefen Wertebereich.

Das erklärt gerade eine ganze Menge.

> Bist du dir sicher, dass der interrupt auch sicher nicht
> mehrmals/öfter auftritt?
> Ansosnten liegt das Problem nicht in der Rechenzeit. Wie sieht es denn
> mit deiner stack Ausnutzung aus?
Das der nicht öfter auftritt ist sehr sicher. Zumindest wüsste ich 
nicht, woher sich der C aufladen sollte wenn ichs nicht im OVF tue...
Wie krieg ich das mit dem Stack raus? Passt da nicht der GCC für auf?

Danke euch nochmal für die Hilfe und entschuldigt meinen Fehler...

LG :)

Habs jetzt so, passt ungefähr:
1
ISR(TIMER1_CAPT_vect){ // triggered via ACO change, used for timenet
2
  
3
  // Catch value
4
  uint16_t icr_value = ICR1;
5
  
6
  if(icr_value > 550){
7
    icr_value = 550;
8
  }
9
  
10
  SPEED = (uint8_t) (((uint16_t) icr_value - 40) >> 2); // 550 - 40 < 512 >> 2 -> fits into 8bit
11
  
12
}

von Alex S. (thor368)


Lesenswert?

Sicher um zwei nach rechts? Dann kiegst du bei 550 aber nur noch 7 bit 
Auflösung.

Thor

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

J. W. schrieb:

> Ist mein grundsätzlicher Einsatz zur Werteskalierung richtig?

Das hängt auch von deiner Schaltung ab. Du misst eine Zeit t und willst 
daraus einen Widerstand berechnen, d.h. du hast eine Funktion

  R = R(t)

Ist R linear, dann ist eine lineare Skalierung wie vorgeschlagen 
sinnvoll.

Ist R jedoch nicht linear (abhängig von deiner Schaltung) und hat R z.B. 
logarithmische Anteile, dann wirfst du durch eine lineare Skalierung 
evtl. die interessanten Bereiche weg, weil die Funktion nicht überall 
die gleiche Steigung hat.

Zunächst wäre also zu klären, wie R(t) aussieht.

von J. W. (jw-lighting)


Lesenswert?

Alex S. schrieb:
> Sicher um zwei nach rechts? Dann kiegst du bei 550 aber nur noch 7 bit
> Auflösung.

Hey, du bist echt ne Hilfe. :) Hatte nen Denkfehler drin. Maximalwert 
bei 10bit ist ja 1023 und nicht 512 :p. Also nur 1 nach rechts. Du hast 
vollkommen recht.

> Ist R linear, dann ist eine lineare Skalierung wie vorgeschlagen
> sinnvoll.

R ist ein linieares 10k-Poti. Lineare Skalierung ist also sinnvoll, 
nicht nur aus aus Gründen der Rechenzeit ;)

Danke nochmals..
LG :)

von Alex S. (thor368)


Lesenswert?

> Hey, du bist echt ne Hilfe. :)
Weiß ich :P

Ein Tipp noch:

Ergänze deinen range check noch zur Sicherheit. Sonst gibts irgendwann 
eine böse Überraschung:
1
ISR(TIMER1_CAPT_vect){ // triggered via ACO change, used for timenet
2
  
3
  // Catch value
4
  uint16_t icr_value = ICR1;
5
  
6
  if(icr_value >= 552)  // overflow check
7
    SPEED = 255;
8
  else if(icr_value <= 40)  // underflow check
9
    SPEED = 0;
10
  else
11
    SPEED = (uint8_t) (((uint16_t) icr_value - 40) >> 1); // 550 - 40 < 512 >> 2 -> fits into 8bit
12
  
13
}

von J. W. (jw-lighting)


Lesenswert?

> Weiß ich :P
Und ich kann dich da nochmals bestätigen :p

An den Underflow hatte ich nämlich nicht gedacht, und direkt den Wert 
für SPEED (GPIOR2) zu schreiben, statt noch zu rechnen ist auch schlau.

Ich für meinen Teil mache nach ca. 10h PC-glotzen jetzt mal ne Pause - 
das wird ja langsam peinlich hier :p

Dank again, LG :)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

J. W. schrieb:
> Alex S. schrieb:
>> Ist R linear, dann ist eine lineare Skalierung wie vorgeschlagen
>> sinnvoll.
>
> R ist ein linieares 10k-Poti.

Das war nicht die Frage.

Die Frage ist, ob R linear mit der gemessenen Zeit t geht. Das ist 
unabhängig davon, ob das Poti linear oder logarithmisch oder sonstwas 
von einer Winkel anhängt. Letzteres ist ja nur ne mechanische 
Eigenschaft, spiegelt sich aber nicht in R(t) wieder.

von Alex S. (thor368)


Lesenswert?

Tach Johann,

du hast natürlich recht. Die RC Ladekurve ist natürlich logarytmisch. 
Das sollte man berücksichtigen. Ein logarytmisches Poti wäre hier von 
Vorteil gewesen.

Man hat aber auch genügend Rechenzeit über um das in software zu tun.

Thor

von J. W. (jw-lighting)


Lesenswert?

Ah, jetzt verstehe ich. Ich habe die ausgelesenen Werte mittlerweile in 
eine Geschwindigkeitssteuerung eingearbeitet. Ebenfalls habe ich sie 
wieder testweise über PWM "ausgegeben". Das Tastverhältnis ändert sich 
durchaus linear, zumindest macht es für mich auf dem Oszi den Anschein, 
dass das weitgehend Linear ist.
Auch die Geschwindigkeitssteuerung ist damit recht intuitiv - ich bin 
zufrieden.

LG :)

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.