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
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.
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.
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.
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=1bis16){
5
for(ausgang=1bis32){
6
if(istHelligkeit>sollHelligkeit[ausgang])
7
ausgang=0;
8
else
9
ausgang=1;
10
}
11
SPIsendalleAusgä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
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
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.
@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
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.
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
unsignedcharnPwm=0;
2
unsignedcharnLeds[128];
3
4
for(unsignedcharnInd=0;nInd<128;nInd++)
5
{
6
if(nLeds[nInd]>nPwm)SetDataPort;elseClrDataPort;
7
ToggleClock;
8
ToggleClock;
9
}
10
LatchRegs;
11
nPwm++;
12
NochmaldasGanze;
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.
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.
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.
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
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.
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)
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
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.
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.
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.
Du kannst den Teil "Byte zusammenbauen" zb so erledigen
1
for(uint8_ty=0;y<16;y++)
2
{
3
uint8_tvalue=0;
4
uint8_tmask=0x01;
5
6
for(uint8_tx=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.
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_ty=0;y<16;y++)
2
{
3
uint8_tvalue=0;
4
uint8_tmask=0x01;
5
6
for(uint8_tx=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"):
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)
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.
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?
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!