Forum: Mikrocontroller und Digitale Elektronik PWM über 74hc595 (Pulsweitenmodulation über Schieberegister) - in C


von Thomas J. (user3289)


Lesenswert?

Hallo zusammen,

vor einiger Zeit habe ich mir mit 16 74HC595 eine 8x16-LED-Matrix 
zusammengelötet. Tetrist und Snake habe ich erfolgreich programmiert und 
auf der Matrix zum Laufen bebracht. Nun würde ich gerne jede LED in 
256-Helligkeitsstufen unterteilen. An jedem der 8 Ausgänge eines 74HC595 
hängt eine LED.

Das Kaptiel "Soft-PWM" habe schon durchgearbeitet:
https://www.mikrocontroller.net/articles/Soft-PWM
Ohne Schieberegister, also wenn ich die Ausgänge direkt am 
Microcontroller anspreche funktioniert es auch.

Ebenso habe ich das Kapitel "Schieberegister" habe ich ebenfalls 
durchgarbeitet, was so ebenfalls funktioniert:
https://www.mikrocontroller.net/articles/AVR-Tutorial:_Schieberegister
(Sonst hätte ja meine Matrix auch nicht ansprechen können).

Nur in der Kombination funktiert es leider nicht. Leider weiß ich auch 
nicht wirlich, wie man da am besten vorgeht. Kann mir hier jemand Tipps 
geben?

Danke schon einmal.

Gruß Thomas

von Karl H. (kbuchegg)


Lesenswert?

Thomas Jäger schrieb:

> vor einiger Zeit habe ich mir mit 16 74HC595 eine 8x16-LED-Matrix
> zusammengelötet.

Ist das ein Tippfehler?
16 Stück 595?

Für eine 8*16 Matrix brauchst du maximal 3.

> Tetrist und Snake habe ich erfolgreich programmiert und
> auf der Matrix zum Laufen bebracht. Nun würde ich gerne jede LED in
> 256-Helligkeitsstufen unterteilen.

Das wird zeitlich eng. Sehr eng.

Gehen wir mal von einer Matrix Refresh-Frequenz von 100Hz aus.
Du brauchst jetzt schon mal das 256-fache davon, damit du die 
Abstufungen hinkriegst. Also sind wir schon bei 25.6kHz
Da jetzt noch den 1:8 Multiplex drauf gerechnet, ergibt 204.8kHz

D.h. du brauchst eine ISR, die mit einer Frequenz von 204.8kHz 
aufgerufen wird (204800 mal in der Sekunde). Kriegt man bei 16Mhz 
Taktfrequenz, einem Vorteiler von 1 und einem CTC Modus mit einer 
Obergrenze von 78 gerade noch hin.
Aber alle 78 Taktzyklen ein Interrupt. Da muss man in der ISR schon 
sparsam sein und ... da bleibt für das restliche Programm nicht mehr 
viel übrig.

Da wirst du wohl deine Ansprüche ein wenig runterschrauben müssen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Falls man kein MUX brauch weil es wirklich 16 74*595 sind, dann hat man 
etwas mehr Luft.  WObei zu klären wäre, ob die Register in Reihe hängen 
oder parallel an 16 Ports (plus 1 Port Takt und 1 Port Latch-Strobe), 
oder vielleicht ein Mittelding davon.

http://www.rn-wissen.de/index.php/Portexpander_am_AVR#Mit_SPI-Hardware

enthält C-Code für eine serial Line mit evtl. mehreren Schieberegistern 
und Verwendung der SPI-Hardware eines ATmega8.  Bei mehreren registern 
parallel muß der Code natürlich angepasst werden.

Controller wurde zwar keiner genannt, aber da eine AVR-Seite aus dem 
Wiki genannt wird, geh ich mal davon aus, daß das die µC-Hardware ist, 
um die es geht.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:
> Falls man kein MUX brauch weil es wirklich 16 74*595 sind,

er wird doch nicht. Oder doch?

Wenn ja: Respekt vor dem Verkabelungsaufwand.

ok. so gesehen kriegt man zeitlich etwas Luft.

@Thomas
Das Prinzip ist dann:
dreh die ISR Aufruffrequenz hoch.
EIne LED, deren Helligkeit auf 1 steht, gibst du dann nur alle 256 
Aufrufe als leuchtend aus, die restlichen 255 mal bleibt sie dunkel. 
EIne LED, deren Helligkeit auf 2 steht, wird von 256 ISR Aufrufen nur 2 
mal als leuchtend ausgegeben, die restliche Zeit ist sie dunkel.

Im Grunde läuft es darauf hinaus, dass ein kompletter Matrix 
Ausgabe-Zyklus aus 256 Aufrufen der ISR besteht, in denen jeweils die 
Matrix komplett neu aufgebaut und ausgegeben wird. Und je nach 
gewünschter Helligkeit einer LED wird die eine gewisse Anzahl mal 
leuchtend und den Rest der Aufrufe als nicht leuchtend an die Matrix 
ausgegeben.

: Bearbeitet durch User
von Joachim (Gast)


Lesenswert?

Hi Thomas,

ich hab sowas in "klein" am Laufen. Mit 4x 595 und nur 16 
Helligkeitsstufen.

Grundprinzip bei PWM über Schieberegister ist ganz einfach, du musst 
eigentlich nur deine Schieberegister in einer Endlosschleife (oder per 
Timer) immer wieder mit deinen Daten befeuern. Und die unterschiedlichen 
Helligkeiten regelst du damit, dass du die Ausgänge in entsprechendem 
Verhältnis eben abwechseln ein- und ausschaltest. Also wenn ich einen 
Ausgang auf 50% haben möchte, dann sende ich 8x ein, gefolgt von 8x aus.

In Pseudocode sieht das dann ca. so aus:
1
sollHelligkeit[3] = 8; // ausgang 3 auf 50%, alle anderen Ausgänge brauchen natürlich auch nen Wert
2
3
while(true) {
4
  for(istHelligkeit = 1 bis 16) {
5
    for(ausgang = 1 bis 32) {
6
      if (istHelligkeit > sollHelligkeit[ausgang])
7
        ausgang = 0;
8
      else
9
        ausgang = 1;
10
    }
11
    SPI send alle Ausgänge
12
  }
13
}

Eine Umsetzung ist recht simple, allerdings stößt man eben echt schnell 
an Performancegrenzen.

Ne Variante währen wohl noch entsprechende LED Treiber Bausteine (ich 
glaube von NXP) die sich selbst um PWM kümmern. Dann kannst du den uC 
damit entlasten und musst die Bausteine bei Zustandsänderung nur noch 
mit den neuen Solldaten füttern.

Gruß Joachim

Gruß Joachim

von Karl H. (kbuchegg)


Lesenswert?

Na ja.
Sowohl PWM als auch streng genommen eine Matrix-Ausgabe (die man 
normalerweise multiplext) machen ohne Timer bzw. zugehöriger ISR keinen 
Sinn mehr.

Wenn er sich also bis jetzt erfolgreich um Timer gedrückt hat, dann ist 
spätestens jetzt der Zeitpunkt gekommen, an dem das nicht mehr geht

von Thomas J. (user3289)


Lesenswert?

Karl Heinz schrieb:
> Thomas Jäger schrieb:
>
>> vor einiger Zeit habe ich mir mit 16 74HC595 eine 8x16-LED-Matrix
>> zusammengelötet.
>
> Ist das ein Tippfehler?
> 16 Stück 595?

Nein, das ist kein Tippfehler. Ich habe es so realisiert, dass ich es 
verstehe und nachvollziehen kann. Habe 16 Schieberegister in Reihe 
geschaltet, an einen Ausgang des Microcontrollers.

>> Nun würde ich gerne jede LED in
>> 256-Helligkeitsstufen unterteilen.
>
> Das wird zeitlich eng. Sehr eng.
>
> Gehen wir mal von einer Matrix Refresh-Frequenz von 100Hz aus.
> Du brauchst jetzt schon mal das 256-fache davon, damit du die
> Abstufungen hinkriegst. Also sind wir schon bei 25.6kHz
> Da jetzt noch den 1:8 Multiplex drauf gerechnet, ergibt 204.8kHz
>
> D.h. du brauchst eine ISR, die mit einer Frequenz von 204.8kHz
> aufgerufen wird (204800 mal in der Sekunde). Kriegt man bei 16Mhz
> Taktfrequenz, einem Vorteiler von 1 und einem CTC Modus mit einer
> Obergrenze von 78 gerade noch hin.
> Aber alle 78 Taktzyklen ein Interrupt. Da muss man in der ISR schon
> sparsam sein und ... da bleibt für das restliche Programm nicht mehr
> viel übrig.
>
> Da wirst du wohl deine Ansprüche ein wenig runterschrauben müssen.

Okay, dass das so eng wird, war mir nicht ganz bewussst. Mir geht es 
auch erst mal um das Prinzip. Aus- und umbauen kann man immer noch. Also 
wenn ich 4 Stufen hätte (0% - 33% - 66% - 100%) würde das auch erst 
einmal reichen.

von Joachim (Gast)


Lesenswert?

@Karl Heinz

Sicherlich ist das die eleganteste Methode und in vielen Fällen kommt 
man nicht drumrum.

Meine Variante nutzt die eh schon vorhandene Endlosschleife in main und 
haut da so schnell es geht eben die Daten per SPI raus. Schneller geht 
einfach nicht.

Einen Interrupt nutze ich für die USART Kommunikation, um den 
Schieberegistern neue Solldaten zu verpassen.

Die USART ISR "kostet" zwar Takte und sorgt für eine unstetige PWM 
Frequenz, aber ob das jetzt 1000Hz oder nur 900Hz sind, sieht man eh 
nicht.

Wie ich finde also ein valider Ansatz.

Gruß Joachim

von Karl H. (kbuchegg)


Lesenswert?

Joachim schrieb:
> @Karl Heinz
>
> Sicherlich ist das die eleganteste Methode und in vielen Fällen kommt
> man nicht drumrum.
>
> Meine Variante nutzt die eh schon vorhandene Endlosschleife in main

Bitte.
Vergiss das ganz schnell wieder.
Das ist alles nicht zielführend.

Das Grundprinzip ist ja ok. Aber es muss in eine ISR
1
volatile uint8_t Leds[8][16];
2
uint8_t FrameCount;
3
4
ISR( ... )
5
{
6
  FrameCount++;
7
8
  // Komplette Matrix einmal ausgeben, wobei nur die LED eingeschaltet
9
  // werden, deren Helligkeitswert kleiner/gleich dem 'FrameCount' sind
10
  for( x = 0; x < 8; x++ )
11
  {
12
    for( y = 0; y < 16; y++ )
13
    {
14
      if( Leds[x][y] <= FrameCount )
15
        ... eine 0 in die Schieberegister eintakten
16
      else
17
        ... eine 1 in die Schieberegister eintakten
18
    }
19
  }
20
21
  Schieberegister auf die Ausgänge durchschalten lassen
22
}

Alles andere ist sinnlos vergeudete Entwicklungszeit. Ob man die SR-Bits 
gleich direkt ausgibt, oder ob man dazu SPI benutzt wird sich hier nicht 
viel reißen (kann aber sein) und ist auch nicht das Problem.

Es ist nicht schwer zu realisieren. Nur muss man sich eben mal an Timer 
wagen. Wenn man mal ein wenig Code vom TO hätte, könnte man da auch ein 
wenig spezifischer werden.


> Die USART ISR "kostet" zwar Takte und sorgt für eine unstetige PWM
> Frequenz, aber ob das jetzt 1000Hz oder nur 900Hz sind, sieht man eh
> nicht.

Bei einer PWM-Helligkeitssteuerung sieht du es aber, wenn sich mitten in 
einem Zyklus die Ausgabefrequenz verändert. Mit einem Timer ist das ganz 
leicht zu vermeiden. Die Matrix leuchtet ruhig und flackerfrei vor sich 
hin. Ganhz abgesehen davon, dass man dann in der Hauptschleife die 
Freiheit zurück gewonnen hat, die Dinge (fast) tun und lassen zu dürfen, 
wie es einem beliebt. Der Timer samt ISR kümmern sich um die Matrix. Die 
Hauptschleife ist davon befreit. Selbst ein _delay_ms sorgt nicht dafür, 
dass die Matrix nicht mehr korrekt abgearbeitet werden würde.

: Bearbeitet durch User
von Thomas E. (thomase)


Lesenswert?

Thomas Jäger schrieb:
> Okay, dass das so eng wird, war mir nicht ganz bewussst. Mir geht es
> auch erst mal um das Prinzip. Aus- und umbauen kann man immer noch. Also
> wenn ich 4 Stufen hätte (0% - 33% - 66% - 100%) würde das auch erst
> einmal reichen.

Der Controller kriegt halt was zu tun.
Ganz einfach in Software.

Du brauchst einen Zähler, den Pwm-Counter. Und ein Array, 128 Bytes, für 
jede Led eines.
1
unsigned char nPwm = 0;
2
unsigned char nLeds[128];
3
4
for(unsigned char nInd = 0; nInd < 128; nInd++)
5
{
6
  if(nLeds[nInd] > nPwm) SetDataPort; else ClrDataPort;
7
  ToggleClock;
8
  ToggleClock;
9
}
10
LatchRegs;
11
nPwm++;
12
Nochmal das Ganze;

Das wird deinen Controller schon einigermassen auslasten. Im 
wesentlichen ist das abhängig vom Pwm-Wert. 32 geht 8 Mal schneller als 
256. Austesten.

mfg.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Thomas Jäger schrieb:

>> Da wirst du wohl deine Ansprüche ein wenig runterschrauben müssen.
>
> Okay, dass das so eng wird, war mir nicht ganz bewussst. Mir geht es
> auch erst mal um das Prinzip. Aus- und umbauen kann man immer noch. Also
> wenn ich 4 Stufen hätte (0% - 33% - 66% - 100%) würde das auch erst
> einmal reichen.

War ein Fehler von mir.
Ich bin von einer üblichen gemultiplexten Matrix ausgegangen.
Dass du tatsächlich für jede LED einen eigenen Ausgangspin hast, hatte 
ich nicht gerafft.

Eine ISR Frequenz von 25kHz reicht für 256 Stufen. Bei 16Mhz sind das 
immerhin 640 Takte von einem ISR Aufruf zum nächsten. Sollte sich 
ausgehen.

von m.n. (Gast)


Lesenswert?


von Thomas J. (user3289)


Lesenswert?

Als Microcontroller kommt ein Atmega8 zum Einsatz.

Und ich merke gerade, dass ich auf diesem Gebiet noch viel zu lernen 
habe. Denn bisher habe ich nur brav den Code aus den Tutorials kopiert 
und etwas angepasst bzw. auf meine Wünsche angepasst.

Werde mal am Wochenende eure Vorschläge und Beispiele ausprobieren. 
Sonst werde ich mich auch mal genauer mit den Themen USART ISR und Timer 
beschäftigen. Denn darüber weiß ich bisher noch am wenigsten.

Sobald ich die ersten Ergebnisse habe würde dann auch mal Code hier 
posten...

Sonst möchte ich mich schon mal für die ganzen Infos bedanken und 
wünsche noch einen schönen Abend.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Thomas Jäger schrieb:
> Als Microcontroller kommt ein Atmega8 zum Einsatz.
>
> Und ich merke gerade, dass ich auf diesem Gebiet noch viel zu lernen
> habe. Denn bisher habe ich nur brav den Code aus den Tutorials kopiert
> und etwas angepasst bzw. auf meine Wünsche angepasst.

schlecht.
es ist immer besser, wenn man die Prinzipien versteht.


FAQ: Timer

von Thomas J. (user3289)


Angehängte Dateien:

Lesenswert?

So, habe mich nun mal hingesetzt und die Kaptiel "ISR/Timer" und "SPI"
durchgearbeitet. (War doch einfacher, als gedacht...)

Nun war ich in der Lage die PWM-Geschichte für meine LED-Matrix
umzusetzen.

Bisher pulsieren zwar nur alle LEDs im gleichen Takt, aber der heutige
Tag ja noch ein paar Stunden :)

Im Anhang mein Code.
(Mir ist bewusst, es für das setzten von Bits legantere Lösungen
gibt...)

Danke noch mal für eure Hilfe, Tipps und Links.

von Thomas J. (user3289)


Lesenswert?

Karl Heinz schrieb:
> volatile uint8_t Leds[8][16];
> uint8_t FrameCount;
>
> ISR( ... )
> {
>   FrameCount++;
>
>   // Komplette Matrix einmal ausgeben, wobei nur die LED eingeschaltet
>   // werden, deren Helligkeitswert kleiner/gleich dem 'FrameCount' sind
>   for( x = 0; x < 8; x++ )
>   {
>     for( y = 0; y < 16; y++ )
>     {
>       if( Leds[x][y] <= FrameCount )
>         ... eine 0 in die Schieberegister eintakten
>       else
>         ... eine 1 in die Schieberegister eintakten
>     }
>   }
>
>   Schieberegister auf die Ausgänge durchschalten lassen
> }

Bin gerade dabei diesen Ansatz mal durchzuprogrammieren. Allerdings 
scheitet es an der Stelle "... eine 0/1 in die Schieberegister 
eintakten". Bisher habe ich immer nur 8 Bit Worte in das Schieberegister 
geschoben, was dann wie folgt aussah:
1
SPDR = 0b10011001; // Schreibe Daten...
2
while(!(SPSR & 0b10000000)); //...bis fertig.
3
PORTB = 0b00000100; // Schreibe Daten...
4
PORTB = 0b00000000; // ...raus.

Wie kann ich denn nur ein einzelnens Bit rausschreiben?

Wenn ich "SPDR = 0b1;" schreibe, setzt er die restlichen 7 bits auf "0" 
:(

ich habe auch über meine Funktion "myPwo" versucht die Bits zu sammeln, 
da ich ja eh eine Array mit 16 x 8 Feldern habe, was sich ja eigentlich 
anbietet. Jedoch läuft dann das Programm nicht mehr schnell genug (habe 
die Vermutung, dass die Rekursion, das zu langsam macht)
1
int myPow(int x, int p) {
2
  if (p == 0) return 1;
3
  if (p == 1) return x;
4
  return x * myPow(x, p-1);
5
}

von Thomas J. (user3289)


Angehängte Dateien:

Lesenswert?

Okay, das Problem war nicht die myPow-Funktion sondern der zu hoch 
gewählte FrameCounter. 256 Schritte waren zu viel des Guten. Habe jetzt 
35 Schritte, die funktionieren, so wie ich mir das vorgestellt habe :) 
(Wobei ich die myPow-Funktion durch ein einfaches switch-case ersetzt 
habe, das sollte nicht so takthungrig sein, wie die rekursive Funktion.)

Im Anhang mein Code (ggf. sieht ja jemand noch Stellen, die man 
optimieren kann, um weitere Helligkeitsstufen zu erreichen) und auch mal 
ein Bild, wie meine Matrix den Code nun darstellt.

Gruß Thomas

von Karl H. (kbuchegg)


Lesenswert?

1
  for(int y = 0; y < 16; y++ )
2
  {
3
  int value = 0;
4
    for(int x = 0; x < 8; x++ )

du zwingst deinen µC völlig unnötig in 16 Bit Arithmetik an Stellen, an 
denen so gar nicht brauchst!

Wenn du Speed haben willst, dann ist das um und auf dafür, dass du 
angepasste Datentypen benutzt!

Um bis 16 zu zählen, reicht ein 8 Bit Wert vollkommen aus. Kein Grund 
den AVR dafür in 16 Bit Arithmetik zu treiben. Die 3 hier vorkommenden 
Variablen können alle ein uint8_t sein.

von Karl H. (kbuchegg)


Lesenswert?

Du brauchst hier
1
  SPDR = value; // Schreibe Daten...
2
  while(!(SPSR & 0b10000000)); //...bis fertig.

im Normalfall nicht warten, bis die SPI fertig ist.
Denn um das nächste auszugebende Byte zusammenzubauen, benötigt der AVR 
ebenfalls Zeit. D.h. in der Zeit, in der die SPI das Byte raustaktet, 
kann der µC schon das nächste Byte zusammenbauen.

Lediglich an einer Stelle musst du tatsächlich warten. Nämlich wenn das 
letzte Byte ausgegeben wurde, ehe dann der RCLK Puls an den 595 die 
Bytes an die Ausgänge legt.

> Okay, das Problem war nicht die myPow-Funktion sondern der zu hoch gewählte 
FrameCounter.

unlogisch.
16000000 / 256 = 62500
62500 / 256 = 244

d.h. mit 256 Stufen kriegst du eine Refresh Rate von 244Hz. Das würde 
sich also locker ausgehen. Wenn nicht, dann deshalb, weil du die ISR zb 
durch unnötige Arithmetik extrem uneffizient gemacht hast, oder aber 
weil dein µC gar nicht mit 16Mhz läuft.

von Karl H. (kbuchegg)


Lesenswert?

Mit solchen Dingen

>   PORTB = 0b00000100; // Schreibe Daten...
>   PORTB = 0b00000000; // ...raus.

wirst du noch viel Freude haben :-)

gerade in einer ISR macht man so etwas NIEMALS. Denn das restliche 
Programm ist darauf angewiesen, dass speziell eine ISR nicht an Pins 
rumfummelt, an denen sie nichts verloren hat!

Einzelbitoperationen!
1
   PORTB |= ( 1 << PB2 );
2
   PORTB &= ~( 1 << PB2 );

das sieht zwar in C komplizieretre aus, aber der Optimizer kann das zu 
einer einzgien Maschineninstruktion optimieren. Und die beeinflusst dann 
auch nur tatsächlich dieses EINE Bit und nicht mehr.

von Karl H. (kbuchegg)


Lesenswert?

Du kannst den Teil "Byte zusammenbauen" zb so erledigen
1
  for( uint8_t y = 0; y < 16; y++ )
2
  {
3
    uint8_t value = 0;
4
    uint8_t mask = 0x01;
5
6
    for(uint8_t x = 0; x < 8; x++ )
7
    {
8
      if( (Leds[y][x]) < FrameCount )
9
        value |= mask;
10
      mask <<= 1;
11
    }
12
13
    SPDR = value;
14
  }
15
16
  while(!(SPSR & ( 1<<SPIF) )
17
   ;
18
  PORTB |= ( 1 << PB2 );
19
  PORTB &= ~( 1 << PB2 );
20
}


und gewöhn dir die Binärschreibweisen ab!
Die sind in praktisch allen Fällen immer die unübersichtlichste und 
dümmste Schreibweise, die du wählen kannst!

Mit
1
    SPCR = 0b01010000;   // Interrupt des SPI freigegeben (Shift)
seh ich im Code genau gar nichts.

Mit
1
  SPCR = ( 1 << SPE ) | ( 1 << MSTR );
hab ich wenigstens Bitnamen, mit denen man im Datenblatt suchen kann, 
wenn man die Kürzel nicht kennt und ich nicht weiß, dass SPE für "SPI 
Enable" bzw MSTR für "Master" steht. Lass den Compiler für dich 
arbeiten. Es gibt keinen Grund dir selber die Bitnummer rauszusuchen und 
zu einem Byte zusammenzustellen. Das einzige was du tust ist, du läufst 
Gefahr dich zu verzählen. Aber sonst erreichst du nichts. Der Compiler 
setzt die 2-te Schreibweise genau zur ersten um.

: Bearbeitet durch User
von Thomas J. (user3289)


Angehängte Dateien:

Lesenswert?

Alles klar, habe die Schreibweisen geändert, auch für "TIMSK" und 
"TCCR0".

Das einzige Problem, was ich jedoch mit deinem Code habe ist, dass
1
  for( uint8_t y = 0; y < 16; y++ )
2
  {
3
    uint8_t value = 0;
4
    uint8_t mask = 0x01;
5
6
    for(uint8_t x = 0; x < 8; x++ )
7
    {
8
      if( (Leds[y][x]) < FrameCount )
9
        value |= mask;
10
      mask <<= 1;
11
    }
12
13
    SPDR = value;
14
15
  }
16
17
  while(!(SPSR & ( 1<<SPIF)));
18
  PORTB |= ( 1 << PB2 );
19
  PORTB &= ~( 1 << PB2 );
Die Ausgabe, wie auf Bild "whileY.JPG", erzeugt.

Wenn ich jedoch Pro Zeile warte, dass das "(!(SPSR & ( 1<<SPIF)))" nicht 
wahr wird, ist die Ausgabe nicht verzerrt ("whileX.JPG"):
1
  for( uint8_t y = 0; y < 16; y++ )
2
  {
3
    uint8_t value = 0;
4
    uint8_t mask = 0x01;
5
6
    for(uint8_t x = 0; x < 8; x++ )
7
    {
8
      if( (Leds[y][x]) < FrameCount )
9
        value |= mask;
10
      mask <<= 1;
11
    }
12
13
    SPDR = value;
14
    while(!(SPSR & ( 1<<SPIF)));
15
  }
16
17
18
  PORTB |= ( 1 << PB2 );
19
  PORTB &= ~( 1 << PB2 );

Im Ahnang sonst noch der vollständige Code.

von Karl H. (kbuchegg)


Lesenswert?

Thomas Jäger schrieb:

> Wenn ich jedoch Pro Zeile warte, dass das "(!(SPSR & ( 1<<SPIF)))" nicht
> wahr wird, ist die Ausgabe nicht verzerrt ("whileX.JPG"):

OK. Dann geht sich das in der Laufzeit ganz knapp nicht mehr aus, dass 
das Pgm für die Zusammenstellung des nächsten Bytes länger braucht als 
die SPI dafür benötigt, es auszugeben.
Aber, du kannsz es so rum machen
1
   while (!(SPSR & ( 1<<SPIF)))
2
     ;
3
   SPDR = value;


d.h. du wartest nicht NACHDEM du die SPI gestartet hast, sondern du 
wartest 'ob die SPI schon wieder aufnahmebereit ist'.


(Allerdings gibt es da eine kleine Falle. Man muss einmalig, ganz am 
Programmanfang, einmal was auf die SPI ausgeben, damit der Mechanismus 
ins laufen kommt. Also am einfachsten in der Init, nachdem die SPI 
konfiguriert wurde, einmalig was ans SPDR zuweisen. Dann wird das SPIF 
Flag nach Beendigung der Operation gesetzt und ab dann läuft alles 
weitere)

von Thomas J. (user3289)


Lesenswert?

Karl Heinz schrieb:
> d.h. du wartest nicht NACHDEM du die SPI gestartet hast, sondern du
> wartest 'ob die SPI schon wieder aufnahmebereit ist'.

Leider bringt das keine Abhilfe, die Ausgabe ist auch dabei, wie auf 
"whileY.JPG" verrutscht.

von Karl H. (kbuchegg)


Lesenswert?

Sieht deine Funktion jetzt so aus?
1
  for( uint8_t y = 0; y < 16; y++ )
2
  {
3
    uint8_t value = 0;
4
    uint8_t mask = 0x01;
5
6
    for(uint8_t x = 0; x < 8; x++ )
7
    {
8
      if( (Leds[y][x]) < FrameCount )
9
        value |= mask;
10
      mask <<= 1;
11
    }
12
13
    while(!(SPSR & ( 1<<SPIF)));
14
    SPDR = value;
15
  }
16
17
  while(!(SPSR & ( 1<<SPIF)));
18
  PORTB |= ( 1 << PB2 );
19
  PORTB &= ~( 1 << PB2 );

Das sollte es eigentlich schon tun.
Der einzige Unterschied zu deiner funktionierenden Version ist, dass die 
Synchronisierung auf 'SPI fertig' jeweils vor der nächsten SPI bzw. 
Schieberegister Aktion gemacht wird und nicht hinter der Ausgabe ans 
SPDR.

Eigenartig. Übersehe ich da jetzt irgendwas?

: Bearbeitet durch User
von Thomas J. (user3289)


Angehängte Dateien:

Lesenswert?

Hatte die zweite While-Schleife nicht drin, die noch mal drauf wartet, 
dass alle Bits geschrieben wurde, bevor sie rausgeschoben werden.

Jetzt funktioniert es.
Konnte nun den framecountMax auf "100" setzten, so dass 100 
Helligkeitsstufen realisiert werden können.

Im Anhang noch der komplette Code.

Vielen Dank, dass du dir die Mühe gemacht hast mir die Vorgehensweise zu 
erklären und mir zu helfen!

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