Forum: Projekte & Code schnelle Wandlung long -> ASCII


von Michael (Gast)


Angehängte Dateien:

Lesenswert?

Die Routine l2a(long l, char *s) wandelt 'l' in einen ASCII-String
's'. Zur Wandlung werden keine Divisionen benötigt, sondern
Subtraktionen, was die Ausführung erheblich beschleunigt.
Mit einem optimierenden Compiler, der die Variablen in Registern hält,
wird die Zahl 1999999999 in weniger als 100µs gewandelt (AVR mit
16MHz).
Auf 'int' abgemagert werden Codegröße reduziert und
Ausführungs-geschwindigkeit gesteigert.

Entsprechend angepaßt lassen sich auch Mantissen von 'float' sehr
schnell wandeln.

von H.Joachim S. (crazyhorse)


Lesenswert?

Alt, aber gut :-)
Im schlechtesten Fall (1999999999) rund 5600 Takte statt rund 23.000 
(ltoa), ansonsten je nach Wert auch deutlich schneller, während ltoa 
immer in etwa gleich lang braucht.

Compiler CodeVision 3.12

von m.n. (Gast)


Angehängte Dateien:

Lesenswert?

Dann hat es ja doch mal jemand brauchen können ;-)

Auch, wenn bei Dir die Taktzyklen von 23000 auf 5600 um den Faktor 4 
gesunken sind, ist da noch viel Luft zur Beschleunigung. Die Routine 
brauchte damals mit einem anderen Compiler < 1600 Taktzyklen.

Aus Neugier habe ich den Quelltext etwas modifiziert und mit einem 
Arduino R3 (ATmega328 mit 16 MHz) getestet und dabei auch besser 
formatiert. Die Endung .ino soll nicht stören, es ist einfacher C-Code.
Die Durchlaufzeit für eine Wandlung beträgt <= 135 µs, was umgerechnet 
<= 2160 Taktzyklen eines AVR entspricht.
Beschleunigend bei dem Code wirkt sich aus, die Tabelle ausserhalb der 
Wandelroutine global anzulegen. Damit sind die Zugriffe je nach Compiler 
etwas schneller.

Falls man nur 16-Bit Werte wandeln möchte:
Die Ausführungszeit von l2a() für die Wandlung von 32767 beträgt 59 µs.
Eine auf int16_t reduzierte Routine braucht für 32767 rund 45 µs.

von m.n. (Gast)


Lesenswert?

Kleine Ergänzung: auf einem STM32F407 mit 168 MHz braucht die gezeigte 
Routine aus l2a.ino <= 5 µs Ausführungszeit.
Augen auf bei der Prozessorwahl ;-)

von H.Joachim S. (crazyhorse)


Lesenswert?

liegt wohl hauptsächlich daran, dass CodeVision prinzipiell keine 
long-Variablen in Registern hält. Ich habe auch nicht weiteren 
Laufzeitverbesserungen gesucht, da ginge sicher noch was.
z.B 2.Tabelle mit 500.0000.000,...
Das wäre dann jeweils ein zus. Vergleich und und könnte bei Ziffer 9 4 
Subtraktionen sparen, max. Laufzeit würde also gesenkt.
Vielleicht auch schauen, ob die Restzahl schon in uint16 passt und dann 
nur noch mit 16bit weiter arbeiten.

Mir reicht es so wie es ist, Konvertierung ist schneller als das 
tatsächliche Senden mit 115kBaud, also lückenlose Ausgabe mehrerer 
long-Zahlen.

von m.n. (Gast)


Lesenswert?

H.Joachim S. schrieb:
> Mir reicht es so wie es ist, Konvertierung ist schneller als das
> tatsächliche Senden mit 115kBaud, also lückenlose Ausgabe mehrerer
> long-Zahlen.

Dann ist ja gut. Entscheidener als eine schnellere Wandlung ist, daß im 
Gegensatz zu ltoa() aus der Lib jede erzeugte Ziffer sofort zur 
Verfügung steht. Selbst, wenn die Wandlung schneller wäre, müßte der µC 
wieder auf die UART warten.
Alternativ könnte man einen Ausgabepuffer verwenden und die Ausgabe per 
ISR erledigen. Aber das weißt Du sicherlich selber.

von H.Joachim S. (crazyhorse)


Lesenswert?

Ich habe ja den Tx-Buffer, einen weiteren wollte ich eben nicht 
verwenden und der hätte auch mein eigentliches Problem (Buszeit RS485) 
nicht gelöst. Master fragt die Werte ab und der slave liefert nun ohne 
Kunstpausen zwischen den einzelnen Zählerwerten.

von Nico W. (nico_w)


Lesenswert?

Ähnlich wird das ganze bei Teacup gemacht. Anstatt den String zu 
speichern geht das hier nur direkt an die Ausgabe. (Display oder Serial)

Int, hex und fixpoint sind auch implementiert. Vielleicht hilft es ja.
https://github.com/Traumflug/Teacup_Firmware/blob/master/msg.c
1
/// list of powers of ten, used for dividing down decimal numbers for sending, and also for our crude floating point algorithm
2
const uint32_t powers[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
3
4
/** write decimal digits from a long unsigned int
5
  \param v number to send
6
*/
7
void write_uint32(void (*writechar)(uint8_t), uint32_t v) {
8
  uint8_t e, t;
9
10
  for (e = 9; e > 0; e--) {
11
    if (v >= powers[e])
12
      break;
13
  }
14
15
  do
16
  {
17
    for (t = 0; v >= powers[e]; v -= powers[e], t++);
18
    writechar(t + '0');
19
  }
20
  while (e--);
21
}

von m.n. (Gast)


Angehängte Dateien:

Lesenswert?

Nico W. schrieb:
> Ähnlich wird das ganze bei Teacup gemacht.

Nicht schlecht, zumal hier die Optimierungen neuerer Compiler wirksam 
werden. Seinerzeit (alter IAR H8-Compiler) mußte ich noch eine 
'temp'-Variable verwenden, damit die Zugriffe auf die Tabelle nicht zur 
Bremse wurden.

Die angepasste Routine mit Arduino UNO R3 getestet braucht für die 
Wandlung in einen String jetzt <= 83 µs. Auch beim STM32F407 (s.o.) 
sinkt der Zeitbedarf von <= 5 µs auf <= 3,4 µs.

von Michael B. (laberkopp)


Angehängte Dateien:

Lesenswert?

H.Joachim S. schrieb:
> Alt, aber gut :-)
> Im schlechtesten Fall (1999999999) rund 5600 Takte statt rund 23.000
> (ltoa), ansonsten je nach Wert auch deutlich schneller, während ltoa
> immer in etwa gleich lang braucht.

Wenn's schnell sein soll: 936 Takte = 58us@16MHz (für 1999999999), bei 
mehr Speicherbedarf.

von Michael B. (laberkopp)


Angehängte Dateien:

Lesenswert?

Ok, man sollte den code nehmen, der erfolgreich kompiliert wird.

von asdfasd (Gast)


Lesenswert?

Die Variante von Michael gefällt mir am besten.

Ein Fehler ist drin: bei 0x80000000 gibt's nen Absturz in der 
Vorzeichenunterdrückung (im 2er-Komplement gibt's zwei Zahlen bei den 
x==-x gilt).

Eine mögliche Optimierung: die digit Tabelle wird nicht wirklich 
benötigt, da die Reihenfolge immer 8, 4, 2, 1, 8, 4 ... ist.
Ein
1
t >>= 1; if (t == 0) /* stellenwechsel */ { t=8; ... }
 würde reichen.

von Michael B. (laberkopp)


Angehängte Dateien:

Lesenswert?

asdfasd schrieb:
> Eine mögliche Optimierung

Tja, macht es auch komplexer, aber sparte weitere 16 Takte.

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.