Forum: Mikrocontroller und Digitale Elektronik STM32F4 Timer, Ansteuerung eines Displays


von riterkuni (Gast)


Lesenswert?

Hallo,

nachdem ich mich die letzte Stunde mit dem Suchen nach Lösungen 
herumgeschlagen habe, wende ich mich an euch.

Vor mir befindet sich ein STM32F4 (Taktfrequenz 168Mhz) und ein Display 
4*20 Zeichen mit einem SSD1803 Cotroller. Derzeit habe ich das Display 
erfolgreich angesteuert mit einer delay Funktion. Da ich unmöglich 
während der Datenverarbeitung 39µS warten kann, würde ich gerne auf 
Timer umstellen.

Funktion derzeit:
Datenbyte an die IOs legen
EnablePin auf 1
1µS delay
EnablePin auf 0
39µS delay --> danach wieder von vorne mit dem nächsten 
Byte/Zeichen/Befehl

40µS entsprechen immerhin 6720 Takte sinnlos warten.


Die delays würde ich gerne durch Timer ersetzen. Timer7 wird so 
initalisiert, dass er alle 50µS einen Interrupt erzeugt.

Die Frage, die sich mir stellt ist:
Wie bekomme ich die Daten gescheit in den Interrupt?

sprintf( buffer, "%9.7f ", doubleVariable); und buffer als Globale 
Variable halte ich für eine schlechte Idee, zumal es bei der sprintf 
Funktion mit volatile eine Warnung geben wird.

Viele Grüße

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

riterkuni schrieb:
> Funktion derzeit:
> Datenbyte an die IOs legen
> EnablePin auf 1
> 1µS delay
> EnablePin auf 0
> 39µS delay --> danach wieder von vorne mit dem nächsten
> Byte/Zeichen/Befehl

Da wird doch anscheinend nur das Datenbyte übergeben, sollte also mit 
einem volatile uint8_t (oder besser mit einen Puffer, s.u.) zu lösen 
sein. Das m.E. schwierigere an der Sache ist das Vermeiden von Blocking, 
wenn die ISR noch nicht abgearbeitet ist. Ein Flag könnte hier hilfreich 
sein, mit dem die ISR signalisiert, das sie gearbeitet hat. Dein stdout 
muss das Flag setzen und abfragen, ob es noch gesetzt ist, dann blockt 
es entweder, oder, besser, schiebt das Datenbyte in einen Fifo, der dann 
im Hintergrund vom Timer abgearbeitet wird.

von riterkuni (Gast)


Lesenswert?

Nachtrag:

Wie bekomme ich die 1µS für den Enable Impuls noch weg?

Idee dazu war: (Display reagiert auf eine fallende Flanke am EnablePin)
EnablePin auf 1
Datenbyte an die IOs legen
EnablePin auf 0
39µS delay --> danach wieder von vorne mit dem nächsten
Byte/Zeichen/Befehl

Bei der Methode gibt es viele Fehler bei der Übertragung. Scheinbar 
benötigt der LCD Controller Zeit zwischen Anlegen des Bytes und 
Enablepin fallende Flanke. Konnte im Datenblatt nichts dazu finden.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

riterkuni schrieb:
> Wie bekomme ich die 1µS für den Enable Impuls noch weg?

Du baust dir eine Statemachine. Ein Flag sagt der Timer Routine, das sie 
das E generieren soll, sonst ist Datenbyte Zeit. Wenn du die Wahl hast, 
wäre aber auch evtl. ein SPI Display einfach simpler...

von riterkuni (Gast)


Lesenswert?

Matthias Sch. schrieb:
> Da wird doch anscheinend nur das Datenbyte übergeben, sollte also mit
> einem volatile uint8_t (oder besser mit einen Puffer, s.u.) zu lösen
> sein. Das m.E. schwierigere an der Sache ist das Vermeiden von Blocking,
> wenn die ISR noch nicht abgearbeitet ist. Ein Flag könnte hier hilfreich
> sein, mit dem die ISR signalisiert, das sie gearbeitet hat. Dein stdout
> muss das Flag setzen und abfragen, ob es noch gesetzt ist, dann blockt
> es entweder, oder, besser, schiebt das Datenbyte in einen Fifo, der dann
> im Hintergrund vom Timer abgearbeitet wird.

Genau so ist es geplant. Der µC erledigt seine Berechnungen. Ergebnis 
geht in einen Buffer (FIFO), und bei jedem Interrupt überträgt die ISR 
ein Zeichen.

Müsste ich nicht aber, wenn ich mit volatile uint8_t immer nur ein 
Zeichen als volatile setze, andauernd im Hauptprogramm überprüfen ob die 
ISR aktiv war? Hört sich auch wieder sehr CPU lastig an :S

von Karl H. (kbuchegg)


Lesenswert?

Wenn du sowieso schon einen Timer einsetzt, würde ich auch mal in die 
folgende Richtung überlegen.

Im Prozessor reservierst du dir ein 4*20 char Array, welches deinen 
'Bildschirmspeicher' darstellt.
Dein Progrtamm kümmert sich nicht darum, wie die tatsächlichen Inhalte 
von diesem Bildschirmspeicher zum LCD kommen. Wenn es etwas auszugeben 
gilt, dann schreibt es das einfach in diesen Speicher.

Im Timer Interrupt wird laufend bei jedem Aufruf jeweils 1 Byte von 
diesem Bildschirmspeicher zum tatsächlichen LCD übertragen. Auf die Art 
wird 'nach und nach' jede Ausgabe in den Bildschirmspeicher dann auch 
tatsächlich auf dem PCD sichtbar. Wobei wir bei "nach und nach" über 
Verzögerungen reden, die für einen Menschen praktisch nicht wahrnehmbar 
sind. Bei jedem Timer Interrupt Aufruf könntest du zb jeweils 
abwechselnd die Sache mit dem Enable Pin und dem Character regeln.
1
Interrupt Routine
2
3
  NrAufruf++;
4
  if( NrAufruf == 3 )
5
    NrAufruf = 0;
6
7
  if( NrAufruf == 0 ) {
8
    Enable auf 1
9
    nächsten char ausgeben
10
  }
11
  else if( NrAufruf == 1 ) {
12
    Enable auf 0
13
  }
14
}

mit den Zeiten müsste man ein bischen spielen, so dass in der Interrupt 
Routine sowohl die 1µS (bzw. ein Vielfaches davon) und die 39µs 
unterzubringen sind.

AUf die Art brauchst du keine komplizierte FIFO und auch der 
Rechenzeitbedarf in der Interrupt Routine ist mehr als überschaubar. 
Wenn das komplette LCD pro Sekunde 5 mal ausgegeben wird, dann langt das 
dicke. Schneller kann kein Mensch sich verändernde Inhalt am LCD 
erfassen.

von riterkuni (Gast)


Lesenswert?

Karl Heinz schrieb:

> Im Prozessor reservierst du dir ein 4*20 char Array, welches deinen
> 'Bildschirmspeicher' darstellt.
> Dein Progrtamm kümmert sich nicht darum, wie die tatsächlichen Inhalte
> von diesem Bildschirmspeicher zum LCD kommen. Wenn es etwas auszugeben
> gilt, dann schreibt es das einfach in diesen Speicher.
>

Am Ende der Berechnungen habe ich int und double. Wie bekomme ich die 
denn zu einem charArray, welches ich aus der ISR erreichen kann?
sprintf macht mir leider Probleme in Kombination mit volatile. Gibt die 
Warnung: passing argument 1 of 'sprintf' discards 'volatile' qualifier 
from pointer target type [enabled by default]

> mit den Zeiten müsste man ein bischen spielen, so dass in der Interrupt
> Routine sowohl die 1µS (bzw. ein Vielfaches davon) und die 39µs
> unterzubringen sind.
>
> AUf die Art brauchst du keine komplizierte FIFO und auch der
> Rechenzeitbedarf in der Interrupt Routine ist mehr als überschaubar.

Die Zeiten sind relativ unkritisch. Selbst wenn ich 50µS Interrups 
nehme, also mit dem Enable Interrupt 2*50µS=100µS, werden 250Hz 
erreicht.

von Karl H. (kbuchegg)


Lesenswert?

riterkuni schrieb:

> Am Ende der Berechnungen habe ich int und double. Wie bekomme ich die
> denn zu einem charArray, welches ich aus der ISR erreichen kann?

Wie kriegst du sie denn jetzt rein?

Nochmal: der ISR ist das piep.schnurz-egal was die Character in diesem 
Buffer bedeuten oder wo sie herkommen. Die gibt die Character einfach 
nur aus. Und zwar laufend und reihum.

Wenn du einen int ausgeben willst, dann benutzt du zb itoa und anstatt 
den so erhaltenen String auf die LCD Funktionen zu geben, kopierst du 
den String in diesen Bildschirmspeicher an die der Ausgabeposition 
entsprechenden Stelle.
Für dein Programm (mit Ausnahme der ISR) IST dieser Bildschirmspeicher 
die Ausgabefläche. Wer auch immer irgendwas auszugeben hat, kopiert den 
entsprechenden Character (oder den String) in diesen Bildschirmspeicher 
und kümmert sich nicht weiter drum, wie die Zeichen dann von dort aufs 
LCD kommen. Das ist der Job des Timerinterrupts.

> sprintf macht mir leider Probleme in Kombination mit volatile.
> Gibt die Warnung: passing argument 1 of 'sprintf' discards
> 'volatile' qualifier from pointer target type [enabled by default]

An dieser Stelle denke ich, ist es zulässig von der Annahme auszugehen, 
dass sprintf die Character auch wirklich schreiben wird und nicht der 
Optimizer alles wegoptimieren wird. Also: volatile einfach wegcasten.

von riterkuni (Gast)


Lesenswert?

Karl Heinz schrieb:
> Wie kriegst du sie denn jetzt rein?

Gar nicht, da liegt ja mein Problem. Wie gesagt, ich habe das ganze 
derzeit provisorisch mit eine delay Funktion gelöst. Soll jetzt aber auf 
TimerInterrups umgestellt werden.

So sah es bis jetzt aus:
1
sprintf(buffer, "%9.7f ", doublevariable);
2
lcd_ausgabe(buffer);
1
void lcd_ausgabe(const char *data) {
2
  while (*data != '\0')
3
    LCD_Data(*data++); //Erkennt ob Formatierungszeichen
4
}
1
void LCD_Data(uint8_t lcddata) {
2
//Hier nur die Struktur.
3
  GPIO_ResetBits();   //Reset Daten- und Steuerleitungen
4
  GPIO_SetBits();     //Setze Daten- und Steuerleitungen
5
6
  GPIO_SetBits();     //Enable Pin setzen
7
  delay_nus(1);       //1µS warten
8
9
  GPIO_ResetBits(GPIOE, E_Display);   //Enable Pin Reset (fallende Flanke, Display übernimmt Daten)
10
11
  delay_nus(43);    //Wartezeit fürs nächste Zeichen
12
13
}


Karl Heinz schrieb:
> Für dein Programm (mit Ausnahme der ISR) IST dieser Bildschirmspeicher
> die Ausgabefläche. Wer auch immer irgendwas auszugeben hat, kopiert den
> entsprechenden Character (oder den String) in diesen Bildschirmspeicher
> und kümmert sich nicht weiter drum, wie die Zeichen dann von dort aufs
> LCD kommen. Das ist der Job des Timerinterrupts.

Das Verstehe ich, so habe ich es mir auch vorgestellt. Damit das 
Hauptprogramm halt nichts mehr mit dem ganzen zutun hat.

von chris (Gast)


Lesenswert?

riterkuni schrieb:
> Gar nicht, da liegt ja mein Problem.

du musst jetzt doch nur deine Funktion "lcd_ausgabe" anpassen, so dass 
sie nicht mehr LCD_Data aufruft, sondern die Zeichen in den Puffer 
schreibt.
Und sicher hast du auch sowas wie lcd_setcursor, das musst du entweder 
auch anpassen, so dass es den Puffer-index entsprechend setzt, oder du 
baust das mit in die "lcd_ausgabe" mit ein:
1
static uint8_t buffer_index;
2
volatile char lcd_buffer[80];
3
4
void lcd_setcursor(uint8_t zeile, uint8_t spalte)
5
{
6
   // buffer_index aus Zeile und Spalte berechnen
7
   buffer_index = ....;
8
}
9
10
void lcd_ausgabe (char* data)
11
{
12
    strcpy(&lcd_buffer[buffer_index], data);
13
}

oder alternativ

1
volatile char lcd_buffer[80];
2
3
void lcd_ausgabe (char* data, uint8_t zeile, uint8_t spalte)
4
{
5
    uint8_t buffer_index;
6
    uint8_t i;
7
8
    // mit Zeile und Spalte den Index ausrechnen und ab da Daten reinkopieren
9
    buffer_index = ....;
10
11
    strcpy(&lcd_buffer[buffer_index], data);
12
13
}

von riterkuni (Gast)


Lesenswert?

Nun klappt es.

Habe folgendes geändert:

sprintf wird jetzt explizit als (char*) gecastet, damit auch keine 
Warnung mehr.
Es gibt jetzt ein Array fürs ganze Display.
Nachdem die Daten im Array sind wird ein "Busy" Flag gesetzt und der 
Timer aktiviert.
Enable und Datenleitungen werden Interruptgesteuert. Es sind 2x 45µS 
Interrups pro Zeichen nötig
Wenn das Array auf das LCD übertragen wurde, wird das "Busy" Flag 
zurückgesetzt und der Timer deaktiviert.

Die Interrups brauchen zusammen knapp 180 Takte, damit kann ich gut 
leben.

Vielen Dank

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.