Estoy tratando de hacer una luz LED RGB con control remoto usando un ATtiny13A.
Sé que el ATtiny85 es más adecuado para este propósito, y sé que eventualmente no podré ajustar todo el código, pero por ahora mi principal preocupación es generar un PWM de software usando interrupciones en modo CTC.
No puedo operar en ningún otro modo (excepto para PWM rápido, OCR0A
ya TOP
que es básicamente lo mismo) porque el código del receptor IR que estoy usando necesita una frecuencia de 38 kHz que genera usando CTC yOCR0A=122
.
Así que estoy tratando (y he visto a gente mencionar esto en Internet) de usar Output Compare A
e Output Compare B
interrumpe para generar un software PWM.
OCR0A
, que también usa el código IR, determina la frecuencia, lo que no me importa. Y OCR0B
determina el ciclo de trabajo del PWM que usaré para cambiar los colores del LED.
Espero poder obtener un PWM con un ciclo de trabajo del 0-100% cambiando el OCR0B
valor de 0
a OCR0A
. Esta es mi comprensión de lo que debería suceder:
Pero lo que realmente está sucediendo es esto (esto es de la simulación Proteus ISIS):
Como puede ver a continuación, puedo obtener un ciclo de trabajo del 25% al 75%, pero para ~ 0-25% y ~ 75-100%, la forma de onda está atascada y no cambia.
Línea AMARILLA: Hardware PWM
Línea ROJA: Software PWM con ciclo de trabajo fijo
Línea VERDE: Software PWM con ciclo de trabajo variable
Y aquí está mi código:
#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
}
fuente
OCR0A
es usado por el código IR, así que solo tengoOCR0B
. Estoy tratando de usarlo para generar software PWM en 3 pines que no son PWM.Respuestas:
Un PWM de software mínimo podría verse así:
Su programa se establece
dutyCycle
en el valor deseado y el ISR emite la señal PWM correspondiente.dutyCycle
es unuint16_t
para permitir valores entre 0 y 256 inclusive; 256 es mayor que cualquier valor posible decurrentPwmCount
y, por lo tanto, proporciona un ciclo de trabajo completo del 100%.Si no necesita 0% (o 100%), puede reducir algunos ciclos utilizando un
uint8_t
modo que0
resulte en un ciclo de trabajo de 1/256 y255
sea 100% o0
sea 0% y255
sea un ciclo de trabajo de 255 / 256Todavía no tienes mucho tiempo en un ISR de 38kHz; Con un pequeño ensamblador en línea, probablemente pueda reducir el recuento de ciclos del ISR de 1/3 a 1/2. Alternativa: ejecute su código PWM solo cada dos segundos de desbordamiento del temporizador, reduciendo a la mitad la frecuencia PWM.
Si tiene varios canales PWM y los pines que está utilizando PMW están todos en el mismo
PORT
, también puede recopilar todos los estados de los pines en una variable y finalmente enviarlos al puerto en un solo paso que solo necesita la lectura de puerto, y con máscara, o con nuevo estado, escribir en el puerto una vez en lugar de una vez por pin / canal .Ejemplo:
Este código asigna el ciclo de trabajo a una
1
salida lógica en los pines; Si sus LED tienen 'lógica negativa' (LED encendido cuando el pin está bajo ), puede invertir la polaridad de la señal PWM simplemente cambiandoif (cnt < dutyCycle...)
aif (cnt >= dutyCycle...)
.fuente
if
rutina de interrupción para ejecutar solo el código PWM en cualquier otro momento. Al hacer esto, si mi código PWM tarda demasiado y se pierde la próxima interrupción de desbordamiento, entonces mi programa estará bien porque la próxima interrupción no iba a hacer nada de todos modos. ¿Es eso lo que querías decir?Como comentó @JimmyB, la frecuencia PWM es demasiado alta.
Parece que las interrupciones tienen una latencia total de una cuarta parte del ciclo PWM.
Cuando se superponen, el ciclo de trabajo se fija dado por la latencia total, ya que la segunda interrupción se pone en cola y se ejecuta después de que salga la primera.
El ciclo de trabajo mínimo de PWM viene dado por el porcentaje de latencia de interrupción total en el período de PWM. La misma lógica se aplica al ciclo de trabajo PWM máximo.
Al observar los gráficos, el ciclo de trabajo mínimo es de alrededor del 25%, y luego la latencia total debe ser ~ 1 / (38000 * 4) = 6.7 µs.
Como consecuencia, el período mínimo de PWM es 256 * 6.7 µs = 1715 µs y frecuencia máxima de 583 Hz.
Algunas explicaciones más sobre posibles parches a alta frecuencia:
La interrupción tiene dos ventanas ciegas cuando no se puede hacer nada, entrando y saliendo de la interrupción cuando el contexto se guarda y se recupera. Dado que su código es bastante simple, sospecho que esto toma una buena parte de la latencia.
Una solución para omitir los valores bajos seguirá teniendo una latencia al menos igual que al salir de la interrupción e ingresar a la siguiente interrupción, por lo que el ciclo de trabajo mínimo no será el esperado.
Mientras esto no sea menor que un paso PWM, el ciclo de trabajo PWM comenzará en un valor más alto. Solo una ligera mejora de lo que tienes ahora.
Veo que ya usas el 25% del tiempo del procesador en las interrupciones, así que ¿por qué no usas el 50% o más? Deja la segunda interrupción y solo agrupa el indicador de comparación. Si usa valores de hasta 128, solo tendrá un ciclo de trabajo de hasta el 50%, pero con la latencia de dos instrucciones que podrían optimizarse en el ensamblador.
fuente