Forum: Compiler & IDEs Schieberegister-LCD vereinfachen


von Johannes (Gast)


Lesenswert?

Hallo liebe Leute!

Habe vor, mit meinem ATmega16 im AVR-Studio mit dem GCC ein LCD 
anzusteuern. Und zwar funktioniert dieses nach dem Schieberegister 
Prinzip.
Man schiebt in Summe 48 Bits (für die 48 Segmente) nacheinander ins LCD, 
gibt nach jedem Bit einen Clock und Lädt abschließend das 
Schieberegister über eine logische "1" am Load-Eingang in das Display.

Nun meine Frage. Wie kann ich in C eine "Vereinfachung" schreiben. Ich 
muss ja schließlich für jede Veränderung des Displays die 48 Bits 
schieben,clocken und loaden. Wenn beispielsweise von bit20 bis bit48 
keine ungleichen werte sind, dann kann ich eine schleife machen....


Danke!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Wie kann ich in C eine "Vereinfachung" schreiben.

Indem du SPI nimmst.

von Falk (Gast)


Lesenswert?

@Johannes

>Man schiebt in Summe 48 Bits (für die 48 Segmente) nacheinander ins LCD,
>gibt nach jedem Bit einen Clock und Lädt abschließend das
>Schieberegister über eine logische "1" am Load-Eingang in das Display.

>Nun meine Frage. Wie kann ich in C eine "Vereinfachung" schreiben. Ich

Was soll denn daran noch gross vereinfacht werden? Deine 48 Bits legst 
du sinnvoll in einem Array ab. Als Anfänger wollen wir mal ein

uint_8t daten[48];

gelten lassen, als Fortgeschrittener wirst du platzsparend ein

uint_8t daten[6];

nehmen. Das kannst du in einer einfachen Schleife in dein 
Schieberegister takten. Sind eine handvoll Zeilen Code. Wenig 
Optimierungspotential.

>muss ja schließlich für jede Veränderung des Displays die 48 Bits
>schieben,clocken und loaden.

Und?

>Wenn beispielsweise von bit20 bis bit48
>keine ungleichen werte sind, dann kann ich eine schleife machen....

Ich liebe die doppelte Verneinung ;-)
Du solltest so oder so eine Schleife machen.

MfG
Falk

P.S. Die von Jörg vorgeschlagene SPI ist nicht die Lösung deines 
Problem. Du hast noch ein grundlegendes Verständnisproblem wie man 
solche Sachen prinzipiell angeht.

von Peter D. (peda)


Lesenswert?

Sobald Du auch nur einmal schiebst, ist das ganze Bitmuster verwurstelt.

Du mußt also immer alle Bits schieben, auch wenn sich nur ein Bit 
ändert.

Ob nun HW-SPI oder SW-SPI macht in der Codegröße kaum einen Unterschied, 
das HW-SPI ist nur etwas schneller.


Vereinfachen muß man der CPU nichts, der ist egal, wie oft sie was 
wiederholen soll.
Mach Dir ne Schleife für 8 Bits, die dann 6 * aufgerufen wird.


Peter

von Johannes (Gast)


Lesenswert?

wie peter zuletzt schrieb, muss ich bei jeder Änderung aufs Neue ALLE 48 
Bits schieben.

Und meine Frage war eben, wie ich beispielsweise den Zählerstand (aus 
dem Zählerregister) "direkt" auf ein LCD des von mir genannten Typs 
ausgeben kann.  Ich kann natürlich eine Funktion schreiben, in der ich 
für jede einzelne Zahl auf dem Display von 1-1000 jeweils 48 Bitmuster 
schreibe. Dann lasse ich in meiner main() abfragen in welchem Bereich 
der Zählerstand steht, und dann gebe ich beispielsweise im 100ms-Raster 
die Zahl am Display aus, dazuaddiert mit der von mir bereitgestellten 
Overflow-Variablen, wenn der Zähler von FF auf 00 geht.

Aber wie kann ich das problem eben vereinfachen?

von Johannes (Gast)


Lesenswert?

und noch eine kleine Frage:

der Takt muss eine bestimmte Zeit auf H-pegel sein. Kann ich da einfach 
eine Schleife in einer Funktion hochzählen lassen, um damit eine 
Verzögerung hinzukriegen?


Bspl:

int Delay(laenge)
{
 for (i=0;i<laenge;i++)
 {
 }
}




int main (void)
{
 DDRA=0b00000001;     //Bit schieben ("1")

 DDRA=0b00000010;     //clocken von L nach H
 Delay(10000);         //Aufruf der Fkt. "Delay"

 DDRA=0b00000000;     //clocken von H nach L

// das ganze 48x mit verschiedenen Schiebebits

 DDRA=0b00000100;     //abschließender Load-Befehl
}

so müsste es doch klappen oder?

von Werner A. (homebrew)


Lesenswert?

Das ändern des DataDirectionRegisters wird dir aber nicht viel helfen...

von Johannes (Gast)


Lesenswert?

lol ups


DDRA/DDRB ist mit PORTA/PORTB zu ersetzen ;)    das kommt vom kopieren!

von Stefan (Gast)


Lesenswert?

Wenn du beim Übersetzen schon die Zeitdauer für das Delay kennst 
("10000"), dann nimm die delay-Funktionen aus der avr-libc. 
http://www.nongnu.org/avr-libc/user-manual/group__util__delay.html

von Joe (Gast)


Lesenswert?

Willst du nun seriell etwas ausgeben oder innerhalb des Port schieben. 
Irgendwie ist mir das Ganze unklar.

Mal unterstellt du suchst eine Funktion die dir seriell etwas mit Clock 
raustakten soll dann wäre es so etwas:
1
void send_char (uint8_t dsp_char_out)  {
2
  uint8_t n;
3
  
4
  for (n=0; n<8; n++)  {           // Sende 8 BIT
5
  if (dsp_char_out & (0x80 >> n))  // Daten an Display ausgeben, MSB first 
6
    {
7
      PORTB |= (1 << DATA);
8
    }
9
    else
10
    {
11
      PORTB &= ~(1 << DATA);
12
    }
13
    PORTB &= ~(1 << CLOCK);        // Ausgabe des Schiebetakt, CLOCK = 0
14
    PORTB |= (1 << CLOCK);         // CLOCK = 1
15
  }
16
  wait (delay_irgendwas);
17
}

Das geht natürlich auch für deine 48 BIT.

von Falk (Gast)


Lesenswert?

@Johannes

>der Takt muss eine bestimmte Zeit auf H-pegel sein. Kann ich da einfach
>eine Schleife in einer Funktion hochzählen lassen, um damit eine
>Verzögerung hinzukriegen?

Nutzt für Verzögerungen die Funktionen der AVR libc, die funktionieren 
garantiert.
Ausserdem wirst du wahrscheinlich keine Delays brauche, denn die 
Schieberegister sind im allgemeinen wesentlich schneller, als du es per 
Software ansteuern kannst.

>so müsste es doch klappen oder?

Nein, du musst ein Schleife programmieren.

Hier mal die einfache Variante.

uint8_t data[48];    // Datenarray, ein Byte pro Bit

for (i=0; i<48; i++)
{
   PORTA = PORTA & data[i];    // Daten auf PA0
   PORTA = PORTA & 0x02;        // Takt auf PA1
}

   PORTA = 0;                  // Takt und Daten löschen
   PORTA = 0x04;               // LOAD am Schieberegister ist PA2
   PORTA = 0;                  // Takt, Daten und LOAD löschen

MFG
Falk

von Peter D. (peda)


Lesenswert?

Johannes wrote:

> Ich kann natürlich eine Funktion schreiben, in der ich
> für jede einzelne Zahl auf dem Display von 1-1000 jeweils 48 Bitmuster
> schreibe.

Sag bloß, Du hast in der Schule alle Zahlen 0...1000 auswendig gelernt.

Ich hab nur die Ziffern 0..9 gelernt und genau so macht man es eben in 
der CPU:

- Zahl in Ziffern zerlegen
- Bitmuster pro Ziffer aus Tabelle lesen
- alle Bitmuster in die 6 Bytes reinschreiben
- die 6 Bytes rausschieben.


Peter

von Peter D. (peda)


Lesenswert?

Falk wrote:

> uint8_t data[48];    // Datenarray, ein Byte pro Bit

Was soll denn daran einfach sein. ?

Da muß man ja erst die Bitmuster auf die einzelnen Bit-Bytes aufteilen.

Das ist nicht einfach, sondern sehr aufwendig.


Peter

von Johannes (Gast)


Lesenswert?

ja, die würde ich gerne nehmen, wenn sie nicht 20 % meines Speichers 
fressen würde ;)

von Peter D. (peda)


Lesenswert?

Die obigen Beispiele sind ja nicht so der Brüller (bezüglich Code, SRAM, 
Laufzeit), besser gehts so:
1
#include <avr\io.h>
2
#include <inttypes.h>
3
4
5
struct bits {
6
  uint8_t b0:1;
7
  uint8_t b1:1;
8
  uint8_t b2:1;
9
  uint8_t b3:1;
10
  uint8_t b4:1;
11
  uint8_t b5:1;
12
  uint8_t b6:1;
13
  uint8_t b7:1;
14
} __attribute__((__packed__));
15
16
17
#define SBIT(port,pin) ((*(volatile struct bits*)&port).b##pin)
18
19
20
#define LCD_CLK         SBIT(PORTD, 0)  // clock
21
#define LCD_CLK_DIR     SBIT(DDRD, 0)
22
#define LCD_DAT         SBIT(PORTD, 1)  // data
23
#define LCD_DAT_DIR     SBIT(DDRD, 1)
24
#define LCD_CE          SBIT(PORTD, 2)  // chip enable
25
#define LCD_CE_DIR      SBIT(DDRD, 2)
26
27
28
void shift_out( uint8_t b )     // send byte
29
{
30
  uint8_t i;
31
32
  for( i = 8; i; i-- ){         // 8 bits
33
    LCD_DAT = 0;
34
    if(b & 0x80 )               // high bit first
35
      LCD_DAT = 1;
36
    b += b;
37
    LCD_CLK = 0;
38
    LCD_CLK = 1;
39
  }
40
}
41
42
43
void lcd_out48( uint8_t * dat ) // send 48 bits (= 6 byte)
44
{
45
  uint8_t i;
46
47
  LCD_DAT_DIR = 1;              // output
48
  LCD_CLK_DIR = 1;              // output
49
  LCD_CE_DIR = 1;               // output
50
  LCD_CE = 1;
51
  LCD_CLK = 1;
52
53
  LCD_CE = 0;                   // CE = 0 (enable shift register)
54
  dat += 6;                     // last byte first
55
56
  for( i = 6; i; i-- ){         // 6 byte
57
    shift_out( *--dat );
58
  }
59
  LCD_CE = 1;                   // CE = 1 (latch data)
60
}

Und für Delays dann noch die Funktionen aus der delay.h nehmen.


Peter

von Johannes (Gast)


Lesenswert?

Danke Falk, deine Löstung habe ich kurz nach deinem Thread auch 
geschrieben :)

Und zwar muss ich aber vorher noch die Array initialisieren....

array[48] {0,1,.........}


ansonsten eben die schleife, wie du es geschrieben hast.


Danke!

von Falk (Gast)


Lesenswert?

Peter, deine Lösung ist wahrscheinlich die Luxus-Effizienz-Super-Kanone, 
aber der OP steht glaub ich programmier-und C-technisch auf ner ganz 
anderen Stufe. Da ist Einfachheit erstmal wichtiger als Effizienz.

MfG
Falk

von Peter D. (peda)


Lesenswert?

Falk wrote:
> Peter, deine Lösung ist wahrscheinlich die Luxus-Effizienz-Super-Kanone,
> aber der OP steht glaub ich programmier-und C-technisch auf ner ganz
> anderen Stufe. Da ist Einfachheit erstmal wichtiger als Effizienz.

Ich denke eigentlich, daß Anfänger nie früh genug damit anfangen können, 
den Code in kleine verdaubare (verstehbare) Häppchen aufzuteilen.

Darin liegt meine Einfachheit.

Ich halte es im Gegenteil für komplizierter, die 48 Bits als monolitisch 
verschweißten Block zu betrachten. Spätestens, wenn er dann die 
einzelnen Ziffern drauf aufteilen will, wirds haarig.


Peter

P.S.:
Die Bitmacros hab ich hier geklaut:

Beitrag "sbit macro für avr-gcc"

Besten Dank an Volker, da kommt ja richtig 8051-Feeling auf.

von Joe (Gast)


Lesenswert?

> da kommt ja richtig 8051-Feeling auf

Ja, die sind genial und bei mir (8051 Fan) ebenso eingepflegt ;-))

von Falk (Gast)


Lesenswert?

@Peter Dannegger

>Ich denke eigentlich, daß Anfänger nie früh genug damit anfangen können,
>den Code in kleine verdaubare (verstehbare) Häppchen aufzuteilen.

Ja. Auf jeden Fall.

Aber dein Code ist ausgewachsener C-Code, bei solchen Sachen wie

 __attribute__((_packed_));

wird auch mir erstmal etwas mulmig.

Und auch sowas ist nicht sonderlich für Anfänger verdaulich

for( i = 6; i; i-- ){         // 6 byte
    shift_out( *--dat );
  }
  LCD_CE = 1;                   // CE = 1 (latch data)

MfG
Falk

von Peter D. (peda)


Lesenswert?

Falk wrote:

> wird auch mir erstmal etwas mulmig.

Dann sollte man sich nicht scheuen, den Autor zu fragen.


>  __attribute__((_packed_));

Ist nur ein Bestandteil des Macros zur bequemen Bitdefinition (alle Bits 
werden zusammen in ein Byte "gepackt").


> for( i = 6; i; i-- ){         // 6 byte

Ist quasi die Standardsyntax für eine Schleife ( i = 6, also 6 mal).


>     dat += 6;                     // last byte first
...
>     shift_out( *--dat );

Das ist zugegeben die einzige etwas verzwickte Zeile:

dat ist der übergebene Zeiger auf das Feld mit den 6 Bytes.

Damit nun mit dem höchstwertigen angefangen wird, wurde 6 addiert und er 
zeigt hinter das Feld.

Bei jedem Schleifendurchlauf wird vor dem Zugriff wieder 1 abgezogen 
"--dat", so daß erst Byte 5, 4, 3, 2, 1 und 0 ausgegeben werden.

Man will nun aber nicht den Zeiger (Adresse) ausgeben, sondern das Byte, 
welches unter dieser Adresse abgelegt wurde und das macht der * 
Operator.

Man kann diese Zeile in die einzelnen Operationen zerlegen:
1
    uint8_t val;
2
3
    dat = dat - 1;           // Zeiger runterzählen
4
    val = *dat;              // Inhalt holen
5
    shift_out( val );        // und ausgeben


>   LCD_CE = 1;                   // CE = 1 (latch data)

Das ist wieder einfach: Pin CE auf 1 (high) setzen


Peter

von Falk (Gast)


Lesenswert?

@ Peter Dannegger

>>  __attribute__((_packed_));

>Ist nur ein Bestandteil des Macros zur bequemen Bitdefinition (alle Bits
>werden zusammen in ein Byte "gepackt").

Das war MIR zu 99% klar. Einen Anfänger irritiert es massiv.

>> for( i = 6; i; i-- ){         // 6 byte

>Ist quasi die Standardsyntax für eine Schleife ( i = 6, also 6 mal).

Dito. Aber z.B. i als solches ist nicht sonderlich anschaulich. Jaja, 
die liebe C-Syntax. Oh wie vermisse ich Pascal seufz

ICH hab das Programm schon verstanden, aber wie gesagt. Didaktisch halte 
ich es für absolut untauglich.

Ich hoffe du verstehst mich nicht falsch. Du bist sicher absolut fit in 
der Materie, aber du kannst nur schwer auf Anfängernivau runterschalten. 
Ich hatte mal nen Prof im Studium, der war genauso. Für den war FFT und 
Laplace wie 1+1. Seine Vorlesung hab ich erst ein paar Semester später 
WIRKLICH kapiert ;-)
Es ist eine Kunst, schwierige Dinge einfach und verständlich zu 
erklären!

>Man kann diese Zeile in die einzelnen Operationen zerlegen:

>    uint8_t val;

>    dat = dat - 1;           // Zeiger runterzählen
>    val = *dat;              // Inhalt holen
>    shift_out( val );        // und ausgeben

Genau DAS würde ich machen. Der entstehende Code ist sowieso der 
gleiche. Aber die Lesbarkeit (für die wir ja als C-Programmierer 
plädieren) steigt enorm. Ausserdem verringert sich das 
"indenFusschiessenRisiko", wenn man bei diesen tierisch verketteten 
Operatoren mal wieder die Prioritäten nicht beachtet hat. ;-)

MfG
Falk

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.