como-cambiar-la-frecuencia-de-un-pwm-en-arduino

Como cambiar la frecuencia de un PWM en Arduino

Hoy vamos a ver cómo cambiar la frecuencia de un pin de salida analógico en Arduino, para lo cuál nos va a tocar configurar los registros que controlan los Timers asociados a los pin PWM. Pero tranquilos, que vamos a dejarlo fácil en una única función.

El empleo de timers es algo normal en procesadores. Sin embargo, frecuentemente nos habréis visto recomendar evitar su manipulación en Arduino dado que puede tener efectos inesperados, sobre todo en librerías de terceros. Entonces, si normalmente recomendamos evitar manipular los Timers en Arduino ¿Para qué querremos modificar ahora su frecuencia? Pues, básicamente, porque esto permite mejorar el comportamiento de ciertos dispositivos cuando son controlados por una señal PWM.

Recordemos que al controlar un dispositivo mediante una señal PWM, realmente estamos encendiendo y apagando el dispositivo por completo múltiples veces por segundo. Eso no es lo mismo que aplicar una auténtica señal analógica y, como normal general, a muchos actuadores el control con PWM les sienta fatal.

Aumentar la frecuencia del PWM puede mejorar el funcionamiento de ciertos dispositivos, porque tienen menos tiempo para “darse cuenta” de que lo hemos apagado. Si la inercia del dispositivo es suficiente, y su frecuencia natural superior a la del PWM, el comportamiento será (casi) idéntico al alimentar con una tensión analógica.

El caso práctico más frecuente lo tenemos al controlar la velocidad de un motor, que al controlar con un PWM apagamos y encendemos el motor múltiples veces por segundo. El motor es una carga inductiva, y cada encendido supone un hermoso picos de arranque. Así lleva a la situación de que al aplicar una reducción de la velocidad, el motor consume más, se calienta más, hace más ruido… cuando, a priori, podríamos pensar que debería ser al revés porque está funcionado a menos velocidad.

Eso tampoco significa que podamos subir la frecuencia de forma indefinida como locos. No todos los drivers que estemos empleando van a soportar la frecuencia y si no la soportan… ¡cataplum!. Hay que mirar cada caso en particular pero, en general, subir la frecuencia del control PWM 4-10 Khz, resulta bastante razonable.

Así que aquí tenéis un chuletario con la frecuencia, prescalers, valores por defecto y consecuencias de cada uno de los pines PWM para procesadores Atmega328p y Atmega 32u2, así como una función para modificarlo de forma sencilla.

Atmega 328p (Arduino Uno, Nano)

Frecuencia

PinsTimerFrecuencia
5, 6Timer062500 Hz
9, 10Timer131250 Hz
3, 11Timer231250 Hz

Valores por defecto

PinsTimerPrescalersFrecuencia
5, 6Timer064977Hz
9, 10Timer164490Hz
3, 11Timer364490Hz

Prescalers

PinsTimerPrescalers
5, 6Timer01 8 64 256 1024
9, 10Timer11 8 64 256 1024
3, 11Timer21 8 32 64 128 256 1024

Consecuencias y efectos

PinsTimerEfecto
5, 6Timer0delay() y millis()
9, 10Timer1Librería servo
3, 11Timer2

Código

//Atmega 328p (Arduino Uno, Nano)
// Frecuencias
// 5, 6   Timer0  62500 Hz
// 9, 10   Timer1  31250 Hz
// 3, 11   Timer2  31250 Hz

// Prescalers
// 5, 6   Timer0  1 8 64 256 1024
// 9, 10   Timer1  1 8 64 256 1024
// 3, 11   Timer2  1 8 32 64 128 256 1024

// Valores por defecto
// 5, 6   Timer0 64  977Hz
// 9, 10   Timer1 64  490Hz
// 3, 11   Timer2 64  490Hz

// Consecuencias
// 5, 6   Timer0  delay() y millis()
// 9, 10   Timer1  Librería servo
// 3, 11   Timer2  

void setPWMPrescaler(uint8_t pin, uint16_t prescale) {
  
  byte mode;
  
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(prescale) {
      case    1: mode = 0b001; break;
      case    8: mode = 0b010; break;
      case   64: mode = 0b011; break; 
      case  256: mode = 0b100; break;
      case 1024: mode = 0b101; break;
      default: return;
    }
    
  } else if(pin == 3 || pin == 11) {
    switch(prescale) {
      case    1: mode = 0b001; break;
      case    8: mode = 0b010; break;
      case   32: mode = 0b011; break; 
      case   64: mode = 0b100; break; 
      case  128: mode = 0b101; break;
      case  256: mode = 0b110; break;
      case 1024: mode = 0b111; break;
      default: return;
    }
  }
  
  if(pin==5 || pin==6) {
    TCCR0B = TCCR0B & 0b11111000 | mode;
  } else if (pin==9 || pin==10) {
    TCCR1B = TCCR1B & 0b11111000 | mode;
  } else if (pin==3 || pin==11) {
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

Atmega 32U (Micro y Leonardo)

Frecuencias

PinsTimerFrecuencia
3, 11Timer064500Hz
9, 10Timer131250Hz
5Timer331250Hz
6, 13Timer431250Hz

Valores por defecto

PinsTimerPrescalersFrecuencia
3, 11Timer064977Hz
9, 10Timer164490Hz
5Timer364490Hz
6, 13Timer464490Hz

Prescalers

PinsTimerPrescalers
3, 11Timer01 8 64 256 1024
9, 10Timer11 8 64 256 1024
5Timer31 8 64 256 1024
6, 13Timer41 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384

Consecuencias

PinsTimerEfecto
3, 11Timer0delay() y millis()
9, 10Timer1
5Timer3
6, 13Timer4

Código

// Atmega 32U
// Frecuencias
// 3, 11  Timer0  64500Hz
// 9, 10  Timer1  31250Hz
// 5  Timer3  31250Hz
// 6, 13  Timer4  31250Hz

// Prescalers
// 3, 11  Timer0  64  1 8 64 256 1024
// 9, 10  Timer1  64  1 8 64 256 1024
// 5  Timer3  64  1 8 64 256 1024
// 6, 13  Timer4  64  1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384

// Valores por defecto
// 3, 11  Timer0  64  977Hz
// 9, 10  Timer1  64  490Hz
// 5  Timer3  64  490Hz
// 6, 13  Timer4  64  490Hz

// Conescuencias
// 3, 11  millis(), delay()
// 9, 10
// 5
// 6, 13

void setPWMPrescaler(uint8_t pin, uint16_t prescale) 
{ 
  byte mode;
  
  if(pin==3 || pin==5 || pin==9 || pin==10 || pin==11) { 
    switch(prescale) {
      case    1: mode = 0b001; break;
      case    8: mode = 0b010; break;
      case   64: mode = 0b011; break; 
      case  256: mode = 0b100; break;
      case 1024: mode = 0b101; break;
      default: return;
    }
    
    
  } else if(pin==6 || pin==13) {
    switch(prescale) {
      case     1: mode = 0b0001; break;
      case     2: mode = 0b0010; break;
      case     4: mode = 0b0011; break;
      case     8: mode = 0b0100; break;
      case    16: mode = 0b0101; break;
      case    32: mode = 0b0110; break;
      case    64: mode = 0b0111; break;
      case   128: mode = 0b1000; break;
      case   256: mode = 0b1001; break;
      case   512: mode = 0b1010; break;
      case  1024: mode = 0b1011; break;
      case  2048: mode = 0b1100; break;
      case  4096: mode = 0b1101; break;
      case  8192: mode = 0b1110; break;
      case 16384: mode = 0b1111; break;
      default: return;
    }
  }
  
  if(pin==3 || pin==11) {
    TCCR0B = TCCR1B & 0b11111000 | mode;
  } else if (pin==9 || pin==10) {
    TCCR1B = TCCR1B & 0b11111000 | mode;
  } else if (pin==5) {
    TCCR3B = TCCR3B & 0b11111000 | mode;
  } else if (pin==6 || pin==13) {
    TCCR4B = TCCR4B & 0b11110000 | mode;
  }
}

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github. github-full