PORTB&=~LCD_RS;// clear RS, this is always a command
8
PORTB|=LCD_E;// set E
9
x=PINA&0x08;// we only need the busy-flag
10
PORTB&=~LCD_E;// clear E
11
// read second nibble to complete access
12
PORTB|=LCD_E;// set E
13
PORTB&=~LCD_E;// clear E
14
return(x);
15
}
Ist die Busy-Flag Abfrage für ein Display mit 4-Bit Interface.
Das Problem ist jetzt, das funktioniert nur mit "-O0".
Sobald ich "-O1" oder höher setze wird das kaputt-optimiert.
Der Code bei "-O0" (leicht gekürzt am Ende):
1
uint8_t display_busy(void)
2
{
3
13c: cf 93 push r28
4
13e: df 93 push r29
5
140: cd b7 in r28, 0x3d ; 61
6
142: de b7 in r29, 0x3e ; 62
7
144: 21 97 sbiw r28, 0x01 ; 1
8
146: 0f b6 in r0, 0x3f ; 63
9
148: f8 94 cli
10
14a: de bf out 0x3e, r29 ; 62
11
14c: 0f be out 0x3f, r0 ; 63
12
14e: cd bf out 0x3d, r28 ; 61
13
uint8_t x;
14
15
DDRA = 0x00; // set all Pins from PORTA to input
16
150: ea e3 ldi r30, 0x3A ; 58
17
152: f0 e0 ldi r31, 0x00 ; 0
18
154: 10 82 st Z, r1
19
PORTB |= LCD_RW; // switch display to read-mode
20
156: a8 e3 ldi r26, 0x38 ; 56
21
158: b0 e0 ldi r27, 0x00 ; 0
22
15a: e8 e3 ldi r30, 0x38 ; 56
23
15c: f0 e0 ldi r31, 0x00 ; 0
24
15e: 80 81 ld r24, Z
25
160: 84 60 ori r24, 0x04 ; 4
26
162: 8c 93 st X, r24
27
PORTB &= ~LCD_RS; // clear RS, this is always a command
28
164: a8 e3 ldi r26, 0x38 ; 56
29
166: b0 e0 ldi r27, 0x00 ; 0
30
168: e8 e3 ldi r30, 0x38 ; 56
31
16a: f0 e0 ldi r31, 0x00 ; 0
32
16c: 80 81 ld r24, Z
33
16e: 87 7f andi r24, 0xF7 ; 247
34
170: 8c 93 st X, r24
35
PORTB |= LCD_E; // set E
36
172: a8 e3 ldi r26, 0x38 ; 56
37
174: b0 e0 ldi r27, 0x00 ; 0
38
176: e8 e3 ldi r30, 0x38 ; 56
39
178: f0 e0 ldi r31, 0x00 ; 0
40
17a: 80 81 ld r24, Z
41
17c: 82 60 ori r24, 0x02 ; 2
42
17e: 8c 93 st X, r24
43
x = PINA & 0x08; // we only need the busy-flag
44
180: e9 e3 ldi r30, 0x39 ; 57
45
182: f0 e0 ldi r31, 0x00 ; 0
46
184: 80 81 ld r24, Z
47
186: 88 70 andi r24, 0x08 ; 8
48
188: 89 83 std Y+1, r24 ; 0x01
49
PORTB &= ~LCD_E; // clear E
50
...
51
1c8: 08 95 ret
Der gleiche Code bei "-O1":
1
uint8_t display_busy(void)
2
{
3
e6: 1a ba out 0x1a, r1 ; 26
4
uint8_t x;
5
6
DDRA = 0x00; // set all Pins from PORTA to input
7
PORTB |= LCD_RW; // switch display to read-mode
8
e8: c2 9a sbi 0x18, 2 ; 24
9
PORTB &= ~LCD_RS; // clear RS, this is always a command
10
ea: c3 98 cbi 0x18, 3 ; 24
11
PORTB |= LCD_E; // set E
12
ec: c1 9a sbi 0x18, 1 ; 24
13
x = PINA & 0x08; // we only need the busy-flag
14
ee: 89 b3 in r24, 0x19 ; 25
15
PORTB &= ~LCD_E; // clear E
16
f0: c1 98 cbi 0x18, 1 ; 24
17
// read second nibble to complete access
18
PORTB |= LCD_E; // set E
19
f2: c1 9a sbi 0x18, 1 ; 24
20
PORTB &= ~LCD_E; // clear E
21
f4: c1 98 cbi 0x18, 1 ; 24
22
f6: 88 70 andi r24, 0x08 ; 8
23
return(x);
24
}
25
f8: 99 27 eor r25, r25
26
fa: 08 95 ret
Nicht nur, dass dabei völlig anderer Code rauskommt bei dem ich mich
frage, wieso nicht auch bei -O0 zumindest sbi und cbi auftauchen.
Warum wird denn bitteschön das "DDRA = 0x00;" rausgeworfen?
Netter Fehler ist auch, dass die Bit-Maskierung mit 0x08 wegoptimiert
wird, ein Test "while(display_busy());" macht dann auch nicht mehr, was
es soll.
Und was macht man dagegen?
Einfach nicht optimieren lassen?
Der Code der dann rauskommt ist ja selbst funktionsfähig nicht witzig.
Rudolph R. wrote:
> Nicht nur, dass dabei völlig anderer Code rauskommt bei dem ich mich> frage, wieso nicht auch bei -O0 zumindest sbi und cbi auftauchen.
Kurze Antwort: Weil das halt eine Optimierung ist, und die hast du mit
-O0 ausdrücklich verboten.
Lange Antwort: Die IO-Ports der AVRs kann man auf zwei verschiedenen
Wegen ansprechen. Einerseits bilden sie einen IO-Adressraum für
spezielle Befehle (IN/OUT/SBI/CBI/SBIS/CBIS). Dieser IO-Adressraum
ist limitiert auf 64 Adressen, für einen Teil der Befehle sogar auf
nur 32 Adressen. Andererseits werden alle IO-Ports (auch die
jenseits der IO-Adresse 63) im SRAM-Adressraum abgebildet, dummerweise
bei den derzeitigen AVRs mit einem Offset von 0x20 zu den IO-Adressen.
Daher bleibt dem Compiler für IO-Zugriffe erst einmal nichts anderes
übrig als die worst-case-Annahme, dass man sie über den SRAM-Bereich
zugreifen muss, denn nur diese Methode funktioniert immer. Der
Zugriff über die speziellen IO-Befehle ist dann anschließend eine
Optimierung, die nur dann eingebaut werden kann, wenn das für den
konkreten Fall an Hand der gegebenen IO-Adresse auch tatsächlich
möglich ist.
> Warum wird denn bitteschön das "DDRA = 0x00;" rausgeworfen?
Was macht dich glauben, dass es das würde? Ich sehe es ganz deutlich
auf Adresse 0xe6.
> Netter Fehler ist auch, dass die Bit-Maskierung mit 0x08> wegoptimiert wird, ...
Ach? Und was steht auf Adresse 0xf6?
> Und was macht man dagegen?
Dein Timing an die Anforderungen des LC-Displays anpassen, denn da
liegt dein Hund im Pfeffer oder der Hase begraben oder sowas: solange
du die Optimierung ausschaltest, ist der generierte Code langsam genug
für das Display. Mit Optimierung hälst du einfach mal das
vorgeschriebene Timing nicht mehr ein.
Was glaubst du, warum schon zig Programmierer vor dir ihre
LCD-Bibliotheken nach all dem Haarerausraufen am Ende lieber
veröffentlicht haben? Damit man das Rad nicht nochmal erfinden muss.
Nichts dagegen, dass du es trotzdem machst, aber dann such die Fehler
bitte als erstes zwischen Tastatur und Stuhl und nutze das als
Möglichkeit, mit solchen Dingen umgehen zu lernen.
Der Code macht bei -O0 und -O1 das gleiche, ABER bei -O1 wird es viel
schneller gemacht. Bang Bang kommen die Signale auf den einzelnen
Leitungen direkt hintereinander.
Ich kann mur gut vorstellen, dass dadurch das im
LCD-Controller-Datenblatt angegebene Timing NICHT eingehalten wird. Bei
-O0 sind gut 4-6 Instruktionen mehr zwischen dem Umschalten der drei
Steuerleitungen und dem Umschalten und dem Einlesen.
Probier doch mal im -O1 Fall zwischen die Statements eine Handvoll asm
volatile ("nop"); reinzusetzen
EDIT1: Jörg war schneller.
EDIT2: Die nops nur als Q&D um die Ursache aufzuklären. Um stabilen,
CPU-frequenzunabhängigen Code zu bekommen, ist das natürlich Mumpitz
Jörg Wunsch wrote:
> Lange Antwort: Die IO-Ports der AVRs kann man auf zwei verschiedenen> Wegen ansprechen.
[...]
Durchaus interessant, dazu fällt mir dann aber letztlich nur ein, dass
ich in C Programmiere, um davon nicht viel wissen zu müssen. ;-)
>> Warum wird denn bitteschön das "DDRA = 0x00;" rausgeworfen?>> Was macht dich glauben, dass es das würde? Ich sehe es ganz deutlich> auf Adresse 0xe6.
Okay, offenbar habe ich das mit Assembler nicht im Griff.
Was mich aber wundert ist, wie die 0x00 dann in R1 kommt.
>> Netter Fehler ist auch, dass die Bit-Maskierung mit 0x08>> wegoptimiert wird, ...>> Ach? Und was steht auf Adresse 0xf6?
Ein Beleg für meine Blindheit?
>> Und was macht man dagegen?>> Dein Timing an die Anforderungen des LC-Displays anpassen, denn da> liegt dein Hund im Pfeffer oder der Hase begraben oder sowas: solange> du die Optimierung ausschaltest, ist der generierte Code langsam genug> für das Display. Mit Optimierung hälst du einfach mal das> vorgeschriebene Timing nicht mehr ein.
Aua, ich muss dringend mal wieder einen Blick in das Datenblatt der
Displays werfen.
Aufgebaut habe ich das ja schon ein paar Mal aber offenbar habe ich
bisher immer "-O0" verwendet.
Da werde ich dann wohl ein paar mehr NOP's brauchen, mit 1 MHz statt mit
8 MHz läuft der Code nämlich auch nicht.
> Was glaubst du, warum schon zig Programmierer vor dir ihre> LCD-Bibliotheken nach all dem Haarerausraufen am Ende lieber> veröffentlicht haben? Damit man das Rad nicht nochmal erfinden muss.
Ist aber langweilig, immer nur Bibliotheken zu benutzen.
Wobei ich den Code auch zum fünften oder sechsten Mal benutze...
Als nächstes dann TWI...
> Nichts dagegen, dass du es trotzdem machst, aber dann such die Fehler> bitte als erstes zwischen Tastatur und Stuhl und nutze das als> Möglichkeit, mit solchen Dingen umgehen zu lernen.
Daher ja die Frage im Thema "- was mache ich falsch?" :-)
Vielen Dank!
Hmm, nach ein paar NOPs und einem scharfen Blick ins Datenblatt läuft es
jetzt durch einen einzigen zusätzlichen NOP auch mit 8 MHz.
1
uint8_tdisplay_busy(void)
2
{
3
uint8_tx;
4
5
DDRA=0x00;// set all Pins from PORTA to input
6
PORTB|=LCD_RW;// switch display to read-mode
7
PORTB&=~LCD_RS;// clear RS, this is always a command
8
PORTB|=LCD_E;// set E
9
asmvolatile("nop");// give display some time to react
10
x=PINA&0x08;// we only need the busy-flag
11
PORTB&=~LCD_E;// clear E
12
// read second nibble to complete access
13
PORTB|=LCD_E;// set E
14
PORTB&=~LCD_E;// clear E
15
return(x);
16
}
Um das sauber zu bekommen muss ich aber wohl noch tiefer graben.
In dem Datenblatt von Hitachi für den HD44780 steht eine maximale
Ausführungszeit für das Lesen des Busy-Flags von "0 µs".
Offensichtlich muss aber doch Zeit vergehen vom Setzen von E bis die
Daten da sind, nicht wirklich viel wenn ein NOP reicht, aber genug, um
nicht ohne auszukommen.
Edit: okay, tER sind 20 ns und tDDR sind 160 ns.
Also muss man schon 180 ns warten nachdem E auf High gegangen ist.
-> mit 4 NOPs läuft das auch bei 20 MHz. ]:-)
Rudolph R. wrote:
>> Lange Antwort: Die IO-Ports der AVRs kann man auf zwei verschiedenen>> Wegen ansprechen.> [...]> Durchaus interessant, dazu fällt mir dann aber letztlich nur ein, dass> ich in C Programmiere, um davon nicht viel wissen zu müssen. ;-)
Musst du ja auch nicht, denn dafür erledigt das der Optimierer für dich.
> Was mich aber wundert ist, wie die 0x00 dann in R1 kommt.
Da die Konstante 0 sehr häufig benötigt wird, belegt der Compiler
per Konvention (seines ABIs) ein Register permanent mit dieser
Konstante. Innerhalb des Compilers besitzt r1 den Aliasnamen
_zero_reg_ dafür. Diese Vorgehensweise ist bei RISC-Architekturen
nicht unüblich, manche RISC-CPUs besitzen zuweilen dafür sogar ein
hart auf 0 verdrahtetes Register.
(Die Wahl von r1 hat sich im Nachhinein mit der Einführung der
MUL-Befehle als nicht sehr glücklich erwiesen, aber eine Umstellung
des ABI ist ein ziemlicher Klimmzug, da er mit vielem existierenden
Code bricht.)
Rudolph R. wrote:
> Offensichtlich muss aber doch Zeit vergehen vom Setzen von E bis die> Daten da sind, nicht wirklich viel wenn ein NOP reicht, aber genug, um> nicht ohne auszukommen.
Du stolperst über ein weiteres AVR-Detail hier (bin ich bei meiner
LCD-Bibliothek damals auch, obwohl ich es eigentlich bereits
wusste): das Samplen der Daten, wenn der nächste Befehl ein port
input ist, erfolgt bereits bevor der port output des aktuellen
Befehls am Ausgang anliegt (und zwar einen halben CPU-Takt davor).
Daher muss man, wenn der Wert einer Port-Eingabe von einer
unmittelbar vorangehenden Port-Ausgabe abhängt, wenigstens einen
weiteren Befehl zwischen beiden einfügen.
@ Rudolph R. (rudolph)
>Hmm, nach ein paar NOPs und einem scharfen Blick ins Datenblatt läuft es>jetzt durch einen einzigen zusätzlichen NOP auch mit 8 MHz.
Oder einfach solide, wie im Tutorial.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmierung
Wobei das BUSY Flag so ziemlich kein Mensch braucht.
MFG
Falk
Falk Brunner wrote:
> Wobei das BUSY Flag so ziemlich kein Mensch braucht.
Begründung?
Die worst-case-Wartezeiten sind laut Datenblatt um einiges
pessimistischer angesetzt, als die tatsächliche Abarbeitung oft
dauert.
Jörg Wunsch wrote:
> Begründung?
Was für Wettrennen veranstaltest du auf einem Text-LCD denn so? Ein 4x16
Display kannst du auch ohne Ready 300mal pro Sekunde vollschreiben.
Falk Brunner wrote:
>>Hmm, nach ein paar NOPs und einem scharfen Blick ins Datenblatt läuft es>>jetzt durch einen einzigen zusätzlichen NOP auch mit 8 MHz.>> Oder einfach solide, wie im Tutorial.>> http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmierung
Daran ist doch garnichts besonders solide.
1
LCD_PORT|=(1<<LCD_EN);
2
_delay_us(1);// kurze Pause
3
// Bei Problemen ggf. Pause gemäß Datenblatt des LCD Controllers verlängern
Da kann ich auch genausogut nach Datenblatt und Takt NOP's reinhängen.
> Wobei das BUSY Flag so ziemlich kein Mensch braucht.
Kommt darauf an.
Ich kann das "_delay_us(42);" aus dem Tutorial nicht gebrauchen.
Das ist dann auch nichts weiter als geraten.
Und am Ende des Display-Codes besonders bitter, finde ich.
Mein Code läuft weitgehend unabhängig vom Display.
Ich benutze ein Array als "Bildschirm-Puffer" in das ich jederzeit von
beliebigen Programm-Teilen aus schreiben kann.
Eine Zeitscheibe in meinem "Scheduler" fragt das Busy-Flag ab.
Ist das Display bereit, wird das nächste Zeichen des Puffers gesendet.
Ist das Display nicht bereit, wird die Zeitscheibe beendet.
Die folgenden Zeitscheiben können ungebremst und beliebig oft in das
Array schreiben und es ist völlig egal, was das Display gerade so
treibt.
@Jörg Wunsch
Vielen Dank für die Informationen und die Geduld!
Und vor allem auch vielen Dank für WinAVR!
Rudolph R. wrote:
> Und vor allem auch vielen Dank für WinAVR!
Da kann ich leider nichts dafür, ich benutze kein Windows. ;-) Das geht
ganz allein auf Eric Weddingtons Kappe... OK, aber ich bin bei einigen
der Dinge beteiligt, die Eric da zusammenpackt.
@ Rudolph R. (rudolph)
>Da kann ich auch genausogut nach Datenblatt und Takt NOP's reinhängen.
Klar, weil man das dann a) per Hand berechen darf wieviel Mikrosekunden
das sind und b) das dann immer wieder neu machen darf, wenn man eine
andere Taktfreqeunz verwendet.
>Ich kann das "_delay_us(42);" aus dem Tutorial nicht gebrauchen.>Das ist dann auch nichts weiter als geraten.
Nöö, das sind garantierte Maximalzeiten aud dem Datenblatt.
>Und am Ende des Display-Codes besonders bitter, finde ich.
???
>Mein Code läuft weitgehend unabhängig vom Display.
Na dann lass es doch einfach weg ;-)
>Ich benutze ein Array als "Bildschirm-Puffer" in das ich jederzeit von>beliebigen Programm-Teilen aus schreiben kann.>Eine Zeitscheibe in meinem "Scheduler" fragt das Busy-Flag ab.>Ist das Display bereit, wird das nächste Zeichen des Puffers gesendet.>Ist das Display nicht bereit, wird die Zeitscheibe beendet.
Und? Applause gefällig? Nur mal zur Information. Das ist ein TUTORIAL,
das die prinzipielle Funktion einem Anfänger näher bringen soll. Keine
HighTec Lösung.
MFG
Falk
Falk Brunner wrote:
>>Da kann ich auch genausogut nach Datenblatt und Takt NOP's reinhängen.>> Klar, weil man das dann a) per Hand berechen darf wieviel Mikrosekunden> das sind und b) das dann immer wieder neu machen darf, wenn man eine> andere Taktfreqeunz verwendet.
Du hast ja Recht - aber inwieweit unterscheidet sich das jetzt zu der
Lösung im Tutorial?
Wie ich schon schrieb, ich kann auch jetzt 4 NOP's einbauen und es
dürfte bis 20 MHz laufen.
>>Ich kann das "_delay_us(42);" aus dem Tutorial nicht gebrauchen.>>Das ist dann auch nichts weiter als geraten.>> Nöö, das sind garantierte Maximalzeiten aud dem Datenblatt.
Aus einem Datenblatt doch wohl eher und ja, es ist ein Tutorial.
>>Und am Ende des Display-Codes besonders bitter, finde ich.>> ???
So, fertig, jetzt noch die restlichen 95 Prozent der Ausführungszeit
abbummeln.
Falk Brunner wrote:
> Und? Applause gefällig? Nur mal zur Information. Das ist ein TUTORIAL,> das die prinzipielle Funktion einem Anfänger näher bringen soll. Keine> HighTec Lösung.
Dann brauchst du aber auch nicht behaupten, das ready-Bit wäre
überflüssig, insbesondere wenn es jemand tatsächlich sinnvoll
benutzt (weil er den Rest der Zeit die CPU was besseres tun lassen
kann).
@ Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
>überflüssig, insbesondere wenn es jemand tatsächlich sinnvoll>benutzt (weil er den Rest der Zeit die CPU was besseres tun lassen>kann).
Und wieviel schneller ist das dann real?
Mfg
Falk
Falk Brunner wrote:
> Und wieviel schneller ist das dann real?
Das sollte Rudolph sagen können, hängt wohl einfach davon ab, was man
sonst noch zu tun hat. Zumindest kann man im Zweifelsfall die CPU
schlafen legen und den Strom sparen, wenn man nix mehr zu tun hat.
Jörg Wunsch wrote:
> (weil er den Rest der Zeit die CPU was besseres tun lassen kann).
Dann wiederum ist es sinnvoller, die LCD-Operationen per Timer-Interrupt
(Scheduler) zu implementieren, also beispielsweise alle 2ms eine
LCD-Operation. Das ist immer noch schnell genug und die Laufzeit einer
LCD-Operation reduziert sich auf ~2µs.
Andreas Kaiser wrote:
> Dann wiederum ist es sinnvoller, die LCD-Operationen per Timer-Interrupt> (Scheduler) zu implementieren, also beispielsweise alle 2ms eine> LCD-Operation. Das ist immer noch schnell genug und die Laufzeit einer> LCD-Operation reduziert sich auf ~2µs.
Genau das habe ich doch gemacht - wie weiter oben beschrieben.
Nur fragt mein Code vorher beim Display nach, ob es bereit ist, Daten
anzunehmen, statt sich drauf zu verlassen, dass das schon irgendwie
passt.
Mein Hauptproblem war, dass ich von verschiedenen Zeitscheiben aus auf
verschiedene Teile des Displays zugreifen wollte, ohne das die sich
gegenseitig blockieren.
Wie schnell das Display mit Daten versorgt wird ist mir ziemlich egal -
so lange es halt nicht 1 "Frame" in 2 Sekunden wird.
Wie schnell die restlichen Teile des Programms reagieren können, war mir
allerdings nicht egal.