PWM 2Khz con Timer1 en modo CTC, no lo consigo

Buenas,
Estoy intentando hacer una PWM de 2Khz con un duty cycle del 50%.
Lo he intentado con este código, pero solo consigo una frecuencia con el osciloscopio de 1Khz, no consigo 2Khz, creo que los cálculos del prescaler y registro de comparación están bien o eso creo.
El duty cycle aun no se como lo puedo hacer y después quiero variar la frecuencia de esta desal de un 300 Hz a los 2Khz.
Pero lo primero es conseguir los 2Khz.
Gracias

#define ledPin 13
void setup()
{
  pinMode(ledPin, OUTPUT);
  
  // TIMER1
  noInterrupts();
  TCCR1A = 0;                          // El registro de control A queda todo en 0
  TCCR1B = 0;                          // limpia registrador
  TCNT1  = 0;                          // Inicializa el temporizador
  OCR1A = 999;                         // registrador de comparación para 2Khz
  TCCR1B |= (1 << WGM12)|(1<<CS11);    // modo CTC, prescaler de 8  
  TIMSK1 |= (1 << OCIE1A);             // habilita interrupción por igualdad de comparación
  interrupts();

}
void loop()
{
 
}
ISR(TIMER1_COMPA_vect)          // interrupción por igualdad de comparación en TIMER1
{
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   //invierte estado del LED
}

Te paso el link a un calculador
AVR Timer Interrupts Calculator
para facilitarte la vida :wink:

Recordá que si usas el timer para bascular lo que sería equivalente a un flip-flop (porque cambia de estado con cada pulso), la frecuencia final va a ser la mitad, por eso con 2KHz obtenés los pulsos a 1KHz.

Saludos

Muchas gracias, ahora lo entendí, por eso en mi calculo me sale 1khz,
hare el calculo para 4Khz y obtendré 2khz.

Como puedo hacer con un potenciómetro que me varié la frecuencia de 300Hz a 2Khz .

Gracias

Vas a tener que hacer los cálculos de los valores que tenés que poner en los registros del timer en base al valor leído del potenciómetro.

Una muy buena fuente de consulta para no romperse la cabeza con la hoja de datos del micro: Timers and counters

Pero tienes disponible una librería (TimerOne) que creo que te va a facilitar muchísimo la vida, si no me equivoco la encuentras en el administrador de librerías de la IDE.

Saludos

He estado mirando la librería de TimerOne (sus ejemplos) y es cierto que es mas fácil generar la señal con esta librería.
Pero no encuentro un ejemplo que pueda estudiar donde varié la frecuencia de 0 al maximo.

Eso es bastante sencillo.

Primero que nada hay que tener presente que el parámetro a pasar a la librería es el período (o sea, la inversa de la frecuencia) en useg.

Luego es cuestión de leer el valor del potenciómetro y con una simple cuenta o usando la función map(), calcular el valor del período para el valor leído y pasarlo a la librería.

Por ejemplo, usando map() se podría hacer algo así

int pote = analogRead(PINPOTE);
unsigned long periodo = map(0, 0, 1023, 500000, 250);

Eso resultaría en una frecuencia del timer de 2 Hz (periodo = 500000 useg) para una lectura 0 hasta 4 KHz (periodo = 250 useg) para una lectura de 1023.

Recuerda que tu salida será 1 Hz hasta 2 KHz (porque resulta la mitad de la frecuencia del timer)

ATENCIÓN: hay un error, ver corrección en post #8

Saludos

@gatul se te deslizó un error al tipear.

unsigned long periodo = map(0, 0, 1023, 500000, 250);

debe ir

unsigned long periodo = map(pote, 0, 1023, 500000, 250);

:man_facepalming:t2:
"Estás en lo cierto Lucas". Gracias

Corrijo:

int pote = analogRead(PINPOTE);
unsigned long periodo = map(pote, 0, 1023, 500000, 250);

Una duda, esto mismo no lo puedo aplicar al código del arriba, ya que me he pegado 3 dias estudiando sobre los Timer y haciendo pruebas para sacar una señal de 2Khz.
No es que desprecie las libreria TimerOne, es por saber hacer las cosas de las dos formas.

Gracias

Yo lo hice así y funcionó bien reguló duty y frecuencia


int duty= 50;
 int _freq = 100;
void setup() {
    DDRB |= (1<<DDB1);

  
  // modo ctc
  TCCR1A &=~ (1<<WGM10);
  TCCR1A |=  (1<<WGM11);
  TCCR1B |=  (1<<WGM12);
  TCCR1B |=  (1<<WGM13);
  
  TCCR1A &=~ (1<<COM1A0);
  TCCR1A |=  (1<<COM1A1);

  //1024
  TCCR1B |=  (1<<CS10);
  TCCR1B &=~ (1<<CS11);
  TCCR1B |=  (1<<CS12);
}

void loop() {
 ICR1 = (F_CPU/1024/_freq) - 1;
OCR1A = (((F_CPU/1024/_freq) - 1)*duty)/100;
}

Y como lo regulas, no veo ningún potenciómetro ni ningún mapeo, no entiendo como lo haces. Me lo explicas por favor

Hola, a las variables duty o freq le agrego el valor fijo, en tu caso deberías agregar la lectura de un potenciometro a las variables para modificar la frecuencia o duty

Yo estoy moviendo el duty con un encoder si te sirve te subo el codigo

#include "avr/interrupt.h"; 
#include <EEPROM.h>
#include <stdio.h>

volatile int value;
volatile int lastEncoded = 0;
int A = 2;       //variable A a pin digital 2 (DT en modulo)
int B = 4;        //variable B a pin digital 4 (CLK en modulo)
int m =8;
int cont;
void setup()   
{  
  
    EEPROM.get(0, value); 
     OCR1A = value;
  pinMode(9,OUTPUT);              
  pinMode(A, INPUT);   // A como entrada
  pinMode(B, INPUT);    // B como entrada
  digitalWrite(A, HIGH);
  digitalWrite(B, HIGH);
  pinMode(m,INPUT_PULLUP);
 
 //Endable Interrupts for PCIE2 arduino Pins (D0-7) 
 PCICR |= (1<<PCIE2);
 //Setup pins 2,4
 PCMSK2 |= (1<<PCINT18);  
 PCMSK2 |= (1<<PCINT20);

 TCCR1A = _BV(COM1A1) | _BV(WGM11);
  TCCR1B = _BV(CS10) | _BV(WGM12) | _BV(WGM13) ;
  ICR1 = 250; 


   sei();
} 

void loop() 
{
  energia();
  duty();
  
} 

ISR (PCINT2_vect) {
 int MSB = digitalRead(A); 
  int LSB = digitalRead(B);
 
  int encoded = (MSB << 1) |LSB;
  int sum  = (lastEncoded << 2) | encoded; 
 
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
    value++;
   
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
    value--;
 
  lastEncoded = encoded; 
 
  if (value <= 0)
    value = 0;
  if (value >= 255)
    value = 255; 
  
  }
 

   void duty() {
 OCR1A = 255 - value;
 
}
void energia (){
if(digitalRead(m)==LOW){ 
cont++;
delay(1);
if (cont >5) {
 EEPROM.put(0,value);
delay(50);
cont = 0; 
}
}
} 

Usa lo mismo que te explicó @gatul para hacerlas variables.

Gracias Surbyte, tienes razón, no me di cuenta y tenia la respuesta delante mia.

Bueno pues el código quedaría a sin:

int duty = 50;
int _freq = 0;
int pinPote = A0;
int pote;


void setup() {
  DDRB |= (1 << DDB1);
  // modo ctc
  TCCR1A &= ~ (1 << WGM10);
  TCCR1A |=  (1 << WGM11);
  TCCR1B |=  (1 << WGM12);
  TCCR1B |=  (1 << WGM13);
  TCCR1A &= ~ (1 << COM1A0);
  TCCR1A |=  (1 << COM1A1);

  //1024
  TCCR1B |=  (1 << CS10);
  TCCR1B &= ~ (1 << CS11);
  TCCR1B |=  (1 << CS12);
}

void loop() {
  ICR1 = (F_CPU / 1024 / _freq) - 1;
  OCR1A = (((F_CPU / 1024 / _freq) - 1) * duty) / 100;
  pote = analogRead(pinPote);
  _freq = map(pote, 0, 1023, 300, 1900);
}

Lo hice para que el mínimo del potenciómetro tenga 300Hz y el máximo 1,9Khz.
Funciona pero es algo lento, cada vez que muevo un poco el potenciómetro la señal desaparece del osciloscopio y aparece a los pocos segundos con la frecuencia ya modificada.
No se si es mi osciloscopio o hice algo mal.

Veo un pequeño error:
_frec nunca puede valer 0 porque te daría error en las divisiones.

F_CPU / 1024 / _freq

arrojaría un resultado indeterminado.

SI bien en este caso particular no te genera problemas porque map() le altera el valor antes de las divisiones, siempre ten presente estos detalles que se pueden volver un quebradero de cabeza.

Me parece que sería mejor que hagas cambios en los registros únicamente si has modificado la posición del potenciómetro

Prueba así

int duty = 50;
int _freq = 1;
int pinPote = A0;
int pote;
int poteAnt = 1024; 

void setup() {
  DDRB |= (1 << DDB1);
  // modo ctc
  TCCR1A &= ~ (1 << WGM10);
  TCCR1A |=  (1 << WGM11);
  TCCR1B |=  (1 << WGM12);
  TCCR1B |=  (1 << WGM13);
  TCCR1A &= ~ (1 << COM1A0);
  TCCR1A |=  (1 << COM1A1);

  //1024
  TCCR1B |=  (1 << CS10);
  TCCR1B &= ~ (1 << CS11);
  TCCR1B |=  (1 << CS12);

}

void loop() {
  pote = analogRead(pinPote);
  if(pote != poteAnt){
    _freq = map(pote, 0, 1023, 300, 1900);
    ICR1 = (F_CPU / 1024 / _freq) - 1;
    OCR1A = (((F_CPU / 1024 / _freq) - 1) * duty) / 100;
    poteAnt = pote;
  }
}

a ver si deja de hacer esa "pausa" que nos comentas.

El valor de poteAnt lo fijé en 1024 porque es un valor que nunca podría alcanzar pote (así me aseguro que se cumple la primer comparación)

Saludos

PD: Me refiero a la primer comparación recién iniciado el arduino.

Me di cuenta que no te dije dónde veia el error, lo digo por ésto

int _freq = 0;

Si bien, como ya dije, en tu caso no molesta, es buena práctica no asignar valores iniciales que podrían traer problemas en otra situación.

Gracias gatul,
Lo acabo de probar y hace lo mismo, muevo el potenciómetro la señal desaparece y aparece a los 5 seg con la frecuencia cambiada

Si probas icr1= pote; directamente

Eso ya lo intente y no funciona.
No creí que fuera tan complicado hacer variar una frecuencia. O por lo menos para mi, he leído muchos tutoriales de timer, y no lo consigo la verdad.