Ich versuche, mit einem ATtiny13A eine ferngesteuerte RGB-LED zu erstellen.
Ich weiß, dass der ATtiny85 für diesen Zweck besser geeignet ist, und ich weiß, dass ich möglicherweise nicht in der Lage bin, den gesamten Code anzupassen, aber im Moment ist es mein Hauptanliegen, eine Software-PWM mithilfe von Interrupts im CTC-Modus zu generieren.
Ich kann nicht in einem anderen Modus betrieben werden (außer für schnellen PWM mit OCR0A
wie TOP
das ist im Grunde dasselbe ist) , weil der IR - Empfänger Code verwende ich eine 38 kHz Frequenz benötigt , die es mit Hilfe erzeugt CTC und OCR0A=122
.
Also versuche ich (und ich habe gesehen, dass Leute dies im Internet erwähnen) die Output Compare A
und Output Compare B
Interrupts zu verwenden, um eine Software-PWM zu generieren.
OCR0A
, der auch vom IR-Code verwendet wird, bestimmt die Frequenz, die mir egal ist. Und OCR0B
bestimmt den Arbeitszyklus der PWM, die ich zum Ändern der LED-Farben verwenden werde.
Ich erwarte, dass ich eine PWM mit einem Tastverhältnis von 0-100% erhalten kann, indem ich den OCR0B
Wert von 0
auf ändere OCR0A
. So verstehe ich, was passieren soll:
Aber was tatsächlich passiert, ist Folgendes (dies stammt aus der Proteus ISIS-Simulation):
Wie Sie unten sehen können, kann ich einen Arbeitszyklus von 25% bis 75% erreichen, aber für ~ 0-25% und ~ 75-100% bleibt die Wellenform einfach hängen und ändert sich nicht.
GELBE Linie: Hardware PWM
ROTE Leitung: Software-PWM mit festem Arbeitszyklus
GRÜNE Linie: Software-PWM mit variierendem Arbeitszyklus
Und hier ist mein Code:
#ifndef F_CPU
#define F_CPU (9600000UL) // 9.6 MHz
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
int main(void)
{
cli();
TCCR0A = 0x00; // Init to zero
TCCR0B = 0x00;
TCCR0A |= (1<<WGM01); // CTC mode
TCCR0A |= (1<<COM0A0); // Toggle OC0A on compare match (50% PWM on PINB0)
// => YELLOW line on oscilloscope
TIMSK0 |= (1<<OCIE0A) | (1<<OCIE0B); // Compare match A and compare match B interrupt enabled
TCCR0B |= (1<<CS00); // Prescalar 1
sei();
DDRB = 0xFF; // All ports output
while (1)
{
OCR0A = 122; // This is the value I'll be using in my main program
for(int i=0; i<OCR0A; i++)
{
OCR0B = i; // Should change the duty cycle
_delay_ms(2);
}
}
}
ISR(TIM0_COMPA_vect){
PORTB ^= (1<<PINB3); // Toggle PINB3 on compare match (50% <SOFTWARE> PWM on PINB3)
// =>RED line on oscilloscope
PORTB &= ~(1<<PINB4); // PINB4 LOW
// =>GREEN line on oscilloscope
}
ISR(TIM0_COMPB_vect){
PORTB |= (1<<PINB4); // PINB4 HIGH
}
quelle
OCR0A
wird vom IR-Code verwendet, also habe ich nurOCR0B
. Ich versuche, damit Software-PWM auf 3 Nicht-PWM-Pins zu generieren.Antworten:
Eine minimale Software-PWM könnte folgendermaßen aussehen:
Ihr Programm stellt
dutyCycle
den gewünschten Wert ein und der ISR gibt das entsprechende PWM-Signal aus.dutyCycle
ist auint16_t
, um Werte zwischen 0 und einschließlich 256 zu berücksichtigen; 256 ist größer als jeder mögliche Wert voncurrentPwmCount
und bietet somit einen vollen Arbeitszyklus von 100%.Wenn Sie nicht 0% (oder 100%) benötigen, können Sie einige Zyklen mit a rasieren,
uint8_t
sodass entweder0
ein Arbeitszyklus von 1/256 entsteht und255
100% oder0
0% und255
ein Arbeitszyklus von 255 / beträgt. 256.In einem 38-kHz-ISR haben Sie immer noch nicht viel Zeit. Mit einem kleinen Inline-Assembler können Sie wahrscheinlich die Zykluszahl des ISR um 1/3 bis 1/2 reduzieren. Alternative: Führen Sie Ihren PWM-Code nur bei jedem zweiten Timer-Überlauf aus und halbieren Sie die PWM-Frequenz.
Wenn Sie mehrere PWM-Kanäle haben und die Pins, die Sie PMW-fähig sind, alle gleich sind
PORT
, können Sie auch alle Pins-Zustände in einer Variablen sammeln und sie schließlich in einem Schritt an den Port ausgeben, der dann nur das Auslesen benötigt. Port und mit Maske oder mit neuem Status einmal in Port schreiben statt einmal pro Pin / Kanal .Beispiel:
Dieser Code ordnet den Arbeitszyklus einem logischen
1
Ausgang an den Pins zu. wenn Ihre LEDs ‚negative Logik‘ haben (LED leuchtet , wenn der Stift ist niedrig ), können Sie die Polarität des PWM - Signals invertieren , indem einfachif (cnt < dutyCycle...)
aufif (cnt >= dutyCycle...)
.quelle
if
in die Interrupt-Routine einfügen, um nur den PWM-Code jedes zweite Mal auszuführen. Wenn mein PWM-Code zu lange dauert und der nächste Überlauf-Interrupt übersehen wird, ist mein Programm in Ordnung, da der nächste Interrupt sowieso nichts bewirken würde. Ist es das, was du meintest?Wie @JimmyB kommentierte, ist die PWM-Frequenz zu hoch.
Es scheint, dass die Interrupts eine Gesamtlatenz von einem Viertel des PWM-Zyklus haben.
Bei Überlappung wird das Tastverhältnis durch die Gesamtlatenz festgelegt, da der zweite Interrupt nach dem Verlassen des ersten in die Warteschlange gestellt und ausgeführt wird.
Das minimale PWM-Tastverhältnis ergibt sich aus dem Prozentsatz der gesamten Interrupt-Latenz in der PWM-Periode. Die gleiche Logik gilt für das maximale PWM-Tastverhältnis.
In den Diagrammen liegt der minimale Arbeitszyklus bei etwa 25%, und dann muss die Gesamtlatenz ~ 1 / (38000 * 4) = 6,7 µs betragen.
Infolgedessen beträgt die minimale PWM-Periode 256 · 6,7 us = 1715 us und eine maximale Frequenz von 583 Hz.
Weitere Erklärungen zu möglichen Patches mit hoher Frequenz:
Der Interrupt verfügt über zwei blinde Fenster, in denen nichts getan werden kann. Wenn der Kontext gespeichert und wiederhergestellt wird, wird der Interrupt beendet. Da Ihr Code ziemlich einfach ist, vermute ich, dass dies einen guten Teil der Latenz beansprucht.
Eine Lösung zum Überspringen der niedrigen Werte hat immer noch eine Latenz, mindestens so wie das Verlassen des Interrupts und das Eingeben des nächsten Interrupts, so dass das minimale Tastverhältnis nicht wie erwartet ist.
Solange dies nicht weniger als ein PWM-Schritt ist, beginnt das PWM-Tastverhältnis bei einem höheren Wert. Nur eine leichte Verbesserung gegenüber dem, was Sie jetzt haben.
Ich sehe, dass Sie bereits 25% der Prozessorzeit in Interrupts verwenden. Warum verwenden Sie nicht 50% oder mehr davon, lassen den zweiten Interrupt und bündeln nur für das Vergleichsflag. Wenn Sie nur Werte bis zu 128 verwenden, haben Sie nur einen Arbeitszyklus von bis zu 50%, jedoch mit der Latenz von zwei Anweisungen, die im Assembler optimiert werden könnten.
quelle