mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Hilfe bei PWM


Autor: Daniel Steffen (derdaniel)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich bräuchte mal eure Hilfe bei einem PWM Problem.

Ich möchte 27 * 10 Bit (Soft-)PWM Kanäle bei mindestens 200Hz 
realisieren.

Meine Methode ist im Moment die Register 24 und 25 per ADIW als Zähler 
zu benutzen. Der Timer0 übernimmt dabei das "Incrementieren".
Nach jedem INC wird R25 > 0b11 überprüft und evtl. zurückgesetzt, 
außerdem werden alle 27 Kanäle auf ne Änderung überprüft usw....

Über die Formel XTAL // F_PWM // B_PWM kommt man bei 8MHz auf ca. 40 
Cycles bei 20MHz auf 98 nach denen der Timer0 ein interrupt auslöst um 
zu "Incrementieren".

Das Problem is nun, dass nur in Gedanken der Mikrocontroller schon nicht 
mehr nachkommt mit dem Überprüfen der 27 Kanäle, mal ganz abgesehen von 
Fernbedienung und som zeug.

Meine Frage an euch: Wenn ich suche finde ich immer wieder Threats wo 
Leute von X Kanälen mit hunderten von Hz sprechen was auf ihren Systemen 
laufen würde, nur finde ich nirgends ne gescheite Anleitung. Also gibts 
ne bessere Methode und wo finde ich die?

Autor: poo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
du solltest das inkrementieren dem eingebauten zähler überlassen, wenn 
dein chip einen hat, dann muss der prozessor nur dann arbeiten, wenn 
wirklich ein flankenwechsel ansteht und er hat zeit für noch andere 
aufgaben.

Autor: Düsendieb (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du lässt den Timer0 einfach im kreis laufen und fragst 27 mal mit 
unterschiedlichen Werten den Timer ab.

z.B.

if (timerwert>= Vergleichswert1) dann Ausgang an; else Ausgang aus;

Das dann 27 mal untereinander

Autor: poo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
dazu musst du halt wissen, welche lampe als nächstes ausgeknippst wird 
(musst halt die tabelle deiner 27 ausgänge nach pulsdauer sortieren)

Autor: Daniel Steffen (derdaniel)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ poo:
Ich Arbeite/"Simuliere" im Moment auf einem ATMEGA48. Sobald das Ganze 
aber verwirklicht wird kommt ein 32 her wegen den 27 IO Pins.
Wenn ich dich richtig verstehe soll ich den Timer selber benutzen, nur 
wie bekomme ich dann raus ob ich einen der 27 Werte erreicht habe 
(stehen ja nur 2 OC einheiten zur verfügung bei dem 16Bit Timer)?

@ Düsendieb:
Du meinst Timer mit 200Hz laufen lassen und in der Hauptschleife einfach 
immer wieder abfragen?
Wenn er in 1/200s von 0 bis 1024 zählen muss kommen trotzdem nur 40 
Cycles raus zwischen zwei werten, also das gleiche Problem wie am Anfang 
beschrieben (oder mach ich irgend wo n denkfehler)?

Autor: Joachim B. (jojo84)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moin,

du hast das Prinzip (glaub ich) noch nicht ganz verstanden. Du willst 
doch eigentlich "nur" regelmäßig abfragen, ob deine Vergleichswerte 
erreicht wurden, oder nicht. Diese Regelmäßigkeit erzeugt dir der Timer 
durch seine Interrupts. Wenn du 10 Bit, also 1024 PWM-Werte bei 200 Hz 
haben willst, dann mußt du deine Abfragen mit einer Frequenz von 1024 * 
200, also 404800 Hz (ca. 405 kHz) machen. Du würdest deinen Timer also 
so einstellen, daß er seine Interrupts mit 405 kHz schmeißt, in dem du 
dann immer deine Zustände abfragst.
Aber mit Verlaub, 27 Kanäle, 10 Bit bei 200 Hz wird "knapp". Vor allem, 
wenn du dann auch noch was nebenher machen willst (Tastverhältnisse über 
einen Zeitraum ändern, Kommunikation...). Ich hab das mal mit 24 
Kanälen, 7 Bit und ca. 110 Hz gemacht. Das lief aber auf nem Tiny45 mit 
3 Schieberegistern dran.
Du könntest vielleicht auf sowas wie den TLC5940 umsteigen. Dann kriegst 
du die Auflösung und die Frequenz locker hin...

Gruß

Autor: df1as (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wieviel RAM steht denn zur Verfügung?

Wenn genug RAM vorhanden ist, z. B. 4000 Byte (1000 x 32 bit), zerfällt 
die 27-fache Abfrage in einen einzigen RAM-Zugriff. Allerdings müssen 
dafür im Gegenzug bei Verstellung eines einzelnen Wertes mindestens 
soviele Bytes in der Tabelle neu beschrieben werden, um die sich der 
Wert geändert hat.

Beispiel:

Alle PWM-Werte werden auf 0 initialisiert, eine Tabelle der aktuellen 
Werte wird auch mitgeführt:
static unsigned long  aulTable [1000];
static unsigned short ausPWM [27], usNeu;
unsigned char ucWert;

Der PWM-Wert "17" soll auf 400 gesetzt werden:
ucWert =  17;
usNeu  = 400;

if (usNeu > ausPWM[ucWert])     /* mehr Einsen */
  for (u = ausPWM[ucWert]; u < usNeu; u++)
    aulTable [u] |= (1 << ucWert);
else                           /* mehr Nullen */
  for (u = usNeu; u < ausPWM[ucWert]; u++)
    aulTable [u] &= ~(1 << ucWert);

ausPWM[ucWert] = usNeu;

Im Interrupt (zeitkritischen Teil) wird es dagegen sehr schnell. Der 
Interrupt muss mit einem Timer auf 200 kHz eingestellt werden:
interrupt void f200kHz (void)
{
  static unsigned short usTimeSlot;
  unsigned long ulPorts = aulTable [usTimeSlot];

  /* Port-Ausgaben */
  bPort0 = ulPorts;
  bPort1 = ulPorts >>  8;
  bPort2 = ulPorts >> 16;
  bPort3 = ulPorts >> 24;

  /* weiterzählen */
  if (++usTimeSlot >= 1000)
    usTimeSlot = 0;
}

Aber wie gesagt, dieses Prinzip geht nur, wenn genügend Speicherplatz 
vorhanden ist und mit der Einschränkung, dass das Verstellen ggf. länger 
braucht.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Joachim B. schrieb:

> Aber mit Verlaub, 27 Kanäle, 10 Bit bei 200 Hz wird "knapp".

Seh ich auch so.

> Vor allem,
> wenn du dann auch noch was nebenher machen willst (Tastverhältnisse über
> einen Zeitraum ändern, Kommunikation...). Ich hab das mal mit 24
> Kanälen, 7 Bit und ca. 110 Hz gemacht.

Wobei der Knackpunkt hier die 7 Bit waren. Jedes Bit PWM-Auflösung 
weniger hilft enorm, weil es die notwendige Interrupt-Frequenz halbiert. 
Ein paar Kanäle mehr oder weniger fallen dagegen nicht so sehr ins 
Gewicht.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mit Falks 'intelligenter Methode'
http://www.mikrocontroller.net/articles/Soft-PWM#I...
müsste noch was gehen.

Autor: Joachim B. (jojo84)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Wobei der Knackpunkt hier die 7 Bit waren. Jedes Bit PWM-Auflösung
> weniger hilft enorm, weil es die notwendige Interrupt-Frequenz halbiert.

Klaro, das stimmt :) . Kommt natürlich noch n bissl darauf an, das du in 
der Zwischenzeit mit den anderen PWM-Werten machst. Angenommen man wolle 
nach jedem Interrupt neue PWM-Werte laden, die aus einer bösen 
rand()-Funktion unter Verwendung des ADC hervorgehen , dann merkt man 
jeden Kanal ;) .

Aber wie gesagt, unter den o.g. Bedingungen hat es bei mir gut geklappt. 
Mit SPI und allem. Prozessorauslastung von 20% oder so (ich weiß es grad 
nicht mehr genau).

Ich würde entweder die Anforderungen herunterschrauben, oder auf Treiber 
zurückgreifen. Der "Intelligente Lösungsansatz" war mir bisher immer zu 
hoch -_0 ...

Gruß

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Joachim B. schrieb:
> Karl heinz Buchegger schrieb:
>> Wobei der Knackpunkt hier die 7 Bit waren. Jedes Bit PWM-Auflösung
>> weniger hilft enorm, weil es die notwendige Interrupt-Frequenz halbiert.
>
> Klaro, das stimmt :) . Kommt natürlich noch n bissl darauf an, das du in
> der Zwischenzeit mit den anderen PWM-Werten machst. Angenommen man wolle
> nach jedem Interrupt neue PWM-Werte laden, die aus einer bösen
> rand()-Funktion unter Verwendung des ADC hervorgehen , dann merkt man
> jeden Kanal ;) .

sowas ähnliches hab ich gemacht.
Ähnlich wie du: An Schieberegister 64 LED mit einer 6 Bit PWM.
rand() wurde benutzt um die Leuchtdauer und die Intensität der einzelnen 
LED zufällig zu verteilen.
Noch eine UART zur Parametereinsteuerung dazu.
Wurde mit 11Mhz schon etwas eng.

Autor: df1as (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Mit Falks 'intelligenter Methode'
> 
http://www.mikrocontroller.net/articles/Soft-PWM#I...
> müsste noch was gehen.

Leider fehlt dort die Version 4, die mit minimalem Code und noch 
wichtiger mit "wirklich" minimaler Rechenzeit im Interrupt auskommt. 
Trade-off dieser Version mit "vollständiger Tabelle" (siehe mein 
Beispiel oben) ist das aufwändigere Verstellen - allerdings: schneller 
als die PWM-Periode selbst macht das Verstellen auch wenig Sinn. 
Außerdem ist dieses Verfahren bei üblicherweise doch eher kleinen 
Verstellschritten ebenfalls nicht mehr langsam. Insofern bliebe 
eigentlich nur der RAM-Bedarf das Kriterium. Aber eine 
Über-alles-Abschätzung des Rechenaufwands muss schon vorher durchgeführt 
werden.

Autor: df1as (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich gebe noch ein weiteres Beispiel mit vollständiger Tabelle, das um 
ein Epsilon in der Interrupt-Ausführung langsamer ist (mit 
Read-modify-write- statt reinen Write-Befehlen), aber dafür fast optimal 
schnell auch in der Verstellung arbeitet.

Dazu beinhaltet die Tabelle nicht den absoluten Port-Status, sondern 
indiziert an entsprechender Stelle den Pegelwechsel.

Alle PWM-Werte werden auf 0 initialisiert, eine Tabelle der aktuellen
Werte wird auch mitgeführt:
static unsigned long  aulTable [1000];
static unsigned short ausPWM [27];

Verstellen eines PWM-Wertes (nicht reentrant, ansonsten schützen):
void SetPWM (unsigned char ucChannel, unsigned short usPWM)
{
  unsigned long ulBit = 1 << ucChannel;

  aulTable [ausPWM[ucChannel]        ] &= ~ulBit; // alten Wert löschen
  aulTable [ausPWM[ucChannel] = usPWM] |=  ulBit; // neuer Wert
}

Für den aktuellen PWM-Durchlauf kann ggf. durch das Umschalten eine 
"0-Runde" entstehen. Theoretisch ließen sich dagegen Vorkehrungen 
treffen (z. B. Synchronisierung durch Double-Buffering oder Abfrage der 
aktuellen PWM-Phase und manuelles Korrigieren des Port-Status in 
geschützter Region - das wäre in wenigen linearen Code-Zeilen getan).

Im Interrupt (zeitkritischen Teil) ist es immer noch sehr schnell. Der
Interrupt muss mit einem Timer auf 200 kHz eingestellt werden:
interrupt void f200kHz (void)
{
  static unsigned short usTimeSlot;
  unsigned long ulPorts;

  if (++usTimeSlot >= 1000) // PWM-Phase
  {
    usTimeSlot = 0;
    bPort0 =
    bPort1 =
    bPort2 =
    bPort3 = 0; // alle Ports auf 0
  }

  ulPorts = aulTable [usTimeSlot]; // aktueller Eintrag
  bPort0 |= ulPorts;
  bPort1 |= ulPorts >>  8;
  bPort2 |= ulPorts >> 16;
  bPort3 |= ulPorts >> 24; // entsprechende Bits setzen
}

Für eine PWM mit 8 Bit Auflösung und 8 Kanälen (entsprechend dem 
Beispiel "Soft-PWM") fielen gerade mal 256 Byte RAM an, für den OP-Code 
vermutlich nicht mehr als 200 Byte und die Interrupt-Last wäre sicher 
noch mal um einen nicht unerheblichen Faktor kleiner als im beim 
sogenannten "Intelligenten Lösungsansatz".

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.