Forum: Mikrocontroller und Digitale Elektronik LCD mit Interrupts?


von Maxim B. (max182)


Lesenswert?

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
von Stephan (Gast)


Lesenswert?

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.

von Maik S. (yellowbird)


Lesenswert?

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 ?

von Maxim B. (max182)


Lesenswert?

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
von Teo D. (teoderix)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

Mach einfach kein komplettes Display-Update. Schick in jedem 
Hauptschleifendurchlauf nur eine Zeile. Oder eine halbe Zeile. Oder noch 
weniger.

von Maxim B. (max182)


Lesenswert?

Ja, das wäre auch möglich.
Aber ich wollte alle gezeigten Ziffer endlos nach links schieben...
Nicht möglich?

von Teo D. (teoderix)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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
von Johnny B. (johnnyb)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

Für Schedulder muß ich noch wachsen. Das ist noch zu kompliziert für 
mich.

von Peter D. (peda)


Lesenswert?


von Teo D. (teoderix)


Lesenswert?

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
von Olaf (Gast)


Lesenswert?

> 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

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

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.

von Mario (Gast)


Lesenswert?

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

von Ecaps (Gast)


Lesenswert?

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.

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

Das Busy ist leider kein Hardwareausgang - jedenfalls am 
"Industriestandard" HD44780. Das wäre ja zu einfach gewesen.

von Teo D. (teoderix)


Lesenswert?

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. |-}

von Maxim B. (max182)


Lesenswert?

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
von Cyblord -. (cyblord)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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
von Teo D. (teoderix)


Lesenswert?

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.... :)

von Ralf G. (ralg)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

wahrscheinlich hat es Sinn, Warteschlange-lcdpuffer als int zu machen?
Dann können in diesem Puffer Daten sowohl auch Befehle gespeichert 
werden.

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

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
von Maxim B. (max182)


Angehängte Dateien:

Lesenswert?

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
von Peter D. (peda)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

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

von Karl B. (gustav)


Lesenswert?

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

von Maxim B. (max182)


Lesenswert?

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
von Peter D. (peda)


Lesenswert?

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".

von Maxim B. (max182)


Lesenswert?

Danke!
Nun mache ich das !

von m.n. (Gast)


Lesenswert?

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 ;-)

von Maxim B. (max182)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

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

von Karl B. (gustav)


Lesenswert?

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

von Dominik (Gast)


Lesenswert?

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

von eProfi (Gast)


Lesenswert?

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).:)

von Dominik (Gast)


Lesenswert?

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

von Maxim B. (max182)


Lesenswert?

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
von Maxim B. (max182)


Lesenswert?

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
von Maxim B. (max182)


Lesenswert?

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,

von Stephan (Gast)


Lesenswert?

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.
von Maxim B. (max182)


Angehängte Dateien:

Lesenswert?

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
von Stephan (Gast)


Lesenswert?

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.

von chris (Gast)


Lesenswert?

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.

von B. P. (skorpionx)


Lesenswert?


von Maxim B. (max182)


Lesenswert?

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
von Roland E. (roland0815)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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?

von Maxim B. (max182)


Angehängte Dateien:

Lesenswert?

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
Noch kein Account? Hier anmelden.