Dimmen von Multiplex-LEDs

Ich multiplexe 32 LEDs in einer 4: 8-Konfiguration auf einem ATMega328 und versuche, sie mit einem wahrscheinlich völlig naiven Verständnis von PWM zu dimmen. Hinweis: Ich multiplexe sie direkt mit 12 Pins vom ATMega, es werden überhaupt keine anderen Chips verwendet. Grundsätzlich schalte ich alle 10 Zyklen der Aktualisierung des gesamten Displays (was 8 des ISR-Interrupts benötigt) nur für 2 davon alles aus, wenn ich es mit 80% Helligkeit anzeigen möchte. Aber in der Praxis bringt mir das nur ein paar pulsierende LEDs.

Zuerst dachte ich, ich würde ISR einfach nicht genug nennen, also änderte ich die Vorskalierung von 8 auf 1, aber das änderte nichts.

Grober Code ist unten. Irgendwelche Vorschläge geschätzt. Ich bin mir sicher, dass ich das VOLLSTÄNDIG falsch mache: P Das Multiplexing funktioniert gut und ich bekomme eine volle Anzeigeaktualisierungsrate von 244 Hz, glaube ich, was gut genug schien. Kann einfach nicht dimmen.

Hinweis: Die "data"-Variable wird in der (nicht gezeigten) loop()-Methode geändert.

uint8_t pwmStep = 0;
uint8_t pwmMax = 10;
float pwmLevel = 0.8f; //range 0.5 - 1.0 (50% - 100%)

void setup()
{
  //port inits and other stuff here...

  // timer 1 setup, prescaler 8
  TCCR1B |= (1 << CS10);

  // enable timer 1 interrupt
  TIMSK1 = _BV(TOIE1);
}

uint8_t col, row = 0;
ISR(TIMER1_OVF_vect) 
{
  //Turn all columns off 
  PORTB |= (_BV(PB0) | _BV(PB1) | _BV(PB2) | _BV(PB3) | _BV(PB4) | _BV(PB5));
  PORTD |= (_BV(PD6) | _BV(PD7));

  if(pwmStep < pwmLevel*pwmMax)
  {
    //set the 4 rows
    for(row=0; row<4; row++)
    {
      if(data & (1UL << (row + (col * 4))))
        PORTC |= _BV(row);
      else
        PORTC &= ~_BV(row);
    } 

    //Enable the current column
    if(col < 6)
      PORTB &= ~_BV(col);
    else
      PORTD &= ~_BV(col);
  }
  col++;
  if(col == 8)
  { 
    col = 0;
    pwmStep++;
    if(pwmStep == pwmMax)
      pwmStep = 0;
  }
}

*Update: * Ich habe versucht, dies mit einem ATTiny85 zu tun, bekomme aber nicht ganz die Ergebnisse, die ich vermutet habe. Es dämpft zwar, aber nur sehr wenig.

Siehe Code unten. Selbst wenn OCR0B auf SEHR klein eingestellt ist, bekomme ich nur eine geringe Dimmung.

#define PRESCALE_1 _BV(0)
#define PRESCALE_8 _BV(1)
#define PRESCALE_64 (_BV(0) | _BV(1))
#define PRESCALE_256 _BV(2)
#define PRESCALE_1024 (_BV(2) | _BV(0))

void setup(){
  DDRB |= _BV(PINB0);

  PRR &= ~_BV(PRTIM0); //Enable Timer 0
  TCCR0A = 0x00; //Outputs disconnected

  // turn on CTC mode, 64 prescaler
  TCCR0B |= _BV(WGM01) | PRESCALE_64;


  OCR0A = 200; //~1200 Hz
  OCR0B = 50;  //~4800 Hz

  //
  TIMSK = _BV(OCIE0B) | _BV(OCIE0A); //Interrupts Enabled
}

ISR(TIM0_COMPA_vect){
  PORTB |= _BV(PINB0); //Set PINB0 on
}

ISR(TIM0_COMPB_vect){
  PORTB &= ~_BV(PINB0); //Set PINB0 off
}

Antworten (3)

Zunächst einmal habe und habe ich Arduino nicht verwendet, aber ich kenne mich mit AVR-Chips und insbesondere mit dem ATmega328p sehr gut aus. Wenn ich Sie richtig verstehe, versuchen Sie, eine 4x8-Matrix von LEDs zu dimmen. Die gesamte Matrix soll auf einmal gedimmt werden, aber nicht jede LED wird immer an sein, also individuelle An/Aus-Steuerung mit kollektivem Dimmen. Dies ist eigentlich eine sehr einfache Sache. Lassen Sie mich zunächst die PWM-Steuerung erklären, da Sie erwähnt haben, dass Sie es möglicherweise nicht richtig machen.

Wenn ich eine LED und einen Vorwiderstand habe und 5V anschließe, leuchtet es bei einer gewissen Helligkeit - ein Faktor des Stroms durch die LED, der durch den Vorwiderstand eingestellt wird. Wenn ich die Spannung senke, sinkt auch der Strom, wodurch die LED gedimmt wird. Wenn ich einen Impuls an die LED sende, ist die effektive Spannung der LED ein Durchschnitt der Impuls-Ein- und Aus-Zustände. Dieser Prozentsatz der Einschaltzeit wird als Arbeitszyklus bezeichnet. Die Frequenz des Pulses selbst gibt an, wie oft er sich wiederholt. Um beispielsweise einen 100-Hz-Impuls mit einem Arbeitszyklus von 50 % zu erzeugen, würde ich ein Signal für 5 ms ein- und dann für 5 ms ausschalten wollen. Die Gesamtdauer beträgt 10 ms. Frequenz = Kehrwert von Periode = 1/10 ms = 100 Hz. Das Tastverhältnis beträgt 5 ms/10 ms = 50 %.

LEDs können sehr schnell ein- und ausschalten, aber das menschliche Auge kann diese Änderungen ab einer bestimmten Frequenz nicht mehr unterscheiden - dieser Wert ist für verschiedene Menschen unterschiedlich. Wenn man bedenkt, dass ein Fernseher in den USA bei 60 Hz (50 Hz anderswo) auffrischt, können wir mit Sicherheit sagen, dass 50 Hz ein gutes Minimum ist, obwohl viele Studien gezeigt haben, dass bei LEDs bestimmte Frequenzen tatsächlich dazu führen können, dass die LED bei gleichem Arbeitszyklus heller erscheint . Eine gebräuchliche Zahl ist 100 Hz.

Die Steuerung einer einzelnen LED und sogar von Gruppen auf diese Weise ist mit einem Timer sehr einfach. Der folgende Code ermöglicht es Timer 1, bei 125 Hz (8 ms Periode) mit Fcpu = 16 MHz zu laufen.

  #define _BV(FOO) (1<<FOO)
  // Set up Timer 1 for 125Hz LED pulse to control brightness
  PRR &= ~_BV(PRTIM1);                  // Enable Timer1 Clock
  TCCR1A = 0x00;                        // Outputs Disconnected
  TCCR1B = _BV(WGM12) |                 // CTC Mode, Top = OCR1A
           _BV(CS11) | _BV(CS10);       // Prescaler = 64
  OCR1A = 1999;                         // Top = (16MHz * 8ms / 64)-1
  OCR1B = LED_DUTY_CYCLE;               // LED PUlse Width
  TIMSK1 = _BV(OCIE1B) | _BV(OCIE1A);   // Interrupts Enabled

In diesem Code findet der Vergleichsvergleich A alle 8 ms statt und erzeugt einen 125-Hz-Impuls. Der Vergleichsabgleich B findet bei jedem Wert statt, der als "LED_DUTY_CYCLE" definiert ist. Stellen Sie OCR1B für eine Einschaltdauer von 80 %, wie Sie bereits erwähnt haben, auf 1600 ein. Dieser Wert könnte auch im Code geändert werden, z. B. wenn ein Benutzer eine Dimmfunktionstaste drückt.

Die LED-Kontrolle findet für die beiden Vergleichsspiele im ISR statt. Die Variable "Ausgänge" wird im Hauptprogramm immer dann aktualisiert, wenn eine LED ein- oder ausgeschaltet sein soll. Jedes Bit dieser Variablen wird einer LED zugeordnet. Um zum Beispiel die LEDs 0 und 5 einzuschalten, sollten die Ausgänge in main auf 0b00100001 gesetzt werden. Die Variable "Helligkeit" kann hauptsächlich aktualisiert werden, um den Arbeitszyklus der LEDs zu steuern. Im COMPA ISR werden die durch „Ausgänge“ aktivierten LEDs eingeschaltet. Dann sollten alle LEDs im COMPB ISR ausgeschaltet werden.

ISR(TIMER1_COMPA_vect){
  PORTD = (outputs & 0xFF);         // Turn On LEDs Q0 - Q7
  OCR1B = brightness;               // Set the pulse width
}
ISR(TIMER1_COMPB_vect){
  PORTD = 0x00;                     // Turn Off LEDs Q0 - Q7
}

In diesem Beispiel sind 8 LEDs mit jedem der 8 Pins von PORTD verbunden. Sie könnten überall platziert werden, dies macht das Codebeispiel nur einfacher zu lesen. Wenn die LEDs verteilt sind, müssten Sie etwas mehr wie folgt tun:

if(outputs & 0x01) PORTD |= LED0; 
if(outputs & 0x02) PORTD |= LED1;
if(outputs & 0x04) PORTC |= LED2;
//...
if(outputs & 0x40) PORTB |= LED6;
if(outputs & 0x80) PORTB |= LED7;

Beachten Sie, dass jede LED einem Bit in "Ausgängen" zugeordnet ist, die LEDs selbst sich jedoch in verschiedenen IO-Ports befinden. Alle aktivierten LEDs werden eingeschaltet.

Die Steuerung einer Matrix ist etwas komplexer, da immer nur eine Spalte eingeschaltet ist. In Anbetracht dessen beträgt die höchstmögliche Einschaltdauer, die Sie erreichen können, 25 %, selbst wenn alle LED-Reihen die ganze Zeit eingeschaltet waren. Das liegt daran, dass jede Spalte nur 1/4 der Gesamtzeit ausmachen würde. Wenn mehr als eine Spalte gleichzeitig eingeschaltet ist, verlieren Sie vollständig Ihre Fähigkeit, einzelne LEDs ein- und auszuschalten. Etwas anderes, was man bei einer Matrix beachten sollte, ist der Stromverbrauch. Abhängig von Ihrer Definition von Zeile und Spalte haben Sie entweder 4 Bänke mit 8 oder 8 Bänke mit 4 parallelen LEDs. Wenn die Kathoden alle miteinander verbunden sind, senkt dieser IO-Pin den Strom durch alle LEDs. Der ATmega328p hat einen maximalen Strom von 40 mA pro Pin und insgesamt 200 mA gleichzeitig. Das Einzelpinproblem ließe sich leicht umgehen, indem man die LEDs durch ein "

4x8 LED-Matrix

Natürlich kann das Ganze um 90 Grad gedreht werden, um es Ihren Bedürfnissen anzupassen. In jedem Fall schalten die 8 "CTRL"-Leitungen eine LED ein, solange das entsprechende "COLUMN"-Signal ebenfalls hoch ist. Die Spaltensteuerung sollte einfach sein und kann im Haupt- oder Timer-Interrupt erfolgen, aber die PWM-Frequenz sollte etwa 4-mal schneller sein als die Spaltenschaltfrequenz, um sicherzustellen, dass das Dimmen der LEDs immer noch korrekt funktioniert. In diesem Fall würde jede Spalte viermal gepulst werden, bevor die nächste Spalte eingeschaltet wird. Aber wie gesagt, bei 4 Spalten ist jede LED maximal 25 % der Zeit eingeschaltet. Wenn Ihre PWM-Einschaltdauer also auf 80 % eingestellt ist, leuchtet die LED wirklich nur 0,8 * 0,25 = 20 % von die Zeit. Vergessen Sie auch nicht, dass beim Umschalten der aktiven Spalte die Steuerung von einer LED-Bank zur nächsten wechselt, also die "Ausgänge".

Bemerkenswert ist auch, dass es keine Rolle spielt, was Sie pulsieren, um die LEDs zu dimmen. Im obigen Beispiel habe ich die Zeilen gepulst, weil es einfach ist, eine bestimmte LED auf diese Weise zu aktivieren. Es würde auch funktionieren, stattdessen das Spaltensteuersignal an das FET-Gate zu pulsieren. Da schließlich nur eine Spalte gleichzeitig eingeschaltet ist, kann jede gemeinsam genutzte Reihe einen Widerstand gemeinsam nutzen. Jede Spalte kann sich keinen Widerstand teilen, da sich die individuelle Helligkeit der LEDs ändern würde, je nachdem, wie viele ein- oder ausgeschaltet sind.

Ich bin mir ziemlich sicher, dass ich das verstehe. Mir war völlig nicht bewusst, dass Sie mit einem solchen Arbeitszyklus umgehen können. Was ich immer noch versuche zu verstehen, ist, wie man dieses Dimmen mit mehreren "Spalten" macht. Ich bin derzeit mit einer gemeinsamen Kathode und 8 4er-Gruppen ausgestattet. Grundsätzlich bezieht jeder E / A-Pin immer nur Strom für 1 LED zu einem bestimmten Zeitpunkt. Ich verwende nur 8 andere Pins und Masse und setze jeweils nur 1 auf Masse, um die Säule einzuschalten. Es hat bisher sehr gut funktioniert. Obwohl ich mich jetzt frage, ob ich den Strom am Spülstift überschreite ...
Es würde davon abhängen, wie viele Ampere gleichzeitig durch jede LED gehen. Im schlimmsten Fall: Wenn alle 8 LEDs einer Bank eingeschaltet sind, können sie jeweils maximal 5 mA betragen (5 * 8 = 40 mA max), wenn sie alle von einem Pin versenkt werden. Deshalb habe ich die Verwendung des FET-Treibers erwähnt, um sie zu versenken. Dann kommt es nur noch auf den insgesamt bezogenen Strom an. Das Chip-Maximum beträgt 200 mA, sodass Sie sicher 20 mA pro LED liefern können ... 160 mA insgesamt, wenn alle 8 eingeschaltet sind.
Seltsam ... Ich habe 4 LEDs gleichzeitig an einen Sink-Pin angeschlossen und jede LED hat eine Nennleistung von etwa 20 mA. Ich glaube nicht, dass sie tatsächlich so hoch laufen, aber ich würde mir mehr als 10 mA vorstellen. Aber bei mir läuft das jetzt seit mehreren Wochen ohne Probleme. Zugegeben, jede Gruppe von 4 LEDs ist immer nur für 1/6400 Sekunde eingeschaltet, bevor sie zur nächsten Gruppe von 4 übergeht. Bei 8 Gruppen wird die gesamte Anzeige mit 800 Hz aktualisiert.
Mein aktueller Update-Timer ist wie folgt konfiguriert: TCCR1A = 0; // setze das gesamte TCCR1A-Register auf 0 TCCR1B = 0; // dasselbe für TCCR1B TCNT1 = 0; // initialisiere den Zählerwert auf 0 // setze das Vergleichsregister für 6400 Hz ( 800-Hz-Bildschirmaktualisierung) erhöht OCR1A = 2500; // = (16 * 10 ^ 6) / (1 * 6400) - 1 // CTC-Modus einschalten TCCR1B | = _BV (WGM12); TCCR1B |= PRESCALE_1; // Timer-Vergleichsinterrupt aktivieren TIMSK1 |= _BV (OCIE1A);
Der Timer ist für einen 6400-Hz-ISR korrekt eingerichtet, aber wofür verwenden Sie ihn: um die LEDs zu pulsieren oder um die Steuerung von einer LED-Bank zur nächsten umzuschalten? Für den LED-Strom spielt es keine Rolle, wie hoch er ist, es spielt eine Rolle, welchen Widerstand Sie in Reihe schalten. Unterschiedliche LEDs lassen unterschiedliche Spannungen fallen, die Quelle minus der LED-Spannung ist die Widerstandsspannung, und die Widerstandsspannung geteilt durch den Widerstand entspricht dem Strom: (Vs - V_LED)/R = I_LED. Beispielsweise fällt eine gewöhnliche rote LED normalerweise um 2 V ab. Aus einer 5-V-Quelle würde ein 300-Ohm-Widerstand in Reihe 10 mA durch die LED liefern.
Beide. Ich führe einen Schrittzähler. Während es sich erhöht, schalte ich die nächste Spalte ein und den Rest aus. Und dann schalte ich eine der 4 LEDs in dieser Spalte ein, die eingeschaltet werden müssen. Das gesamte Display wird also effektiv mit 800 Hz aktualisiert. Siehe Code hier: pastebin.com/PW2mFEqc Gedanken? Huh ... stell dir vor. Ich habe die aktuelle Mathematik gemacht: P Ich verwende eine 1,9-Vf-LED und einen 330-Ohm-Widerstand, alle bei 5 V, für etwas weniger als 10 mA Strom pro. x4 LEDs pro Sink-Pin < ~ 40 mA ... kein Wunder, dass ich es nicht durchgebrannt habe.
Sie sollten Ihren Code viel öfter kommentieren (wie jede Zeile), damit jemand anderes verstehen kann, was Sie zu tun versuchen. Wenn ich bei größeren Projekten ein paar Tage nicht an etwas arbeite, brauche ich ewig, um herauszufinden, was ich getan habe! Da Sie in meinem Beispiel 8 statt 4 Spalten verwenden, sind Ihre LEDs nur 1/8 der Zeit ohne Dimmen eingeschaltet. Ich würde vorschlagen, dass Sie den größten Teil dieses Codes aus der ISR ziehen, um ihn in main zu platzieren. Außerdem sehe ich nicht, wie Sie überhaupt PWM verwenden, aber vielleicht, weil es nicht kommentiert wird!
Im Moment war ich der einzige, der es wissen musste ... aber es wird kommentiert, bevor es veröffentlicht wird. Mir ist klar, dass sie nur auf 1/8 der Zeit sind, aber ich musste den Strom pro Spalte minimieren. Warum sollte ich es in die Hauptsache setzen? Dann habe ich keine Kontrolle über die Aktualisierungsfrequenz ... Ich mache kein richtiges PWM, das war das Problem ... aber ich täusche es mit dieser Zeile vor: if (pwmStep < pwmLevel); Ich erhöhe pwmStep jedes Mal, wenn ich einen Scan durch alle 8 Spalten abschließe. Bis zu 10. Wenn also pwm step 5 und pwmLevel 5 ist, werden die letzten 5 Updates übersprungen und das Licht ist nur 50% der Zeit an ... sinnvoll?
Wissen Sie vielleicht, wie Sie Ihren obigen Beispielcode für den ATTiny85 ausführen? Ich versuche, es darauf zu portieren, habe aber kein Glück, dass es richtig funktioniert.
Es sollte genau so funktionieren. Es hat immer noch einen 16-Bit-Timer 1, aber die Registernamen sind für diese MCU unterschiedlich. Werfen Sie einen Blick auf das Datenblatt des ATtinyx5. Insbesondere sollten Sie sich Abschnitt 12.3 – Registerbeschreibungen für Timer 1 auf Seite 88 ansehen: atmel.com/Images/… Normalerweise würde ich den 8-Bit-Timer0 für alles verwenden, da er weniger Strom verbraucht (die anderen Timer mit dem Register zur Leistungsreduzierung deaktivieren). , PRR), aber ich denke, Arduino verwendet bereits timer0 für verschiedene Dinge.
Außerdem ist der tiny25 billiger als der 85er und bietet viel Platz für die meisten kleinen Programme. Das ist einer der Gründe, warum ich erwähnt habe, dass Sie einen Großteil Ihres Codes von der ISR nach main verschoben haben. Sie wollen so wenig Code wie möglich in Ihrem ISR – das hält den Code viel kleiner. Sehen Sie sich diese großartigen App-Hinweise zum Schreiben von effizientem C-Code an: atmel.com/Images/doc1497.pdf und atmel.com/Images/doc8453.pdf
Eigentlich bin ich mir ziemlich sicher, dass die ATTinyx5-Serie nur 8-Bit-Timer hat. Ich kann einfach nicht die richtigen Registernamen einstellen, da sie alle unterschiedlich sind: P
Auf dem Arduino-winzigen Kern, den ich verwende, verwendet er sowieso Timer1 für millis (), also sollte ich wahrscheinlich nur Timer0 verwenden ... Vorschläge? Ich kann die ATTiny85 Timer0-Register anscheinend nicht mit dem Beispiel abgleichen, das Sie hatten.
Nahm einen Stich, der auf dem ATTiny85 funktioniert. Siehe hinzugefügter Code in Frage oben. Gedanken?
Entschuldigung, Sie haben recht. Nur 8-Bit-Timer. In diesem Fall können Sie keine Werte über 255 verwenden, sodass die maximale Auflösung geringer ist. Das einzige, was ich falsch sehen kann, ist es sollte sein: TCCR0A = _BV(WGM01); Sie haben versucht, es mit TCCR0B einzustellen. Abgesehen davon sollte es gut funktionieren. Um zu sehen, ob die LED tatsächlich gedimmt wird, versuchen Sie, Ihren OCR0B-Wert alle 100 ms zwischen 0 und 200 zu erhöhen/zu verringern. Sie sollten sehen, dass die LED alle 100 ms dunkler/heller wird, wenn sich der OCR0B-Wert ändert.
Ahhh ... das funktioniert viel besser. Das einzige Problem, das ich habe, ist das Timing zwischen dem Ändern des Tastverhältniswerts. Beim Versuch, den arduino-winzigen Kern zu verwenden, und obwohl ich denke, dass ich den richtigen Timer verwende, um den Aufruf von millis () oder delay () nicht zu stören, scheint es, dass mein Timer-Setup die Arbeit stört.
Damit bist du auf dich allein gestellt. Wie ich schon sagte, habe ich es nicht getan, noch möchte ich Arduino verwenden. Ich entwerfe meine eigenen eingebetteten Systeme um jeden Chip herum, den ich verwenden möchte. Es gibt mir so viel mehr Freiheit und Kontrolle. Persönlich denke ich, dass Arduino ein wertloser Overhead ist, der seine Benutzer nur daran hindert, zu lernen, wie die Dinge tatsächlich funktionieren.
Nun, gibt es eine Möglichkeit, eine Zeitverzögerung in direktem AVR-Code zu machen? Das nutze ich auch lieber.
Ja viele! Und ich denke, Sie meinen "C-Code". Die am häufigsten mit AVR verwendeten Programmiersprachen sind C und Assembly. Ich glaube, Arduino verwendet ein modifiziertes C++, das eine erweiterte Version von C ist. Es gibt eine Header-Datei, die für Verzögerungen verwendet werden kann. Sehen Sie sich die Dokumentation hier an: nongnu.org/avr-libc/user-manual/group__util__delay.html Oder Sie könnten Ihre eigene ISR-Routine erstellen, um den Fluss Ihres Programms zu steuern. Ich verwende fast immer den Timer 0, um eine 1-ms-Uhr zu erstellen, und benutze diese dann, um alles im Programm zu timen: Verzögerungen, PWM, Tastenprüfungen usw.

Ich würde damit beginnen, die Gleitkommaoperationen aus einem Interrupt auf einem AVR zu entfernen, da sie sehr CPU-intensiv sein können und je nach Taktgeschwindigkeit der Interrupt möglicherweise nicht abgeschlossen ist, wenn der Timer bereit ist, das nächste Mal ausgelöst zu werden. Zum Beispiel ändern:

uint8_t pwmMax = 10;
float pwmLevel = 0.8f;
....
if(pwmStep < pwmLevel*pwmMax)

Zu:

uint8_t pwmMax = 100;
uint8_8 pwmLevel = 80;
if(pwmStep < pwmLevel)

Idealerweise möchten Sie für PWM das Schalten verschachteln. Zum Beispiel werden Sie im Moment über beispielsweise 10 Interrupts Folgendes tun:

1 1 1 1 1 1 1 1 0 0

Wobei folgende Reihenfolge ideal wäre:

1 1 1 1 0 1 1 1 1 0

Das würde die Dinge jedoch verkomplizieren, und bei über 50 Hz würde ich nicht erwarten, dass das Flackern sichtbar ist, sodass das nicht nach Ihrem eigentlichen Problem klingt.

Ich stimme PeterJ in Bezug auf Interleaving respektvoll nicht zu: Obwohl dies ideal ist, tut dies kein kommerzieller LED-Dimmer, AFAIK. Alle, die ich gesehen habe, verschachteln die Ein- und Ausschaltzyklen nicht so, wie Sie es vorschlagen. Und wenn PWM schnell genug ist, spielt es keine Rolle.

Beachten Sie, dass die Zuordnung von PWM zu Intensität nicht linear, sondern exponentiell ist. Unter Verwendung von Zweierpotenzen beträgt die Anzahl der Zyklen, die die LED einschalten muss, 2^(Intensität) oder 2^(Intensität)-1. Ich bevorzuge letzteres, also bleiben wir dabei. Das bedeutet, dass Sie für 4 Intensitäten (Intensitätsstufen 0-3) Ihre PWM-Periode in 7 Zyklen aufteilen müssen und Ihre LEDs für 0, 1, 3 bzw. 7 Zyklen eingeschaltet sein sollten.

Auch das löst natürlich nicht dein Problem. Sollen die LEDs einzeln oder gemeinsam gedimmt werden? Im letzteren Fall können Sie die PWM-Einrichtungen von AVR verwenden und keine eigenen programmieren. Wenn dies ein sofort einsatzbereiter AVR-Chip ist, hat er den 8-fachen Teiler, sodass Sie 1 MHz ausführen, und es kann sein, dass Ihr PWM-Code zu langsam ist. Sie können das wie folgt ausschalten, ohne Sicherungen durchzubrennen:

CLKPR = (1<<CLKPCE);  // CLKPCE bit must be set immediately before CLKPS bits
CLKPR = 0;  // System clock divider = 1

Dies sollte zumindest Ihre LEDs schneller blinken lassen :) Aber in jedem Fall wäre ein Schaltplan hilfreich.

Ich verwende ein Standard-Arduino-Setup ATMega328, es läuft also mit 16 MHz. Ich bin mir nicht wirklich sicher, wie ich das eingebaute PWM verwenden würde, da der Chip nur 3 PWM-Pins hat und ich glaube, ich würde mindestens 4 benötigen (da ich 4 gemeinsame Kathoden für die LEDs habe). Richtig? Selbst wenn es genug gäbe, wie würde ich das tun?
Um die Pinbelegung zu verdeutlichen, sind dies diejenigen, auf die es ankommt: // Gemeinsame Kathoden als Ausgänge einrichten DDRC = _BV (PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3); //Zeilen als Ausgänge einrichten DDRB = _BV(PB0) | _BV(PB1) | _BV(PB2) | _BV(PB3) | _BV(PB4) | _BV(PB5); DDRD = _BV(PD6) | _BV(PD7);
Und ... ich muss die LEDs gemeinsam dimmen. Nicht alle von ihnen sind immer an, aber es ist nur ein ganzes Display, das gedimmt wird. Zu wissen , wie man sie einzeln macht, wäre cool für zukünftige Projekte, aber nicht das, was ich jetzt brauche.
Die scheinbare Helligkeit ist eher logarithmisch als linear, aber wenn man farbige LEDs verwendet, ist der scheinbare Farbton von zB Rot 10 %, Blau 20 % etwa derselbe wie der von Rot 5 %, Blau 10 %.