Reemplace el bucle con temporizadores en este código ATtiny

¿Cómo puedo reescribir lo siguiente para usar temporizadores? Tenga en cuenta que este es un ATtiny que solo tiene Timer0 y Timer1

Esencialmente, estoy tratando de programar PWM en muchos pines de salida, más que en los pines PWM incorporados. El siguiente código está ligeramente simplificado, pero el código real utiliza la manipulación directa de puertos y no hace casi nada más, por lo que el resultado debería ser casi tan bueno como el PWM integrado.

La aplicación prevista está diseñada para funcionar con una batería pequeña, por lo que el ahorro de energía es un objetivo de diseño importante. Además, dado que se trata de ATtiny, cada byte del espacio del programa es importante.

O, convénceme de que no vale la pena intentar usar temporizadores en este caso.

void loop() {
  static int8_t procession = 0;
  static uint32_t lastTime = millis();
  static int8_t pos = 0;

  if (lastTime > millis() + 200) {
    lastTime = millis();
    if (++procession == 9) {
      procession = 0;
    }
  }

  const uint32_t ON_CYCLE = 5;
  const uint32_t OFF_CYCLE = 5;

  digitalWrite(procession, HIGH);

  delayMicroseconds(ON_CYCLE);

  if (OFF_CYCLE > 0) {
    digitalWrite(procession, LOW);
    delayMicroseconds(OFF_CYCLE);
  }
}
¿Cuál es su frecuencia PWM y la resolución requerida (número de ciclos de trabajo diferentes)?
¿Cómo se pueden actualizar las salidas PWM y, para cada una: frecuencia, resolución y tasa máxima a la que se pueden actualizar para producir un nuevo ciclo de trabajo? También me gustaría conocer otros requisitos, como las relaciones entre ellos (tiempo muerto, fase, etc.)
¿Qué has probado? ¿Qué arrojó su búsqueda de "avr software pwm" y qué no funcionó para usted?
Te estoy convenciendo de que no uses temporizadores... En serio, ya tienes un código que funciona, ¿por qué cambiarlo?
Bueno, supongo que también quería una excusa para usarlo para tratar de entenderlo mejor.
Esta es en realidad una buena pregunta. Muchos de los tutoriales de microcontroladores hacen un uso intensivo de las funciones de retardo (), etc., que evitan que la CPU haga algo útil durante el tiempo. Hacen que los principiantes tengan un mal comienzo. Aquí hay alguien preguntando cómo hacer retrasos correctamente. ¿Podemos ayudar?

Respuestas (2)

Por supuesto, puede reemplazar cualquier tipo de bucle y retraso con los temporizadores de hardware. Esto casi siempre resultará en una operación más eficiente y predecible. No ha especificado la cantidad de canales PWM que desea usar, o la frecuencia/ciclos de trabajo que desea para ninguno de ellos. Para PWM "bit-banging", está limitado a la cantidad de líneas de E/S disponibles (aunque, siempre puede agregar registros de desplazamiento externos para aumentar considerablemente este límite).

También está limitado en la frecuencia de uso de la CPU. Cuanto más rápido corre el reloj, más cosas puedes hacer en la misma cantidad de tiempo. Esto será más importante a medida que agregue canales PWM. La velocidad de reloj AVR predeterminada es de 8 MHz, reducida a 1 MHz internamente. Arduino normalmente usa un cristal externo de 16 MHz. ¡Este detalle es importante!

Cada temporizador tendrá varios registros de "coincidencia de comparación" para usar en las solicitudes de servicio de interrupción generadas. Básicamente, el temporizador contará hasta que alcance el valor en un registro de coincidencia de comparación, luego podría activar un ISR, restablecer el conteo, etc. En mi opinión, existen numerosas formas de crear múltiples PWM con un solo temporizador:

Múltiples PWM de la misma frecuencia y ciclo de trabajo

Este caso es bastante trivial, pero con un solo temporizador, puede configurar cualquier número de pines de salida en "Comparar coincidencia A" (que también restablece el conteo) y borrar estos pines en "Comparar coincidencia B". El valor de "B" sería algo entre 1 y el valor de "A", según el ciclo de trabajo deseado.

Múltiples PWM de la misma frecuencia con diferentes ciclos de trabajo

Esto es un poco más complicado, pero definitivamente factible. Similar al primer caso, usaremos "A" para configurar todos los pines PWM y restablecer el conteo. El ISR "B" es un poco más complejo, pero aquí está la esencia:

  1. cree una matriz global de valores de tiempo, que represente el "tiempo de inactividad" de cada pin PWM.
  2. Establezca el registro B de comparación de coincidencias en el menor de estos "tiempos de inactividad"
  3. En el ISR "B", verifique si el valor del registro es igual al "tiempo de apagado" de cada línea PWM en la matriz de tiempo (siempre debe ser igual a uno de ellos cuando activa el ISR) luego apague eso patas).
  4. Recorra la matriz de tiempo para el SIGUIENTE tiempo de apagado y establezca el registro "B" en ese valor, que se usará para activar el próximo ISR.

Este proceso le dará tantos pines PWM como desee con diferentes ciclos de trabajo (variables) (tiempos) pero todos deben tener la misma frecuencia.

Múltiples PWM con diferentes frecuencias y diferentes ciclos de trabajo

Este último caso que presentaré se basa en las ideas de los anteriores, pero agregará una variable de conteo que se incrementa cuando se activa el temporizador ISR. Cada vez que se activa este ISR, ha pasado una cantidad de tiempo exacta y conocida, por lo que puede usar variables de contador para decidir cuándo ocurre un evento. Por ejemplo, podría usar una variable para contar hasta 100 antes de establecer un pin, luego contar hasta 100 antes de borrarlo, todo mientras otra variable cuenta hasta 200 para hacer lo mismo. Son 2 variables para 2 canales PWM independientes.

Por supuesto, este método es el más complicado en términos de código, pero debería parecer familiar: es esencialmente lo que su código estaba haciendo en el bucle del programa principal, simplemente movió esta funcionalidad a los temporizadores de hardware y generó ISR sin los retrasos. liberando su bucle principal y CPU para hacer cosas más importantes que mirar los "NOP" de ASM durante miles de ciclos de reloj.

Notas adicionales

No he usado ningún código aquí, pero puedo agregar algo si es más específico con sus requisitos, o dar una pista sobre lo que ya sabe hacer.

Además, supongo que estás usando un AVR de 8 bits aquí. Si es así, le recomiendo que preste atención a los tipos de variables requeridos. Usar un entero de 32 bits para un valor constante de 5 es una tontería... use uint8_t para cualquier valor menor a 256.

Si está realmente preocupado por la potencia y el tamaño del código, deje las cosas de Arduino y las capas innecesarias de abstracción.

¿Cómo puedo reescribir lo siguiente para usar temporizadores?

Escribí esto hace un tiempo para controlar los servos, pero el concepto básico es el mismo: producir ciclos de trabajo variables de forma independiente en cualquier cantidad de pines.

https://dannyelectronics.wordpress.com/2017/02/18/driving-multiple-servos-off-a-pic-timer2-ranged-extended/

tiene enlaces a otros enfoques que usan diferentes periféricos en PIC y AVR, pero el concepto básico es el mismo.

puede modificar los parámetros para adaptarlos a sus necesidades.

el código se ejecuta en su totalidad en el isr, por lo que su ciclo principal puede hacer lo que sea necesario, como alterar los ciclos de trabajo...