Datum: 16.01.2008 22:22
Hi! Ich habe den folgenden Code:
uint8_t display_busy(void) { uint8_t x; DDRA = 0x00; // set all Pins from PORTA to input PORTB |= LCD_RW; // switch display to read-mode PORTB &= ~LCD_RS; // clear RS, this is always a command PORTB |= LCD_E; // set E x = PINA & 0x08; // we only need the busy-flag PORTB &= ~LCD_E; // clear E // read second nibble to complete access PORTB |= LCD_E; // set E PORTB &= ~LCD_E; // clear E return(x); } |
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):
uint8_t display_busy(void) { 13c: cf 93 push r28 13e: df 93 push r29 140: cd b7 in r28, 0x3d ; 61 142: de b7 in r29, 0x3e ; 62 144: 21 97 sbiw r28, 0x01 ; 1 146: 0f b6 in r0, 0x3f ; 63 148: f8 94 cli 14a: de bf out 0x3e, r29 ; 62 14c: 0f be out 0x3f, r0 ; 63 14e: cd bf out 0x3d, r28 ; 61 uint8_t x; DDRA = 0x00; // set all Pins from PORTA to input 150: ea e3 ldi r30, 0x3A ; 58 152: f0 e0 ldi r31, 0x00 ; 0 154: 10 82 st Z, r1 PORTB |= LCD_RW; // switch display to read-mode 156: a8 e3 ldi r26, 0x38 ; 56 158: b0 e0 ldi r27, 0x00 ; 0 15a: e8 e3 ldi r30, 0x38 ; 56 15c: f0 e0 ldi r31, 0x00 ; 0 15e: 80 81 ld r24, Z 160: 84 60 ori r24, 0x04 ; 4 162: 8c 93 st X, r24 PORTB &= ~LCD_RS; // clear RS, this is always a command 164: a8 e3 ldi r26, 0x38 ; 56 166: b0 e0 ldi r27, 0x00 ; 0 168: e8 e3 ldi r30, 0x38 ; 56 16a: f0 e0 ldi r31, 0x00 ; 0 16c: 80 81 ld r24, Z 16e: 87 7f andi r24, 0xF7 ; 247 170: 8c 93 st X, r24 PORTB |= LCD_E; // set E 172: a8 e3 ldi r26, 0x38 ; 56 174: b0 e0 ldi r27, 0x00 ; 0 176: e8 e3 ldi r30, 0x38 ; 56 178: f0 e0 ldi r31, 0x00 ; 0 17a: 80 81 ld r24, Z 17c: 82 60 ori r24, 0x02 ; 2 17e: 8c 93 st X, r24 x = PINA & 0x08; // we only need the busy-flag 180: e9 e3 ldi r30, 0x39 ; 57 182: f0 e0 ldi r31, 0x00 ; 0 184: 80 81 ld r24, Z 186: 88 70 andi r24, 0x08 ; 8 188: 89 83 std Y+1, r24 ; 0x01 PORTB &= ~LCD_E; // clear E ... 1c8: 08 95 ret |
Der gleiche Code bei "-O1":
uint8_t display_busy(void) { e6: 1a ba out 0x1a, r1 ; 26 uint8_t x; DDRA = 0x00; // set all Pins from PORTA to input PORTB |= LCD_RW; // switch display to read-mode e8: c2 9a sbi 0x18, 2 ; 24 PORTB &= ~LCD_RS; // clear RS, this is always a command ea: c3 98 cbi 0x18, 3 ; 24 PORTB |= LCD_E; // set E ec: c1 9a sbi 0x18, 1 ; 24 x = PINA & 0x08; // we only need the busy-flag ee: 89 b3 in r24, 0x19 ; 25 PORTB &= ~LCD_E; // clear E f0: c1 98 cbi 0x18, 1 ; 24 // read second nibble to complete access PORTB |= LCD_E; // set E f2: c1 9a sbi 0x18, 1 ; 24 PORTB &= ~LCD_E; // clear E f4: c1 98 cbi 0x18, 1 ; 24 f6: 88 70 andi r24, 0x08 ; 8 return(x); } f8: 99 27 eor r25, r25 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.
Datum: 16.01.2008 22:40
Sorry, am Anfang überlegt, am Ende nicht dran gedacht... Das ist mit WinAVR und einem ATMega16 als Target im AVR-Studio.
Datum: 16.01.2008 22:59
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.
Datum: 16.01.2008 23:06
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
Datum: 16.01.2008 23:25
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!
Datum: 16.01.2008 23:56
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.
uint8_t display_busy(void) { uint8_t x; DDRA = 0x00; // set all Pins from PORTA to input PORTB |= LCD_RW; // switch display to read-mode PORTB &= ~LCD_RS; // clear RS, this is always a command PORTB |= LCD_E; // set E asm volatile ("nop"); // give display some time to react x = PINA & 0x08; // we only need the busy-flag PORTB &= ~LCD_E; // clear E // read second nibble to complete access PORTB |= LCD_E; // set E PORTB &= ~LCD_E; // clear E return(x); } |
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. ]:-)
Datum: 17.01.2008 08:59
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.)
Datum: 17.01.2008 09:02
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.
Datum: 17.01.2008 10:26
@ 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-Tu... Wobei das BUSY Flag so ziemlich kein Mensch braucht. MFG Falk
Datum: 17.01.2008 10:54
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.
Datum: 17.01.2008 11:01
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.
Datum: 17.01.2008 20:38
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-Tu... Daran ist doch garnichts besonders solide.
LCD_PORT |= (1<<LCD_EN); _delay_us(1); // kurze Pause // 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!
Datum: 17.01.2008 22:41
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.
Datum: 18.01.2008 00:15
@ 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
Datum: 18.01.2008 08:07
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.
Datum: 18.01.2008 09:04
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).
Datum: 18.01.2008 10:26
@ 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
Datum: 18.01.2008 10:58
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.
Datum: 18.01.2008 11:00
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.
Datum: 18.01.2008 19:23
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.
Antwort schreiben
Die Angabe einer Email-Adresse ist freiwillig. Wenn Sie automatisch per Email über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
- Suchfunktion und Betreffsuche benutzen - vielleicht gibt es schon einen ähnlichen Beitrag
- Aussagekräftigen Betreff wählen
- Im Betreff angeben um welchen Controllertyp es geht (AVR, PIC, ...)
- Groß- und Kleinschreibung verwenden
- Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang
- JPEG-Dateien (.jpg) nur für Fotos verwenden, Schaltpläne, Screenshots usw. als PNG oder GIF anhängen
Formatierung (mehr Informationen...)
- [c]C-Code[/c]
- [avrasm]AVR-Assembler-Code[/avrasm]
- [pre]vorformatierter Text (z.B. Code in anderen Sprachen)[/pre]
- [math]Formel in LaTeX-Syntax[/math]
- [[Titel]] - Link zu Artikel