Перейти к содержанию
    

Как состряпать алгоритм синусоидального ШИМ?

После радости по поводу возможности установки аппаратного Dead-Time в ATMega128 появилась мысль: а что мне даст эта возможность? Если бы я использовал комплементарный ШИМ, то это было бы необходимо. Однако, в моём варианте коммутации нижние ключи просто открываются и закрываются на половину периода синуса. Ну, допустим, я устанавливаю скважность ШИМ на нижних ключах равной нулю или 100%. И как здесь будет влиять Dead-Time? Мне нужна пауза после окончания каждой половины периода синуса во время которой и нижний и верхний ключи будут закрыты.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, MPetrovich сказал:

Мне нужна пауза после окончания каждой половины периода синуса во время которой и нижний и верхний ключи будут закрыты.

 

В 08.08.2023 в 10:24, Сергей Борщ сказал:

Для дерганья ногой в строго определенные моменты времени у таймеров в модуле compare есть режимы "Clear OCnA/OCnB/OCnC on compare match" и "Set OCnA/OCnB/OCnC on compare match".

Просто в прерывании, где записываете очередной отсчет синуса, на определенном отсчете прописываете нужное значение в OCR нужного выхода таймера  и переключаете выход таймера в один из этих режимов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

 

image.png.568408e542bfd62d2fd93d3a625f301d.png

Вот картинка для двух фаз. Красным показаны места, где нужен Dead-Time. Я не улавливаю где нужно Clear OCnA/OCnB/OCnC on compare match" и где "Set OCnA/OCnB/OCnC on compare match".

Вот так сейчас выглядит обработчик прерывания:

ISR(TIMER1_OVF_vect)//PWM_ON interrupt
{
   num = akk/(36864/36);//for 36-num 36*1024=36864
   
   OCR1A = PH1H[num];  //ШИМ 1  
   if(OCR1A == 0) {TCCR1A &= ~0x80; _delay_us(1); PORTA |= 1;}//PWM is OFF, LOW key is ON
   else {PORTA &= ~1; _delay_us(1); TCCR1A |= 0x80;}//LOW key is OFF, PWM is ON
   OCR1B = PH2H[num];  //ШИМ 2 
   if(OCR1B == 0) {TCCR1A &= ~0x20; _delay_us(1); PORTA |= 2;}//PWM is OFF, LOW key is ON
   else {PORTA &= ~2; _delay_us(1); TCCR1A |= 0x20;}//LOW key is OFF, PWM is ON 
   OCR1C = PH3H[num];  //ШИМ 3
   if(OCR1C == 0) {TCCR1A &= ~0x08; _delay_us(1); PORTA |= 4;}//PWM is OFF, LOW key is ON
   else {PORTA &= ~4; _delay_us(1); TCCR1A |= 0x08;}//LOW key is OFF, PWM is ON 

   akk += akk_shift;
   if(akk >= 36864) akk -= 36864;
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

13 минут назад, MPetrovich сказал:

Я не улавливаю где нужно Clear OCnA/OCnB/OCnC on compare match" и где "Set OCnA/OCnB/OCnC on compare match".

Там, где нужно перевести ногу в 0 - Clear, где нужно в 1 - Set.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 minutes ago, Сергей Борщ said:

Там, где нужно перевести ногу в 0 - Clear, где нужно в 1 - Set.

Это как раз не вызывает вопросов) Как это разнести по ключам? Нужно вставить задержки там, где сейчас

_delay_us(1);

Вернее даже при первом  и при последнем выполнении условия

if(OCR1A == 0)

Это как раз и есть начало и конец отрицательной полуволны синуса.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

36 минут назад, MPetrovich сказал:

Вернее даже при первом  и при последнем выполнении условия

Поэтому я и писал, что удобнее анализировать фазу (значение фазового аккумулятора) или индекс в таблице. Примерно так, если я правильно понял ваше желание и ничего не напутал:

auto Index = f(Phase);                                      // находим текущую позицию в таблице
auto Sin_PWM = Envelope_table[Index];                       // достаем из таблицы значение ШИМ
OCR_HI = Sin_PWM;

if((Phase >= ANGLE1 - Phase_step) && (Phase < ANGLE1))      // в следующем цикле перевалим через ANGLE1, надо будет включить транзистор
{
   OCR_HI = Sin_PWM - DEAD_TIME_DELAY;                      // устанавливаем момент переключения чуть раньше включения верхнего транзистора
   TCCRxA = ...;                                            // устанавливаем режим Set on compare
}
else if((Phase >= ANGLE2 - Phase_step) && (Phase < ANGLE2)) // в следующем цикле перевалим через ANGLE2, надо будет выключить транзистор
{
   OCR_LO = Sin_PWM + DEAD_TIME_DELAY;                      // устанавливаем момент переключения чуть позже отключения верхнего транзистора
   TCCRxA = ...;                                            // устанавливаем режим Clear on compare
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, Сергей Борщ said:

Поэтому я и писал, что удобнее анализировать фазу (значение фазового аккумулятора) или индекс в таблице.

Я попробовал с привязкой к индексу для одной фазы. Таблица для этой фазы 36 значений разделена пополам - 18 значений синуса от 0 до Пи, 18 нулей. Таймер настроен в режиме сброса в ноль на ноге OC1A при совпадении значения счётчика с регистром OCR1A (Clear on Compare). Написал пробный код для проверки установки задержки перед включением нижнего ключа:

OCR1A = PH1H[num];
if(Prev_num == 17) {OCR1A -= Dead_time;}
if(num > 17){TCCR1A &= ~0x80; PORTA |= 1;}//PWM is OFF, LOW key is ON
else {PORTA &= ~1; TCCR1A |= 0x80;}//LOW key is OFF, PWM is ON

Вылезли косяки.

- значение Dead_Time для 1мкСек в циклах счётчика должно быть: 1мкСек/(1/31250Гц)/256=8. Но значения меньше 80 вообще не влияют на DutyCycle;

- Задержка вносится в несколько последних импульсов. Но этого я ожидал. Причём чем меньше будет частота синуса, тем больше будет этих урезаных импульсов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 hours ago, Сергей Борщ said:
if((Phase >= ANGLE1 - Phase_step) && (Phase < ANGLE1)) 

Эту строчку наверное можно переписать как

if((akk >= 36864/2 - akk_shift) && (akk < 36864/2))  {OCR1A -= 8;} 

где akk/2 соответствует углу Пи, а полный akk=2*Пи

Попробовал вставить в код - задержка не вставляется.

Вот так теперь выглядит прерывание для одной фазы:

   OCR1A = PH1H[num];
   //if((Phase >= ANGLE1 - Phase_step) && (Phase < ANGLE1))      
   if((akk >= (36864/2 - akk_shift)) && (akk < (36864/2)))  {OCR1A = PH1H[num]-8;}  
   if(num > 17){TCCR1A &= ~0x80; TCCR3A |= 0x80;}//PWM is OFF, LOW key is ON
   else {TCCR3A &= ~0x80; TCCR1A |= 0x80;}//LOW key is OFF, PWM is ON

Верхний ключ на ОС1А, нижний на ОС3А.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

И снова здравствуйте😄

За время, которое прошло с последнего моего поста в этой теме, я уже запустил несколько асинхронников со своей прогой. 

Вернулся к теме поскольку захотел отредактировать код для работы с изменяющимся напряжением сети. Есть довольно известная постоянная  для асинхронников - v/f. Она означает, что если мотор разработан и изготовление для определённых напряжения и частоты сети, то соотношение v/f  - константа для всех напряжений и частот, с которыми будет работать мотор выдавая тот же момент, что и для расчётных напряжения и частоты. Для сети 220вольт 50 Герц эта константа равна 4,4. Регулируя частоту вращения нужно изменять амплитуду напряжения фазовых синусоид. 

К-т на который нужно множить значения Duty Cycle получается V=f/50. Соответственно, что при частоах меньше 50Гц он будет меньше нуля. Делить значения рабочего цикла ШИМ на числа не кратные степени 2 чревато длинной процедурой деления, а у меня и так контроллер молотит довольно длинные процедуры обработки прерывания с частотой 32кГц. 

Хотелось бы каким-то образом оптимизировать процесс. Всё, что пока пришло на ум - таблица значений, из которой таскать значения для фиксированного количества частот. Однако, это не решает проблему, поскольку к-ты я буду таскать готовые, но делить то на них значения рабочего цикла всё равно придётся! 

Может есть какие-то решения и я просто про них не знаю? 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

uint16_t Data;

uint16_t Scale = 0.7 * (1 << 16);

uint16_t Result = (Data * Scale) >> 16;

Для 8-битного контроллера uint8_t и сдвиг на 8

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 часов назад, MPetrovich сказал:

Делить значения рабочего цикла ШИМ на числа не кратные степени 2 чревато длинной процедурой деления, а у меня и так контроллер молотит довольно длинные процедуры обработки прерывания с частотой 32кГц. 

Хотелось бы каким-то образом оптимизировать процесс.

Если коэффициент = const, то деление запросто заменяется умножением. Которое занимает 1-2 такта на >= Cortex-M3.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

>= Cortex-M3 32b на 32b аппаратно и делит; вот только у вопрошавшего - восьмибитка ;-)
Изменено пользователем Obam

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Obam сказал:

>= Cortex-M3 32b на 32b аппаратно и делит; вот только у вопрошавшего - восьмибитка 😉

Даже в 8-битках, если они имеют команды умножения (любой разрядности), то замена деления умножением также должна дать выигрыш в скорости. А если у них при этом нет команд деления, то выигрыш будет даже больше, чем на Cortex.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

19 hours ago, Сергей Борщ said:
uint16_t Data;

uint16_t Scale = 0.7 * (1 << 16);

uint16_t Result = (Data * Scale) >> 16;

Для 8-битного контроллера uint8_t и сдвиг на 8

Извиняюсь за бестолковость, но как в 8-битном виде выглядит число 0,7? 

13 hours ago, jcxz said:

Даже в 8-битках, если они имеют команды умножения (любой разрядности), то замена деления умножением также должна дать выигрыш в скорости. А если у них при этом нет команд деления, то выигрыш будет даже больше, чем на Cortex.

Не сочтите за труд, напишите как умножить на 0,7 в 8-битных числах... 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 3/23/2024 at 4:57 PM, MPetrovich said:

Извиняюсь за бестолковость, но как в 8-битном виде выглядит число 0,7?

0.7*256 = 179.

On 3/23/2024 at 4:57 PM, MPetrovich said:

Не сочтите за труд, напишите как умножить на 0,7 в 8-битных числах... 

a*0.7 = ((a*0.7)*256)>>8 = (a*179)>>8

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...