Salida 2 formas de onda PWM con cambio de fase de 90 grados

Hasta ahora, pude obtener dos salidas con la resolución correcta (35 kHz-75 kHz con una resolución no inferior a 0,7 kHz) usando el siguiente código. Me pregunto, ahora, cómo puedo obtener un cambio de fase entre las dos salidas PWM (que usan el Timer1 y el Timer1 de 16 bits ICR1).

Intenté escribir la línea TCNT1 += 1/freq/4;entre la última y la penúltima línea de código ( OCR1A=...y OCR1B =...), pero no funcionó.

   //set port B to output 
   DDRB |= 0xFF; 

   // wgm mode 1110 (fast pwm w/ TOP = ICR1
  TCCR1A |= 1<<WGM11 ; 
  TCCR1B |= 1<<WGM12 | 1<<WGM13; 

   //set OC1/A and OC1B on compare match w/ ICR1 , clear them at bottom
   TCCR1A |=  1<<COM1A1 | 1<<COM1A0| ; 
   TCCR1A |=  1<<COM1B1 | 1<<COM1B0 ; 

   //pre-scaler = 1
   TCCR1B |=  1<<CS10; 


   ICR1 = 16000000/freq; // input compare value = (clock freq) / (desired freq)

   // 50% duty cycle on OCR1A/B
   OCR1A  = ICR1/2;
   //TCNT1 += 1/freq/4; //this line did not do anything
   OCR1B = ICR1/2;
¿Quiere que ambas formas de onda tengan siempre un ciclo de trabajo del 50% y pueda ajustar la frecuencia y el cambio de fase?
Estoy simulando la salida de un codificador de cuadratura. Así que necesito poder variar la frecuencia, pero el ciclo de trabajo siempre será del 50 % y el cambio de fase siempre será de más o menos 90 grados, aunque la demora para esto cambiará junto con la frecuencia. Consulte la Respuesta 2 a continuación para conocer mi estado actual.
El TCNT es un contador que está siendo actualizado por el hardware, por lo que cualquier cambio que realice en él simplemente restablece su valor en el momento en que lo cambia. Consulte mi respuesta a continuación para conocer una forma de hacer que las dos salidas se activen en diferentes momentos del ciclo TCNT.

Respuestas (4)

Si su aplicación solo requiere formas de onda con un ciclo de trabajo del 50 %, entonces puede usar los modos de salida de comparación alterna para generar un par de señales con cambio de fase ajustable entre ellas.

Los modos de alternancia alternarán su salida respectiva cada vez que haya una coincidencia de comparación, por lo que al ajustar los 2 registros de comparación de salida entre sí, cambia la relación de fase. Ajustas la frecuencia de ambas señales juntas cambiando el TOP del contador.

¿Tener sentido?

Aquí hay un código de demostración para un Arduino Uno. Emitirá ondas cuadradas de 50 KHz en los pines 9 y 10 de Arduino, y alternará cambios de fase de 0, 90 y 180 grados, haciendo una pausa en cada uno durante un segundo.

// This code demonstrates how to generate two output signals
// with variable phase shift between them using an AVR Timer 

// The output shows up on Arduino pin 9, 10

// More AVR Timer Tricks at http://josh.com

void setup() {
  pinMode( 9 , OUTPUT );    // Arduino Pin  9 = OCR1A
  pinMode( 10 , OUTPUT );   // Arduino Pin 10 = OCR1B

  // Both outputs in toggle mode  
  TCCR1A = _BV( COM1A0 ) |_BV( COM1B0 );


  // CTC Waveform Generation Mode
  // TOP=ICR1  
  // Note clock is left off for now

  TCCR1B = _BV( WGM13) | _BV( WGM12);

  OCR1A = 0;    // First output is the base, it always toggles at 0


}

// prescaler of 1 will get us 8MHz - 488Hz
// User a higher prescaler for lower freqncies

#define PRESCALER 1
#define PRESCALER_BITS 0x01

#define CLK 16000000UL    // Default clock speed is 16MHz on Arduino Uno

// Output phase shifted wave forms on Arduino Pins 9 & 10
// freq = freqnecy in Hertz (  122 < freq <8000000 )
// shift = phase shift in degrees ( 0 <= shift < 180 )

// Do do shifts 180-360 degrees, you could invert the OCR1B by doing an extra toggle using FOC

/// Note phase shifts will be rounded down to the next neared possible value so the higher the frequency, the less phase shift resolution you get. At 8Mhz, you can only have 0 or 180 degrees because there are only 2 clock ticks per cycle.  

int setWaveforms( unsigned long freq , int shift ) {

  // This assumes prescaler = 1. For lower freqnecies, use a larger prescaler.

  unsigned long clocks_per_toggle = (CLK / freq) / 2;    // /2 becuase it takes 2 toggles to make a full wave

  ICR1 = clocks_per_toggle;

  unsigned long offset_clocks = (clocks_per_toggle * shift) / 180UL; // Do mult first to save precision

  OCR1B= offset_clocks;

  // Turn on timer now if is was not already on
  // Clock source = clkio/1 (no prescaling)
  // Note: you could use a prescaller here for lower freqnencies
  TCCR1B |= _BV( CS10 ); 

}

// Demo by cycling through some phase shifts at 50Khz  

void loop() {

  setWaveforms( 50000 , 0 );

  delay(1000); 

  setWaveforms( 50000 , 90 );

  delay(1000); 

  setWaveforms( 50000 , 180 );

  delay(1000); 


}

Aquí hay algunos rastros de alcance de los cambios de 0, 90 y 180 grados respectivamente...

cambio de 0 grados

cambio de 90 grados

cambio de 180 grados

¡Vaya, muchas gracias! Esto funciona bien, excepto por una cosa: a veces, la ola que debería estar adelantada 90 grados en realidad termina retrasada. Puedo verlo girar en el osciloscopio, y no estoy seguro de por qué sucede: parece aleatorio y, a veces, en realidad voltea hacia atrás y se corrige solo. ¿Podría estar omitiendo conteos de reloj en el registro de comparación? ¡Gracias de nuevo!
¿Supongo que ves el flip cuando estás cambiando activamente los parámetros? Si es así, es probable que se trate de un problema de tiempo. Debido a que no hay doble almacenamiento en búfer de los registros OCR en los modos CTC, debe tener mucho cuidado al realizar actualizaciones. Es posible que se pierda una comparación, lo que significaría perder un cambio, lo que haría que la ola se volviera al revés hasta la próxima vez que se pierda un cambio.
La mejor manera de evitar este problema depende mucho de los requisitos específicos de su aplicación. Una forma es reiniciar el temporizador desde cero cada vez que cambie la fase o la frecuencia. Esto es súper simple y siempre da como resultado la generación correcta de formas de onda mientras se ejecuta, pero causa fallas en el momento en que hace el cambio. ¿Funcionaría algo como esto para su aplicación?
¿Debería comenzar una nueva pregunta sobre este tema? Creo que el código lo ilustraría mejor. Pero básicamente lo que estoy haciendo es tener una onda cuadrada de entrada que determine la frecuencia, y las salidas tienen que coincidir con esa frecuencia. Estoy usando pulseIn() para determinar el período de la entrada, convertirlo en frecuencia y luego enviarlo a la función setWaveforms. A altas frecuencias, pulseIn tiene una resolución un poco mala (creo que una posible solución a mi problema sería mejorar esto), así que lo suavizo promediando 100 muestras a la vez.
Luego introduzco esto en la función setWaveforms continuamente. Supongo que lo que podría hacer es esperar a que itere un par de cientos de veces, y solo luego cambiar la forma de onda, de modo que no comience en 8Mhz y vaya hacia abajo a donde debe estar cada vez ... Le daré eso un tiro en realidad. Actualización: Esto realmente funcionó a las mil maravillas, ¡gracias de nuevo!
¿Tiene alguna sugerencia sobre cómo podría medir la frecuencia de una entrada de onda cuadrada que oscila entre 35K y 75K Hz? Estoy buscando interrupciones, pero según esta página web ( gammon.com.au/interrupts ), una interrupción externa tarda al menos 5 microsegundos. Mientras tanto, solo hay 13 microsegundos entre dos flancos descendentes o ascendentes consecutivos en una onda cuadrada de 75 kHz. ¿Cómo debo abordar esto?
Los temporizadores AVR tienen una función incorporada llamada Captura de entrada que está diseñada para medir con precisión cosas como esta. Visite atmel.com/Images/doc8365.pdf y avíseme si tiene alguna pregunta.
¿Crees que podrías echarle un vistazo a este nuevo post si tienes la oportunidad? electronics.stackexchange.com/questions/175549/…
Me encuentro con uno de dos problemas: o no puedo obtener una resolución lo suficientemente alta (que sé que no debería ser un problema con el ICR) o la máquina de estado se atasca y no devuelve nada útil. El enlace en el comentario anterior tiene mi estado actual.
¿Es posible que la función setWaveforms devuelva un valor para que los gráficos se puedan trazar en el trazador en serie?
Simplemente puede verificar el valor del pin usando uint8_t v = digitalRead();y luego enviar ese valor al puerto serie usando algo como Serial.writeln(v);dentro de su archivo loop().

No puede tener un cambio de fase entre múltiples señales en modo PWM usando un solo temporizador. Cada turno tendrá que estar en un temporizador separado, y deberá compensar cada contador en la cantidad adecuada.

¿No hay forma de agregar un retraso en la inicialización o algo así? El problema es que los otros 2 temporizadores no tienen un registro de captura de entrada, por lo que no me permiten ajustar la frecuencia tan fácilmente, ni tienen una resolución tan precisa.
Si no, ¿debería usar un microcontrolador diferente? Tal vez podría sugerir algo... Investigué el dsPIC30F4011/4012 y el PWM de control del motor parece que funcionaría (es decir, PWM de dos salidas de frecuencia variable con cambio de fase de 90 grados), pero no estoy seguro si eso es exagerado. ¿Quizás un AtTiny?
Si lee las partes relevantes de la hoja de datos sobre cómo funciona el temporizador, debería ser obvio por qué no puede hacer esto: el temporizador establece o borra los pines relevantes cuando el temporizador se desborda, por lo que todas las salidas en el temporizador tendrán eso borde en común. ¿Qué AVR es este? La mayoría de ellos tienen >1 temporizador que admite valores TOP ajustables.
Lo siento, debería haber incluido eso: ATMEL328P
Los tres temporizadores del ATMega328P pueden tener valores TOP configurados escribiendo en sus respectivos registros OCRxA.
Pero tenga en cuenta que las bibliotecas Arduino usan el temporizador 0 para sus rutinas de tiempo ( delay()et alia).
Correcto, vi que si me meto con el temporizador 0, las rutinas de tiempo se estropean. ¿Podría explicar cuál es la diferencia entre usar ICR y OCR para establecer el valor máximo del temporizador? Creo que eso podría ser lo que me está desanimando. Sé que cada uno encendió sus respectivas banderas en el partido, pero no estoy seguro de qué sucede después y cómo afecta eso al pwm real.
Solo OCRxn es capaz de activar cambios de pin.
¿No es ICR1 el valor del contador en el que el pin sube en el código anterior?
Porque es TOP, sí. Pero es el hecho de que es TOP para ese modo, no porque sea ICRn.
Entonces estás diciendo que OCRxn puede activar pines independientemente de si es TOP, lo tengo. En ese caso, ¿cuál es el punto de ICRn? ¿Está literalmente allí para marcar el momento en que suceden los eventos? ¿Cuál es la ventaja de usar ICRn como TOP en lugar de OCRxn?
Libera OCRxn con el fin de activar cambios de pin en un punto arbitrario del conteo.
Muchas gracias por tu ayuda. Ahora estoy intentando que los temporizadores 0 y 2 funcionen, pero creo que me falta algo; aquí está mi código actual: // modo wgm 101 (pwm con corrección de fase con TOP = OCRA) TCCR0A |= 1<<WGM00 ; TCCR0B |= 1<<WGM02; //establecer COM0B1 (modo no inversor) TCCR0A |= 1<<COM0B1 ; // preescalador = 1 TCCR0B |= 1<<CS00; // frecuencia arbitraria OCR0A = 55; DDRD = 0xFF;
No importa, ¡entendido! Gracias de nuevo por tu ayuda, eres increíblemente amable al publicar respuestas para los novatos.

Este código aún no es del todo correcto: el cambio de fase se complica cada vez más a medida que disminuye la frecuencia, y también tendré que agregar un preescalador para cualquier cosa por debajo de 62 kHz, ¡pero a 75 kHz funciona!

Supongo que ahora la pregunta no es "¿Cómo obtengo un retraso entre los dos PWM?" sino más bien, "¿Cómo consigo que la demora se escale con la frecuencia?"

int main ()   
{
  //************************* Timer 0 *********************************

    // wgm mode 111 (fast pwm w/ TOP = OCRA)
TCCR0A |=  1<<WGM00 | 1<<WGM01; 
TCCR0B |= 1<<WGM02; 

    //set COM0x1 (non-inverting mode)
    TCCR0A |=  1<<COM0A1 ; 
    TCCR0A |=  1<<COM0B1 ; 

    //pre-scaler = 1
    TCCR0B |=  1<<CS00; 




    // arbitrary frequency
OCR0A  = 220; //counts until TCNT = OCR0A then resets
    OCR0B = OCR0A/2; //on until TCNT = OCR0B then off

     // turn on pin D5
    DDRD |= 1<<PIND5;

   TCNT2 += OCR0A/4 ; //add a 90 degree delay to TCNT2... or something like that

//**************************** Timer 2 ****************************************
    // wgm mode 111 (phase-corrected pwm w/ TOP = OCRA)
TCCR2A |=  1<<WGM20 | 1<<WGM21; 
TCCR2B |= 1<<WGM22; 

    //set COM0x1 (non-inverting mode)
    TCCR2A |=  1<<COM2A1 ; 
    TCCR2A |=  1<<COM2B1 ; 

    //pre-scaler = 1
    TCCR2B |=  1<<CS20; 




    // same frequency as above pwm on timer 0
OCR2A  = OCR0A; 
    OCR2B = OCR2A/2;



     // turn on pin D3
    DDRD |= 1<<PIND3;
    }
¿Intentó restablecer el retraso cada vez que configuraba la frecuencia?
Cada vez que cambiaba la frecuencia (OCR0A = 220) tenía que volver a cargar el archivo en el Arduino, así que si eso es lo que quieres decir, entonces sí.

Runtime Micro le muestra cómo tener ondas cuadradas de cambio de fase de un solo temporizador (por ejemplo, 50% de servicio). Un temporizador de 16 bits (Nano, Uno, 2560) utiliza un modo que no es PWM pero brinda una verdadera capacidad de cambio de fase. Los cambios en la frecuencia no afectan la relación de fase que configure.

El código para ello se incluye en el siguiente enlace...

https://runtimemicro.com/Forums/RTM_TimerCalc/Examples-and-Tips/Arduino-Timer-Phase-Shifted-Square-Waves

Tenga en cuenta que el generador de forma de onda del temporizador de 16 bits utiliza un modo de alternancia; por lo tanto, la salida de frecuencia es la mitad de la frecuencia del ciclo del temporizador.

Desaconsejamos las respuestas que dependen en gran medida de un enlace a otro sitio. Si ese sitio se reorganiza o pasa a 404, entonces la respuesta pierde su valor. Intente resumir (no copiar) la información importante del otro sitio e incluirla en su respuesta.
Déjame resumir. Si las ondas cuadradas con cambio de fase son aceptables para el OP, los problemas de cambio de fase se mitigan utilizando tácticas FOC (comparación de salida forzada). Esto permite un cambio de fase ajustable en tiempo de ejecución estable. Esta información ahora se incluye en el artículo de TimerCalc mencionado anteriormente junto con el diagrama de compilación, el código de ejemplo y el video de captura de alcance. Además, en mi opinión, el PWM de cambio de fase es práctico con un mega2560 que usa (digamos) tres temporizadores de 16 bits iniciados en diferentes momentos en el mismo reloj del sistema. [Editado por un moderador.]
Un cuarto temporizador funciona a la mitad de la frecuencia del ciclo del temporizador PWM para lograr la sincronización necesaria de 120 grados. Las interrupciones luego habilitan los temporizadores en los puntos definidos por OCR. Dado que el 2560 funciona a 16 MHz, cualquier sesgo de inicio en los ISR se puede minimizar recortando los valores de OCR. El mega2560 tiene 4 temporizadores de 16 bits disponibles. [Editado por un moderador.]