Выход 2 сигнала ШИМ с фазовым сдвигом 90 градусов

Пока что мне удалось получить два выхода при правильном разрешении (35 кГц-75 кГц при разрешении не хуже 0,7 кГц), используя приведенный ниже код. Теперь мне интересно, как я могу получить фазовый сдвиг между двумя выходами PWM (которые оба используют 16-битный Timer1 и ICR1).

Я попытался написать строку TCNT1 += 1/freq/4;между последней и предпоследней строками кода ( OCR1A=...и OCR1B =...), но это ничего не дало.

   //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;
Вы просто хотите, чтобы обе формы волны всегда имели рабочий цикл 50%, и вы можете регулировать частоту и фазовый сдвиг?
Я моделирую выход квадратурного энкодера. Поэтому мне нужно иметь возможность изменять частоту, но рабочий цикл всегда будет 50%, а фазовый сдвиг всегда будет плюс-минус 90 градусов, хотя задержка для этого будет меняться вместе с частотой. См. ответ 2 ниже, чтобы узнать мой текущий статус.
TCNT — это счетчик, который обновляется аппаратно, поэтому любые изменения, которые вы в него вносите, просто сбрасывают его значение в момент изменения. Проверьте мой ответ ниже, чтобы узнать, как заставить два выхода срабатывать в разное время в цикле TCNT.

Ответы (4)

Если вашему приложению требуются сигналы только с коэффициентом заполнения 50 %, вы можете использовать переключатель сравнения режимов вывода, чтобы сгенерировать пару сигналов с регулируемым фазовым сдвигом между ними.

Режимы переключения будут переключать свои соответствующие выходные данные каждый раз при совпадении сравнения, поэтому, настраивая два выходных регистра сравнения относительно друг друга, вы изменяете соотношение фаз. Вы настраиваете частоту обоих сигналов вместе, изменяя TOP для счетчика.

Есть смысл?

Вот некоторый демонстрационный код для Arduino Uno. Он будет выводить прямоугольные волны 50 кГц на контакты 9 и 10 Arduino и циклически изменять фазовые сдвиги на 0, 90 и 180 градусов с паузой на каждую секунду.

// 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); 


}

Вот некоторые следы прицела сдвига на 0, 90 и 180 градусов соответственно...

смещение на 0 градусов

смещение на 90 градусов

сдвиг на 180 градусов

Вау, спасибо большое! Это работает хорошо, за исключением одного: иногда волна, которая должна быть впереди на 90 градусов, на самом деле оказывается отстающей. Я вижу, как он переворачивается на осциллографе, и я не уверен, почему это происходит — это кажется случайным, а иногда он действительно переворачивается обратно и корректируется. Может быть, это пропуск тактов в регистре сравнения? Еще раз спасибо!
Я предполагаю, что вы видите флип, когда активно меняете параметры? Если это так, то это, вероятно, проблема времени. Поскольку в режимах CTC нет двойной буферизации регистров OCR, вы должны быть очень осторожны при выполнении обновлений. Вы можете пропустить сравнение, что будет означать отсутствие переключателя, что перевернет волну до следующего раза, когда вы пропустите переключатель.
Лучший способ избежать этой проблемы во многом зависит от ваших конкретных требований к приложению. Один из способов — перезапускать таймер с нуля каждый раз, когда вы меняете фазу или частоту. Это очень просто и всегда приводит к правильной генерации сигнала во время работы, но вызывает сбои в момент переключения. Будет ли что-то подобное работать для вашего приложения?
Должен ли я начать новый вопрос на эту тему? Я думаю, код лучше проиллюстрирует это. Но в основном то, что я делаю, это входная прямоугольная волна, определяющая частоту, и выходы должны соответствовать этой частоте. Я использую pulseIn() для определения периода ввода, преобразовываю его в частоту, а затем передаю его функции setWaveforms. На высоких частотах pulseIn немного дрянной с разрешением (я думаю, что возможным решением моей проблемы было бы его улучшение), поэтому я сглаживаю его, усредняя 100 выборок за раз.
Затем я непрерывно подаю это в функцию setWaveforms. Я полагаю, что я мог бы подождать, пока он повторится пару сотен раз, и только затем изменить форму сигнала - так, чтобы он не начинался с 8 МГц и не опускался до того места, где он должен быть каждый раз ... Я дам это на самом деле выстрел. Обновление: это действительно сработало как шарм, еще раз спасибо!
Есть ли у вас какие-либо предложения о том, как я мог бы измерить частоту входного сигнала прямоугольной формы, которая колеблется от 35K до 75K Гц? Я изучаю прерывания, но, согласно этой веб-странице ( gammon.com.au/interrupts ), внешнее прерывание занимает не менее 5 микросекунд. Между тем, между двумя последовательными передними или передними фронтами в прямоугольной волне 75 кГц всего 13 микросекунд. Как мне с этим справиться?
Таймеры AVR имеют встроенную функцию под названием Input Capture, которая предназначена для точного измерения подобных вещей. Посетите atmel.com/Images/doc8365.pdf и дайте мне знать, если у вас возникнут вопросы!
Как вы думаете, вы могли бы взглянуть на этот новый пост, если у вас будет такая возможность? electronics.stackexchange.com/questions/175549/…
Я столкнулся с одной из двух проблем: либо я не могу получить достаточно высокое разрешение (я знаю, что это не должно быть проблемой для ICR), либо конечный автомат зависает и не возвращает ничего полезного. Ссылка в комментарии выше имеет мой текущий статус.
Возможно ли, чтобы функция setWaveforms возвращала значение, чтобы графики можно было строить на последовательном плоттере?
Вы можете просто проверить значение вывода, используя uint8_t v = digitalRead();, а затем вывести это значение на последовательный порт, используя что-то вроде Serial.writeln(v);вашего файла loop().

У вас не может быть фазового сдвига между несколькими сигналами в режиме ШИМ с использованием одного таймера. Каждая смена должна быть на отдельном таймере, и вам нужно будет сместить каждый счетчик на соответствующую сумму.

Нет возможности добавить задержку инициализации или что-то в этом роде? Проблема в том, что другие 2 таймера не имеют регистра захвата ввода, поэтому они не позволяют мне так же легко регулировать частоту - и у них нет такого точного разрешения.
Если нет, следует ли использовать другой микроконтроллер? Может быть, вы могли бы что-то предложить ... Я посмотрел на dsPIC30F4011/4012, и ШИМ управления двигателем кажется, что он поможет (а именно, ШИМ с двумя выходами с переменной частотой и фазовым сдвигом на 90 градусов), но я не уверен если это лишнее. Может AtTiny?
Если вы прочитали соответствующие части таблицы данных о том, как работает таймер, должно быть очевидно, почему вы не можете этого сделать - таймер либо устанавливает, либо очищает соответствующие контакты, когда таймер переполняется, поэтому все выходы на таймере будут иметь это край общий. Какой это АВР? Большинство из них имеют> 1 таймер, который поддерживает настраиваемые значения TOP.
Извините, нужно было включить это: ATMEL328P
Все три таймера на ATMega328P могут иметь значения TOP, настроенные путем записи в соответствующие регистры OCRxA.
Но обратите внимание, что библиотеки Arduino используют таймер 0 для своих процедур синхронизации ( delay()и др.).
Верно, я видел, что если я возьмусь за таймер 0, процедуры синхронизации будут нарушены. Не могли бы вы объяснить, в чем разница между использованием ICR и OCR для установки верхнего значения таймера? Я думаю, это может быть то, что сбивает меня с толку. Я знаю, что каждый из них устанавливает свои соответствующие флаги при совпадении, но я не уверен, что происходит потом и как это влияет на фактический PWM.
Только OCRxn может инициировать смену контактов.
Разве ICR1 не является значением счетчика, при котором вывод становится высоким в приведенном выше коде?
Потому что это ТОП, да. Но дело в том, что это ТОП для этого режима, а не потому, что это ICRn.
Итак, вы говорите, что OCRxn может запускать пины независимо от того, является ли он TOP, понял. В таком случае, какой смысл в ICRn? Буквально просто для того, чтобы отметить время, в которое происходят события? В чем преимущество использования ICRn в качестве TOP вместо OCRxn?
Он освобождает OCRxn для запуска смены контактов в произвольной точке счета.
Большое вам спасибо за вашу помощь. Сейчас я пытаюсь заставить работать таймеры 0 и 2, но мне кажется, что я что-то упускаю; вот мой текущий код: // режим wgm 101 (ШИМ с фазовой коррекцией w/ TOP = OCRA) TCCR0A |= 1<<WGM00 ; TCCR0B |= 1<<WGM02; //установить COM0B1 (неинвертирующий режим) TCCR0A |= 1<<COM0B1 ; //премасштабирование = 1 TCCR0B |= 1<<CS00; // произвольная частота OCR0A = 55; ДДРД = 0xFF;
Неважно - понял! Еще раз спасибо за вашу помощь, вы невероятно любезны, что публикуете ответы нубам.

Этот код по-прежнему не совсем корректен — фазовый сдвиг становится все более и более запутанным по мере уменьшения частоты, и мне также придется добавить предварительный масштабатор для всего, что ниже 62 кГц, — но на 75 кГц он работает!

Я предполагаю, что теперь вопрос не в том, «Как мне получить задержку между двумя PWM?» а скорее: «Как мне заставить задержку масштабироваться с частотой?»

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;
    }
Вы пробовали сбрасывать задержку каждый раз, когда устанавливаете частоту?
Каждый раз, когда я менял частоту (OCR0A = 220) мне приходилось перезаливать файл на Arduino, так что если вы это имеете в виду, то да.

Runtime Micro показывает вам, как получить прямоугольные импульсы с одновременным фазовым сдвигом (например, 50%-ная нагрузка). 16-разрядный таймер (Nano, Uno, 2560) использует режим без ШИМ, но дает возможность истинного фазового сдвига. Изменения частоты не влияют на установленное вами соотношение фаз.

Код для него включен по следующей ссылке...

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

Обратите внимание, что 16-разрядный генератор сигналов таймера использует режим Toggle, поэтому выходная частота составляет 1/2 частоты цикла таймера.

Мы не рекомендуем ответы, которые в значительной степени зависят от ссылки на другой сайт. Если этот сайт реорганизуется или получит ошибку 404, то ответ станет бесполезным. Пожалуйста, попробуйте обобщить (не скопировать) важную информацию с другого сайта и включить ее в свой ответ.
Позвольте мне подытожить. Если прямоугольные сигналы со сдвигом по фазе приемлемы для OP, проблемы с фазовым переворотом устраняются с помощью тактики FOC (принудительное сравнение выходных данных). Это обеспечивает стабильный фазовый сдвиг, регулируемый во время работы. Эта информация теперь включена в ранее упомянутую статью TimerCalc вместе со схемой сборки, примером кода и видео захвата области действия. Кроме того, IMO, ШИМ со сдвигом по фазе удобен для мега2560, использующего, скажем, три 16-битных таймера, запускаемых в разное время на одних и тех же системных часах. [Отредактировано модератором.]
4-й таймер работает с половиной частоты цикла таймера ШИМ для достижения необходимой синхронизации в 120 градусов. Затем прерывания включают таймеры в определенных точках OCR. Так как 2560 работает на частоте 16 МГц, любой пусковой перекос в ISR может быть сведен к минимуму путем обрезки значений OCR. mega2560 имеет 4 16-битных таймера. [Отредактировано модератором.]