ATtiny13A - No se puede generar software PWM con modo CTC

8

Estoy tratando de hacer una luz LED RGB de 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 utilizando interrupciones en modo CTC.

No puedo operar en ningún otro modo (excepto para PWM rápido con OCR0A como 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 intentando (y he visto a personas que mencionan esto en Internet) usar las interrupciones Output Compare A y Output Compare B para generar un software PWM.

OCR0A , que también es usado por el código IR, determina la frecuencia, lo cual no me importa. Y OCR0B , determina el ciclo de trabajo del PWM que usaré para cambiar los colores de los LED.

Estoy esperando poder obtener un PWM con un ciclo de trabajo de 0-100% cambiando el valor OCR0B de 0 a OCR0A . Este es mi entendimiento de lo que debería suceder:

Peroloquerealmenteestásucediendoesesto(estoesdelasimulacióndeProteusISIS):

Comopuedeveracontinuación,puedoobtenerunciclodetrabajodeaproximadamente25%-75%,peropara~0-25%y~75-100%,laformadeondaestábloqueadaynocambia.

líneaAMARILLA:HardwarePWM

LíneaROJA:SoftwarePWMconciclodeserviciofijo

LíneaVERDE:SoftwarePWMconciclodetrabajovariable

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
}
    
pregunta Pouria P

2 respuestas

8

Un software PWM mínimo podría tener este aspecto:

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 en el valor deseado y el ISR emite la señal PWM correspondiente. dutyCycle es un uint16_t para permitir valores entre 0 y 256 inclusive; 256 es más grande 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 eliminar algunos ciclos usando un uint8_t de modo que cualquiera de los resultados 0 tenga un ciclo de trabajo de 1/256 y el 255 sea del 100%. o 0 es 0% y 255 es un ciclo de trabajo de 255/256.

Aún no tienes mucho tiempo en un ISR de 38 kHz; con un poco de ensamblador en línea, es probable que pueda reducir la cantidad de ciclos del ISR en 1/3 a 1/2. Alternativa: ejecute su código PWM solo con cada desbordamiento del temporizador, reduciendo a la mitad la frecuencia de PWM.

Si tiene múltiples canales PWM y los pines en los que está PMW-ing 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 luego solo necesita la lectura de puerto, y con máscara, o con estado nuevo, escritura en puerto 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 salida lógica 1 en los pines; Si sus LED tienen 'lógica negativa' (el LED está encendido cuando el pin está bajo ), puede invertir la polaridad de la señal PWM simplemente cambiando de if (cnt < dutyCycle...) a if (cnt >= dutyCycle...) .

    
respondido por el JimmyB
2

Como comentó @JimmyB, la frecuencia PWM es demasiado alta.

Parece que las interrupciones tienen una latencia total de un cuarto del ciclo de PWM.

Al superponerse, 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 salir de la primera.

El ciclo de trabajo de PWM mínimo 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 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 en el extremo que sale 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 al salir de la interrupción y al 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 de PWM, el ciclo de trabajo de PWM comenzará a un valor más alto. Solo una pequeña mejora de lo que tienes ahora.

Veo que ya usas el 25% del tiempo del procesador en interrupciones, así que, ¿por qué no usas el 50% o más, dejas la segunda interrupción y solo te pones en el marcador 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.

    
respondido por el Dorian

Lea otras preguntas en las etiquetas