Hi
ich verwende das STM32F407 discovery board und möchte damit das hd44780
display ansteuern. Ich verwende den internen Oszillator und betreibe
meinen uC mit 16MHz.
Die LCD Routinen hier auf uC.net basieren auf die _delay_us() Funktion
aus der AVR-Bibliothek.
Ich möchte mir meine eigene delay Routine schreiben, weiß aber nicht so
recht, wie viel Zeit in meiner for-Loop vergeht.
Ist das aus dem übersetzten Code ablesbar?
Aus meiner main Funktion:
1
for(;;)
2
{
3
inti=0;
4
for(i;i<1000;i++)
5
{
6
// Do nothing
7
}
8
}
Der übersetzte Code, in der sich die for Schleife befindet:
1
45funcGPIOInit();
2
0800021e:bl0x800024a<funcGPIOInit>
3
46funcIntInit();
4
08000222:bl0x8000266<funcIntInit>
5
50inti=0;
6
08000226:movsr3,#0
7
08000228:strr3,[r7,#4]
8
51for(i;i<1000;i++)
9
0800022a:b.n0x8000232<main+30>
10
0800022c:ldrr3,[r7,#4]
11
0800022e:addsr3,#1
12
08000230:strr3,[r7,#4]
13
08000232:ldrr3,[r7,#4]
14
08000234:cmp.wr3,#1000;0x3e8
15
08000238:blt.n0x800022c<main+24>
Allerdings weiß ich nicht, ob dieser Ansatz zielführend ist.
Danke für eure Hilfe.
Gruß,
Alex schrieb:> und betreibe meinen uC mit 16MHz.
Eine Schande :(
Alex schrieb:> Ich möchte mir meine eigene delay Routine schreiben, weiß aber nicht so> recht, wie viel Zeit in meiner for-Loop vergeht.
Delay ist Murks, das geht schöner. Ansonsten wenn du HAL benutzt gibt es
die HAL delay Funktion. Wenn nicht benutz den Systick counter oder einen
Timer.
Alex schrieb:> Ich möchte mir meine eigene delay Routine schreiben, weiß aber nicht so> recht, wie viel Zeit in meiner for-Loop vergeht.
Notfalls kannst du es mit einem LA oder Oszi nachmessen. Oder du
verwendest eine Entwicklungsumgebung mit Debugger, der in der Lage ist
Programmlaufzeiten und/oder Taktzyklen zu messen.
> // Do nothing
Und pass auf, dass dir nicht irgendeine Compileroptimierung das "// Do
nothing" mitsamt der Schleife rausschmeißt ;-)
Das erste, was ich bei meinen Prozessoren machen, ist irgendein Timer in
Gang bringen. Oft einen im Sekundenbereich (für externe Synchronisation)
, und einen im Millisekundenbereich (mehr für interne Zwecke).
Gibts es auf einem STM32F407 keine Timer?? Schwache Architektur.
Obwohl ich bei
https://www.st.com/en/microcontrollers-microprocessors/stm32f407-417.html
lese: Up to 17 timers: 16- and 32-bit running at up to 168 MHz
Wohl eher schwacher Programmierer?
Hi,
PittyJ schrieb:> Wohl eher schwacher Programmierer?
Ja, sogar sehr schwacher Programmierer. :)
Da ich aber neu im Bereich uC bin (ein paar Wochen), erlaube ich mir
diese Unschönheiten für den Anfang.
Obwohl...
Timer und Interrupts habe ich bereits erfolgreich programmiert, und die
delays über Timer funktionieren super im Bereich 20us bis hin zu 15ms,
also in dem Bereich, den ich für meine LCD Ansteuerung benötige.
Ich dachte allerdings bis dato, dass ein dedizierter Timer für eine LCD
Initialisierung mit Kanonen auf Spatzen geschossen ist, auch wenn man
bis zu 17 Timer zu verfügung hat.
Guest schrieb:> Alex schrieb:>> und betreibe meinen uC mit 16MHz.>> Eine Schande :(
Ja, mit PLL habe ich mich noch nicht auseinander gesetzt. Das kommt
später dran. Schritt für Schritt. :)
Klaus S. schrieb:> Ich benutze folgende Routinen:
Hi Klaus,
den SysTick Timer kannte ich noch gar nicht - mein Reference manual
schreibt darüber nichts, und Google weist mich auf die CMSIS Bibliothek
hin. Da werde ich mich mal einlesen. Das wäre in der Tat eine elegante
Lösung, ohne dafür extra einen Timer zu opfern (auch wenn es genug Timer
zur Verfügung gibt).
Wieder etwas Neues dazu gelernt, danke. :)
Wolfgang schrieb:> Oder du> verwendest eine Entwicklungsumgebung mit Debugger, der in der Lage ist> Programmlaufzeiten und/oder Taktzyklen zu messen.
Rein aus Neugier (auch für spätere/andere Zwecke):
Ich verwende STM32CubeIDE und kann meinen STM32F407 sogar debuggen.
Aber... wie genau finde ich die Zeit heraus, die für die Ausführung
bestimmter Code Passagen benötigt wird? Ich verwende den Debugger bis
jetzt meist, um Funktionsaufrufe etc. zu überprüfen.
Zeit/Zyklusmessungen habe ich damit noch nicht durchgeführt.
Danke und Gruß,
Alex schrieb:> Ich verwende STM32CubeIDE
da bekommst du doch ein HAL_Delay() geschenkt.
Zyklenzähler war doch schon genannt, DWT.
Zeiten mit HAL_GetTick() wenn es gröber auch ok ist.
Johannes S. schrieb:> da bekommst du doch ein HAL_Delay() geschenkt.
Ja, aber HAL möchte ich (noch) nicht verwenden. Ich habe die Ambition,
die ersten Schritte ohne HAL zu gehen, um die Hardware besser kennen zu
lernen.
Gruß,
Dann nimm einen Timer und gut ist. Du wirst mich andere Timing Aufgaben
haben und meistens lassen die sich mehr oder weniger alle mit einem
Timer erschlagen. Dazu sind die schließlich gemacht, der hat die ja
nicht eingebaut damit sie gut aussehen sondern damit man sie benutzt. Du
kaufst dir ja auch meinen uC mit FPU wenn du dann doch alles mit
integern berechnest. Und keiner geht hin und macht Software SPI wenn er
3 oder 4 hardware controller dafür hat...
Man kann in HAL bzw. LL Funktionen ja abgucken, da wird der SysTick
benutzt und das sind nur ein paar Zeilen den zu aktivieren. Im SysTick
Interrupt wird nur ein Zähler hochgezählt. Das HAL_Delay merkt sich dann
nur den aktuellen Stand und wartet solange Zähler < Soll ist.
Init ist in LL_InitTick in _ll_utils.h Und die ISR im Startupcode
setzen.
Alex schrieb:> Aber... wie genau finde ich die Zeit heraus, die für die Ausführung> bestimmter Code Passagen benötigt wird?
Der Weg über den Assembler Code war schon richtig. In der Doku des
Befehlssatzes steht für jeden Befehl, wie viele CPU Takte er benötigt.
Teilweise hängt die Anzahl von Bedingungen ab.
Bei höheren Taktfrequenzen wirst du die allerdings mit zusätzlichen
Verzögerungen wegen Flash Wait States herumschlagen müssen, die
größtenteils aber nicht komplett durch eine Prefetch Cache ausgeglichen
werden.
Um das Ganze zu vereinfachen: Messe die Zeit einfach mit einer Stoppuhr
und passe die Zahl so lange an, bis es stimmt. Fange mal damit an:
Ich merke: Ihr seid Ingenieure/ITler und keine Pädagogen :)
Auch wenn ich mich geschlagen gebe, für die LCD Initialisierung nun
einen Timer zu verwenden, bin ich mir sicher, dass man aus dem
übersetzten Maschinencode (indirekt) ablesen kann, wie viele Zyklen für
einen Codeauschnitt verwendet werden.
Hier ein Beispiel, dass es möglich sein sollte:
https://ucexperiment.wordpress.com/2013/06/15/the-lost-art-of-cycle-counting/
Gruß,
Alex schrieb:> Auch wenn ich mich geschlagen gebe, für die LCD Initialisierung nun> einen Timer zu verwenden, bin ich mir sicher, dass man aus dem> übersetzten Maschinencode (indirekt) ablesen kann, wie viele Zyklen für> einen Codeauschnitt verwendet werden.
Aus dem Maschinencode die Zeit herauszulesen, die für die Abarbeitung
der Befehle benötigt wird ist prinzipiell möglich (mit ein paar
Ausnahmen mit unbestimmter Zyklendauer, z.B. Division). Die Frage ist
nur ob das Sinnvoll ist, schließlich willst du ja nicht in Assembler
programmieren und ob der Compiler mit unterschiedlichen
Optimierungsstufen dann immer den selben Maschinencode generiert, das
steht auf einem ganz anderen Blatt. Zusätzliche Fallstricke, die oben
schon erwähnt wurden sind dann Flash-Watestates, Prefetch-Buffer und bei
größeren Controllern (z.B. Cortex-M7) dann noch zusätzliche Caches.
Kurzum, nimm einen Timer bzw. den CYCCNT. Der Grund warum das in dem
AVR-Tutorial anders gemacht wurde ist schlicht und einfach, dass der AVR
vergleichsweise wenige Timer hat, so etwas wie einen Cycle-Counter nicht
kennt und man sich beim AVR um Flash-Watestates und Caches keine Sorgen
machen braucht.
Alex schrieb:> Hier ein Beispiel, dass es möglich sein sollte:> https://ucexperiment.wordpress.com/2013/06/15/the-lost-art-of-cycle-counting/
Der Artikel ist alt, das Thema verliert immer mehr an Aktualitaet.
Moderne Prozessoren habe keine fixen Ausfuehungszeiten pro Befehl wegen:
- Pipelining
- Out-Of-Order Execution
- variablen Takt, Drosselung, Hitzemanagment
- Interruptroutinen
- Speicherzugriffszonen
- Codealignment
usw.
D.h. ist es sicher nuetzlich das simple Cycle-Zaehlen durch Besseres zu
ersetzen.
leo
leo schrieb:> D.h. ist es sicher nuetzlich das simple Cycle-Zaehlen durch Besseres zu> ersetzen.
Und nicht unwichtig: vielleicht soll der Prozessor noch mehr was
arbeiten, als nur stumpf zu zaehlen.
leo
leo schrieb:> Moderne Prozessoren habe keine fixen Ausfuehungszeiten pro Befehl wegen:> - Pipelining> - Out-Of-Order Execution> - variablen Takt, Drosselung, Hitzemanagment> - Interruptroutinen> - Speicherzugriffszonen> - Codealignment> usw.
Okay, das war mir nicht so ganz klar. Vielen Dank für den Hinweis. Damit
schließe ich das Kapitel Zykelzählen nun endgültig ab.
Dann gehe ich mit der Zeit und verwende einen Timer für meine delays und
wenn ich Zykel zählen muss, werde ich es über den SysTick machen. :)
Vielen Dank an alle.
Spoiler alert:
Dies wird sicherlich nicht der letzte Thread dieser Art sein :D
Schönes Wochenende.
Gruß,
Alex schrieb:> Auch wenn ich mich geschlagen gebe, für die LCD Initialisierung nun> einen Timer zu verwenden, bin ich mir sicher, dass man aus dem> übersetzten Maschinencode (indirekt) ablesen kann, wie viele Zyklen für> einen Codeauschnitt verwendet werden.
Du verwendest den Timer nicht für die LCD Initialisierung sondern für
die delay_us() Funktion. Und die kannst du überall verwenden. Der Timer
wird ja nur einmal initialisiert und dann nur noch gelesen.
Praktischerweise initialisiert man den Prescaler passend zum Systemtakt.
Dann muss sich die delay_us() nicht darum kümmern, die kann immer mit us
arbeiten.
Natürlich kann man beim Cortex-M4 genau wie beim AVR die Befehlszyklen
zählen. Es ist nur sehr viel Fleißarbeit weil man viele Sonderfälle
berücksichtigen muss (siehe oben). Das Hauptproblem ist, in der
Dokumentation von ARM die Tabelle zu finden ;) Es lohnt sich aber nicht,
weil heutzutage auch der Linker die Befehle ändern kann, sogar dann,
wenn man in einem ganz anderen Programmteil etwas geändert hat. Man
müsste die Zyklen nach jedem Übersetzen neu ausrechnen.
Wer nur mit/auf den Timer wartet, kann auch Zyklen zaehlen.
Auf genaue Werte kommt es bei LCD sowieso nicht an.
Man kann natuerlich auch auf Systick warten.
Richtig waere eine Queue in die "kuenftige" Ereignisse einsortiert
werden, und erst dann abgearbeitet werden, wenn der Zeitpunkt
erreicht ist.
auweia schrieb:> Wer nur mit/auf den Timer wartet, kann auch Zyklen zaehlen.> Auf genaue Werte kommt es bei LCD sowieso nicht an.> Man kann natuerlich auch auf Systick warten.>> Richtig waere eine Queue in die "kuenftige" Ereignisse einsortiert> werden, und erst dann abgearbeitet werden, wenn der Zeitpunkt> erreicht ist.
Magst du das näher erläutern? Ich verstehe deinen Beitrag so, dass du
eine Art House keeping machen würdest, bei der die entsprechenden Pins
fürs LCD dann mit hereinkommen und nach bestimmten Zeitpunkten
schlichtweg gesetzt oder gelöscht werden.
Das ginge wahrscheinlich in die Richtung state machine, oder
missverstehe ich dich nun völlig?
PittyJ schrieb:> Oft einen im Sekundenbereich (für externe Synchronisation)> , und einen im Millisekundenbereich (mehr für interne Zwecke).
Für diese Anwendung bräuchtest Du einen Timer mit einer Auflösung von
1µS (bzw 10µs für andere Befehle), und das ist Blödsinn. Zumal man ein
Display auch nicht zigmal pro Sekunde ändert, weil das gar nicht lesbar
wäre.
Busy-wait mit CYCCNT ist da ein sehr guter Weg. Man könnte natürlich
auch das busy-Flag des Displays abfragen, wäre aber mehr Aufwand ohne
realen Gegenwert. Zumal man dann bei 5V-toleranten Displays auch noch
einen Levelshifter verwenden müßte.
Nop schrieb:> Für diese Anwendung bräuchtest Du einen Timer mit einer Auflösung von> 1µS (bzw 10µs für andere Befehle), und das ist Blödsinn. Zumal man ein
Ich habe ein 44780 vor Jahren mal per FPGA bedient. Und ich kann mich an
sehr lange Delays erinnern:
Wait for 15ms, nach Power on
wait for 4.1ms, nach ...
von daher passt ein Millisekunden Timer noch ganz gut.
Der OP wollte bei 16Mhz auch schon mindestens 1000 Takte warten, also
auch schon auf 16Khz = 62 usec gehen.
Ich kann da nichts im Mikrosekundenbereich finden.
Alex schrieb:> Die LCD Routinen hier auf uC.net basieren auf die _delay_us() Funktion> aus der AVR-Bibliothek.>> Ich möchte mir meine eigene delay Routine schreiben, weiß aber nicht so> recht, wie viel Zeit in meiner for-Loop vergeht.
Und was soll das?
Schreib dir lieber deinen eigenen Displaytreiber. Und zuvor richtest du
dir in deiner Firmware eine Systemuhr ein. Normalerweise reicht dafür
ein Zeit-Takt von 10 ms völlig aus. Und für die wenigen Momente, wo du
beim Initialisieren des Displays mal eine Wartezeit einhalten mußt,
reicht es aus, eben mal 20 oder 30 ms lang zu warten. Ist ja nur am
Anfang direkt nach dem Reset. Für später liest du einfach das
Busy/Ready-Flag des Displays aus und fertig ist die Laube.
W.S.
W.S. schrieb:> Für später liest du einfach das> Busy/Ready-Flag des Displays aus und fertig ist die Laube.
Nein nix ist die Laube fertig!
Denn die Displays brauchen einen Write-Zyklus der in der
Grössenordnung von mindestens ein paar Mikrosekunden ablaufen
muss, dafür arbeiten die STM Controller zu schnell um ohne
Delays auskomen zu können.
jo mei schrieb:> W.S. schrieb:>> Für später liest du einfach das>> Busy/Ready-Flag des Displays aus und fertig ist die Laube.>> Nein nix ist die Laube fertig!
W.S. will uns nur mal wieder sagen, dass er von nix ne Ahnung hat ;)
Je komplexer die Prozessoren werden, desto schwieriger wird es, die
exakte Laufzeit vorherzusagen. Zu viele Einflüsse spielen mit hinein.
Wenn man Delays in einem Zeitrahmen benötigt, bei dem ein Timer nicht
sinnvoll ist, oder verschwendet wäre, hat man deshalb heute meist
Systicks oder ähnliche Mechanismen. Ist einem auch das zuwider oder will
partout mit Delayloops arbeiten, dann sollte man die selbstkalibrierend
einrichten.
Das bedeutet, dass man exakt eine solche Schleife im Speicher platziert.
Es muss eine einzige sein, weil verschiedene Alignments und andere
Schweinereien einem sonst einen Streich spielen können. Und dann
verwendet man beim Start einmalig einen Timer, um die Laufzeit von z.B.
1000 Durchläufen dieser Schleife zu messen. Das ergibt dann einen
Umrechnungsfaktor.
Alex schrieb:> Ich dachte allerdings bis dato, dass ein dedizierter Timer für eine LCD> Initialisierung mit Kanonen auf Spatzen geschossen ist, auch wenn man> bis zu 17 Timer zu verfügung hat.
Warum sollte man dafür einen dedizierten Timer benötigen. Der Timer wird
z.B. als 1ms Interrupt konfiguriert und da kann sich jede Task dran
bedienen. Für dein Display-Delay wartest du einfach eine bestimmte
Anzahl von diesen Timerinterrupts ab.
Wolfgang schrieb:> Der Timer wird> z.B. als 1ms Interrupt konfiguriert und da kann sich jede Task dran> bedienen. Für dein Display-Delay wartest du einfach eine bestimmte> Anzahl von diesen Timerinterrupts ab.
Naja, den Systick als LCD-Buszyklus-Timer zu benutzen ist schon
etwas schräg. Weil er zum einen vergleichweise langsam ist gegen-
über den ca 1-3 usec die man braucht, und weil er, wenn man ihn
deutlich schneller auslegt, allmählich dem Prozessor zuviel
(und unnötige) Interrupt-Last abverlangt.
Der DWT Cycle Count ist sicherlich die beste Lösung für usec
Timings.
Klaus S. schrieb:> Ich benutze folgende Routinen:
jo mei schrieb:> Der DWT Cycle Count ist sicherlich die beste Lösung für usec> Timings.
Leider auch nicht immer. Wer die MPU einsetzt möchte auf SCB, DWT usw.
nur privilegierte Zugriffe erlauben und ein syscall für ein us-Delay ist
ja auch nicht der Hit.
jo mei schrieb:> Weil er zum einen vergleichweise langsam ist gegen-> über den ca 1-3 usec die man braucht ...
Wer redet dabei auch von 1-3 µs. Das wäre eher grober Unfug.
Guck mal ins Datenblatt vom HD44780, was da noch so auftaucht:
Bei der Initialisierung werden bspw. Mindestwartezeiten von 15ms, 4.1ms
oder 40ms gefordert (Fig. 25/26 8-Bit bzw. 4-Bit Interface). Da hätte
ich jetzt keine Hemmungen, einen 1ms Timerinterrupt zu bemühen.
Wolfgang schrieb:> Wer redet dabei auch von 1-3 µs. Das wäre eher grober Unfug.
Du hast es offensichtlich noch nicht kapiert.
Ich rede hier von einem normalen Bus-Zyklus. Und das ist
absolut kein grober Unfug sondern der Normalfall der beim
Schreiben oder Lesen vorkommt.
Wenn dir das nicht klar ist dann musst du wohl durch Datenblastt
lesen noch etwas schlauer werden.
PittyJ schrieb:> Ich habe ein 44780 vor Jahren mal per FPGA bedient. Und ich kann mich an> sehr lange Delays erinnern:
Aber nicht z.B. für den Enable-Puls. Der muß mindestens 0.5µs sein, also
nimmt man 1µs, um auf der sicheren Seite zu sein. Oder 40µs, um ein
Datenbyte zu verarbeiten, also gibt man 80µs, wenn man das busy-Flag
nicht abfragt. Kein Problem mit Busy-Wait.
Daß es auch Kommandos gibt, die lange brauchen (speziell CLEAR), steht
dem nicht entgegen. Die werden aber fast nur für die Initialisierung
gebraucht, wo es meistens relativ egal ist.