Use PWM e ISR al mismo tiempo en AVR

¿Es posible usar salidas AVR PWM e interrupciones ISR al mismo tiempo? Tengo un proyecto que estoy tratando de hacer en un ATMega328P y necesito 3 salidas PWM pero TAMBIÉN necesito poder usar interrupciones ISR de dos temporizadores diferentes para hacer otro multiplexado y manejo de botones (oh sí, también necesito utilizar interrupciones externas INT0 e INT1).

¿Hay alguna manera de hacer ambas cosas?

Actualización para aclaración: aquí está la configuración completa. Tengo 3 LED RGB para los cuales necesito PWM para cada uno de sus canales. Este PWM puede funcionar a la misma frecuencia pero necesita ciclos de trabajo independientes para cada canal para que pueda crear cualquier color que necesite. Dado que el ATMega328P no tiene 9 PWM independientes, necesito falsificarlo. Así que mi plan era usar multiplexar el PWM. Básicamente, configure PWM para RGB1, cambie a RGB2 y luego a RGB3, todo a > 400 Hz. De esa manera, solo necesito 3 canales PWM en el chip y puedo alternar qué LED se está conectando a tierra (son cátodos comunes). Entonces, necesito una interrupción ISR para manejar la multiplexación en sí y luego generalmente uso una interrupción ISR de frecuencia más baja (~ 100 Hz) para manejar las pulsaciones de botones (básicamente mi forma de hacer el rebote, lo encuentro bastante efectivo). Entonces, como puede ver, necesito 3 canales PWM y 2 ISR.

Sí, con bastante facilidad, pero la efectividad depende de las velocidades a las que necesite que funcione cada uno de estos... ¿Algún detalle más de lo que está tratando de hacer?
Tal vez, más específicamente, ¿qué frecuencia (o rango de frecuencias) debe tener cada una de las salidas PWM? ¿Qué frecuencia planeó usar con los ISR para el manejo de botones?
Ver actualización de la publicación original.

Respuestas (2)

Es bastante simple crear sus propios canales PWM en el software, especialmente cuando no son muy rápidos, como lo que está tratando de hacer. Cuando juegue con los temporizadores, tenga en cuenta que esto es realmente solo un contador reducido del reloj de la CPU que se reinicia cuando llega a un cierto valor SUPERIOR . Puede usar un solo temporizador para hacer muchas cosas diferentes.

Descripción general del temporizador

El ATmega328 tiene tres temporizadores: dos de 8 bits y uno de 16 bits. La única razón para usar el temporizador de 16 bits es si necesita una resolución adicional, más precisa para sus valores ideales de frecuencia y ciclo de trabajo. El temporizador 0 de 8 bits usa la menor cantidad de energía cuando está encendido, por lo que recomiendo usarlo tanto como sea posible. Cada temporizador tiene fuentes de interrupción independientes y tiene salidas dedicadas que se pueden configurar para pasar automáticamente a LO o HI en ciertos momentos, creando un PWM de hardware automático . Todo lo que tiene que hacer es actualizar los tiempos del ciclo de trabajo y el hardware hace el resto por usted. Sin embargo, puede hacer esto fácilmente en el software para crear muchos más canales PWM.

Consideraciones de hardware

Para empezar, voy a suponer que esto es lo que estás tratando de hacer:

Multiplexación RGB

Hay tres LED RGB. Una sola línea controla cada uno de los tres LED internos, y esta línea es compartida por el mismo color de cada LED. Otra línea de control es responsable de conectar a tierra estos LED de cátodo común, totalizando seis líneas de control en total. Solo un LED RGB está encendido a la vez, pero las líneas de control del suelo se mezclan lo suficientemente rápido como para que nadie se dé cuenta. He incluido una "caja negra" como control de tierra porque no sé cómo estás hundiendo la corriente del LED. Si opta por la Opción 1, un pin de E/S pasa a LO para absorber la corriente de un LED. Si hace esto, recuerde que la corriente total del pin de E/S es de 40 mA, por lo que cada LED interno solo puede usar (40/3) = 13 mA. La opción 2 soluciona este problema usando un transistor para absorber la corriente (también funcionará un BJT con una resistencia de base), sin embargo, esto cambia el control de LO activo a HI activo, impulsando la base/puerta para hundir la corriente del LED a través del emisor/fuente. Se puede usar una sola resistencia en cada línea de control para limitar la corriente del LED, ya que solo uno de los tres LED conectados estará encendido a la vez.

Tenga en cuenta que al multiplexar de esta manera, cada LED estará encendido durante un máximo del 33,33 % del tiempo. El brillo de cada LED será inferior al esperado y el color RGB también se verá afectado.

Consideraciones de PWM

No puedo decirte exactamente qué hacer porque no dijiste cuál era la velocidad de reloj de tu sistema. Sé que para el Arduino es de 16 MHz. Sin embargo, los chips AVR vienen por defecto usando el oscilador interno de 8 MHz reducido a 1 MHz. Este preescalador de reloj del sistema también se puede cambiar en el software. Esto se analiza en la Hoja de datos en la Sección 9.11 "Prescaler del reloj del sistema".

Tiene dos frecuencias de las que preocuparse: la frecuencia de cambio de color RGB (qué tan rápido pulsar cada LED individual) y la frecuencia de cambio de control de suelo RGB (qué tan rápido cambiar el control de un LED RGB al siguiente). Una de estas frecuencias debería ser un poco más alta que la otra. Yo sugeriría una frecuencia de pulso más rápida.

Por ejemplo: cambiar el control LED cada 2 ms significa un período de cambio de control total de 6 ms creando una frecuencia de 167 Hz. La frecuencia del pulso debe ocurrir dentro de los 2 ms que cada LED está habilitado. Depende de usted cuántos deben caber en estos 2 ms, pero yo diría que al menos 5. Por lo tanto, se debe usar una frecuencia de pulso de 2,5 kHz (5/2 ms = 2,5 kHz). De esa manera, cada LED pasará por 5 ciclos completos de pulsos durante los 2ms que la línea de control de tierra lo habilita. Puedes jugar con estos números para ver qué pasa...

control de software

Una vez que descubre qué tan rápido realmente debe ser todo, el control del software es relativamente fácil, pero hay algunas formas diferentes de hacerlo.

Opción 1

Puede configurar un solo temporizador en el modo CTC con el valor SUPERIOR establecido por comparación A para crear la frecuencia de pulso más rápida. En nuestro ejemplo, debería ser 2,5 kHz. Cada vez que este ISR se activa, los controles de color de los LED deben configurarse en HI. Una variable en este ISR también podría contar cuántas veces se dispara. La quinta vez, cambie el control de un LED al siguiente y restablezca el conteo de ISR. Esto crea un temporizador de 2 ms dentro del ISR de 2,5 kHz.

Con la coincidencia de comparación A configurada para activarse al comienzo de cada ciclo de pulso de LED, use la coincidencia de comparación B para activarse cuando un LED debe apagarse (la línea de control de color se borra). Este valor deberá actualizarse dentro del ISR de comparación B para el próximo LED que deba apagarse. El "tiempo de inactividad" se puede actualizar en main.

He hecho esto varias veces y sé que funciona bien, pero será necesario pensar en cómo saber qué color LED apagar y cómo establecer el siguiente valor B de comparación. ISR B debe dispararse 3 veces (una para cada línea de control de color) cada vez que ISR A se dispare para iniciar un nuevo ciclo PWM. Dejaré que averigües esos detalles...

Pseudocódigo:

// Every 1 ISR iteration is start of new PWM cycle
// Every 5 ISR iterations is time to switch control to next RGB LED
ISR_A{

  // Time to switch control from one RGB LED to the next
  if(++cycle_count == 5){
  TURN_OFF(ctrl_R | ctrl_G | ctrl_B); // make sure the previous LED is OFF
  cycle_cnt = 0;                      // reset count

  // Switch from LED 1 to LED 2
  if(ctrl_gnd1){
    TURN_OFF(ctrl_gnd1);
    TURN_ON(ctrl_gnd2);
    R_LED_time = R_LED2_ON;
    G_LED_time = G_LED2_ON;
    B_LED_time = B_LED2_ON;
  }
  // Switch from LED 2 to LED 3
  else if(ctrl_gnd2){
    TURN_OFF(ctrl_gnd2);
    TURN_ON(ctrl_gnd3);
    R_LED_time = R_LED3_ON;
    G_LED_time = G_LED3_ON;
    B_LED_time = B_LED3_ON;
  }
  // Switch from LED 3 to LED 1
  else{
    TURN_OFF(ctrl_gnd3);
    TURN_ON(ctrl_gnd1);
    R_LED_time = R_LED1_ON;
    G_LED_time = G_LED1_ON;
    B_LED_time = B_LED1_ON;
  }
  // This is the start of a new PWM cycle, turn on the color control lines
  TURN_ON(ctrl_R | ctrl_G | ctrl_B);
}

// This ISR will trigger when the next LED color control line should be turned off
ISR_B{

// ISR Trigger at RED LED duty cycle
if(COMPARE_MATCH_B = R_LED_time)
  TURN_OFF(ctrl_R);

// ISR Trigger at Green LED duty cycle
if(COMPARE_MATCH_B = G_LED_time)
  TURN_OFF(ctrl_G);

// ISR Trigger at Blue LED duty cycle
if(COMPARE_MATCH_B = B_LED_time)
  TURN_OFF(ctrl_B);

// Need to compare color control times to determine when the next ISR_B should trigger.
// ... Your Algorithm here...
}

opcion 2

Esta podría ser la opción más fácil, pero implicará más tiempo de reloj para funcionar, lo que significa que el reloj del sistema tendrá que funcionar más rápido... Después de saber qué tan rápido debe ser el pulso del LED, determine qué resolución de control de color desea... Es decir, para cambiar el brillo de un solo LED, incrementa su ciclo de trabajo en un 0,5%, 1%, 10%, etc. Para simplificar, usaré una resolución del 1%. Eso significa que necesitará configurar el temporizador (en modo CTC) para que sea 100 veces la frecuencia del pulso...

Este ISR ahora se activará a 250 kHz. Pondrás un contador variable en él que cuenta los disparadores de ISR. En 0 (o 100, como quieras hacerlo) ese es el inicio/final del ciclo PWM, así que configura todas las líneas de control de color en HI. Cuando este contador alcance un número específico, borre la línea de control de color con ese ciclo de trabajo específico. Se deben usar 3 conjuntos de valores de tiempo, con el que está en uso determinado por el RGB que se está controlando. Este control cambiará cada 100 * 5 disparadores de ISR ya que este ISR ocurre 100 veces más a menudo que el de la Opción 1.

Pseudocódigo:

// Every 100 ISR iterations equals 1 PWM cycle 
// Every 500 ISR iterations is time to switch control to next RGB LED
ISR_A{

  // Time to switch control from one RGB LED to the next
  if(++cycle_count == 500){
    TURN_OFF(ctrl_R | ctrl_G | ctrl_B); // make sure the previous LED is OFF
    cycle_cnt = 0;                      // reset count

    // Switch from LED 1 to LED 2
    if(ctrl_gnd1){
      TURN_OFF(ctrl_gnd1);
      TURN_ON(ctrl_gnd2);
      R_LED_time = R_LED2_ON;
      G_LED_time = G_LED2_ON;
      B_LED_time = B_LED2_ON;
      break;
    }
    // Switch from LED 2 to LED 3
    else if(ctrl_gnd2){
      TURN_OFF(ctrl_gnd2);
      TURN_ON(ctrl_gnd3);
      R_LED_time = R_LED3_ON;
      G_LED_time = G_LED3_ON;
      B_LED_time = B_LED3_ON;
      break;
    }
    // Switch from LED 3 to LED 1
    else{
      TURN_OFF(ctrl_gnd3);
      TURN_ON(ctrl_gnd1);
      R_LED_time = R_LED1_ON;
      G_LED_time = G_LED1_ON;
      B_LED_time = B_LED1_ON;
      break;
    }
  }

  // Time to turn OFF the red LED control line
  if(++pulse_count == R_LED_time)
    TURN_OFF(ctrl_R);

  // Time to turn OFF the green LED control line
  if(pulse_count == G_LED_time)
    TURN_OFF(ctrl_G);

  // Time to turn OFF the blue LED control line    
  if(pulse_count == B_LED_time)
    TURN_OFF(ctrl_B);

  // This is the start of a new PWM cycle, turn on the color control lines
  if(pulse_cnt == 100){
    TURN_ON(ctrl_R | ctrl_G | ctrl_B);
    pulse_cnt = 0;
  }
}

Lo complicado de este método es la sincronización de ISR en comparación con el reloj del sistema. A 250 kHz con un reloj del sistema de 16 MHz, solo habrá 64 ciclos de reloj del sistema entre cada disparo de ISR. El código tiene que ejecutarse a tiempo, o no funcionará correctamente sin reducir significativamente la frecuencia de pulso o la resolución del ciclo de trabajo. Es por eso que la Opción 1 es mejor.

Opción 3

Si todas las diferentes variables de conteo le parecen confusas, puede probar algo completamente diferente. Utilice el temporizador 1 de 16 bits que tiene 4 posibilidades de interrupción: desbordamiento, OCR1A, OCR1B y captura de entrada ICR1. Con el temporizador en modo normal, use el preescalador más grande disponible (1024) para bajar la frecuencia del reloj del sistema lo más bajo posible; esto sería 15,6 kHz de un reloj de sistema de 16 MHz, o alrededor de 1 kHz de un reloj de sistema de 1 MHz. El ISR de desbordamiento es el comienzo del ciclo PWM. Use esto para establecer todas las líneas de control de color en ALTO y cuente estos ciclos de ISR para cambiar el control de LED al siguiente. Luego, use cada una de las tres fuentes ISR restantes como ciclo de trabajo para los LED de colores: OCR1A para ctrl-R, OCR1B para ctrl-G e ICR1 para ctrl-B. Estos valores de tiempo pueden cambiar en cualquier momento (en el principal o en otro ISR) y se actualizan inmediatamente en modo normal.

Estas no son las únicas formas (o las mejores formas) de hacer esto, pero son las que he usado en el pasado con éxito. Hagas lo que hagas, esta es la respuesta del LED que debes esperar:

Señales de multiplexación RGB

Observe que cada LED tiene su propio ciclo de trabajo, pero todos funcionan a la misma frecuencia. Cada RGB solo está habilitado 1/3 del tiempo total. También puede verificar sus botones dentro del pulso ISR. Simplemente use otro contador variable para crear un temporizador de 10 ms (mencionó 100 Hz) dentro de ISR A, similar al temporizador utilizado en las líneas de control terrestre de multiplexación.

Tu pregunta no tiene mucho sentido. Es como decir, ¿mi vecino puede regar su césped y puedo ponerme los pantalones al mismo tiempo? Eso depende.

328 tiene 3 temporizadores: 0, 1, 2. Si sus 3 PWM son todos de la misma base de tiempo, es decir, la misma frecuencia pero diferentes ciclos de trabajo, entonces sí. Dedicaría un temporizador para generar los 3 PWM y los otros 2 generarían ISR de desbordamiento.

INT0, INT1 se pueden usar independientemente de los temporizadores, por lo que deberían poder usarse si esos pines están libres.

Pensé que cada uno de los 3 temporizadores tenía 2 salidas PWM asociadas. ¿Es realmente posible hacer 3 salidas PWM desde solo 1 de los temporizadores de hardware? Eso sería perfecto ya que dejaría los otros 2 temporizadores libres para lo que sea.
@AdamHaile Sospecho que la sugerencia aquí es sobre el uso del software PWM. Hardware PWM tiene la limitación de dos canales PWM, vinculados a pines específicos y funcionando a la misma frecuencia, en cada canal de temporizador.
Vale... eso es lo que pensaba. Pero, ¿puedo seguir usando el PWM de Timer1 al mismo tiempo que uso interrupciones ISR? Probablemente pueda trabajar con ambos funcionando a la misma frecuencia: P
Puede configurar PWM para cambiar cuando el contador alcance un valor de comparación. Esto controla su ciclo de trabajo. El valor "TOP" y el contador preescalar controla su frecuencia. Puede habilitar una interrupción de desbordamiento para que se produzca cuando su contador llegue a "TOP". Por lo tanto, puede usar ISR de desbordamiento si el período que desea es el mismo que 1/(frecuencia pwm)