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
}
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 "
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 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.
Adam Haile
Kurt E. Tuchmacher
Adam Haile
Adam Haile
Kurt E. Tuchmacher
Adam Haile
Kurt E. Tuchmacher
Adam Haile
Adam Haile
Kurt E. Tuchmacher
Kurt E. Tuchmacher
Adam Haile
Adam Haile
Adam Haile
Kurt E. Tuchmacher
Adam Haile
Kurt E. Tuchmacher
Adam Haile
Kurt E. Tuchmacher