STM8S Простой частотник для трехфазного асинхронного двигателя.
Ссылка на видео в YouTubeНаписание данной статьи подтолкнуло просьба моего зрителя на YouTube и конечно личная нужда в трехфазном частотнике для асинхронного двигателя на фрезерный станок. Как говорится убьем два зайца сразу, напишу статью и будет работать станок.
Для меня частотник должен быть максимально прост и дешевым по этому отсутствует защита от замыкания, система торможения и многие другие излишества которые предлагают нам промышленные, но присутствует защита диодного моста от больших токов во время включения устройства(заряд конденсаторной батареи) в сеть, в виде реле и ограничивающих резисторов.
Тут выложу свою схему устройства и программу, но программа будет минимальной, простая индикация на TM1638, включение двигателя и смена вращения, для себя конечно я постараюсь сделать более продвинутое устройство которое продемонстрирую в другом видео.
Если заинтересуетесь созданием данного устройства то используйте мою программу для расчёта синуса ШИМ «Расчет таблицы SIN для управления асинхронным трехфазным двигателем» в разделе «Программы».
Начнем со схемы устройства, которая ниже.
Особенности схемы, резисторы R14-R15 должны быть не менее 2W, конденсаторы C2-C3-C4 SMD 1206 не менее 25V, C12-C13 не менее 400V, вывод J4 для подключения конденсатора не менее 400V(емкость зависит от мощности двигателя, примерно 1 киловатт-1000 микрофарад), J2 питание может быть от 12-16V, логика питается через стабилизатор U2, в остальном используем компоненты с допустимым напряжением не ниже питающего. Транзисторы IGBT FGA25N120antd можно заменить на подходящие, лишь бы допустимое напряжение было не ниже 400V, хорошо подойдут мосфеты.
Теперь как мы будем управлять через ШИМ трехфазным движком, происходить это будет с помощью комплементарных выводов TIM1, которые в свою очередь управляются через регистры Capture/Compare 1 Register, Capture/Compare 2 Register, Capture/Compare 3 Register далее как А-В-С, у каждого регистра есть пара выводов(верхний нижний ключ) которые связаны между собой, когда на одном высокий уровень на другом низкий, за исключением случая Deadtime(оба закрыты). Управление происходит с помощью изменения величины импульса через функции TIM1_SetCompare1, TIM1_SetCompare2, TIM1_SetCompare3. Уникальность заключается в создании искусственного нуля делается это просто, например у меня период TIM1 равен 1904 делим по полам, получаем 952 это и будет искусственный ноль, потому что если мы установим на А-В-С одновременно эту продолжительность импульса то все верхние и нижние ключи будут работать одинаково, токов в обмотках возникать не будет. Вот например давайте в А оставим импульс 952, в В 1776 и С 128 то ток будет течь из В в С 1776-128=1648 части периода, из В в А 1776-952=824 и из А в С 952-128=824 и последние из C токи не вытекают только в тикают потому что в основном открыт нижний ключ.
Вот так меняя импульс ШИМ для верхнего и нижнего ключа будут меняться токи и их направленность. Как вы наверное знаете а если нет то интернет в помощь, что фазы смещены на 120 градусов(это 360 деленная на 120 будет 3) так и у меня массив синуса в котором все фазы ШИМ смещены на 14 относительно друг друга(в массиве 42 значения, значит 42/3=14 настолько смещены фазы).
Осталась сама программа, в которой настроен TIM1 для вращения с частотой 50Hz для этого устанавливаем частоту тактирования контроллера 16MHz, период TIM1 будет 1904 и пред делитель равный 4 и так как массив синуса состоит из 42 значения получаем 1904*4*42*(1/16000000)=0.019992 миллисекунд что и равно 1/50Hz. Ниже опишу две важные функции это установка мертвого времени и настройка комплементарных выводов.
TIM1_BDTRConfig( TIM1_OSSISTATE_DISABLE, TIM1_LOCKLEVEL_OFF, 16, TIM1_BREAK_DISABLE, TIM1_BREAKPOLARITY_LOW, TIM1_AUTOMATICOUTPUT_DISABLE);
Основное назначение этой функции установка состояния выводов при возникновении короткого замыкания и как возвращать их в рабочее состояние если есть защита, которой у нас нет по этому нас интересует только цифра 16 что значит задержка между переключением верхнего и нижнего ключа будет 1 микросекунда, в datasheet смотрите как его правильно установить Deadtime.
Вторая функция.
TIM1_OC1Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
Первый параметр TIM1_OCMODE_PWM1-режим включения, выключение верхнего и нижнего ключа(если импульс больше нуля включается верхний ключ после достижения счетчика размеру импульса отключается верхний ключ включается нижний и наоборот).
TIM1_OUTPUTSTATE_ENABLE-включаем выводы верхних ключей.
TIM1_OUTPUTNSTATE_ENABLE-включаем выводы нижних ключей.
Четвертый параметр размер импульса, тут у меня 0 по этому будут включены нижние ключи, пока не изменим этот параметр.
TIM1_OCPOLARITY_HIGH-полярность для верхнего ключа так как управляем через ir2101 должна быть положительна.
TIM1_OCNPOLARITY_HIGH-также полярность для нижнего ключа.
TIM1_OCIDLESTATE_SET-включаем Deadtime для верхних ключей.
TIM1_OCNIDLESTATE_SET-включаем Deadtime для нижних ключей.
Пример пуска аналогичного устройства с отключённой защитой по току на двигателе 2.2 kW и конечно плачевный результат, от большого пускового тока взорвались силовые транзисторы.
Для пуска на более мощных двигателях вы должны ограничить пусковые токи уменьшив импульс ШИМ или дополнить код программы своей разработкой плавного пуска двигателя.
#include "stm8s.h"
void send_command(uint8_t com);
void send_data(uint8_t *send_data_display);
void display_data(uint32_t multiplier);
uint8_t scan_button(void);
void pause(uint32_t p);
uint8_t result=0;
//Наша таблица синуса, 42 результата.
uint16_t arr_pwm[42]={952 ,1094 ,1233 ,1365 ,1488 ,1600 ,1696 ,1776 ,1838 ,1880 ,
1901 ,1901 ,1880 ,1838 ,1776 ,1696 ,1600 ,1488 ,1365 ,1233 ,
1094 ,952 ,810 ,671 ,539 ,416 ,304 ,208 ,128 ,66 ,
24 ,3 ,3 ,24 ,66 ,128 ,208 ,304 ,416 ,539 ,671 ,810};
//Счетчик значения фаз, А В С.
uint8_t count_A=0;
uint8_t count_B=14;
uint8_t count_C=28;
//Статус режима работы 0-стоим 1 и 2-направление вращения.
uint8_t status_pwm=0;
uint8_t send_data_display[16];
uint8_t symbol[10]={ 63, 6, 91, 79, 102, 109, 125, 7, 127, 111};
int main( void ){
CLK_HSIPrescalerConfig( CLK_PRESCALER_HSIDIV1);
//Настраиваем порты для SPI.
GPIO_Init( GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST);
GPIO_Init( GPIOC, GPIO_PIN_7, GPIO_MODE_OUT_OD_HIZ_FAST);
SPI_Init( SPI_FIRSTBIT_LSB, SPI_BAUDRATEPRESCALER_8, SPI_MODE_MASTER, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_DATADIRECTION_1LINE_TX, SPI_NSS_SOFT, 0x07);
SPI_Cmd( ENABLE);
//Настраиваем выводы для ШИМ и TIM1 для управления двигателем.
GPIO_Init( GPIOC, GPIO_PIN_1, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init( GPIOC, GPIO_PIN_2, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init( GPIOC, GPIO_PIN_3, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init( GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init( GPIOB, GPIO_PIN_1, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init( GPIOB, GPIO_PIN_2, GPIO_MODE_OUT_PP_LOW_FAST);
TIM1_TimeBaseInit( 3, TIM1_COUNTERMODE_UP, 1904, TIM1_OPMODE_REPETITIVE);
TIM1_BDTRConfig( TIM1_OSSISTATE_DISABLE, TIM1_LOCKLEVEL_OFF, 16, TIM1_BREAK_DISABLE, TIM1_BREAKPOLARITY_LOW, TIM1_AUTOMATICOUTPUT_DISABLE);
TIM1_OC1Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
TIM1_OC2Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
TIM1_OC3Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
TIM1_ClearFlag( TIM1_FLAG_UPDATE);
TIM1_ITConfig( TIM1_IT_UPDATE , ENABLE);
TIM1_OC1PreloadConfig( ENABLE);
TIM1_OC2PreloadConfig( ENABLE);
TIM1_OC3PreloadConfig( ENABLE);
TIM1_CtrlPWMOutputs( ENABLE);
enableInterrupts();
TIM1_Cmd( ENABLE);
//Ожидаем когда зарядятся конденсаторы через ограничительные резисторы, включаем реле.
pause(200000);
GPIO_Init( GPIOA, GPIO_PIN_3, GPIO_MODE_OUT_PP_HIGH_SLOW);
while(1){
send_command(140);
send_command(64);
//Отображаем статус работы.
display_data(50+status_pwm);
pause(100);
result=scan_button();
pause(100);
if( result!=scan_button()){ result=0;}
//Проверяем нажатие кнопок, меняем статус.
if( result!=0){
if( result==1){ status_pwm=0;}
if( result==2){ status_pwm=1;}
if( result==4){ status_pwm=2;}
}
}
}
//Тут происходит основное действие, проверяем статус если не ноль то двигатель крутится.
INTERRUPT_HANDLER( TIM1_UPD_OVF_TRG_BRK_IRQHandler, 11)
{
if(status_pwm>0){
//Выбераем вращение.
if( status_pwm==1){
TIM1_SetCompare1( arr_pwm[count_A]);
TIM1_SetCompare2( arr_pwm[count_B]);
TIM1_SetCompare3( arr_pwm[count_C]);
}
if( status_pwm==2){
TIM1_SetCompare1( arr_pwm[count_A]);
TIM1_SetCompare2( arr_pwm[count_C]);
TIM1_SetCompare3( arr_pwm[count_B]);
}
//Прибавляем счетчики фаз.
count_A++;
count_B++;
count_C++;
//Если счетчик больше массива устанавливаем ноль.
if( count_A==42){ count_A=0;}
if( count_B==42){ count_B=0;}
if( count_C==42){ count_C=0;}
//Если статус равен нулю то выключаем двигатель.
}else{ TIM1_SetCompare1(0); TIM1_SetCompare2(0); TIM1_SetCompare3(0);}
TIM1_ClearFlag( TIM1_FLAG_UPDATE);
}
void send_command( uint8_t com){
GPIO_WriteLow( GPIOC, GPIO_PIN_7);
SPI_SendData( com);
while( ( SPI->SR & SPI_FLAG_BSY) );
GPIO_WriteHigh( GPIOC, GPIO_PIN_7);
}
uint8_t scan_button(void){
uint8_t scan_result;
GPIO_WriteLow( GPIOC, GPIO_PIN_7);
SPI_SendData( 66);
while( ( SPI->SR & SPI_FLAG_BSY) );
SPI_BiDirectionalLineConfig( SPI_DIRECTION_RX);
while( !( SPI->SR & SPI_FLAG_RXNE) );
scan_result=SPI->DR;
while( !( SPI->SR & SPI_FLAG_RXNE) );
scan_result|=( SPI->DR<<1);
while( !( SPI->SR & SPI_FLAG_RXNE) );
scan_result|=( SPI->DR<<2);
while( !( SPI->SR & SPI_FLAG_RXNE) );
scan_result|=( SPI->DR<<3);
GPIO_WriteHigh( GPIOC, GPIO_PIN_7);
SPI_BiDirectionalLineConfig( SPI_DIRECTION_TX);
return scan_result;
}
void send_data( uint8_t *send_data_display){
GPIO_WriteLow( GPIOC, GPIO_PIN_7);
SPI_SendData( 192);
while( ( SPI->SR & SPI_FLAG_BSY) );
for( uint8_t i=0;i<16; i++){
SPI_SendData( send_data_display[i]);
while( !( SPI->SR & SPI_FLAG_TXE) );
}
while( ( SPI->SR & SPI_FLAG_BSY) );
GPIO_WriteHigh( GPIOC, GPIO_PIN_7);
}
void display_data( uint32_t multiplier){
multiplier= multiplier-(multiplier/10000000*10000000);
multiplier=multiplier-(multiplier/1000000*1000000);
multiplier= multiplier-(multiplier/100000*100000);
multiplier= multiplier-(multiplier/10000*10000);
multiplier= multiplier-(multiplier/1000*1000);
multiplier= (multiplier-multiplier/100*100);
send_data_display[12]= symbol[multiplier/10]|128;
multiplier= multiplier-(multiplier/10*10);
send_data_display[14]= symbol[multiplier];
send_data(send_data_display);
}
void pause( uint32_t p){
for( uint32_t i=0; i<p; i++){}
}
#ifdef USE_FULL_ASSERT
void assert_failed( uint8_t* file, uint32_t line)
{
while (1){}
}
#endif
И еще программа из которой я убрал все лишнее, работает просто, включаем в розетку устройство и двигатель начинает вращаться с частотой 50Hz.
#include "stm8s.h"
void pause(uint32_t p){for(uint32_t i=0; i<p; i++){}}
uint16_t arr_pwm[42]={952 ,1094 ,1233 ,1365 ,1488 ,1600 ,1696 ,1776 ,1838 ,1880 ,
1901 ,1901 ,1880 ,1838 ,1776 ,1696 ,1600 ,1488 ,1365 ,1233 ,
1094 ,952 ,810 ,671 ,539 ,416 ,304 ,208 ,128 ,66 ,
24 ,3 ,3 ,24 ,66 ,128 ,208 ,304 ,416 ,539 ,671 ,810};
uint8_t count_A=0;
uint8_t count_B=14;
uint8_t count_C=28;
int main( void ){
CLK_HSIPrescalerConfig( CLK_PRESCALER_HSIDIV1);
GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(GPIOC, GPIO_PIN_2, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(GPIOC, GPIO_PIN_3, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(GPIOB, GPIO_PIN_1, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(GPIOB, GPIO_PIN_2, GPIO_MODE_OUT_PP_LOW_FAST);
TIM1_TimeBaseInit( 3, TIM1_COUNTERMODE_UP, 1904, TIM1_OPMODE_REPETITIVE);
TIM1_BDTRConfig( TIM1_OSSISTATE_DISABLE, TIM1_LOCKLEVEL_OFF, 16, TIM1_BREAK_DISABLE, TIM1_BREAKPOLARITY_LOW, TIM1_AUTOMATICOUTPUT_DISABLE);
TIM1_OC1Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
TIM1_OC2Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
TIM1_OC3Init( TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0, TIM1_OCPOLARITY_HIGH, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_SET);
TIM1_ClearFlag( TIM1_FLAG_UPDATE);
TIM1_ITConfig( TIM1_IT_UPDATE , ENABLE);
TIM1_OC1PreloadConfig( ENABLE);
TIM1_OC2PreloadConfig( ENABLE);
TIM1_OC3PreloadConfig( ENABLE);
TIM1_CtrlPWMOutputs( ENABLE);
enableInterrupts();
TIM1_Cmd( ENABLE);
pause( 200000);
GPIO_Init( GPIOA, GPIO_PIN_3, GPIO_MODE_OUT_PP_HIGH_SLOW);
while(1){
//Тут ваша программа.
}
}
INTERRUPT_HANDLER( TIM1_UPD_OVF_TRG_BRK_IRQHandler, 11)
{
TIM1_SetCompare1( arr_pwm[count_A]);
TIM1_SetCompare2( arr_pwm[count_B]);
TIM1_SetCompare3( arr_pwm[count_C]);
count_A++;
count_B++;
count_C++;
if(count_A==42){ count_A=0;}
if(count_B==42){ count_B=0;}
if(count_C==42){ count_C=0;}
TIM1_ClearFlag( TIM1_FLAG_UPDATE);
}
#ifdef USE_FULL_ASSERT
void assert_failed( uint8_t* file, uint32_t line)
{
while (1){}
}
#endif
ВНИМАНИЕ ОПАСНОЕ НАПРЯЖЕНИЕ!!!Для пробного пуска используйте блок питания около 40 вольт и маломощный двигатель, если все пройдет удачно подключайте к 220в, очень не приятно когда взрываются транзисторы, удачи.