PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Geschickte Beschleunigung von kritischem Code?



thewulf00
27.04.2009, 09:10
Hallo Leute,

wir haben gestern unsere (PAL-)Kamera zum Laufen gebracht und schaffen es nun tatsächlich, ganze 80 Pixel pro Zeile zu lesen (bei aktuell 16MHz). Das ist mehr als doppelt so viel, wie anfangs ging, und das nur mit Code-Optimierungen.

Nun Frage ich mich, aber bin leider mit meinem Latein bzw. C am Ende, wie man das Einlesen noch schneller ablaufen lassen könnte. Inline-Assembler wäre auch eine gute Möglichkeit, aber ich habe einfach zu wenig Ahnung von Assembler in Kombination mit C.

Das Problem ist, dass die Pixel in ein Array/einen Puffer müssen, damit ich alle Pixel nach dem Einlesen auch verarbeiten kann.

Der aktuelle, stark optimierte Code zum Einlesen einer Zeile sieht nun so aus:
(Falls sich jemand fragt, wie wir das Zeilenende erkennen: Wie messen mit dem Oszi nach, wie lang PD0 high ist, und legen das getriggert über das Signal der Kamera, dann sehen wir, ob PD0 länger high ist, als die Zeile dauert. Dann wird der Wert MAX_X angepasst, bis es knapp vor dem Sync endet.)


#define MAX_X 80
volatile uint8_t global_cam_pic[MAX_X+1];

static inline void read_cam_line()
{
register uint8_t cnt asm("r3");

PORTD |= _BV(PD0);

cnt = MAX_X;
do
{
global_cam_pic[cnt]=ADCH;
}while (cnt--);

PORTD &= ~_BV(PD0);

}

Ich hatte auch schon ein paar Alternativen probiert, z.B. mit Pointern, aber die sind ja gleich alle 16bit und deshalb dauert es dann wesentlich länger.

Eine andere Idee war es, das Array irgendwie an den Anfang des RAMs legen zu können, um dann einfach von MAX_X auf Null runter zu schreiben, damit dann die while-Schleife wieder auf Null prüfen kann. (was mit einem ASM-Befehl geht, anstatt zweien)

Hat noch jemand Optimierungsideen?

Weitere Frage: Da ich den internen ADC benutze: Weiß jemand, wie man ihn gescheit übertakten kann, so dass man noch 8bit hat, und trotzdem >1.6MHz lesen kann?

thewulf00
27.04.2009, 09:27
Komisch ist, dass dieser Thread garnicht in der Liste der letzten Threads auftaucht. Bestimmt ein INSERT verloren gegangen. Mal sehen, obs dieser Push-Post ändern kann... :-)

p_mork
27.04.2009, 15:11
Hallo thewulf00!



register uint8_t cnt asm("r3");


Ich bin mir da zwar nicht 100% sicher, aber ich glaube dass das voll nach hinten losgeht, weil cnt dann wirklich als Variable vorhanden sein muss. 'register' sollte man generell nicht benutzen, es sei denn man weiss genau was man tut.



do
{
global_cam_pic[cnt]=ADCH;
}while (cnt--);


Hier wird 81 mal eingelesen und nicht 80.



Ich hatte auch schon ein paar Alternativen probiert, z.B. mit Pointern, aber die sind ja gleich alle 16bit und deshalb dauert es dann wesentlich länger.


Um den Wert aus dem Array zu laden, werden intern sowieso Pointer verwendet, insoweit wundert es micht, dass es länger dauert. Kannst Du bitte den genauen Code posten?

Mein eigener Vorschlag wäre sowas hier:


define MAX_X 80

volatile uint8_t global_cam_pic[MAX_X];

static inline void read_cam_line()
{
PORTD |= _BV(PD0);

uint8_t *p = global_cam_pic + MAX_X;

do
{
*--p = ADCH;
}while (p > global_cam_pic);

PORTD &= ~_BV(PD0);

}

Wenn es vom Compiler so übersetzt wird wie man es als Mensch machen würde, würde man pro Pixel nur 7 Takte benötigen.

MfG Mark

Besserwessi
27.04.2009, 16:26
Das einfachste und bste wäre hier wohl wirklich inline ASM. SOnt müßte man mal sehen was denn GCC bis jetzt aus dem Code macht. Also einfachmal den entsprechenden abschnitt aus dem .lst File zeigen.
Man muß aber auch noch sehen wie man das mit dem AD wandler syncronisiert. Sonst hat man nur jeden AD wert 10 mal im Array, was auch nicht wirklich weiter hilft.

radbruch
27.04.2009, 18:42
Zum Thema höhere Sampleraten des ADCs:
https://www.roboternetz.de/phpBB2/viewtopic.php?t=33070

Gruß

mic

SprinterSB
27.04.2009, 23:04
Was soll denn überhaupt die Zeile mit dem ADCH? Das liest doch den ADC viel zu schnell aus.

radbruch
28.04.2009, 00:43
Das verstehen die "Kollegen" vom mikrocontroller.net auch nicht:

https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=337162#337162

Ich weiß nicht ganz genau wie, aber es funktioniert.

Gruß

mic

[Edit]
Wenn der ADC fertig ist wartet er bis ADCH gelesen wurde und startet dann erneut. Ab jetzt läuft die Zeit. Bei Prescaler /2 bleiben ca. 26 Takte bis ein neuer Wert gemessen wurde und der alte Wert gespeichert sein muss. Ich bin jetzt zu faul zum Zählen, aber rein gefühlsmässig braucht meine do *pixelzeiger-Funktion wohl auch so um die30 Takte. Das würde dem ADC reichen ein neues Ergebniss bereitzustellen. Nur so ein Gedanke...

thewulf00
28.04.2009, 08:04
Danke für die vielen Antworten:
@p_mork: Mit dem Register kann ich schon umgehen, keine Angst. Ich habe mir genug Quellen dazu durchgelesen. Und diese Zeile bedeutet nichts anderes, als dass die Variable nicht im RAM zu finden ist, sondern in r3, d.h. in einem Register, so dass er bei einem Zugriff nicht aus dem RAM lesen oder hineinschreiben muss. Diese Zeile hat den Ablauf stark beschleunigt.
In den Quelle, die ich benutzt habe, ist angegeben, dass r3 vom GCC unbenutzt bleibt. Und selbst wenn es benutzt würde, außerhalb der Schleife ists mir egal.

Ja natürlich wird 81 mal gelesen. Durch meine Experimente hatte ich für diesen Post den Originalzustand wiederhergestellt und vergessen, dass das '--' vor dem Cnt stehen muss.

Ich hatte den selben Code benutzt wie Du, nur dass ich per Array-Zugriff die Adresse berechnet hatte, das macht aber keinen Unterschied:

uint8_t *p = &global_cam_pic[MAX_X];

Das komische ist, dass dieser Ablauf länger dauert, als der ohne Pointer. Deshalb bin ich ja so enttäuscht.


@Besserwessi:
Ich glaube auch, dass man mit Inline-Assembler noch was kitzeln kann.
Man könnte ja z.B. den Beginn des Arrays nehmen, und dann drauf los schreiben, ohne jedes Mal das High-Byte des RAMs mit setzen zu müssen. Die Startadresse des Puffers ist nämlich glücklicherweise auf 512. (Warum auch immer) Dann könnte man sogar über 200 Pixel schreiben, ohne das High-Byte des Pointers neu setzen zu müssen.

Fakt ist, dass ich tatsächlich 80 unterschiedliche Pixel bekomme. Ich habe das ja schon auf den PC übertragen und mir angeschaut. Genau, wie Radbruch sagt, keiner versteht so genau, warum, aber der ADC läuft schneller als im DB angegeben.


@sprinterSB: Siehe meinen Abschnitt an Besserwessi und Radbruchs Antwort: Ich lese de facto 80 unterschiedliche Bits in 50µs.


@radbruch: Danke, den Link habe ich gesucht.
Ja, das kann schon sein, aber das Problem ist, dass ich nicht denke, dass ich 26 Takte brauche. :-) Aber wie gehabt, wird wahrscheinlich einfach unten abgeschnitten.

Besserwessi
28.04.2009, 16:45
Mit inline ASM hätte man vor allem den Vorteil, dass mangenau abzählen kann wie viele Takte die Schleife braicht und dann alle 26 Takte (Das wäre die maximale Geschwindigkeit den ADC) einen Wert ausließt. Das Limit von 26 Takten könnte eventuell sogar der Compiler erreichen. In ASM kann man da auch noch gemütlich was anderes nebenbei machen oder auf das Ready Flag warten.

Ob der ADC bei maximaler Geschwingdigkeit 8( MHz ADC Clock satt der maximal vorgesehenen 1 MHz) noch was sinnvolles raus giebt ist aber einen andere Frage. Da der ADC mindestens 13 Takte braicht, sollte man also wenigstens rund 1,6 µs ja Wandlung brauchen, es sei denn da wäre ein Fehler im Datenblatt und eine Teiler von 1 wäre möglich.

radbruch
28.04.2009, 18:18
Hallo

Auf die 26 Prozessortakte komme ich ja über die 13 ADC-Takte bei prescaler /2. Das schafft man auch in C locker, die Frage ist eben, was man einliest wenn der ADC noch wandelt.

Erhält man die gültigen Bits die bisher schon gewandelt wurden, angefangen vom MSB? Und nach dem Lesen von ADCH wird die Wandlung neu gestartet? Dann würde eine Wandlung weniger als 13 Zyklen dauern und das wäre die Möglichkeit weniger als 10 Ergebnissbits zu wandeln. Das dies möglich ist wird im Datenblatt erwähnt aber nicht näher beschrieben (Startbeitrag in obrigem Sampleratenthread (https://www.roboternetz.de/phpBB2/viewtopic.php?t=33070)). Aber genau das ist es was den Unterschied zur Standartanwendung des ADCs mit Prüfung des Flags ausmacht. So könnte man das Tempo vielleicht sogar noch steigern. Es gibt noch viel zu erforschen :)

Weil meine Einleseschleife gleichzeitig noch auf Zeilenende prüft (Wert < Schwarz) dauert sie etwas länger als eure reinen Einleseschleifen. Aber vermutlich nicht wesentlich viel länger weil ihr ja auch auf eine Abbruchbedingung für die Schleife prüfen müßt.

Wie lange die Wandlung absolut benötigt hängt ja vom Systemtakt ab. Mein 8MHz-Mega32 erreicht mit Prescaler /2 ausreichend genaue Messwerte, möglicherweise schafft der ADC das bei 16MHz-Takt nicht mehr.

Gruß

mic

thewulf00
28.04.2009, 22:00
Man könnte ja auch davon ausgehen, dass wir dann einfach einen externen, schnelleren ADC benutzen.
Meine Fragestellung zielt allein auf schnelleren Code.

Besserwessi
28.04.2009, 22:14
Die ADC-register sollten eigentllich gepuffert sein und immer das Ergebins der letzten gültigen Wandlung geben.
Zumindestens nach dem Datenblatt macht es mit dem internen ADC keinen Sinn, schneller zu werden als 26 Zyklen. Wenn man einen eventuellen Fehler unterstellt vielleicht noch 13 Zyklen.

Die 26 Zyklen sollte man in C erreichen, die 13 Zyklen vielleicht auch noch. In ASM sind beide Genrzen keine Problem.

Ein optimierte ASM Version wäre z.B. so:

IN ADCH bzw. PORT
st x+, ...
cpi xl,...
BRNE ...

Das sollten also 3 Zyklen voll ausgeschrieben, bzw. 6 Zyklen mit Schleife sein. ggf. Auch ein mittlerer Wert mit verdoppelter Schleife.

thewulf00
29.04.2009, 07:09
Dabei sollte ich aber prüfen, ob der Speicherpointer nicht nach ein paar Takten überläuft, so dass ich das High-Byte erhöhen müsste, weil das ja hier nicht vorkommt, korrekt?

Besserwessi
29.04.2009, 17:18
Die Befehle mit dem X+ usw. arbeiten mit 16 Bit Zeigern (Xh,Xl). Man muß also nur sehen das man nach der richtigen Zahl von Elementen anhält. Bei mehr als 255 Werten müßte man den Vergleich am Ende der Schleife anpassen.

thewulf00
29.04.2009, 18:24
Genau, das meinte ich ja.