Guten Tag! Ich habe eine Frage: Text-LCD ist ziemlich langsam: man muß immer ca. 40 us nach jedem Befehl warten. Bei 16 MHz AVR bedeutet das 640 CPU-Takt. Diese Zeit könnte ich viel besser für Datenanalyse benutzen, stattdessen wird diese Zeit einfach vergeudet. USART kann zwar noch in dieser Zeit dank Interrupts etwas machen, aber sowieso, es bleibt CPU-Zeit, die man besser nützen könnte... Das alles hat zu Folge, daß ich für mit LCD gezeigte MIDI-Daten ziemlich große 256 bytes-Puffer benutzen muß, sonst kommt es zu Datenverlust, wenn ein MIDI-Regler gedreht wird. Wie wäre es möglich, statt nutzlos auf Bereitschaft von LCD zu warten, etwas nützliches zu machen? Wenn Timer-Interrupts, dann wie richte ich Prozeß ein? Irgendwie komme ich mit Gedanken nicht klar... Könnte mir jemand vielleicht helfen? Ich hatte schon Gedanken, einfach ein ATMEGA88 extra für LCD benutzen, dafür zweite USART von ATMEGA1284P für Datenaustausch benutzen: Hauptkontroller kann dann schnell Daten an ATMEGA88 schicken und weiter machen, während ATMEGA88 alle Befehle in Puffer legt und ohne Eile mit LCD kommuniziert. Aber vielleicht gibt es reine Programmlösung? Das wäre schon allein als Übung für mich interessant... Vielen Dank im voraus!
:
Bearbeitet durch User
bei einfachen Text LCD bietet es sich an den Text 1:1 im RAM abzulegen und im Interrupt immer nur ein Zeichen zu schreiben. Zeilen *Spalten *4-5 Aktualisierungen pro Sekunde. zB.
Hmm ... Timer -> Interrupt -> Funktionsaufruf zum Befehl an LCD senden ? Timer -> Variable auf 1 -> im Ablauf senden (wenn diese Funktion regelmäßig im Programmablauf drin ist) Variable wieder auf 0 eine Mischung daraus ?
Bisher habe ich so gemacht: ein Text-Puffer 4x20=80 bytes. Alle Manipulationen in Puffer, danach nach LCD alles senden. Aber 4x20=80 Zeichen x ca. 45 us = 3,6 ms, für jeden Austausch mit LCD. Wenn ich MIDI-Daten anzeigen will, dann bekomme ich ca. jede 350 us ein MIDI-byte, schiebe Daten in RAM-Puffer (ca. 50 us) um 3 Zeichen nach links (zwei Ziffer + Leerzeichen) und sende Daten nach LCD (noch 3,6 ms). Das ist zu viel...
:
Bearbeitet durch User
Stephan schrieb: > immer nur ein Zeichen zu schreiben. Zeilen *Spalten > *4-5 Aktualisierungen pro Sekunde. zB. Seh ich auch so. Das BusyBit abfragen dauert genauso lang. Das zu nutzen für 'nö nächstes mal bitte' ist eigentlich völlig unnötig, Taktverschwendung. Stells dir als Warteschlange vor, die alle >~100msn ein Zeichen sendet, bis die Schlange leer ist.
Mach einfach kein komplettes Display-Update. Schick in jedem Hauptschleifendurchlauf nur eine Zeile. Oder eine halbe Zeile. Oder noch weniger.
Ja, das wäre auch möglich. Aber ich wollte alle gezeigten Ziffer endlos nach links schieben... Nicht möglich?
Maxim B. schrieb: > Zeichen x ca. 45 us Er möchte aber doch genau diese Wartezeit vermeiden, in der Zeit noch was andres tun. Was auch immer, awayne.
Teo D. schrieb: > Das BusyBit abfragen dauert genauso lang. Das zu nutzen für 'nö nächstes > mal bitte' ist eigentlich völlig unnötig, Taktverschwendung. Ja, ich habe das nun auch verstanden: BUSY bringt in Realität nichts. Aber wenn ich das nicht erprobt hätte, so hätte ich wohl immer gedacht, daß es mit BUSY schneller geht :) Idee, volle LCD-Erneuerung nur ein paar Mal pro Sekunde zu machen - das scheint mir eine Lösung zu sein! Danke! Aber diese 40-45 us - kann man sie doch irgendwie besser auszutzen, als einfach warten?
:
Bearbeitet durch User
Am besten verwendest Du einen kleinen Schedulder, z.B. FreeRTOS, dann machst Du für das LCD einen eigenen Task, der dann beliebig langsam die Befehle ausführen kann und den Rest Deiner Anwendung nicht tangiert.
Maxim B. schrieb: > Ja, das wäre auch möglich. > Aber ich wollte alle gezeigten Ziffer endlos nach links schieben... > Nicht möglich? Also einen DisplayShift. Den kannst du eh nur max. alle ~150ms senden (oben solltens eigentlich >~ 100µs sein), sonnst brauchst du garnicht Shiften. LCDs sind nicht wirklich gut für Lauftext geeignet, lesen/erkennen soll man ja auch noch was(?). Nur schnell was reinschiften als Animation, geht auch nicht viel schneller, da sonnst Augenkrebs droht. ;) Maxim B. schrieb: > Ja, ich habe das nun auch verstanden: BUSY bringt in Realität nichts. > > Aber wenn ich das nicht erprobt hätte, so hätte ich wohl immer gedacht, > daß es mit BUSY schneller geht :) Das auszulesen ordne ich auch nur als "Ja ich kanns :-)" ein. Oder was für faule Programmierer, die sich auch nich um die sehr langsamen Befehle kümmern o. auch nur dran denken wollen. :) Maxim B. schrieb: > Für Schedulder muß ich noch wachsen. Das ist noch zu kompliziert für > mich. Echt, alle xx-ms ein Flag zu setzen, das in der Hautschleife abfragen und löschen.... Lass dich durch so schwurblige begriffen nur nich verwirren. ;)
:
Bearbeitet durch User
> Bisher habe ich so gemacht: ein Text-Puffer 4x20=80 bytes. Alle > Manipulationen in Puffer, danach nach LCD alles senden. Teil das ganze in acht halbe LCDs auf. Dann brauchst du nur ein Byte fuer die DirtyBits. Alle 50 bis 200ms (Geschmacksache) pruefst du im IRQ ob etwas Dirty ist und uebertraegst dann einen Block. Sollte es nix zutun geben so kannst du einen Zaehler decrementieren. Ist der auf null initialisiert du dein Display und setzt alles auf Dirty. So kannst du dein LCD im laufenden Betrieb an und abstecken und es faengt sich auch falls es mal (z.B wegen EMV) abstuerzen sollte. Das ganze kann man natuerlich noch lustig erweitern. So koennte es zwei Textbuffer geben. Einen fuer normale Anwendungsinfos und einen Zweiten fuer Debuginformationen. Dann kannst du einfach dazwischen hin und herschalten. Olaf
Die Busyflag-Abfrage beim HD44780 ist relativ kurz, ich habe das in Assembler, allerdings in einer Schleife, so gelöst:
1 | LCD1: |
2 | push Temp ; auf busy-flag warten |
3 | ldi Temp,$30 ; R/W,RS=output,DB4..7=input |
4 | out DDRC,Temp ; |
5 | ldi Temp,$20 ; RS=0,R/W=1=read |
6 | out PortC,Temp ; |
7 | LCD2: |
8 | sbi PortD,E_LCD ; Enable _/~ |
9 | nop ; |
10 | nop ; Enable min. 450 ns |
11 | nop ; |
12 | nop ; |
13 | nop ; |
14 | nop ; |
15 | nop ; |
16 | nop ; |
17 | in Temp,PinC ; read BusyFlag |
18 | cbi PortD,E_LCD ; Enable ~\_ |
19 | nop ; |
20 | nop ; Enable min. 450 ns |
21 | nop ; |
22 | nop ; |
23 | nop ; |
24 | nop ; |
25 | nop ; |
26 | nop ; |
27 | sbi PortD,E_LCD ; Enable _/~ , dummy for 2nd nibble |
28 | nop ; |
29 | nop ; Enable min. 450 ns |
30 | nop ; |
31 | nop ; |
32 | nop ; |
33 | nop ; |
34 | nop ; |
35 | nop ; |
36 | cbi PortD,E_LCD ; Enable ~\_ |
37 | nop ; |
38 | nop ; |
39 | nop ; |
40 | nop ; |
41 | nop ; |
42 | nop ; |
43 | nop ; |
44 | nop ; |
45 | sbrc Temp,Busy ; Skip, if LCD ready (Busy-Flag=0) |
46 | rjmp LCD2 ; |
47 | ldi Temp,$3F ; (R/W,RS,DB4..7)=outputs |
48 | out DDRC,Temp ; |
49 | pop Temp ; fetch databyte |
Blöderweise ist beim Arduino "LCD-keypad-shield" der R/W fest auf write gelegt, man kann daher Busy nicht abfragen.
Ich habe jede 1ms einen IRQ. Die meisten LCD Befehle sind in 1ms durch. Ich habe einen Ringbuffer wie bei einer RS232 implementiert, der jede 1ms ein Zeichen hinaus schickt.Zur Sicherheit frage ich in der IRQ Routine den Status ab und lasse halt ggf den einen IRQ aus. Im nächsten ist es dann draußen. Im nächsten Schritt habe ich dann für jede Zeile Platz im RAM genommen und diese nur dann komplett hinaus gesandt, wenn sich was geändert hat. Mario
Ich hab schon ne Weile nix mehr mit AVRs gemacht, aber wie wäre es wenn du das BUSY dazu nutzt, um dir einen externen Interrupt zu generieren? Sowas wie EXTx oder PCINTx oder so...den Pin von A nach E umzukonfigurieren sollte nicht soviel Zeit beanspruchen.
Das Busy ist leider kein Hardwareausgang - jedenfalls am "Industriestandard" HD44780. Das wäre ja zu einfach gewesen.
Christoph K. schrieb: > Die Busyflag-Abfrage beim HD44780 ist relativ kurz, ich habe das in > Assembler, allerdings in einer Schleife, so gelöst: Dauert nur in etwas solange wie ein ein Schreibvorgang. Wenn eh gesichert ist, das nur geschrieben wird wenn das LCD nichts zu tun hat, warum dann noch Zeit mit der Busy-Abfrage verschwenden. PS: Hab mir dein Listing nicht angesehen, aber was passiert wenn keine Freigabe vom Busy_Flag kommt? ;) Olaf schrieb: > Sollte es nix zutun geben so kannst du einen Zaehler decrementieren. Ist > der auf null initialisiert du dein Display und setzt alles auf Dirty. Kommt halt immer drauf an, wo man da seine Prämissen setzen möchte. Bzw. wie weit man den Aufwand treiben möchte, dieses /Gebäude erdbebensicher/ zu machen. Ich bin auch immer geneigt das wie Olaf zu lösen, dann aber meist doch zu faul. |-}
Vielen Dank an alle!!! Nun habe ich neuen Ideen!!! Im allgemeinen klar, was ich machen muß. >> Teo Derix: Echt, alle xx-ms ein Flag zu setzen, das in der Hautschleife >> abfragen >>und löschen.... das habe ich immer so und nicht anders gemacht, um Interrupts so kurz wie nur möglich zu halten. Normalerweise benutze ich dafür GPIOR0, da hier Compiler mit sbi und cbi alles kürzer macht. In Interrupt, auch ISR_NAKED, wird lediglich Bit in GPIOR0 gesetzt, alles anderes in Hauptschleife. Mit Busy ist so: ich habe zwei Varianten für bedingte Compielen gemacht, mit Busy und mit Delay.
1 | //#define LCD_BUSY 1
|
2 | #define LCD_DELAY 2
|
1 | #ifdef LCD_BUSY
|
2 | // Wartet auf BUSY=0
|
3 | void lcd_busy_null( void ){ |
4 | unsigned char data; |
5 | |
6 | LCD_PORT_RS &= ~(1<<LCD_RS); // RS = 0, Befehle/BUSY |
7 | |
8 | // DB4-DB7 als Eingaenge schalten
|
9 | LCD_DDR_DB7 &= ~(1<<LCD_DB7); |
10 | LCD_DDR_DB6 &= ~(1<<LCD_DB6); |
11 | LCD_DDR_DB5 &= ~(1<<LCD_DB5); |
12 | LCD_DDR_DB4 &= ~(1<<LCD_DB4); |
13 | |
14 | // RW in Lesemodus (LCD als Ausgang)
|
15 | LCD_PORT_R_W |= (1<<LCD_R_W); |
16 | |
17 | do { // LCD ablesen |
18 | data = 0; |
19 | LCD_PORT_EN |= (1<<LCD_EN); // Enable auf 1 setzen |
20 | _delay_us( LCD_ENABLE_US ); // kurze Pause |
21 | if( LCD_PIN_DB7 & (1 << LCD_DB7)) data |= (1<<7); // BUSY lesen |
22 | LCD_PORT_EN &= ~(1<<LCD_EN); // Enable auf 0 setzen |
23 | _delay_us( LCD_ENABLE_US ); // kurze Pause |
24 | |
25 | LCD_PORT_EN |= (1<<LCD_EN); // Enable auf 1 setzen |
26 | _delay_us( LCD_ENABLE_US ); // kurze Pause |
27 | LCD_PORT_EN &= ~(1<<LCD_EN); // Enable auf 0 setzen |
28 | |
29 | } while(data); // warten bis BUSY=1, weiter wenn BUSY=0 |
30 | |
31 | // RW in Schreibemodus (LCD als Eingang)
|
32 | LCD_PORT_R_W &= ~(1<<LCD_R_W); |
33 | |
34 | // DB4-DB7 als Ausgaenge schalten
|
35 | LCD_DDR_DB7 |= (1<<LCD_DB7); |
36 | LCD_DDR_DB6 |= (1<<LCD_DB6); |
37 | LCD_DDR_DB5 |= (1<<LCD_DB5); |
38 | LCD_DDR_DB4 |= (1<<LCD_DB4); |
39 | }
|
40 | #endif /* LCD_BUSY */ |
und weiter immer so ähnlich:
1 | // Sendet ein Datenbyte an das LCD
|
2 | void lcd_data( uint8_t data ) { |
3 | |
4 | #ifdef LCD_BUSY
|
5 | lcd_busy_null(); |
6 | #endif
|
7 | |
8 | LCD_PORT_RS |= (1<<LCD_RS); // RS auf 1 setzen |
9 | lcd_out( data ); // zuerst die oberen, |
10 | lcd_out( data<<4 ); // dann die unteren 4 Bit senden |
11 | |
12 | #ifdef LCD_DELAY
|
13 | _delay_us( LCD_WRITEDATA_US ); |
14 | #endif
|
15 | }
|
In Theorie kann das etwas Zeit sparen, da Busy erst vor dem neuen Schreiben geprüft wird. D.h. MK schreibt in LCD, dann macht etwas anderes, bis noch mal ein Schreibvorgang in Frage kommt und erst dann wird es auf Busy gewartet. In Praxis habe ich aber keine Unterschiede bemerkt. Aber wenn ich versuche etwas mit Simulator zu prüfen, klappt es mit Busy-Variante nicht. Ich habe deshalb alles zuerst mit Delay geprüft und dann auf Busy umgeschaltet. Solange meine Platte auch fürs Lesen aus LCD gerichtet ist, kann ich wohl auch Busy weiter nutzen. Aber ich denke, wenn ich etwas neues mache, dann bleibt nur Delay. Einfach kein Sinn. Viele Grüße,
:
Bearbeitet durch User
Teo D. schrieb: > Wenn eh gesichert ist, das nur geschrieben wird wenn das LCD nichts zu > tun hat, warum dann noch Zeit mit der Busy-Abfrage verschwenden. Man stelle sich vor, man tauscht mal gegen ein langsameres LCD aus. Oder es braucht halt wegen irgendeinem nicht einkalkuliertem Faktor grade mal etwas länger. Darum ist es immer sinnvoll auf das Busy Flag zu warten bevor man was schreibt.
Also, ob ich mir das richtig einbilde: In Timer-Interrupt so einmal im 5 ms wird Flag "LCD_Daten_Bereit" abgefragt und wenn gesetzt, wird Flag "LCD_Daten_Schreiben" gesetzt. In der Hauptschleife wird ab und zu (wenn alles anderes schon erledigt) Flag "LCD_Daten_Schreiben" abgefragt und wenn gesetzt, wird Schreibvorgang für 1 Byte durchgeführt, LCD-Schreibpuffer aktualisiert und wenn noch nicht leer, wird Flag "LCD_Daten_Bereit" wieder gesetzt - bis durch Timer-Interrupt wieder Zeit kommt, wo Schreiben erlaubt wird. Etwa so? So kann LCD-Schreiben nicht öfter als ein Zeichen pro 5 ms gemacht werden, für 80 Zeichen 400 ms gebraucht, was noch als akzeptabel für langsame LCD erscheint... Dann brauche ich evtl. weder Busy noch Delay... Statt 5 ms kann das auch 1 ms oder gar 100 us heißen, Hauptsache garantiert mehr als notwendige Delay. Richtig? Dann brauche ich zwei LCD-Puffer: in einem werden alle Veränderungen gemacht. Zweite macht Anpassung zwischen Schreib-Wünschen und timer-gesteuerten Schreibvorgängen.
:
Bearbeitet durch User
Cyblord -. schrieb: > Darum ist es immer sinnvoll auf das Busy Flag zu warten bevor man was > schreibt. Bei meinen Projekten ist der Konstruktor der alleinige Bediener, oder nur einen Anruf weit entfernt. :) Für den Außeneinsatz, auf zB. einem 450Kg schweren Bodenverdichter, hab ich auch noch nichts gemacht. Hab noch nicht viel mit LCDs in C gemacht, ist sozusagen noch /im Aufbau/. Da kommt halt nur das gerade benötigte dazu. Und ja, seit die Busy-Abfrage drin ist, nutz ich diese, aus faulheit. PS: Manchmal nervt das programmieren so, das ich mich manchmal mit solch halb sinnvollen Postings ablenke.... :)
Maxim B. schrieb: > Also, ob ich mir das richtig einbilde: > ... > Etwa so? > ... > Richtig? > So macht man das. > Dann brauche ich zwei LCD-Puffer: in einem werden alle Veränderungen > gemacht. Zweite macht Anpassung zwischen Schreib-Wünschen und > timer-gesteuerten Schreibvorgängen. Zwei Puffer brauchst du dann, wenn du nur die Änderungen übertragen willst. Ist eleganter, zeitsparender und bei höheren Refreshraten natürlich auch flimmerfreier.
wahrscheinlich hat es Sinn, Warteschlange-lcdpuffer als int zu machen? Dann können in diesem Puffer Daten sowohl auch Befehle gespeichert werden.
Wenn man alle Pins des LCD an einem Port hat, muss man nicht die Bits einzeln setzen, wie oben: " // DB4-DB7 als Ausgaenge schalten LCD_DDR_DB7 |= (1<<LCD_DB7); LCD_DDR_DB6 |= (1<<LCD_DB6); LCD_DDR_DB5 |= (1<<LCD_DB5); LCD_DDR_DB4 |= (1<<LCD_DB4);" usw. Das LCD hat ja üblicherweise 7 Bit, dann muss man nur noch das achte Portbit berücksichtigen, dass das nicht ungewollt mitklappert. Auch das ist beim LCD-Keypad-shield ziermlich ungünstig verteilt. http://www.shieldlist.org/dfrobot/lcd Das hier meine ich, wird unter vielen Herstellernamen angeboten.
:
Bearbeitet durch User
Ich habe mir eine universelle Variante gemacht, wo ich alle Pins so legen kann, wie das für die Platte bequemer ist. Da LCD sowieso sehr langsam ist, macht das kaum noch Nachteile für Geschwindigkeit. Dafür viel mehr Flexibilität, als alles auf einem Port zu halten. MK hat viele Funktionen, die mit bestimmten Pins fest verbunden sind. So kann man für LCD einfach nehmen, was übrig bleibt. >> Auch das ist beim LCD-Keypad-shield Ich mache gewöhnlich eine eigene Platte. Das geht recht schnell mit DipTrace und LED-UV (selbst gemacht). das auf dem Bild habe ich jetzt.
:
Bearbeitet durch User
Maxim B. schrieb: > wahrscheinlich hat es Sinn, Warteschlange-lcdpuffer als int zu machen? > Dann können in diesem Puffer Daten sowohl auch Befehle gespeichert > werden. Eine Warteschlange hat aber den Nachteil, daß Ausgaben auf die gleiche Zeichenpositionen veralten können und das sie überlaufen kann. Besser ist es daher, einen LCD-Puffer für alle Zeichenstellen anzulegen, in den man direkt reinschreibt. Und der Timerinterrupt (1ms) kümmert sich im Hintergrund um das Schreiben ins LCD. Alle 80ms (4*20 LCD) ist dann das gesamte LCD refresht. So habe ich das im obigen Beispiel gemacht.
Peter D. schrieb: > Maxim B. schrieb: >> wahrscheinlich hat es Sinn, Warteschlange-lcdpuffer als int zu machen? >> Dann können in diesem Puffer Daten sowohl auch Befehle gespeichert >> werden. > > Eine Warteschlange hat aber den Nachteil, daß Ausgaben auf die gleiche > Zeichenpositionen veralten können und das sie überlaufen kann. Hat Vor- und Nachteile, ich habe das LCD beim letzen Projekt an einem Schieberegister (Hallo Peda) angeschlossen, und dieses via Hardware SPI angesteuert. Da fand ich eine Warteschlange schon sinnvoll, da ich auch viel am CGRAM arbeite. Ich hatte jetzt keine Speichernot auf dem AVR, daher hab ich auch gleich noch pro Befehl die Ausführungszeit mit in die Warteschlange gepackt. Die Lösung ist aus Sicht des Hauptprogramms "shoot and forget". Wird vom SPI Transfer Complete Interrupt und zusätzlich mit einer Callback-Routine von meiner Hauptuhr abgearbeitet. Die SPI-Frequenz liegt auch recht niedrig, das LCD ist ja ziemlich langsam, da kann man schon noch was im Hauptprogramm abarbeiten. Gruß Dominik
Teo D. schrieb: > PS: Hab mir dein Listing nicht angesehen, aber was passiert wenn keine > Freigabe vom Busy_Flag kommt? ;) Den Watchdog aktivieren. OK, zum Thema. Habe hier den DCF77-Decoder, der eine "starre" Timerinterruptportabfage braucht, direkte LCD-Ausgaberoutine würde da erheblich stören. Insgesamt brauche ich mindestens 3 SRAM Puffer. (Für die Plausibilitätsprüfung n+1 evtl. noch mehr.) Die LCD-Ausgabe erfolgt byteweise, charakterweise aus der Pufferabfrage in der Main-Routine. Dabei werden noch Umwandlungsroutinen BCD nach binär binär nach ASCII etc. durchlaufen. ciao gustav
Peter D. schrieb: > Besser ist es daher, einen LCD-Puffer für alle Zeichenstellen anzulegen, > in den man direkt reinschreibt. So habe ich von Anfang an gemacht. 80 bytes Puffer, dort alles nach Bedarf geändert und mit einer Funktion immer ganze LCD erneuert. Aber zu oft erneuert, nach jeder Veränderung in Puffer, das war mein Fehler. > Und der Timerinterrupt (1ms) kümmert > sich im Hintergrund um das Schreiben ins LCD. Alle 80ms (4*20 LCD) ist > dann das gesamte LCD refresht. So habe ich das im obigen Beispiel > gemacht. Ich werde das auch ausprobieren, Danke! Übrigens, mit Tasten und Drehgeber benutze ich Ihre Variante, noch mal Danke! Mit Drehgeber ist zwar nicht ganz so gut, obwohl zweite Variante, mit weniger Impulse - trotzdem bleibt jede zweite increment/dekrement zwischen Rasterpunkt. Chinesische Drehgeber, ich habe sie leider 20 Stück gekauft... Aber das ist ein anderes Thema, hier nur nebenbei... Also, Hauptprogramm verändert nach Bedarf die Daten in Puffer. Und in Interrupts wird permanent Zeichen um Zeichen nach LCD geschickt. Da für Befehl CURSOR_HOME bis 2 ms gebraucht wird, muß ich bei CURSOR_HOME 1 Takt weglassen oder als Takt 2 ms nehmen, richtig?
:
Bearbeitet durch User
Maxim B. schrieb: > Da für Befehl CURSOR_HOME bis 2 ms gebraucht wird, muß ich bei > CURSOR_HOME 1 Takt weglassen oder als Takt 2 ms nehmen, richtig? Den Befehl braucht man nicht. Man nimmt "Set DDRAM address".
Wenn man ganz faul ist, eine schnelle Ausgabe braucht und sich etwas Besonderes leisten möchte, kann man auch eine OLED-Anzeige verwenden. Der µC muß nicht warten, sondern die Anzeige wartet auf den µC ;-)
m.n. schrieb: > Wenn man ganz faul ist, eine schnelle Ausgabe braucht und sich etwas > Besonderes leisten möchte, Mein nächstes Spielzeug wird bestimmt ein Funk-LED-Liednummerbrett für die Kirche (ich bin zu faul geworden, die Liednummer einzustecken). Ich kaufe schon langsam chinesische LED-Module dafür (langsam, um 22-Euro-Grenze nicht zu überschreiten :) ). Dort kann man wohl sehr schnell alles schreiben... Allerdings kommt AVR dann auch auf die Grenze, wenn auch aus einem anderem Grund... Aber vielleicht mache ich das mit MAX7219, ich werde noch überlegen... Die Proben habe ich schon gemacht, alles ist realisierbar... Aber zuerst MIDI.
Karl B. schrieb: > Habe hier den DCF77-Decoder, der eine "starre" Timerinterruptportabfage > braucht, direkte LCD-Ausgaberoutine würde da erheblich stören. > Insgesamt brauche ich mindestens 3 SRAM Puffer. > (Für die Plausibilitätsprüfung n+1 evtl. noch mehr.) Erschließt sich mir nicht ganz, scannst Du den Pin? Also mein DCF77-Decoder hängt über einen Tiefpass am µC und steuert dann einen externen Interrupt... Am Anfang der ISR frage ich dann den timeStamp von der internen Clock ab und werte anhand der Differenz zur letzen fallenden Flanke aus ob es sich um ein gültiges Bit handelt... dann nachdem alle bits reingeshiftet wurden und zum Minutenbeginn die ~2 Sec Pause kommt wird der Code zwischengespeichert für den Parity check... Gruß Dominik
Dominik schrieb: > Erschließt sich mir nicht ganz, scannst Du den Pin? Hi, ist zwar OT, aber möchte interessehalber trotzdem antworten. Habe zwei Varianten ausprobiert. Die mit den Intererupts auf Flankentriggerung hatte den Nachteil, das sie in gestörter Umgebung dauernd INT0 Interrupts feuerte, die das Program schließlich abstürzen ließen. Auch bei nachfolgend wieder ungestörtem Empfang synchronisierte sich das Programm nicht mehr. Da es bei meiner Anwendung nicht auf die genaue Sekundenflankenerkennung ankommt, sondern um eine halbwegs 50-Hz-netzfrequenzunabhängige (;-) Uhr handelt, polle ich mit 1/100 sec Interrupt den Empfangs-Port. Dieser Timer-Interrupt sollte nicht gestört werden. Wenn eine LCD-Ausgabe in diese ISR reinkommt, stürzt das Programm ab. Deswegen Ausgabe-Routine in der Main-Loop und über diverse SRAM-Puffer. Ansonsten zeigt das Programm bei Empfangsstörungen (Kaffemühle neben die Antenne gestellt z.B.) zwar Lottozahlen, bleibt aber nicht hängen und synchronisiert bei einwandfreiem Empfang wieder. Experimentiere gerade mit der "Gangreserve". Die funktioniert zwar bei völlig fehlendem Signal, werde sie aber noch so umstricken müssen, dass sie den Decoder abklemmt, wenn Signal zugemüllt. Die handelsüblichen Funkuhren synchronisieren ja zu festgelegten angenommenen störungsarmen Zeiten mitten in der Nacht. Meine Uhr ständig. soweit OT ciao gustav
Stimmt ist OT, aber irgendwie gehört es ja zum dem Interrupt Thema: Ich habe mit dem Tiefpass ganz gute Erfahrungen gemacht (Kommt natürlich auch auf das jeweilige DCF-Modul an), breche die ISR aber auch sofort ab, wenn keine Mindestzeit vorliegt, zusätzlich könnte man den Interrupt natürlich auch je für z.B. 0.9 Sekunden sperren. Karl B. schrieb: > Ansonsten zeigt das Programm bei Empfangsstörungen (Kaffemühle neben > die Antenne gestellt z.B.) zwar Lottozahlen, bleibt aber nicht hängen > und synchronisiert bei einwandfreiem Empfang wieder. > Experimentiere gerade mit der "Gangreserve". Die funktioniert zwar bei > völlig fehlendem Signal, werde sie aber noch so umstricken müssen, dass > sie den Decoder abklemmt, wenn Signal zugemüllt. Ich bin schlussendlich dazu übergegangen nur noch ein komplettes Signal zuzulassen und auf Gültigkeit zu prüfen, ist zwar auch keine Garantie, aber bisher hatte ich keine Lottozahlen mehr. Die Prüfung ob die empfangende Zeit nach der vorherigen liegt wäre noch eine Verbesserung. Hier eine nicht besonders schöner aber funktionaler Codeauschnitt zur DCF-Paritätsprüfung:
1 | uint8_t DCF77_decode(void) |
2 | { |
3 | if(dcf77lastDecoded==1) |
4 | return 0; |
5 | uint64_t timeStampLocal; |
6 | uint64_t timeCodeLocal; |
7 | ATOMIC_BLOCK(ATOMIC_FORCEON) |
8 | { |
9 | timeStampLocal=dcf77LastTimeStamp; |
10 | timeCodeLocal=dcf77LastTimeCode; |
11 | } |
12 | //Startbit |
13 | if(!((timeCodeLocal>>DCF77_BIT_START)&0x01)) |
14 | { |
15 | return 0; |
16 | } |
17 | |
18 | //Minute |
19 | uint8_t minuteBCD=(timeCodeLocal>>DCF77_BIT_OFFSET_MINUTE)&DCF77_BIT_MASK_MINUTE; |
20 | uint8_t minute=(minuteBCD&0x0F)+10*(minuteBCD>>4); |
21 | if((parity_even_bit(minuteBCD)!=((timeCodeLocal>>(DCF77_BIT_OFFSET_MINUTE+7))&0x01)) |
22 | ||(minute>59)) |
23 | { |
24 | return 0; |
25 | } |
26 | |
27 | //Hour |
28 | uint8_t hourBCD=(timeCodeLocal>>DCF77_BIT_OFFSET_HOUR)&DCF77_BIT_MASK_HOUR; |
29 | uint8_t hour=(hourBCD&0x0F)+10*(hourBCD>>4); |
30 | if((parity_even_bit(hourBCD)!=((timeCodeLocal>>(DCF77_BIT_OFFSET_HOUR+6))&0x01)) |
31 | ||(hour>23)) |
32 | { |
33 | return 0; |
34 | } |
35 | |
36 | //Day/WeekDay/Month/Year |
37 | uint32_t calendarBCD=(timeCodeLocal>>DCF77_BIT_OFFSET_DAY)&0x003FFFFF; |
38 | uint8_t day; |
39 | uint8_t weekDay; |
40 | uint8_t month; |
41 | uint8_t year; |
42 | |
43 | uint8_t parity=parity_even_bit(calendarBCD&0xFF); |
44 | parity+=parity_even_bit((calendarBCD>>8)&0xFF); |
45 | parity+=parity_even_bit((calendarBCD>>16)&0x3F); |
46 | parity%=2; |
47 | if(parity!=((timeCodeLocal>>(DCF77_BIT_OFFSET_DAY+22))&0x01)) |
48 | { |
49 | return 0; |
50 | } |
51 | else //Decode Calendar Data |
52 | { |
53 | day=(uint8_t)((calendarBCD&0x0F)+10ul*((calendarBCD&DCF77_BIT_MASK_DAY)>>4)); |
54 | calendarBCD>>=(DCF77_BIT_OFFSET_WEEKDAY-DCF77_BIT_OFFSET_DAY); |
55 | weekDay=(calendarBCD&DCF77_BIT_MASK_WEEKDAY); |
56 | if(weekDay>7) |
57 | { |
58 | return 0; |
59 | } |
60 | calendarBCD>>=(DCF77_BIT_OFFSET_MONTH-DCF77_BIT_OFFSET_WEEKDAY); |
61 | month=(calendarBCD&0x0F)+10ul*((calendarBCD&DCF77_BIT_MASK_MONTH)>>4); |
62 | if(month>12) |
63 | { |
64 | return 0; |
65 | } |
66 | calendarBCD>>=(DCF77_BIT_OFFSET_YEAR-DCF77_BIT_OFFSET_MONTH); |
67 | year=(calendarBCD&0x0F)+10ul*((calendarBCD&DCF77_BIT_MASK_YEAR)>>4); |
68 | if(year<18) |
69 | { |
70 | return 0; |
71 | } |
72 | switch(month) |
73 | { |
74 | //31 days |
75 | case(1): |
76 | case(3): |
77 | case(5): |
78 | case(7): |
79 | case(8): |
80 | case(10): |
81 | case(12): |
82 | if(day>31) |
83 | return 0; |
84 | break; |
85 | //30 days |
86 | case(4): |
87 | case(6): |
88 | case(9): |
89 | case(11): |
90 | if(day>30) |
91 | return 0; |
92 | break; |
93 | // February |
94 | case(2): |
95 | if((((year%4)==0)&&(day>29)) |
96 | ||(((year%4)!=0)&&(day>28))) |
97 | return 0; |
98 | break; |
99 | } |
100 | } |
101 | |
102 | //All Checks for Parity and Plausibility passed |
103 | dcf77lastValidTimeStamp=timeStampLocal; |
104 | dcf77lastValidMinute=minute; |
105 | dcf77lastValidHour=hour; |
106 | dcf77lastValidDay=day; |
107 | dcf77lastValidWeekDay=weekDay; |
108 | dcf77lastValidMonth=month; |
109 | dcf77lastValidYear=year; |
110 | dcf77lastDecoded=1; |
111 | return 1; |
112 | } |
Hier die Codeausschnitte zur Gangreserve:
1 | uint64_t DCF77_getTime(uint8_t *hour, uint8_t *minute, uint8_t *second) |
2 | { |
3 | uint64_t timeStamp=SYSTEMCLOCK_getTime64(); |
4 | uint64_t ticksElapsed=timeStamp-dcf77lastValidTimeStamp; |
5 | |
6 | uint64_t secondsElapsed=(ticksElapsed/SYSTEMCLOCK_TICKS_PER_SECOND); |
7 | *second=(uint8_t) (secondsElapsed%60ull); |
8 | |
9 | uint64_t minutesElapsed=(secondsElapsed/60ull)+((uint64_t) dcf77lastValidMinute); |
10 | *minute=(uint8_t) (minutesElapsed%60ull); |
11 | |
12 | uint64_t hoursElapsed=(minutesElapsed/60ull)+((uint64_t) dcf77lastValidHour); |
13 | *hour=(uint8_t) (hoursElapsed%24ull); |
14 | |
15 | return timeStamp; |
16 | } |
17 | |
18 | uint64_t DCF77_getDate(uint8_t *day, uint8_t *month, uint8_t *year, uint8_t *weekDay) |
19 | { |
20 | uint64_t timeStamp=SYSTEMCLOCK_getTime64(); |
21 | uint64_t ticksElapsed=timeStamp-dcf77lastValidTimeStamp; |
22 | |
23 | uint64_t secondsElapsed=(ticksElapsed/SYSTEMCLOCK_TICKS_PER_SECOND); |
24 | uint64_t minutesElapsed=(secondsElapsed/60ull)+((uint64_t) dcf77lastValidMinute); |
25 | uint64_t hoursElapsed=(minutesElapsed/60ull)+((uint64_t) dcf77lastValidHour); |
26 | uint64_t daysElapsed=hoursElapsed/24ull; |
27 | *day=dcf77lastValidDay; |
28 | *weekDay=dcf77lastValidWeekDay; |
29 | *month=dcf77lastValidMonth; |
30 | *year=dcf77lastValidYear; |
31 | |
32 | if(daysElapsed>0) |
33 | for(uint16_t d=0; d<daysElapsed; d++) |
34 | { |
35 | *weekDay=(*weekDay==7) ? 1 : *weekDay+1; |
36 | (*day)++; |
37 | switch(*month) |
38 | { |
39 | //31 days |
40 | case(1): |
41 | case(3): |
42 | case(5): |
43 | case(7): |
44 | case(8): |
45 | case(10): |
46 | case(12): |
47 | if(*day==32) |
48 | { |
49 | *day=1; |
50 | if(*month==12) |
51 | { |
52 | *month=1; |
53 | (*year)++; |
54 | } |
55 | else |
56 | { |
57 | (*month)++; |
58 | } |
59 | } |
60 | break; |
61 | //30 days |
62 | case(4): |
63 | case(6): |
64 | case(9): |
65 | case(11): |
66 | if(*day==31) |
67 | { |
68 | *day=1; |
69 | (*month)++; |
70 | } |
71 | break; |
72 | // February |
73 | case(2): |
74 | if((((*year%4)==0)&&(*day==29)) |
75 | ||(((*year%4)!=0)&&(*day==28))) |
76 | { |
77 | (*day)=1; |
78 | (*month)++; |
79 | } |
80 | break; |
81 | } |
82 | } |
83 | return timeStamp; |
84 | } |
Gruß Dominik
if(*day==32) ich würde auf größer 31 abfragen. if(*month==12) hier muss es gleich 13 oder größer 12 heißen. Und die Schaltjahrabfrage ist auch nicht ganz korrekt (2100 ist kein SJ).:)
eProfi schrieb: > if(*day==32) > ich würde auf größer 31 abfragen. > if(*month==12) > hier muss es gleich 13 oder größer 12 heißen. > Und die Schaltjahrabfrage ist auch nicht ganz korrekt (2100 ist kein > SJ).:) In der parity abfrage wird größer geprüft... der andere code block ist ja ne schleife da ist gleich schon ok und die ==12 ist der jahreswechsel bei tag 32. was nicht heißen soll, das der code absolut fehlerfrei ist, der ist q+d ;-) Das mit dem Schaltjahr könnte allerdings ein Problem werden... Bei guter Temperierung ist das Programm dann vielleicht wirklich noch im flash... und vllt. braucht man dann sogar die Langzeitgangreserve weil das DCF-Signal abgeschaltet wurde... gruß Dominik
Nun habe ich eine Lösung auf Idee von Peter Dannegger geschmiedet.
1 | /****************/
|
2 | main.c |
3 | |
4 | ....
|
5 | |
6 | ISR(TIMER3_COMPA_vect, ISR_NAKED){ // Timer 3 interrupt. |
7 | |
8 | FLAGG |= (1<<F_PULS); // Flag fuer Puls einsetzen |
9 | |
10 | reti(); // da ISR_NAKED |
11 | }
|
12 | |
13 | int main(void){ |
14 | init(); |
15 | sei(); |
16 | while(1){ |
17 | // Bedienung von Puls 1 ms
|
18 | if( FLAGG & (1<<F_PULS) ){ |
19 | timer3_puls(); |
20 | lcd_puff_lcd(); // LCD erneuern |
21 | |
22 | FLAGG &= ~(1<<F_PULS); // Flag fuer Puls = 0 |
23 | }
|
24 | |
25 | }
|
26 | return 0; |
27 | }
|
28 | |
29 | /****************/
|
30 | lcd.h |
31 | #define LCD_ZEICHEN_PRO_ZEILE 20
|
32 | #define LCD_ZEILEN 4
|
33 | #define LCD_PUFFER (LCD_ZEICHEN_PRO_ZEILE * LCD_ZEILEN)
|
34 | #define PUF_ERNEUERN (LCD_PUFFER + 4)
|
35 | |
36 | #define LCD_DDADR_LINE1 0x00
|
37 | #define LCD_DDADR_LINE2 0x40
|
38 | #define LCD_DDADR_LINE3 (LCD_DDADR_LINE1 + LCD_ZEICHEN_PRO_ZEILE)
|
39 | #define LCD_DDADR_LINE4 (LCD_DDADR_LINE2 + LCD_ZEICHEN_PRO_ZEILE)
|
40 | |
41 | #define LCD_SET_DDADR 0x80
|
42 | |
43 | /****************/
|
44 | lcd.c |
45 | |
46 | ...
|
47 | |
48 | static unsigned char lcd_puff[LCD_ZEILEN] [LCD_ZEICHEN_PRO_ZEILE] = { |
49 | {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, |
50 | {' ',' ','I','c','h',' ','b','i','n',' ','M','a','x','i','m',' ',' ',' ',' ',' '}, |
51 | {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, |
52 | {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}}; |
53 | |
54 | static unsigned char lcd_erneuern = 0; |
55 | |
56 | // Sendet eine 4-bit Ausgabeoperation an das LCD
|
57 | static void lcd_out( uint8_t data ) { |
58 | LCD_PORT_DB4 &= ~(1<<LCD_DB4); |
59 | if (data & 0x10){ |
60 | LCD_PORT_DB4 |= (1<<LCD_DB4); |
61 | }
|
62 | LCD_PORT_DB5 &= ~(1<<LCD_DB5); |
63 | if (data & 0x20){ |
64 | LCD_PORT_DB5 |= (1<<LCD_DB5); |
65 | }
|
66 | LCD_PORT_DB6 &= ~(1<<LCD_DB6); |
67 | if (data & 0x40){ |
68 | LCD_PORT_DB6 |= (1<<LCD_DB6); |
69 | }
|
70 | LCD_PORT_DB7 &= ~(1<<LCD_DB7); |
71 | if (data & 0x80){ |
72 | LCD_PORT_DB7 |= (1<<LCD_DB7); |
73 | }
|
74 | |
75 | LCD_PORT_EN |= (1<<LCD_EN); // Enable auf 1 setzen |
76 | _delay_us( LCD_ENABLE_US ); // kurze Pause |
77 | LCD_PORT_EN &= ~(1<<LCD_EN); // Enable auf 0 setzen |
78 | }
|
79 | |
80 | void lcd_puff_data(unsigned char data, unsigned char platz){ // Schreibt data in lcd-puffer |
81 | signed char index_x, index_y; |
82 | if((platz >= 0) && (platz < LCD_PUFFER)){ |
83 | index_x = platz / 4; |
84 | index_y = platz % 4; |
85 | lcd_puff[index_x][index_y] = data; |
86 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben |
87 | }
|
88 | }
|
89 | |
90 | void lcd_puff_null(void){ // LCD-Puffer = Leerzeichen |
91 | for(int x=0;x<LCD_ZEILEN;x++){ |
92 | for(int y=0;y<LCD_ZEICHEN_PRO_ZEILE;y++){ |
93 | lcd_puff[x][y] = ' '; // Leerzeichen |
94 | }
|
95 | }
|
96 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben |
97 | }
|
98 | |
99 | void lcd_puff_schieb_links(unsigned char data){ // alle Symbole in LCD-Puffer nach links |
100 | // unten rechts = data
|
101 | unsigned char neu_dat, alt_dat; |
102 | neu_dat = data; |
103 | |
104 | for(int x=(LCD_ZEILEN - 1);x>=0;x--){ |
105 | for(int y=(LCD_ZEICHEN_PRO_ZEILE - 1);y>=0;y--){ |
106 | alt_dat = lcd_puff[x][y]; |
107 | lcd_puff[x][y] = neu_dat; |
108 | neu_dat = alt_dat; |
109 | }
|
110 | }
|
111 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben |
112 | }
|
113 | |
114 | void lcd_puff_schieb_rechts(unsigned char data){ // alle Symbole in LCD-Puffer nach rechts |
115 | // oben links = data
|
116 | unsigned char neu_dat, alt_dat; |
117 | neu_dat = data; |
118 | |
119 | for(int x=0;x<LCD_ZEILEN;x++){ |
120 | for(int y=0;y<LCD_ZEICHEN_PRO_ZEILE;y++){ |
121 | alt_dat = lcd_puff[x][y]; |
122 | lcd_puff[x][y] = neu_dat; |
123 | neu_dat = alt_dat; |
124 | }
|
125 | }
|
126 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben |
127 | }
|
128 | |
129 | unsigned int lcd_ziffer(unsigned char data){ // wandelt in char-HEX |
130 | unsigned int word = 0; |
131 | unsigned char byte; |
132 | byte = data>>4; // hohe haelfte |
133 | if (byte > 9) byte += 7; |
134 | byte += 0x30; |
135 | word = (unsigned char)byte; |
136 | word = (word << 8); |
137 | |
138 | byte = data & 0x0f; // tiefe Haelfte |
139 | if (byte > 9) byte += 7; |
140 | byte += 0x30; |
141 | word |= (unsigned char)byte; |
142 | |
143 | return word; |
144 | }
|
145 | |
146 | /* Funktion fuer automatische Kopieren aus puffer nach LCD, wird jede 1 ms gemacht */
|
147 | void lcd_puff_lcd(void){ // LCD-Puffer >> LCD, jede 1 ms |
148 | unsigned char data; |
149 | if(lcd_erneuern){ |
150 | static signed char lcd_adr_zeile = ( -1 ); // Zeilenadresse fuer LCD-Erneuern |
151 | static signed char lcd_adr_zeichen = (LCD_ZEICHEN_PRO_ZEILE - 1); // Zeichenadresse fuer LCD-Erneuern |
152 | // Voreinstellungen, um erste Befehl war: (LCD_SET_DDADR + LCD_DDADR_LINE1)
|
153 | lcd_erneuern--; |
154 | |
155 | // Puffer incrementieren
|
156 | lcd_adr_zeichen++; |
157 | if(lcd_adr_zeichen == LCD_ZEICHEN_PRO_ZEILE){ |
158 | lcd_adr_zeichen = (-1); |
159 | LCD_PORT_RS &= ~(1<<LCD_RS); // Befehl |
160 | switch(++lcd_adr_zeile){ |
161 | case 0 : |
162 | data = (LCD_SET_DDADR + LCD_DDADR_LINE1); // LCD auf Line 1 |
163 | break; |
164 | case 1 : |
165 | data = (LCD_SET_DDADR + LCD_DDADR_LINE2); // LCD auf Line 2 |
166 | break; |
167 | case 2 : |
168 | data = (LCD_SET_DDADR + LCD_DDADR_LINE3); // LCD auf Line 3 |
169 | break; |
170 | case 3 : |
171 | data = (LCD_SET_DDADR + LCD_DDADR_LINE4); // LCD auf Line 4 |
172 | lcd_adr_zeile = (-1); |
173 | break; |
174 | default : |
175 | data = 0; |
176 | break; |
177 | }
|
178 | } else { |
179 | LCD_PORT_RS |= (1<<LCD_RS); // Daten |
180 | data = lcd_puff[lcd_adr_zeile][lcd_adr_zeichen]; |
181 | }
|
182 | |
183 | // Auf LCD schreiben
|
184 | lcd_out(data); |
185 | lcd_out(data << 4); |
186 | }
|
187 | }
|
Mir scheint es bequemer zu sein, Puffer als 4x20 zu machen, da so einfacher geht, Befehle in die gesamte Schleife einzubinden. Um nicht zwecklos immer auf LCD zu schreiben, ist ein Zähler deklariert: lcd_erneuern. Nach jeder Aktualisierung von Puffer wird lcd_erneuern mit einer Zahl aktiviert, Zeichenzahl + Befehle. So wird sich diese Automatik nach 84 Schreibvorgänge beruhigen, falls im Puffer keine Änderungen vorkommen. Geschickter wäre es wohl, Puffer als Ring einzulegen, so könnte CPU viel Zeit beim Schieben sparen...
:
Bearbeitet durch User
Mit einem Ring-Puffer habe ich auch ausprobiert. So kann man viel Zeit beim Schieben sparen. Komisch - aber alles scheint zu funktionieren...
1 | lcd.h |
2 | ...
|
3 | #define LCD_ZEICHEN_PRO_ZEILE 20
|
4 | #define LCD_ZEILEN 4
|
5 | #define LCD_PUFFER (LCD_ZEICHEN_PRO_ZEILE * LCD_ZEILEN)
|
6 | |
7 | #define LCD_DDADR_LINE1 0x00
|
8 | #define LCD_DDADR_LINE2 0x40
|
9 | #define LCD_DDADR_LINE3 (LCD_DDADR_LINE1 + LCD_ZEICHEN_PRO_ZEILE)
|
10 | #define LCD_DDADR_LINE4 (LCD_DDADR_LINE2 + LCD_ZEICHEN_PRO_ZEILE)
|
11 | |
12 | #define LCD_ZY_LINE1 0
|
13 | #define LCD_ZY_LINE2 21
|
14 | #define LCD_ZY_LINE3 42
|
15 | #define LCD_ZY_LINE4 63
|
16 | #define LCD_ZY_MAX 84
|
17 | |
18 | #define PUF_ERNEUERN 0
|
19 | |
20 | lcd.c |
21 | ...
|
22 | |
23 | static unsigned char lcdpuff[LCD_PUFFER] = {}; |
24 | static unsigned char lcdpuff_index = 0; |
25 | static unsigned char lcd_erneuern = PUF_ERNEUERN; |
26 | |
27 | /* Mit lcd_erneuern = PUF_ERNEUERN wird Automatik gestartet, die LCD-Puffer
|
28 | nach LCD Zeichen um Zeichen jede 1 ms kopiert */
|
29 | |
30 | void lcd_puff_data(unsigned char data, unsigned char platz){ // Schreibt data in lcd-puffer |
31 | |
32 | unsigned char yy; |
33 | if(platz < LCD_PUFFER){ |
34 | yy = lcdpuff_index + platz; |
35 | if(yy >= LCD_PUFFER) yy -= LCD_PUFFER; |
36 | lcdpuff[yy] = data; |
37 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben |
38 | }
|
39 | }
|
40 | |
41 | void lcd_puff_null(void){ // LCD-Puffer = Leerzeichen |
42 | |
43 | for(int x=0;x<LCD_PUFFER;x++){ |
44 | lcdpuff[x] = ' '; // Leerzeichen |
45 | }
|
46 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben*/ |
47 | }
|
48 | |
49 | void lcd_puff_schieb_links(unsigned char data){ // alle Symbole in LCD-Puffer nach links |
50 | // unten rechts = data
|
51 | unsigned char ttt; |
52 | lcdpuff_index++; |
53 | if(lcdpuff_index >= LCD_PUFFER) lcdpuff_index = 0; |
54 | ttt = lcdpuff_index + (LCD_PUFFER - 1); |
55 | if(ttt >= LCD_PUFFER) ttt -= LCD_PUFFER; |
56 | lcdpuff[ttt] = data; |
57 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben */ |
58 | }
|
59 | |
60 | void lcd_puff_schieb_rechts(unsigned char data){ // alle Symbole in LCD-Puffer nach rechts |
61 | // oben links = data
|
62 | if(lcdpuff_index == 0) lcdpuff_index = LCD_PUFFER; |
63 | lcdpuff_index--; |
64 | lcdpuff[lcdpuff_index] = data; |
65 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben */ |
66 | }
|
67 | |
68 | /* Funktion fuer automatische Kopieren aus puffer nach LCD, wird jede 1 ms gemacht */
|
69 | void lcd_puff_lcd(void){ // LCD-Puffer >> LCD, jede 1 ms |
70 | // lcd_erneuern = 0 startet Kopieren von Puffer nach LCD
|
71 | unsigned char data = 0; |
72 | unsigned char data_index = 0; |
73 | if(lcd_erneuern != LCD_ZY_MAX){ |
74 | |
75 | switch(lcd_erneuern){ |
76 | |
77 | case LCD_ZY_LINE1 : |
78 | data = (LCD_SET_DDADR + LCD_DDADR_LINE1); // LCD auf Line 1 |
79 | LCD_PORT_RS &= ~(1<<LCD_RS); // Befehl |
80 | break; |
81 | |
82 | case LCD_ZY_LINE2 : |
83 | data = (LCD_SET_DDADR + LCD_DDADR_LINE2); // LCD auf Line 2 |
84 | LCD_PORT_RS &= ~(1<<LCD_RS); // Befehl |
85 | break; |
86 | |
87 | case LCD_ZY_LINE3 : |
88 | data = (LCD_SET_DDADR + LCD_DDADR_LINE3); // LCD auf Line 3 |
89 | LCD_PORT_RS &= ~(1<<LCD_RS); // Befehl |
90 | break; |
91 | |
92 | case LCD_ZY_LINE4 : |
93 | data = (LCD_SET_DDADR + LCD_DDADR_LINE4); // LCD auf Line 4 |
94 | LCD_PORT_RS &= ~(1<<LCD_RS); // Befehl |
95 | break; |
96 | |
97 | default : |
98 | |
99 | LCD_PORT_RS |= (1<<LCD_RS); // Daten |
100 | if(lcd_erneuern < LCD_ZY_LINE2){ |
101 | data_index = lcd_erneuern - 1; |
102 | } else if(lcd_erneuern < LCD_ZY_LINE3) { |
103 | data_index = lcd_erneuern - 2; |
104 | } else if(lcd_erneuern < LCD_ZY_LINE4) { |
105 | data_index = lcd_erneuern - 3; |
106 | } else { |
107 | data_index = lcd_erneuern - 4; |
108 | }
|
109 | |
110 | data_index += lcdpuff_index; |
111 | if(data_index >= LCD_PUFFER) data_index -= LCD_PUFFER; |
112 | |
113 | data = lcdpuff[data_index]; |
114 | }
|
115 | |
116 | lcd_erneuern++; |
117 | // Auf LCD schreiben
|
118 | lcd_out(data); |
119 | lcd_out(data << 4); |
120 | }
|
121 | }
|
Leider ist das keine Lösung, wo man nur mit Anpassen von #define verschiedene LCD bedienen kann - für so etwas reicht mein Geschick noch nicht...
:
Bearbeitet durch User
Guten Tag, ich habe eine weitere Frage: das ist alles schön und gut: ich schreibe in RAM-Puffer, und alles kommt auf LCD. Aber wie könnte ich LCD über Puffer noch vollständiger simulieren? Ich möchte gerne Cursor sichtbar machen, wo Eingabe gerade stattfindet - aber Eingabe sollte über RAM-Puffer erfolgen. Ist das machbar? Vielen Dank,
Im Puffer zwischen dem bestehenden und dem zu zeigenden Zeichen im zB. Sekundentakt umschalten. Den Rest macht ja die ISR.
Beitrag #5367181 wurde vom Autor gelöscht.
Danke! Ich werde das ausprobieren! Wahrscheinlich brauche ich dann zwei char: Zeiger für blinkende Stelle und data für kommenden Wert. Evtl. auch dritte als Zähler für Blinkfrequenz. Ich habe schon von Anfang an auch LED so etwa gesteuert: in 1 ms-ISR wird int decrementiert und wenn 1->0, wird LED gelöscht. Zum Einschalten schreibt Hauptprogramm gewünschte Wert in ms in int. Deshalb denke ich, hier sollte das auch klappen. So nebenbei habe ich auch nun DDS ausprobiert, für MIDI-Noten von Nr. 0 bis Nr. 127, Fd = 50 KHz.
1 | void tim2_synth(void){ // Wenn F_SYNTH = 1 |
2 | if( FLAGG & (1<<F_SYNTH) ){ |
3 | synth_freq += tonfrequenz[synth_note]; |
4 | if(synth_freq & 0x800000){ |
5 | ZV_PORT |= (1<<ZV); |
6 | } else { |
7 | ZV_PORT &= ~(1<<ZV); |
8 | }
|
9 | }
|
10 | }
|
Funktioniert ohne alles anderes zu stören (LCD, Tasten, Drehgeber, MIDI-Empfang)! Sehr nützliche Versuchsbau - wenn AVR nach 10000 mal am Ende wird, so werde ich viel mehr über Programmieren wissen, als vorher. Es lohnt sich, so etwas selber zu bauen und nicht mit fertigen fremden Platten.
:
Bearbeitet durch User
Wenn Du generell mit ISR und Puffer arbeitest, kannst Du den R/W vom Display auf GND legen. Du liest ja nicht vom Display und hast einen Port übrig, bei Bedarf.
Schreib die Daten aufs Display und nur aktualisieren wenn sich wirklich was ändert. Hier muss man sich jetzt die Frage stellen ob den gesamten Inhalt aktualisiert oder nur die entsprechenden Zeichen.
Stephan schrieb: > Wenn Du generell mit ISR und Puffer arbeitest, kannst Du den R/W vom > Display auf GND legen. Danke, schon gemacht. Nur habe ich keine Lust, diese schon fertige Platte zu ändern. Deshalb bekommt R/~W schon bei INIT 0 und so auch bleibt. Für die Zukunft mache ich diese Dummheit nicht mehr. Aber ich sollte auch das ausprobieren :) chris schrieb: > Schreib die Daten aufs Display und nur aktualisieren wenn sich wirklich > was ändert. Danke, schon gemacht. Dafür dient in Programm für LCD-erneuern
1 | #define PUF_ERNEUERN 0
|
2 | #define LCD_ZY_MAX 84
|
3 | static unsigned char lcd_erneuern = PUF_ERNEUERN; |
Alle, die in Puffer schreiben, schreiben auch lcd_erneuern = PUF_ERNEUERN;
1 | if(lcd_erneuern >= LCD_ZY_MAX) |
, wird Erneuerung zu Ende. chris schrieb: > Hier muss man sich jetzt die Frage stellen ob den gesamten Inhalt > aktualisiert oder nur die entsprechenden Zeichen. Ich denke, das wäre zu aufwendig und bringt so gut wie nichts, da AVR-Auslastung ziemlich klein bleibt, einmal in 1 ms eine Operation mit LCD durchzuführen. Ich weiß zwar wirklich nicht, ob diese Lösung mit ISR so universell ist. Aber Systemtakt gibt es so gut wie immer, und nach jedem Schreiben während den nächsten 40 us etwas nützliches zu tun (bei 16 MHz sind das 640 Befehle! )- das ist Grund, ein paar Zeilen mehr zu schreiben. Vor mir steht nun eine andere Frage: ich habe LCD-Puffer als Ring gemacht. Das beschleunigt Schieben auf LCD:
1 | void lcd_puff_schieb_links(unsigned char data){ // alle Symbole in LCD-Puffer nach links |
2 | // unten rechts = data
|
3 | lcdpuff_index++; |
4 | if(lcdpuff_index >= LCD_PUFFER) lcdpuff_index = 0; |
5 | |
6 | lcdpuff[lcd_puff_adresse(lcdpuff_index,(LCD_PUFFER - 1))] = data; |
7 | lcd_erneuern = PUF_ERNEUERN; // Notwendigkeit, LCD zu schreiben |
8 | }
|
Dafür aber muß die Puffer-Adresse etwas aufwendiger berechnet werden:
1 | static inline unsigned char lcd_puff_adresse(unsigned char base, unsigned char index){ |
2 | /* aus base und index wird faktische Adresse gerechnet */
|
3 | unsigned char yy; |
4 | yy = base + index; |
5 | if(yy >= LCD_PUFFER) yy -= LCD_PUFFER; |
6 | return yy; |
7 | }
|
Wird Puffer statisch gemacht, so wird es einfacher mit Adresse, aber Schieben wird um fast zwei Ordnungen langsamer. Von anderer Seite wird Schieben von LCD-Inhalt nicht so oft gebraucht... Welche Variante ist sinnvoller? Aber das sind Operationen, die nur gemacht werden, wenn in Puffer geschrieben wird. Nicht die jede 1 ms 84 mal nach Erneuerung gemacht werden.
:
Bearbeitet durch User
Man könnte das busy auf einen Int-fähigen Eingang legen und auf die Flanke triggern. Ich hab bei meinen Displays bisher immer per Timer geschrieben.
Roland E. schrieb: > Man könnte das busy auf einen Int-fähigen Eingang legen und auf die > Flanke triggern. Wie sollte das im Schaltplan aussehen?
Ich habe für weitere Entwicklung doch etwas geändert. Unschön, mit Drähtchen, aber AVR habe ich fest gelötet. Deshalb möchte ich schon solange umprogrammieren, wie es noch geht, vor dem ich eine andere Platte mache (10 000 mal ja von Atmel versprochen). Nun sind LED mit Data von LCD gemeinsam, da kein Lesen von LCD mehr vorgesehen ist. Ich hoffe, kurze Unregelmäßigkeiten während Datenaustausch werden auf LED kaum sichtbar. Auch Erweiterungsbuchse bekommt etwas mehr Leitungen, so daß für +5 Volt und Grund nur je eine Leitung bleibt - hoffentlich bringt das kein Problem, es werden keine stromhungrige Erweiterungsplatten benutzt. Da Programmodule von Anfang an mit beliebigen Ports für Tasten usw. gedacht sind, wird nur minimale Programmkorrektur gebraucht.
:
Bearbeitet durch User
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.