Por supuesto, puedes 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 has especificado el número de canales PWM que quieres usar, o la frecuencia / ciclos de trabajo que quieres para cualquiera de ellos. Para el PWM de "golpeo de bits", estás limitado al número de líneas de E/S disponibles (aunque, siempre podrías añadir registros de desplazamiento externos para aumentar en gran medida este límite).
También está limitada la frecuencia de la CPU en uso. Cuanto más rápido sea el reloj, más cosas podrás hacer en la misma cantidad de tiempo. Esto será más importante a medida que añadas canales PWM. La velocidad de reloj por defecto del AVR es de 8MHz, escalada a 1MHz internamente. Arduino suele utilizar un cristal externo de 16MHz. ¡Este detalle es importante!
Cada temporizador tendrá varios registros de "coincidencia de comparación" para utilizarlos en las solicitudes de servicio de interrupción generadas. Básicamente, el temporizador contará hasta que alcance el valor de un registro de comparación, y entonces podría activar una ISR, reiniciar la cuenta, etc. En mi cabeza, hay numerosas maneras de crear múltiples PWMs usando un solo temporizador:
Múltiples PWM de la misma frecuencia y ciclo de trabajo
Este caso es bastante trivial, pero usando un solo temporizador, puedes establecer cualquier número de pines de salida en la "Comparación A" (que también reinicia la cuenta), y borrar estos pines en la "Comparación B". El valor de "B" sería algo entre 1 y el valor de "A", dependiendo de tu 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 se puede hacer. Al igual que en el primer caso, usaremos "A" para activar todos los pines PWM y reiniciar la cuenta. El ISR "B" es un poco más complejo, pero esto es lo esencial:
- crear una matriz global de valores de tiempo, representando el "tiempo de apagado" de cada pin PWM.
- Ajuste el registro de comparación B al menor de estos "tiempos muertos"
- En el ISR "B", comprueba 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 se dispara el ISR) entonces apaga ese pin(s).
- Recorre la matriz de tiempo para el próximo tiempo de desactivación, y establece el registro "B" a ese valor, que se utilizará para desencadenar el siguiente ISR.
Este proceso te proporcionará tantos pines PWM como quieras con diferentes ciclos de trabajo (tiempos de encendido), pero todos deben tener la misma frecuencia.
Múltiples PWMs con diferentes frecuencias y diferentes ciclos de trabajo
Este último caso que presentaré se basa en las ideas de los anteriores, pero añadirá una variable de conteo que se incrementa cuando el ISR del temporizador se dispara. Cada vez que este ISR se dispara, ha pasado una cantidad de tiempo exacta y conocida, por lo que puedes utilizar las variables de conteo para decidir cuándo se produce un evento. Por ejemplo, puedes usar una variable para contar hasta 100 antes de activar un pin, y luego contar hasta 100 antes de desactivarlo, mientras otra variable cuenta hasta 200 para hacer las mismas cosas. Eso es 2 variables para 2 canales PWM independientes.
Por supuesto, este método es el más desordenado en términos de código, pero debe parecer familiar - es esencialmente lo que su código estaba haciendo en el bucle del programa principal, sólo ha movido esta funcionalidad a los temporizadores de hardware y generó ISRs sin los retrasos duros, liberando su bucle principal y la CPU para hacer cosas más importantes que mirar ASM "NOP" durante miles de ciclos de reloj.
Notas adicionales
No he utilizado ningún código aquí, pero puedo añadir algo si eres más específico con tus requisitos, o dar una pista de lo que ya sabes hacer.
Además, supongo que estás usando un AVR de 8 bits. Si es así, te recomiendo que prestes atención a los tipos de variables que necesitas. Usar un entero de 32 bits para un valor constante de 5 es una tontería... usa uint8_t para cualquier cosa inferior a 256.
Si realmente te preocupa la potencia y el tamaño del código, deja las cosas de Arduino y las capas de abstracción innecesarias.