Forum: Mikrocontroller und Digitale Elektronik Software_PWM - Verständnisproblem am Beispiel Moodlight - Erlärbär gewünscht


von Christian S. (roehrenvorheizer)


Lesenswert?

Hallo allerseits,


ich habe das Moodlight hier


Beitrag "noch ein AVR Moodlight"

https://www.mikrocontroller.net/attachment/137389/AVR-Moodlight-20120308.tar.gz


nachgebaut mit geringen Abwandlungen in der Hardware, die aber nicht 
relevant sind. Den IRMP-Teil habe ich heraus genommen, um ihn nur durch
RC5-Empfang zu ersetzen. In den allerersten Versuchen ließ sich das 
Lichtelein mit vier verschiedenen Fernbedienungen (vier Protokolle!) 
über IRMP bedienen.

Es funktioniert! Auf Mega 48 und größer und sogar mit dem AT90S4433, der 
ja nur 128 Bytes SRAM hat.

Aber die Steuerung über RC5 funtioniert auch, aber nicht gut, bzw nur 
mit Einschränkungen, die vermutlich der Autor der Software so nicht 
hatte:

- der RC5-Empfang ist fehlerfrei
- Umschaltung der Modi funktioniert
- A_KEY_PLAY - Taste funktioniert oft, aber nicht immer
- A_KEY_OPERATE -Taste schaltet zwar das Licht aus, aber nie wieder an. 
Die Hauptschleife wird nur noch alle ca 10 Sekunden durchlaufen.
- A_KEY_FWD -Taste schaltet den Farbwechsel schneller, aber unerwünscht 
auf maximale Geschwindigkeit. Abstufungen unmöglich.
- A_KEY_REV - Taste schaltet den Farbwechsel langsamer, aber direkt auf 
minimale Gweschwindigkeit ohne jede Abstufung. Haupütschleife braucht ca 
0,5 Sekunden.

Zur Korrektur der oben genannten Fehler bräuchte ich als Voraussetzung 
erst mal das Verständnis der Funktionsweise dieser PWM.

Klar ist, was "ml.flags" macht.
In "void moodlight_step(void)" werden den Variablen  level_r, level_g, 
level_b neue Werte zugewiesen je nach Modus. Unklar ist, was genau 
gemacht wird.

Unklar ist, was im Interrupt genau gemacht wird: ISR(TIMER1_COMP_vect).

Unklar ist ebenso, was hier genau gemacht wird: setup_pwm(void). Die 
Erklärung dabei reicht mir nicht wirklich. Es wird sortiert und 
geschaut, welche LEDs überhaupt bedient werden müssen.

Was mit dem Arrays genau gemacht wird: uint8_t pwm_next[4]; uint8_t 
pwm_data[4]; uint16_t pwm_time[4];



Mir fehlt das Verständnis, wie diese PWM grundlegend funktioniert. 
Exponentiell längeres Beschäftigen mit dem Thema bringt nichtmal linear 
voranschreitenden Fortschritt.


Es wäre halt schön, wenn es richtig funktionieren würde. Natürlich 
könnte man irgendwelche festen Werte einstellen und sich das wechselnde 
Farbspiel
anschauen.

Vielleicht hat jemand da den Durchblick direkt, weil eine Software-PWM 
immer nach dem gleichen Schema abläuft, ähnlich wie beim Berechnen 
digitaler Filter ja jeder unbeteiligte von Weitem sofort sieht, daß da 
auf dem Blatt eine SINC-Funtion drauf steht.

Kann mir da jemand bitte ansatzweise auf die Sprünge helfen?

mit freundlichem Gruß

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Warum hast Du denn IRMP durch einen simplen RC5-Decoder ersetzt?

Ist doch eigentlich ein Rückschritt - zumal IRMP auch RC5 decodieren 
kann.

von Axel S. (a-za-z0-9)


Lesenswert?

Christian S. schrieb:
> ich habe das Moodlight hier
> Beitrag "noch ein AVR Moodlight"

Ja, das kenn ich ;)

> Den IRMP-Teil habe ich heraus genommen, um ihn nur durch
> RC5-Empfang zu ersetzen.

Und warum das? IRMP kann doch selber schon RC5.

> Zur Korrektur der oben genannten Fehler bräuchte ich als Voraussetzung
> erst mal das Verständnis der Funktionsweise dieser PWM.
...

> In "void moodlight_step(void)" werden den Variablen  level_r, level_g,
> level_b neue Werte zugewiesen je nach Modus. Unklar ist, was genau
> gemacht wird.

Die drei level_* Variablen sind das Endergebnis aller Verarbeitung und 
geben die Helligkeit der 3 LED vor. Wichtiger für das Verständnis ist
1
struct {
2
    uint8_t  mode;      /* the active moodlight mode */
3
    uint8_t  delay;     /* run moodlight_step() every "delay" PWM cycles */
4
    uint16_t cycle;     /* counter used by modes */
5
    uint8_t  flags;     /* on/off, stopped, direction */
6
} ml;

mode wählt eines der (derzeit 3) Programme aus. Die Variable wird in 
moodlight_step() verwendet in der großen switch() Anweisung.

delay ist sozusagen der Vorteiler. Die Variable tick wird bei jedem 
PWM-Zyklus eins runter gezählt. Beim Nulldurchgang wird dann 
moodlight_step() aufgerufen und setzt tick wieder auf delay.

cycle ist der Hauptzähler jedes Moodlight-Programms. Die Idee dahinter 
ist, daß jedes Programm eine zyklische Folge von Farbkombinationen 
durchläuft. Wenn man die Farbkombinationen durchnumerieren würde, dann 
wäre cycle einfach die Nummer der aktuellen Konbination. Und 
moodlight_step() würde cycle einfach eins hochzählen und die nächste 
Kombination aus z.B. einer Tabelle holen. Den "Anschlag" für den Zähler 
setzt dabei jedes Programm für sich selber.

Da die beiden ersten Programme die drei Grundfarben einfach nur per 
linearer Rampe hoch- bzw. runterdimmen, braucht man aber keine Tabelle, 
sondern kann die Position jeder Farbe auf der Rampe direkt aus dem 
Zählerstand ausrechnen. Das machen die if() Kaskaden innerhalb der 
switch() Klammer in moodlight_step(). Das dritte Programm ist sogar noch 
einfacher, weil es einfach nur zwischen 7 Mischfarben umschaltet.

> Unklar ist, was im Interrupt genau gemacht wird: ISR(TIMER1_COMP_vect).

Das ist das Arbeitspferd der PWM. Der Interrupt wird innerhalb einer 
PWM-Periode maximal 4-mal aufgerufen. Einmal zum Beginn (Zählerstand 0) 
und dann bis zu 3 weitere Male bei Zählerständen die durch die 
Helligkeiten der 3 Kanäle bestimmt werden. Weil diese ISR sehr kurz sein 
muß, ist sie tabellengesteuert über die 3 Arrays:

> uint8_t pwm_next[4]; uint8_t pwm_data[4]; uint16_t pwm_time[4];

> Unklar ist ebenso, was hier genau gemacht wird: setup_pwm(void).

Hier werden die 3 vorgenannten Arrays anhand der level_* Variablen 
befüllt. Auch die Gammakorrektur findet hier statt.

> Mir fehlt das Verständnis, wie diese PWM grundlegend funktioniert.

Ja, die ist etwas trickreich :D

Nehmen wir einfach mal an, unsere 3 PWM-Kanäle sollten mit 10%, 20% und 
50% Helligkeit leuchten und wir hätten 10ms PWM-Periode (=100Hz 
Wiederholrate). Dann hätten wir folgende Impulse zu erzeugen:
1
Pin1: ...##__________________##____________...
2
3
Pin2: ...####________________####__________...
4
5
Pin3: ...##########__________##########____...
6
7
         <-   PWM-Periode  ->
8
9
ISR:    x x x     x

Innerhalb einer PWM-Periode ändern sich unsere 3 Ausgangspins genau 4 
Mal:

1. zu Beginn werden alle Pins auf H gesetzt
2. nach 1ms wird Pin 1 wieder auf L zurück gesetzt
3. nach 2ms wird Pin 2 wieder auf L zurück gesetzt
4. nach 5ms wird Pin 3 wieder auf L zurück gesetzt

Eine herkömmliche (dumme) PWM-Implementierung löst die ISR bei jedem 
möglichen Umschaltpunkt aus. Wenn sie z.B. eine Auflösung von 1:1000 
schaffen will (knapp 10 Bit) dann alle 10ms/1000 = 10µs. Allerdings 
wissen wir bereits, daß sie in 996 Fällen nichts zu tun haben wird. 
Nur zu den 4 Zeitpunkten die ich oben mit x gekennzeichnet habe, ist 
wirklich etwas zu tun. Und was ich nun mache: ich löse die ISR auch 
wirklich nur 4-mal aus.

Das Array pwm_time[] enthält dazu die bis zu 4 Zählerstände, bei denen 
die ISR gemäß obigem Schema per Output-Compare ausgelöst wird. Das Array 
pwm_data[] enthält 3 Bits entsprechend den 3 Pins. Und pwm_next[] 
verkettet die Zeitpunkte. Dadurch kann man Zeitpunkte überspringen. Das 
braucht man, wenn z.B. 2 Kanäle die gleiche Helligkeit haben.

Im Extremfall sind alle 3 LED komplett aus (oder alle auf 100%). Dann 
muß die ISR nur einmal beim Zählerstand 0 aufgerufen werden. Auch das 
ist eine Besonderheit dieser Implementierung: sie erlaubt echte 0% und 
100%, bei denen die LED nie an- bzw. nie ausgeschaltet wird.

Was wäre noch zu sagen? Die PWM-Periode entspricht einem Durchlauf des 
(16 Bit) Timer1. Die Gamma-Tabelle mappt jeden der 204 möglichen 
Helligkeitswerte auf einen Zählerstand. Der Abstand zwischen zwei 
aufeinanderfolgenden Tabelleneinträgen muß dabei groß genug sein, daß 
die ISR dazwischen fertig wird. Dadurch ist die Anzahl der 
realisierbaren Helligkeitswerte begrenzt.

Es gibt noch einen kleinen Trick: durch die Gamma-Korrektur werden die 
Abstände zwischen den möglichen Schaltpunkten nach rechts hin (gegen 
Ende der PWM-Periode) immer größer. Da wir nun nicht wollen, daß 
Änderungen der Helligkeiten der PWM dazwischen pfuschen, werden 
sämtliche Änderungen an den 3 level_* Variablen (in moodlight_step()) 
bzw. den 3 Arrays (in  setup_pwm()) in die letzte "Lücke" vor dem 
Zähler-Nulldurchgang gelegt.

> Kann mir da jemand bitte ansatzweise auf die Sprünge helfen?

Wenn obiges nicht hilft, dann weiß ich auch keinen Rat mehr.

von Christian S. (roehrenvorheizer)


Lesenswert?

Hallo,

IRMP habe ich entfernt, da es mir recht umfangreich erscheint und es 
auch in der Handhabung der diversen Einstellungen komplizierter ist, 
weshalb ich mich auf den simplen RC5-Decoder beschränken wollte, auch um 
den Code zu verkleinern.
In aller Regel verwende ich nur diese eine RC5-Fernbedienung für 
µC-Experimente. Der Rückschritt war somit billigend in Kauf genommen. 
Wie gesagt, es funktionierte ja zu Beginn mit immerhin vier 
verschiedenen Fernbedienungen.

Es freut mich, daß Axel sich gemeldet hat, denn er kenn ja seinen Code 
am besten.
Danke für die Erklärungen. Die Ausführungen muß ich erst mal in aller 
Ruhe durcharbeiten. Vielleicht kann ich dann eine "ich habe es 
kapiert"-Meldung bringen.


Es handelt sich wohl um eine PWM wie in diesem Artikel, Absatz 
"Intelligenter Lösungsansatz"
https://www.mikrocontroller.net/articles/Soft-PWM#Noch_mehr_LEDs
.



mit freundlichem Gruß

von Christian S. (roehrenvorheizer)


Lesenswert?

Hallo,

ich möchte mich noch nachträglich bei Frank M für das IRMP-Projekt und 
bei Axel für den zur Verfügung gestellten Moodlight-Code bedanken.

In der Zwischenzeit konnte ich Fehler beseitigen. Nur das 
Einschalt-Problem nach dem Ausschalten ist noch geblieben und hält sich 
hartnäckig.

mit freundlichem Gruß

von Axel S. (a-za-z0-9)


Lesenswert?

Christian S. schrieb:
> In der Zwischenzeit konnte ich Fehler beseitigen. Nur das
> Einschalt-Problem nach dem Ausschalten ist noch geblieben und hält sich
> hartnäckig.

Christian S. schrieb:
> A_KEY_OPERATE -Taste schaltet zwar das Licht aus, aber nie wieder an.
> Die Hauptschleife wird nur noch alle ca 10 Sekunden durchlaufen.

Kannst du mal deinen Code zeigen?

Es ist wichtig, daß du beim Einschalten die tick Variable manuell auf 
1 setzt. Das sorgt dafür, daß beim nächsten PWM-Zyklus sofort 
moodlight_step() ausgeführt wird und die LED-Helligkeiten updated (die 
waren ja beim Ausschalten auf 0 gesetzt worden). Sonst kann es bis zu 2 
Sekunden dauern bis man den Effekt vom Einschalten sieht.

Ich habe den Code jetzt einige Male refactored und das alles in eine 
Funktion moodlight_wakeup() ausgelagert. K.A. ob das in deiner Version 
der Software schon drin ist. Wenn ja, reicht es diese Funktion 
aufzurufen.

von Christian S. (roehrenvorheizer)


Angehängte Dateien:

Lesenswert?

Hallo,

nach dieser Ermunterung zur Fehler-Diskussion anbei mein Quelltext.


Folgender Fehler ist beseitigt:
Das aprupte Umschalten der Geschwindigkeit von minimal auf maximal lag 
an dem fehlenden Löschen des Variablen-Inhaltes der Variablen i,
die die gedrückte Taste nach "main" überträgt. Sie muß nach der 
Auswertung gelöscht werden und nur dann. Ein Fehler also, den ich wohl 
selbst vor mehr als einem Jahr beim Umbau von IRMP auf den simplen 
RC5-Decoder eingebaut habe. Vorwärts-Rückwärts geht perfekt.

Da ich gestern meine Mitleser nicht so sehr mit Fehlermeldungen fordern 
mochte, kommt nun die Fehlerbeschreibung zu dem verbliebenen Fehler.
Nach Korrektur der restlichen Fehler ist einer noch immer geblieben:
Die on/off-Umschaltung geht immer nur aus, aber nicht wieder an.

Löscht man diese Abfrage "if (ml.flags & ML_FLAG_OFF) { return; }  in 
"void moodlight_step(void)", läßt sich das on/off-Flag tatsächlich 
korrekt umschalten.
Ist die Zeile mit drin, gelingt das Einschalten nie wieder und die 
Hauptschleife in Main wird nur alle 10 Sekunden durchlaufen. (siehe 
toggle LED in main)
Jedes Mal wenn die Toggle-Led aufgeblitzt hat, erscheinen 3...9 Ausgaben 
auf dem Terminal, falls die UART-Ausgaben in moodlight.c aktiviert sind.
Mit einer Ausgabe aufs UART läßt sich das Flag überprüfen und es wird 
das gesetzte Flag fortwährend angezeigt:
if (ml.flags == ML_FLAG_OFF)  uartputs( " OFF \n\r" );
Definiert man eine zusätzliche Taste, die nur Einschalten kann, 
funktioniert sie auch nicht. Das Flag bleibt immer gesetzt.



Gegenspieler sind diese beiden hier in "void moodlight_remote(void)":
    case A_KEY_OPERATE: /* on/off */      //schaltet ab, aber nicht mehr 
ein. Hauptschleife wird nur noch selten durchlaufen. Deshalb zu seltener 
RC5-Empfang.
        {
                ml.flags ^= ML_FLAG_OFF;
                if (ml.flags & ML_FLAG_OFF) {
                    level_r = 0;
                    level_g = 0;
                    level_b = 0;
                } else {
                    moodlight_wakeup();

            }
            break;
        }

und

  case A_KEY_7:
    {
        ml.flags = 0;
                //ml.flags &= ~ML_FLAG_OFF;    //das einzelne Bit Nr7 
löschen
                moodlight_wakeup();
        break;
    }



Bisher habe ich mich nur mit der Beseitigung der Fehler bis zum 
beschrieben Stand befasst, so daß dieses Thema hier

"... tick Variable manuell auf 1 setzt."

noch nicht erprobt ist. Jedenfalls geht es auch nach Minuten nicht mehr 
an, da ja das Flag immer gesetzt bleibt.


Zum Quelltext:

Die UART-Ausgaben sind zur Zeit deaktiviert, funktionieren aber.
Die ungeschickt aufgebauten header-Dateinen möge man mir nachsehen, ich 
bin an anderen Problemen hängen geblieben als diese zu ordnen.
Sleep-mode heraus genommen.
Der RC5-Decoder ist derjenige von Peter.
Alles mit Temperatur und deren Anzeige ignorieren, da dies zu einem 
ehemaligen anderen kombinierten Projekt gehörte. Ist auskommentiert.
Die zusätzlich eingebaute Taste, die nur einschalten soll, geht auch 
nicht.
Grundlage ist der Code von Axel:    Datum: 08.03.2012 10:01 siehe Link 
ganz oben

Hoffentlich ist es nicht wegen Stack-Überlauf.

LCD-Routines_3.c
main_test1.c
rc5_3.c
moodlight_3.c
uart_chr2.c

Device: at90s4433

Program:    3366 bytes (82.2% Full)
(.text + .data + .bootloader)

Data:        100 bytes (78.1% Full)
(.data + .bss + .noinit)


Es wäre schon schön, wenn das auch noch gehen würde.

Aus dem Originaltext: "In der Hoffnung, jemanden zu inspirieren :)"

mit freundlichem Gruß

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.