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 software PWM usando interrupciones en el modo CTC.
No puedo operar en ningún otro modo (excepto PWM rápido con OCR0A
as 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 y OCR0A=122
.
Así que estoy tratando (y he visto a personas mencionar esto en Internet) usar las interrupciones Output Compare A
para Output Compare B
generar un PWM de software.
OCR0A
, que también es utilizado por el código IR, determina la frecuencia, que no me importa. Y OCR0B
determina el ciclo de trabajo del PWM que usaré para cambiar los colores de los LED.
Espero poder obtener un PWM con un ciclo de trabajo de 0-100% cambiando el OCR0B
valor de 0
a OCR0A
. Este es mi entendimiento 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 simplemente se atasca 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
}
Un PWM de software mínimo podría verse así:
volatile uint16_t dutyCycle;
uint8_t currentPwmCount;
ISR(TIM0_COMPA_vect){
const uint8_t cnt = currentPwmCount + 1; // will overflow from 255 to 0
currentPwmCount = cnt;
if ( cnt <= dutyCyle ) {
// Output 0 to pin
} else {
// Output 1 to pin
}
}
Su programa establece dutyCycle
el valor deseado y el ISR emite la señal PWM correspondiente. dutyCycle
es a uint16_t
para permitir valores entre 0 y 256 inclusive; 256 es mayor que cualquier valor posible de currentPwmCount
y, por lo tanto, proporciona un ciclo de trabajo completo del 100 %.
Si no necesita el 0 % (o el 100 %), puede reducir algunos ciclos usando un ciclo uint8_t
de 0
trabajo de 1/256 y 255
100 % o 0
0 % y 255
un ciclo de trabajo de 255/ 256.
Todavía no tienes mucho tiempo en un ISR de 38kHz; utilizando un pequeño ensamblador en línea, probablemente pueda reducir el recuento de ciclos del ISR entre 1/3 y 1/2. Alternativa: ejecute su código PWM solo cada dos desbordamientos 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 paso que luego solo necesita la lectura de- port, and-with-mask, or-with-new-state, write-to-port una vez en lugar de una vez por pin/canal .
Ejemplo:
volatile uint8_t dutyCycleRed;
volatile uint8_t dutyCycleGreen;
volatile uint8_t dutyCycleBlue;
#define PIN_RED (0) // Example: Red on Pin 0
#define PIN_GREEN (4) // Green on pin 4
#define PIN_BLUE (7) // Blue on pin 7
#define BIT_RED (1<<PIN_RED)
#define BIT_GREEN (1<<PIN_GREEN)
#define BIT_BLUE (1<<PIN_BLUE)
#define RGB_PORT_MASK ((uint8_t)(~(BIT_RED | BIT_GREEN | BIT_BLUE)))
uint8_t currentPwmCount;
ISR(TIM0_COMPA_vect){
uint8_t cnt = currentPwmCount + 1;
if ( cnt > 254 ) {
/* Let the counter overflow from 254 -> 0, so that 255 is never reached
-> duty cycle 255 = 100% */
cnt = 0;
}
currentPwmCount = cnt;
uint8_t output = 0;
if ( cnt < dutyCycleRed ) {
output |= BIT_RED;
}
if ( cnt < dutyCycleGreen ) {
output |= BIT_GREEN;
}
if ( cnt < dutyCycleBlue ) {
output |= BIT_BLUE;
}
PORTx = (PORTx & RGB_PORT_MASK) | output;
}
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 cambiando if (cnt < dutyCycle...)
a if (cnt >= dutyCycle...)
.
if
en la rutina de interrupción para ejecutar solo el código PWM cada dos veces. 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 superpone, 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 se sale de la primera.
El ciclo de trabajo mínimo de PWM viene dado por el porcentaje total de latencia de interrupción en el período de PWM. La misma lógica se aplica al ciclo de trabajo máximo de PWM.
Mirando 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 de 256*6,7 µs = 1715 µs y una 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 se guarda y recupera el contexto. 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 al salir de la interrupción y entrar en la siguiente interrupción, por lo que el ciclo de trabajo mínimo no será el esperado.
Siempre que no sea inferior a un paso de PWM, el ciclo de trabajo de PWM comenzará con un valor más alto. Sólo una ligera mejora de lo que tienes ahora.
Veo que ya usa el 25 % del tiempo del procesador en interrupciones, entonces, ¿por qué no usa el 50 % o más, deja la segunda interrupción y solo agrupa para el indicador de comparación? Si usa valores solo hasta 128, tendrá solo hasta un 50% de ciclo de trabajo, pero con la latencia de dos instrucciones que podrían optimizarse en ensamblador.
Arce
Puria P
OCR0A
es utilizado por el código IR, por lo que solo tengoOCR0B
. Estoy tratando de usarlo para generar software PWM en 3 pines que no son PWM.jimmyb
Puria P
jimmyb
jimmyb
Puria P
jimmyb
Arce
Puria P