Forum: Compiler & IDEs PWM mit Timer0


von Ronny Schulz (Gast)


Lesenswert?

Ich versuche gerade in paar LEDs zu dimmen. Das geht aber nicht so
recht. Das ganze soll PWM-gesteuert sein. Die 3 LED sind aber immer
voll an. Egal welchen Wert "value" hat. Hier mal meine Code.
Vorübergehend sind mal 3 Ports geschalten. Alles über einen ULN2003AN.

void abled_init(unsigned char value)
{
  ABLED_PORT_DDR |= 1<<PD4 | 1<<PD5 | 1<<PD6;        /* Port auf Ausgang 
*/
  ABLED_PORT_OUT &= ~(1<<PD4 | 1<<PD5 | 1<<PD6);      /* Low ausgeben */
  cli();                            /* IRQs abschalten */
  TCCR0 = 1<<WGM00 | 1<<WGM01 | 1<<CS02 | 1<<CS00;      /* Timer 0: Fast
PWM, clk/1024, bei 8 MHz */
  TCNT0 = value;                        /* Startwert setzen */
  OCR0 = 0;                          /* Output compare auf 0 setzen */
  TIMSK |= 1<<OCIE0;                      /* Output compare -> IRQ */
  sei();                            /* IRQs einschalten */
  return;
}

void abled_change(unsigned char value)
{
  cli();                            /* IRQs abschalten */
  TCNT0 = value;                        /* Startwert neu setzen */
  sei();                            /* IRQs einschalten */
  return;
}

SIGNAL(SIG_OUTPUT_COMPARE0)
{
  ABLED_PORT_OUT |= 1<<PD4 | 1<<PD5 | 1<<PD6;        /* High ausgeben */
}

SIGNAL(SIG_OUTPUT_OVERFLOW0)
{
  ABLED_PORT_OUT &= ~(1<<PD4 | 1<<PD5 | 1<<PD6);      /* Low ausgeben */
}

von Florian Hrubesch (Gast)


Lesenswert?

Von dem her was ich grad im datasheet gesehen hab, ist timer0 nicht für
pwm vorgesehen und es gibt gar kein compare register und auch keinen
compare interrupt.
Du müsstes das alles im overflow machen und bei jedem overflow tcnt0
neu setzen um so ne art pwm zu bekommen.
cu Flo

von Ronny Schulz (Gast)


Lesenswert?

Hmm .. ich hatte natürlich vergessen zu erwähnen, um welchen Controller
es sich handelt. Ich rede hier vom ATmega16. Der hat natürlich diese
Funktionalität.

von Florian Hrubesch (Gast)


Lesenswert?

OK.
Sieht so aus als hättest du dein ocr0 auf null stehen.
tcnt0 brauchst bzw darfst du auf keinen fall ändern wenn du was änderst
dan ocr0 damit definierst du dann die helligkeit.

TCNT0 steht blos der Wert drinn den der Timer grad hat, da würd ich ned
drann rumspielen.
Bei jedem inkrement von tcnt0 durch den timer wird tcnt0 mit ocr0
verglichen und der entsprechende interrupt ausgelöst.
Bei jedem overflow wird der overflow ausgelöst.
Da nun OCR0 auf null ist wird beim overflow deine leds ausgemacht und
sofort wieder ein, weil dein ocr0= 0 ist.
Deswegen leuchten die led andauernd hell.
cu Flo

von Ronny Schulz (Gast)


Lesenswert?

Upsa .. einfach mal was verwechselt. :)

Der gröbste Fehler, war jedoch der IRQ-Vektor. Da habe ich den falschen
angegeben. Jetzt läuft es auch soweit. Nur das die LEDs schon bei einem
Wert von 100 mit etwa voller Helligkeit leuchten. Schöner wäre
natürlich, wenn man das prozentual von 0 - 254 aufteilen könnte. Dazu
müssten aber die Timings genau auf die LED bzw. den ULN2003A
zugeschnitten sein. Oder wie kann man das verbessern.

Eine andere Frage stellt sich mir jetzt erst. Wie kann ich da mehrere
LEDs verschieden hell leuchten lassen. Ich habe ja nicht unedlich viele
Timer zur Verfügung, wo ich das Tastverhältnis einstellen kann. Da der
Overflow ja immer zu gleichen Zeit kommt könnte ich mir vorstellen, die
Auschaltzeit im "Compare"-IRQ für jede LED zu verändern. Das würde
aber heißen, dass das Compare-Register ständig in der IRQ-Routine
angepasst werden müsste bzw. es garnicht mehr über einen PWM-Timer
gemacht wird, sondern einen der eben bei jedem Overflow aufgerufen
wird, welches ich dann zeitlich durch 255 teilen müsste. Wie würdet ihr
das machen?

Hier nochmal der fehlerbereinigte Code:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>

#include "abled.h"


unsigned char abled_table_rgb[3][3] = {
  { 0xFF, 0xFF, 0xFF },
  { 0xFF, 0xFF, 0xFF },
  { 0xFF, 0xFF, 0xFF }
};


void abled_init(unsigned char value)
{
  if (value) {
    ABLED_PORT_DDR |= 1<<PD4 | 1<<PD5 | 1<<PD6;      /* Port auf Ausgang 
*/
    ABLED_PORT_OUT |= 1<<PD4 | 1<<PD5 | 1<<PD6;      /* High ausgeben */
    cli();                          /* IRQs abschalten */
    TCCR0 = 1<<WGM00 | 1<<WGM01 | 1<<CS02;          /* Timer 0: Fast 
PWM,
clk/256, bei 8 MHz */
    TCNT0 = 0;                        /* Zähler auf 0 setzen */
    OCR0 = value;                      /* Output compare setzen */
    TIMSK |= 1<<OCIE0 | 1<<TOIE0;              /* Overflow + Output 
compare ->
IRQ */
    sei();                          /* IRQs einschalten */
  }
  return;
}

void abled_change(unsigned char value)
{
  cli();                            /* IRQs abschalten */
  OCR0 = value;                        /* Output compare neu setzen */
  sei();                            /* IRQs einschalten */
  return;
}

SIGNAL(SIG_OUTPUT_COMPARE0)
{
  ABLED_PORT_OUT &= ~(1<<PD4 | 1<<PD5 | 1<<PD6);      /* Low ausgeben */
}

SIGNAL(SIG_OVERFLOW0)
{
  ABLED_PORT_OUT |= 1<<PD4 | 1<<PD5 | 1<<PD6;        /* High ausgeben */
}

von Florian Hrubesch (Gast)


Lesenswert?

Hmm der msp430f149 hat 10 ausgängen für pwm;) Der 16bit timer vom avr
hat glaub ich auch mehrer compare-register.
Du musst die leds ja auch ned mit maximal möglicher geschwindigkeit
dimmen sondern es reicht ja wenn du ne pwm von 100Hz hast(also timer
einmal pro 100tel sekunde voll durchgelaufen) das müsste sich dann in
software aufsetzend auf dem timerinterrupt auch für mehrer leds machen
lassen.
Ich glaub das Problem das deine leds ned voll regelbar sind sind die
schaltzeiten probier mal nen anderen prescaler --> langsamer
cu Flo

von Ronny Schulz (Gast)


Lesenswert?

Die 10 würde wahrscheinlich immernoch nicht reichen. ;)

Das mit den einzelnen LEDs klappt zumindest mit 3 Stück schon so
halbwegs. Ich setze jedes mal in der IRQ-Routine einen neuen OCR0, der
dann der nächsthöhere aus einer Tabelle mit den Helligkeitswerten ist.
Die PWM mache ich jetzt mi 8MHz/256. Wenn ich auf /1024 gehe flackert
der ganze Kram.

Dafür gibt es allerdings andere Probleme, die ich in einem neuen Thread
setze. Irgendwas haut da mit C selbst nicht hin.

Danke für deine Hilfe. Wenn die Routine mal fertig ist, werde ich die
in die Codesammlung posten. Vielleicht braucht ja jemand sowas noch.

von Jörg Wunsch (Gast)


Lesenswert?

Wie viele LEDs willst Du denn steuern?

Das hört sich danach an, als würde der Controller weiter nichts tun
als die LEDs anzusteuern, dann kann man sicher genauso die PWMs in
Software schreiben.  Da kannst Du dann so viele davon haben, wie Du
freie Portpins hast (zumal die Genauigkeit in dem Falle eher eine
untergeordnete Rolle spielen dürfte).

von Ronny Schulz (Gast)


Lesenswert?

Ich dachte da so an ca. 12 LEDs ... Die Genauigkeit ist natürlich
relativ uninteressant. Viel mehr soll der Controller nacher wirklich
nicht zu tun haben. Da kommen nur noch 2 - 3 Taster ran. Die man
natürlich durch die wenigen Funktionen, die man benötigt, u.U. auch
doch IRQs abfragen könnte. Aber das ist relativ egal.

von Jörg Wunsch (Gast)


Lesenswert?

Ich würde mir eine Zeitgeberfunktion schreiben (bzw. hab' ich schon
:-), die timer queues verwaltet.  Für jeden Kanal läßt man dann das
An- und Abschalten durch einen software timer vornehmen.  Braucht
einen einzigen hardware timer mit entsprechender Auflösung.

Wenn man die Auflösung zu hoch ansetzt, wird die Interruptlast sicher
nicht zu verachten sein, andererseits wirst Du vermutlich kaum mehr
als 50 Helligkeitsstufen unterscheiden können müssen, und 50 Hz
Flimmerfrequenz müßten auch genug sein.  Das bedeutet eine
Zeitgeberrate von 2500 Hz bzw. 400 µs.  Wenn Du nur 1 MHz Taktfrequenz
hast, kann das schon bißchen knapp werden (Du hast dann maximal 400
ausführbare Befehle zwischen den Interrupts), bei höherem Takt ist das
aber sicher kein Problem mehr.

von Ronny Schulz (Gast)


Lesenswert?

Naja wenn ich meine LEDs mal alle habe, werde ich das ganze verdrahten
und dann kann ich mich ja an die wirkliche Programmierung machen. Die
Hardwareseite ist ja nicht so das Problem. Aber dauern wird das sicher
dennoch ein weilchen. Allerdings ist es erstmal schln zu wissen, wie
man das hinbekommt. Und im Prinzip funktuniert ja meine Lösung schon
mehr oder weniger.

Zeig doch mal deine Queue her. ;)

von Jörg Wunsch (Gast)


Lesenswert?

> Zeig doch mal deine Queue her. ;)

OK, ich habe die Möglichkeit, timertick() aus einem Interruptkontext
direkt zu rufen, noch in den Hauptzweig meiner CVS-Version
zurückgepatcht.  Wenn Du die Bibliothek mit -DTMR_INTPROTECT
compilierst, darfst Du timertick() entsprechend direkt aus der
Timer-ISR heraus rufen (dafür wird mehr Code gebraucht).

Normalerweise sollte das aber eher so sein

volatile unsigned char timer_ticked;

SIGNAL(SIG_OUTPUT_COMPARExx)
{
  /* timer running in CTC mode, just trigger timertick() */
  timer_ticked = 1;
}


...
int
main(void)
{
  ...
  for (;;) {
    if (timer_ticked) {
      timer_ticked = 0;
      timertick();
    }
    ...
  }
}

http://www.sax.de/~joerg/timer.tar.gz

Die union timeoutarg erlaubt es, 16-bit (int) Werte direkt zu
übergeben, aber auf saubere Weise stattdessen einen Zeiger auf einen
größeren Datenbereich an die nach dem Timeout gerufene Funktion zu
übergeben.

von Ronny Schulz (Gast)


Lesenswert?

Hmm .. letztendlich will ich ja dennoch PWM nutzen und das geht ja im
CTC-mode nicht. Ich denke aber ich werde einfach deinen Tipp mit der
Queue nutzen. Einfach eine Queue aufbauen. Alle Ports und Zeiten
reinqueuen und dann den Timer starten. Somit ist das eine sehr flexible
Bibliothelk, die halt nur auf gewünschte Ports ein geünschtes
Software-PWM zur Verfügung stellt. Die Zeiten könnte man sich dann
idealerweise in der Initialisierung ausrechnen. So das dann nachher nur
noch der Pointer zur Queue und die Zeiten übertragen werden.

Programmiertechnisch ist das allerdings ein recht hoher Aufwand. Da
muss ich wirklich überlegen, ob ich mir die Mühe machen will. :)

von Jörg Wunsch (Gast)


Angehängte Dateien:

Lesenswert?

Da mich der Spaß selbst interessiert hat, hier mal eine
Implementierung des ganzen.  8 Software-PWM-Kanäle an Port B,
die LEDs sind low-aktiv.  Getestet auf STK500/502 (also mit
ATmega128), sollte sich einfach für andere Controller ändern
lassen.

Was mir beim Spielen aufgefallen ist: die Helligkeit der LEDs
zumindest am STK500 ändert sich bei geringen Tastverhaltnissen
drastisch, bei großen Tastverhältnissen kaum noch.  Zwischen
50 und 100 % kaum noch eine optische Änderungen.  Darum bin ich
erstens von 50 auf 250 PWM-Stufen gegangen (sonst waren die
Sprünge im unteren Bereich zu groß) und habe zweitens in der
kleinen Spielerei, die die PWM-Werte verändert, bei 50 %
aufgehört und fange von vorn an.

Möglicherweise ist dieser Effekt aber bei anderen LEDs nicht so
stark ausgeprägt.

von Stefan Kleinwort (Gast)


Lesenswert?

@Jörg:
Habe den Effekt auch schon beobachtet. Nachdem das schon > 10 Jahre her
ist, nehme ich an, dass es auf so ziemlich alle LEDs zutrifft ;-)
Optisch passt die Ausgabe, wenn man einen quadratischen Zusammenhang
programmiert:

PWM-Einzeit(16 Bit) = Helligkeit(8 Bit) * Helligkeit(8 Bit)

Stefan

von Ronny Schulz (Gast)


Lesenswert?

War bei meinen Tests aber auch so. Daher scheint das völlig normal zu
sein. So kann man sich nacher noch überlegen, wo die 100% erreicht
sind.

Überhaupt steigt die Helligkeit nicht linear an, was man gut bei einer
LED sieht, die man langsam voll aufdreht und dann wieder langsam runter
dreht. Und das eben ständig wiederholt.

Ich denke das Grundprinzip funktioniert erstmal. Deshalb werde ich mal
sehen, dass ich meine Schaltung komplett aufbaue und dann erst richtig
anfange mit programmieren.

von TobyTetzi (Gast)


Lesenswert?

Hallo,

ein sehr alter Thread, aber trotzdem!

Hast Du mittlerweile eine laufende Versin der RGB Ansteuerung?

Würde mich auch mal intressieren, deinen Code zu sehen.
Ebenso die abled.h.

Gruß Toby

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.