Forum: Mikrocontroller und Digitale Elektronik LCD Befehl zu langsam in BASCOM


von Gregor (Gast)


Lesenswert?

Hallo und grüß euch,

ich habe ein Problem, ich hoffe ihr könnt mir helfen.

Ich habe ein vordeffiniertes Protokoll, dass von einem M8 über die usart 
weggeschickt wird, also die Daten halt.Es ist eine eindraht rs232 
Verbindung.Also TX und RX sind zusammengeschaltet. Auf dem zweitem M8 
ist ein 2x16 Display angeschlossen. Der soll die Daten anzeigen, die er 
vom ersten M8 bekommt und ein byte zurückschicken.

Das Protokoll sieht so aus: der erste m8 schickt 34 byts los,das dauert 
40ms. Danach soll der zweite M8 mit dem Display die Daten anzeigen und 
aber 5ms,nachdem er die bytes erhalten hat, ein byte wegschicken. danach 
sind noch mal 25ms pause. Also das gesamte Spiel dauert 70ms. Dann geht 
alles wieder von vorne los.

Es funktioniert alles perfekt, solange ich den LCD-Befehl weglasse! 
führe ich ihn aus, wirft es alles über den Haufen und die Timings 
stimmen nicht mehr!
Was könnte ich tun?

Hier mal mein Code:
1
$regfile = "M8def.dat"
2
$initmicro
3
$crystal = 8000000
4
$hwstack = 32
5
$swstack = 10
6
$framesize = 40
7
$baud = 9600
8
9
Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2
10
Config Lcd = 16 * 2
11
Cursor Off
12
13
Txd_enable Alias Ucsrb.txen
14
Txd_pullup Alias Portd.1
15
Rxd_enable Alias Ucsrb.rxen
16
Rxd_pullup Alias Portd.0
17
18
Enable Interrupts
19
20
21
Config Com1 = 9600 , Synchrone = 0 , Parity = Odd , Stopbits = 1 , Databits = 9 , Clockpol = 0
22
Open "com1:" For Random As #1
23
On Urxc Rx_isr
24
Enable Urxc
25
Text = ""
26
27
28
29
Do
30
31
Reset Txd_enable                                            'TX ausschalten
32
Set Rxd_enable                                              'RX einschalten
33
34
If Len(text) = 34 Then                                      'Wenn 34 bytes empfangen
35
36
'lcd text;                                                   ' am Display anzeigen
37
38
Set Txd_enable                                              'TX einschalten
39
Reset Rxd_enable                                            'RX ausschalten
40
41
42
Waitms 4
43
Ucsrb.txb8 = 0
44
Udr = 240                                                   'Das Zeichen F0 senden
45
Do : Loop Until Ucsra.udre = 1
46
Waitms 10
47
48
Text = ""
49
End If
50
51
Loop
52
End
53
54
'*************************************************************************
55
56
Rx_isr:
57
Text = Text + Chr(udr)
58
Return
59
60
_init_micro:
61
Config Portd = &B11111100
62
Set Rxd_pullup
63
Reset Txd_pullup
64
Return

Danke, Gregor

von Gast (Gast)


Lesenswert?

> wirft es alles über den Haufen und die Timings stimmen nicht mehr!

Was stimmt denn genau nicht mehr?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Wieso gibst du nicht einfach jedes Zeichen sofort nach dem Empfang auf 
dem Display aus?

BTW:
Deine Programmstruktur/Denkweise ist grundlegend falsch  :-o
Üblicherweise sollte der Ablauf eines Programms vom User-Interface 
entkoppelt werden.
D.h. hauptsächlich wird eingelesen, gearbeitet und geschrieben. Und nur 
nebenher angezeigt. Denn es macht überhaupt nichts aus, wenn das Display 
z.B. nur 3 mal pro Sekunde aktualisiert wird. Oder wenn wegen hohen 
Rechenbedarfs das Display mal eine Sekunde stillsteht...

von micha (Gast)


Lesenswert?

Kenne kein BASCOM :-)

Mach Dir eine eigene LCD-Ausgabe Routine, am besten per Interrupt und 
Zeichenweise, nicht den ganzen String auf einmal. Wahrscheinlich besteht 
die vorandene darauf, allen Text zu schreiben, evetuell mit hart 
codierten Waitstates nach jedem Zeichen. Wenn das "nur" 10 ms sind macht 
das für die ganzen 34 Zeichen schon 5 von deinen Protokoll-Zyklen. Und 
Update des Textes am LCD nur wenn nötig. Alle 70ms kannst Du sowieso auf 
einem LCD nicht sehen. 1 mal pro Secunde reicht evetuell auch.

von Peter D. (peda)


Lesenswert?

Es gibt 2 Sachen, die die Programmstruktur einfacher machen.

1. Du legst ne FIFO für den UART-Empfang an.
Damit wird der Empfang von der Auswertung entkoppelt.
D.h. es können ruhig schon neue Zeichen eintreffen und nichts geht 
verloren:

Beitrag "AVR-GCC: UART mit FIFO"

2. Du legst nen Textspeicher für das LCD an und gibst im Hintergrund per 
Timerinterrupt (z.B. alle 1ms) ein Byte an das LCD.
Damit kann die Applikation direkt in den Speicher schreiben und die 
LCD-Zeit stört nicht mehr:

Beitrag "Formatierte Zahlenausgabe in C"

Der Mensch kann eh nicht mehr als 2..5 Werte pro s ablesen.
Mit 70ms (14/s) bist Du also schon zu schnell, d.h. nicht mehr 
ergonomisch.


Peter

von Gregor (Gast)


Lesenswert?

Vielen lieben Dank für eure Antworten,

Dass der code nicht ordentlich durchdacht und geschrieben ist, kommt 
davon, dass ich es einfach nicht besser kann. Bin Einsteiger, aber ein 
bisschen was geht ja.

Nun kann ich mich genauer in das Thema einarbeiten, da ich ein paar neue 
Stichwörter habe, woran ich mich halten kann!

Ich werd jetzt mal versuchen, nicht das ganze Display auf einmal zu 
beschreiben, sondern byteweise mit 1ms dauer dazwischen. Und natürlich 
in einem Interrupt.

Wie das mit dem aktualisieren und so geht, muss ich auch noch schauen!

vielen Dank mal, ich hab jetzt wirklich genug zu tun, wie ich das sehe 
;o)

Lg
Gregor

von Gregor (Gast)


Lesenswert?

@Peter,darf ich dich noch was fragen bitte?

Dein zweiter Punkt behandelte ja den Textspeicher. Da ich C nicht kann 
und somit den Code nicht verstehe, wollte ich dich fragen, ob du mir das 
vielleicht kurz genauer erklären könntest?

Habe ich da in der Hauptroutine den LCD Befehl drinnen? Wie lege ich 
einen Textspeicher an? Meinst du einen String?

Ich weis, blöde Fragen, aber ich weis es leider nicht...

Lg
Gregor

von Karl H. (kbuchegg)


Lesenswert?

Du hast ein 2-Zeilen / 20 Zeichen Display.

Dann machst du dir im Programm 2 String Variable mit jeweils 20 Zeichen.
Jeder String korrespondiert mit einer Zeile im Display.

Dazu einen Timer, der einen regelmässigen Interrupt auslöst.
In diesem Interrupt wird ein Zeichen aus dem String an seine jeweilge 
Position im LCD ausgegeben.

Will dein Programm eine Ausgabe machen, dann schreibt es das 
auszugebende einfach in den String an die richtige Position. Der 
Timerinterrupt sorgt dann dafür, dass irgenwann später diese Ausgabe 
auch tatsächlich auf dem LCD landet.

Vorteil: Dein Progamm wird nicht durch Ausgaben aufgehalten. In den 
String schreiben geht schnell

Nachteil: Ausgaben erscheinen etwas zeitverzögert auf dem Display. Wobei 
man diesen 'Nachteil' relativieren muss. Die Zeitverzögerung ist so 
gering, dass sie niemandem auffällt.

von Gregor (Gast)


Lesenswert?

Hallo Karl,

ich habe das jetzt mal soweit probiert, deine Lösung umzusetzen. Vorerst 
mal nur die obere Zeile des 2x16 Displays.

In der Timer Interrupt Routine wird der String an das Display 
weitergegeben. Nur leider funktioniert es nicht. Lasse ich den LCD und 
Locate Befehl weg, funktioniert die Verbindung! Überprüfe ich mit Hterm 
und RS232.

Hier mal mein Code. Könntest du mir sagen, was ich falsch mache?
1
$regfile = "M8def.dat"
2
$initmicro
3
$crystal = 8000000
4
$hwstack = 32
5
$swstack = 10
6
$framesize = 40
7
$baud = 9600
8
9
Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2
10
Config Lcd = 16 * 2
11
Cursor Off
12
13
14
Tasterauf Alias Portc.2
15
Portc.2 = 1
16
Tasterunter Alias Portc.1
17
Portc.1 = 1
18
Txd_enable Alias Ucsrb.txen
19
Txd_pullup Alias Portd.1
20
Rxd_enable Alias Ucsrb.rxen
21
Rxd_pullup Alias Portd.0
22
23
Dim Rs232byte As Byte
24
Dim R As Byte
25
Dim I As Byte
26
Dim Z As Byte
27
Dim L As Byte
28
Dim X As Byte
29
Dim Taste As Byte
30
Dim Text As String * 40
31
'Dim Rs232buffer(41) As Byte At Text Overlay
32
Dim Oben As String * 17
33
Dim Unten As String * 17
34
R = 0
35
I = 0
36
Z = 0
37
L = 2
38
X = 0
39
Config Timer1 = Timer , Prescale = 1                        'Konfiguriere Timer1
40
Enable Timer1                                               'schalte den Timer1 ein
41
On Timer1 Isr_von_timer1                                    'verzweige bei Timer1 überlauf zu   Isr_von_Timer1
42
Timer1 = 49536
43
44
45
Config Com1 = 9600 , Synchrone = 0 , Parity = Odd , Stopbits = 1 , Databits = 9 , Clockpol = 0
46
Open "com1:" For Random As #1
47
On Urxc Rx_isr
48
Enable Urxc
49
Enable Interrupts
50
51
52
53
Do
54
55
Reset Txd_enable                                            'TX ausschalten
56
Set Rxd_enable                                              'RX einschalten
57
58
If Len(text) = 34 Then
59
Oben = Mid(text , 2 , 16)                                   '16 bytes oben
60
Unten = Mid(text , 18 , 16)                                 '16 bytes unten
61
Set Txd_enable                                              'TX ausschalten
62
Reset Rxd_enable
63
Waitms 5
64
Ucsrb.txb8 = 0
65
Udr = Taste                                                 'zeichen senden
66
Do : Loop Until Ucsra.udre = 1
67
Waitms 10
68
Text = ""
69
End If
70
71
Loop
72
End
73
74
'*************************************************************************
75
76
Rx_isr:
77
Incr I
78
Text = Text + Chr(udr)
79
Return
80
81
Isr_von_timer1:                                             'ISR von Timer1
82
Timer1 = 49536                                              '49536Timer1 soll wieder von 34285 wegzählen
83
Incr Z
84
Locate 1 , Z
85
Lcd Mid(oben , Z , 1);                                      'Mid(oben , 1 , 1);
86
If Z = 16 Then
87
Z = 0
88
End If
89
Return
90
91
_init_micro:
92
Config Portd = &B11111100
93
Config Portc = &B00000000
94
Set Rxd_pullup
95
Reset Txd_pullup
96
Return

von Peter D. (peda)


Lesenswert?

Gregor schrieb:

> weitergegeben. Nur leider funktioniert es nicht. Lasse ich den LCD und
> Locate Befehl weg, funktioniert die Verbindung! Überprüfe ich mit Hterm
> und RS232.

Diese beiden Befehle sollten eigentlich nur max 100µs dauern, d.h. bei 
1ms Interrupt kein Problem.
Vielleicht hast Du was falsch eingestellt, schau mal in die Erklärung zu 
den beiden Befehlen.
Am besten ist natürlich die Befehle selber zu programmieren, dann kann 
man sie optimal anpassen.

Ich hab in den Interrupt zusätzliche States eingefügt, die den Befehl 
zum Setzen der 1. bzw. 2. Zeile machen.
Damit erfolgt pro Interrupt nur ein LCD-Zugriff und man kann das 
Busywaiting komplett weglassen.
Denn bis zum nächsten Interrupt sind ja garantiert 50µs vergangen.


Den MID-Befehl als Arrayzugriff zu mißbrauchen, könnte auch viel länger 
dauern als nötig. Besser per Index auf das Arrayelement zugreifen.




> Hier mal mein Code. Könntest du mir sagen, was ich falsch mache?

Sieht eigentlich o.k. aus (außer dem seltsamen MID-Befehl), aber Bascom 
ist nicht meine Stärke.

Hier mal die Statemachine in C, wie ich das meine:
1
#define LCD_COLUMN      20
2
#define LCD_LINE        2
3
4
5
uint8_t lcd_buff[LCD_LINE * LCD_COLUMN];        // text display memory
6
7
8
ISR( TIMER0_OVF_vect )                          // interrupt every 1ms
9
{
10
  static uint8_t i = 0;                         // statemachine counter
11
12
  i++;                                          // count up
13
  switch( i ){
14
    case 1:                                     // state 1:
15
        lcd_pos( 0, 0 ); break;                 // line 0, column 0
16
17
    case 2 ... 1 + LCD_COLUMN:                  // state 2 ... 21:
18
        lcd_data( lcd_buff[i-2] ); break;       // data of 1. line
19
20
    case 2 + LCD_COLUMN:                        // state 22:
21
        lcd_pos( 1, 0 ); break;                 // line 1, column 0
22
23
    case 3 + LCD_COLUMN ... 2 + 2 * LCD_COLUMN: // state 23 ... 42:
24
        lcd_data( lcd_buff[i-3] ); break;       // data of 2. line
25
26
    default:                                    // state 43:
27
        i = 0;                                  // and again
28
  }
29
}

Peter

von Karl H. (kbuchegg)


Lesenswert?

Den Locate zb brauchst du ja nicht bei jeder Zeichenausgabe. Das LCD 
setzt seinen internen Cursor ja sowieso nach dem Ausgeben eines Zeichens 
an die nächste Position.
Locate brauchst du doch nur, wenn die Zeile gewechselt werden muss.
1
Lcd Mid(oben , Z , 1);                                      'Mid(oben , 1 , 1);

Hmm. Da ist mein BASCOM zu schwach. Aber das sieht mir nicht sehr 
zeitsparend aus. Da wird offenbar Stringverarbeitung betrieben um ein 
einzelnes Zeichen auszugeben.
1
Timer1 = 49536                                              '49536Timer1 soll wieder von 34285 wegzählen

Wenn du den Timer voll durchzählen lässt, wird es dann besser?

von Gregor (Gast)


Lesenswert?

Hallo,

danke für eure Antworten, da habe ich wieder einiges zu probieren.

Nur so nebenbei:

Was ist besser für eine Uart Empfangsroutine?

1:
Rx_isr:
Incr I
Text = Text + Chr(udr)
Return

oder

2:
Rx_isr:
rs232buffer(i)=UDR
return

Bei der ersten Routine empfange ich ja Strings, bei der zweiten Routine 
kommen die Werte in ein Array.

Was findet ihr, wäre besser?

Lg
Gregor

von STK500-Besitzer (Gast)


Lesenswert?

>Bei der ersten Routine empfange ich ja Strings, bei der zweiten Routine
>kommen die Werte in ein Array.

>Was findet ihr, wäre besser?

Aus C-Sicht betachtet: Egal. in C sind Strings auch nichts anderes als 
Arrays, die noch ein Endezeichen besitzen.

von STK500-Besitzer (Gast)


Lesenswert?

Die Antwort dürfte ziemlich sinnlos gewesen sein.
Auf das Array kannst doch einfacher zugreifen, weil du einen Index auf 
die Elemente hast, und nicht erst großartig mit "mid(..." rumbasteln 
musst.

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.