Скалярный частотник для однофазного асинхронного двигателя

в 18:49, , рубрики: arduino, diy или сделай сам, преобразователь частоты, Производство и разработка электроники

Начнём с того, что у каждого программера должен быть токарный станок. Ну… Или, как минимум, у меня он должен быть. И пусть даже без ЧПУ. Это моя мечта.
И мечта сбылась. Станок куплен, привезён, поставлен на место парковки и… Надо бы его включить. А включить его не так и просто. И если не искать простых путей, то нужен «частотник», а по-научному: преобразователь частоты.
И пусть я в этом профан, но я его сделал.


И вот с подключения двигателя и начинаются интересности. Сам по себе я в таком профан, есть некоторые общие знания, но как оно реально работает — понятия не имел. А уж когда вместо ожидаемых 3-х выводов 3-х фазника я узрел 4, да ещё и не 3 обмотки с общей точкой, а отдельные 2, да ещё с разным сопротивлением… Ну, я, кхм, скажем так — «удивился».

Так вот, двигатели. Они бывают трёхфазные, трёхфазные, включенные треугольником через конденсаторы и… однофазные конденсаторные.

Трёхфазные — «полноценные» асинхронники. 3 обмотки, по-хорошему включённые звездой и повешенные на полноценные 3 фазы. Ну, или 3-х фазный частотник, коих валом на али.
Трёхфазные + треугольник + конденсаторы. Тут мы уже теряем в КПД, мощности и моменте, ну да если 3-х фаз нету, то вполне себе решение. Дёшево, просто, надёжно, сердито.

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

Базовая схема включения (картинка не моя, честно найденная на просторах интернета):
Скалярный частотник для однофазного асинхронного двигателя - 1

В общем, грубо, принцип такой: Есть стартерная обмотка, она наводит ЭДС в короткозамкнутом роторе. Со смещением по фазе включается рабочая обмотка. Она «отталкивает» замагниченный якорь, начинается вращение. При повороте на некий угол, всё повторяется. Мотор начинает крутиться.

Итого — нужно 2 фазы, смещённые на некий угол. Обычно это 90 градусов. Это и обеспечивается пусковым конденсатором. Ну а после набора оборотов — якорь начинает работать от самой же рабочей обмотки и стартерную обмотку можно вообще даже отключить. Ну, или запитать от рабочего конденсатора, существенно меньшей ёмкости.

Но это всё теория. А на практике то чего хочется? Хочется частотник. Что б само разгонялось, тормозило, крутилось в обе стороны, ну и с разными оборотами, конечно!
И вот тут становится всё несколько сложнее. Дело в том, что таких частотников в продаже в разы меньше. И стоят они в разы больше. В общем — экзотика.

А ведь, если так задуматься, то разница от 3-х фазного не так и велика. И даже можно использовать ту же схемотехнику. При этом, есть те же 3 отвода: общий, стартерная обмотка, рабочая обмотка. И всё дело в прошивке. А значит — это можно сделать. И пусть это будет не векторное управление с кучай математики, а простое скалярное, но… как умею.

Итак — что требуется. Для начала посмотрим на графики синусойды и косинусойды (у нас же смещение 90 градусов) УСЛОВНО оно будет выглядеть так:

Скалярный частотник для однофазного асинхронного двигателя - 2

Т.е. задача элементарна: «притягиваем» к земле общий, подаём пачку положительных импульсов в стартерную обмотку, подаём пачку импульсов в рабочую. Затем подтягиваем общий к плюсу и подаём пачку отрицательных импульсов к стартерной, а затем и рабочей обмотке. Т.о. грубо получаем имитацию смены полярности и сдвиг фаз. От того, как часто мы это будем делать — будет зависеть «частота».

В общем — теория проста. В качестве контроллера ATMega 328 с бутлоадером ардуины (да по сути, сама ардуина, просто без лишней обвязки), в качестве драйвера IR2130 (старая, но с IR2136 не сложилось) и выходные ключи IRG4BC30. Дя моего 1.1КВт-го мотора этого более чем достаточно.

(Если повторять схему, то просто делается плата, в Arduino Duemilanove заливается скетч, затем Mega328 выдёргивается и впаивается в плату. Готово.)

А вот дальше… Дальше я погрузился в удивительный мир индуктивностей и силовой электроники. И всё оказалось не так и просто, как думалось вначале:

1) Скорость открытия и закрытия ключей — это важно. Deadtime — это ОЧЕНЬ важно.
2) Включение балласта — обязательно с использованием диода, обращённого плюсом на силовой фильтрующий конденсатор. Иначе выбросы при снятии нагрузки с индуктивности на раз выводят из строя силовые IGBT.
3) Охлаждение. На плате маленькие радиаторы — плохо. Либо перегреется, либо надо обдувать. Но если обдувать, то вся металлическая взвесь от станка, рано или поздно, закоротит что-то и будет бах.
3+) Слюда, а точнее ОЧЕНЬ ТОНКАЯ слюда, это плохо. Она пробивается и получается то, что в заглавии статьи. В то же время силиконовые термопрокладки хуже по теплопроводности. Ну а керамика… У меня её нет.
4) Торможение методом длинной пачки импульсов одной полярности на одну обмотку, быстро перегревает транзисторы и они сгорают. А так же, очень весело прыгает мотор, раскрученный до 3-х тыс оборотов и остановленный за 0.3 сек до 0.
5) Когда у вас всё заработает и вы расслабитесь, включите схему без балласта и нажмёте пуск — будет бах. Это приводит ещё и к замене драйвера.

Скетч: Сейчас реализовано вращение в обе стороны с плавной перестройкой частоты 25-75Гц с шагом 0.25. Была идея с тормозом. Сейчас закомментировано и надо будет менять схему. А именно, идея такая: я правильно подавал импульсы одной полярности, НО. Это надо делать через балластное сопротивление отдельным ключом.
Силовая часть: схема ещё будет дорабатываться, но на данный момент станок стоит в не отапливаемом помещении и работать с ним крайне сложно из-за замерзания масла.

Скетч
#define Hi_ST 10    //Стартерный
#define Hi_WK 11    //Рабочий
#define Hi_M 12    //Общий
#define Lo_ST 7
#define Lo_WK 8
#define Lo_M 9
#define SoftStart 6 //реле мягкого старта конденсатора

#define LedFwd 4  //Светодиод "туда"
#define LedRew 5  //Светодиод "обратно"
#define LedStp 13 //Светодиод стоп
#define CmdFwd 2 //Кнопка "туда"
#define CmdRew 3 //Кнопка "обратно" Обе вместе - стоп.

#define ValFwd 4 //Крутилка "Туда"
#define ValRew 5 //Крутилка "Обратно"

#define RemoteFwd 1 //Тут должнен быть пультик...
#define RemoteRew 0 //Тут должнен быть пультик...

int nn = 0;
byte WkState = 0;

void setup() 
{   
	//Serial.begin(9600);

	pinMode(CmdFwd, INPUT);
	pinMode(CmdRew, INPUT);
	
	pinMode(Hi_ST, OUTPUT);
	pinMode(Hi_WK, OUTPUT);
	pinMode(Hi_M, OUTPUT);
	pinMode(Lo_ST, OUTPUT);
	pinMode(Lo_WK, OUTPUT);
	pinMode(Lo_M, OUTPUT);
	pinMode(SoftStart, OUTPUT);
	
	pinMode(LedFwd, OUTPUT);
	pinMode(LedRew, OUTPUT);
	pinMode(LedStp, OUTPUT);

	delay(1500);
	digitalWrite(SoftStart, HIGH);
	Indicate();
}


void loop() 
{
	//Управление
	bool cmdF = digitalRead(CmdFwd);
	bool cmdR = digitalRead(CmdRew);
	bool RemF = digitalRead(RemoteFwd);
	bool RemR = digitalRead(RemoteRew);

	if ((cmdF && !cmdR))// || ) //Кнопка или команда пультика.
	{
		if(WkState != 1 && nn > 0)
		{
			WkState = 1; //Это не Бангладеш. Это необходимость СРАЗУ переключить лампочку, а тока потом тормозить мотор.
			Indicate();
			Brake(); //Тормоз
		}
		WkState = 1; //А это на случай если томозить не надобно. Например после стопа.
		Indicate();
	}
	if (!cmdF && cmdR)// || ) //Тут должнен быть пультик...
	{
		if(WkState != 2 && nn > 0)
		{
			WkState = 2;
			Indicate();
			Brake(); //Тормоз
		}
		Indicate();
		WkState = 2;
	}
	if (cmdF && cmdR)
	{
		if(WkState != 0)
		{
			WkState = 0;
			Indicate();
			Brake(); //Тормоз
		}
		Indicate();
		WkState = 0;
	}
	
	//Исполнение
	if (WkState == 0) //Стоп
	{
		nn = 0;
		delay(50);
		return;
	}

	
	nn ++; //Стартерная задержка (циклы работы стартерной обмотки "на полную")
	if (nn > 30000) nn = 30000; 

	if (WkState == 1) //Вперёд
	{
		int delays = GetDelayByVal(1024-analogRead(ValFwd)); //Ну наоборот я распаял. Бывает, чо... Инвертируем
		RotateFwd(delays-400);
	}
	else //Назад
	{
		int delays = GetDelayByVal(analogRead(ValRew));
		RotateRew(delays-400);
	}

}

//Крутим вперёд. Тут оборот
void RotateFwd(int delays)
{
	digitalWrite(Lo_M, HIGH); //Включаем общий минус
	SendPosST(delays);
	SendPosWK(delays);
	digitalWrite(Lo_M, LOW); //Отключаем общий минус. 
	
	delayMicroseconds(200); //Отрицательная полярность
	
	digitalWrite(Hi_M, HIGH);
	SendNegST(delays);
	SendNegWK(delays);
	digitalWrite(Hi_M, LOW);

	delayMicroseconds(120);
}

//Крутим назад. Тут оборот
void RotateRew(int delays)
{
	digitalWrite(Lo_M, HIGH); //Включаем общий минус
	SendPosST(delays);
	digitalWrite(Lo_M, LOW); //Отключаем общий минус. 

	delayMicroseconds(133);
	digitalWrite(Hi_M, HIGH);
	SendNegWK(delays); 	
	SendNegST(delays);
	digitalWrite(Hi_M, LOW);

	delayMicroseconds(133);
	digitalWrite(Lo_M, HIGH); //Включаем общий минус
	SendPosWK(delays);
	digitalWrite(Lo_M, LOW); //Отключаем общий минус.

	delayMicroseconds(50);
}

//Отправка пачки импульсов
void SendPulse(int pin, int delays) 
{
	byte pwrCycle = 0;
	while(delays > 0) //Крутимся в цикле, пока не закончится время на такт
	{
		pwrCycle ++;
		if (delays < 0)
			return;
		if (delays < 300) //Если осталось менее 300мкс, то проще запаузиться.
		{
			delayMicroseconds(delays);
			return;
		}
		
		if (pwrCycle < 5){	digitalWrite(pin, HIGH);}
		delayMicroseconds(min(1200,delays));  //Длина импульса. 1200мкс
		digitalWrite(pin, LOW);
		
		if (delays < 300)//Если осталось менее 300мкс, то проще запаузиться.
		{
			delayMicroseconds(delays);
			return;
		}

		delayMicroseconds(200); 
		delays -= 1400; //Примерное время цикла
	}
}

void SendPosWK(int delays)
{
	SendPulse(Hi_WK,delays);
}
void SendNegWK(int delays)
{
	SendPulse(Lo_WK,delays);
}
void SendPosST(int delays)
{
	if (nn < 100) //Включаем стартерную
	{ SendPulse(Hi_ST,delays); } 
	else
	{ 
		if (delays > 3000) //Если частота мала - уменьшаем импульс до 25% времени
		{
			delayMicroseconds(delays*0.37);
			SendPulse(Hi_ST,delays*0.25);
			delayMicroseconds(delays*0.37);
		}
		else
		{
			delayMicroseconds(delays); //Для коротких интервалов, можно и 100% гнать стартерную - всё равно импульс будет 1-2шт и короткий
		}
	}
}
void SendNegST(int delays)
{
	if (nn < 100) //Включаем стартерную
	{ SendPulse(Lo_ST,delays); } 
	else
	{
		if (delays > 3000)
		{
			delayMicroseconds(delays*0.33);
			SendPulse(Lo_ST,delays*0.33);
			delayMicroseconds(delays*0.33);
		}
		else
		{
			delayMicroseconds(delays);
		}
	}
}

//Тут должен будет жить тормоз. По идее под него надо отдельнй транзистор, подающий постоянку на обмотки через резистор.
void Brake()
{

	AllOff();
	delay(3000);
	return;

	//Serial.println("Brake");

	//Тормозим тупо импульсами одной полярности. 
	//ЗЫ, это убило 3 транзистора. Тогда я понял что так делать не надо. Но тормозит зверски. Мотор аж подпрыгивает.
	digitalWrite(Lo_M, HIGH);
	for (nn = 0; nn < 100; nn ++)
	{
		digitalWrite(Hi_ST, HIGH);
		delay(3);
		digitalWrite(Hi_ST, LOW);
		delayMicroseconds(250);
		digitalWrite(Hi_WK, HIGH);
		delay(1);
		digitalWrite(Hi_WK, LOW);
		delay(3);
	}
	AllOff();
}

void AllOff()
{
	digitalWrite(Hi_ST, LOW);
	digitalWrite(Hi_WK, LOW);
	digitalWrite(Hi_M, LOW);
	digitalWrite(Lo_ST, LOW);
	digitalWrite(Lo_WK, LOW);
	digitalWrite(Lo_M, LOW);
	delayMicroseconds(500);
}

void Indicate()
{
	digitalWrite(LedStp, (WkState == 0 ? 1:0));
	digitalWrite(LedFwd, (WkState == 1 ? 1:0));
	digitalWrite(LedRew, (WkState == 2 ? 1:0));
}

//от 25 до 75гц с шагом 0.25 и 511 (центр пегулятора) = 50гц
int GetDelayByVal(int val)
{
	if (val < 5) return 10000;
	if (val < 10) return 9900;
	if (val < 15) return 9803;
	if (val < 20) return 9708;
	if (val < 25) return 9615;
	if (val < 30) return 9523;
	if (val < 35) return 9433;
	if (val < 40) return 9345;
	if (val < 45) return 9259;
	if (val < 50) return 9174;
	if (val < 55) return 9090;
	if (val < 60) return 9009;
	if (val < 65) return 8928;
	if (val < 70) return 8849;
	if (val < 76) return 8771;
	if (val < 81) return 8695;
	if (val < 86) return 8620;
	if (val < 91) return 8547;
	if (val < 96) return 8474;
	if (val < 101) return 8403;
	if (val < 106) return 8333;
	if (val < 111) return 8264;
	if (val < 116) return 8196;
	if (val < 121) return 8130;
	if (val < 126) return 8064;
	if (val < 131) return 8000;
	if (val < 136) return 7936;
	if (val < 141) return 7874;
	if (val < 147) return 7812;
	if (val < 152) return 7751;
	if (val < 157) return 7692;
	if (val < 162) return 7633;
	if (val < 167) return 7575;
	if (val < 172) return 7518;
	if (val < 177) return 7462;
	if (val < 182) return 7407;
	if (val < 187) return 7352;
	if (val < 192) return 7299;
	if (val < 197) return 7246;
	if (val < 202) return 7194;
	if (val < 207) return 7142;
	if (val < 212) return 7092;
	if (val < 217) return 7042;
	if (val < 223) return 6993;
	if (val < 228) return 6944;
	if (val < 233) return 6896;
	if (val < 238) return 6849;
	if (val < 243) return 6802;
	if (val < 248) return 6756;
	if (val < 253) return 6711;
	if (val < 258) return 6666;
	if (val < 263) return 6622;
	if (val < 268) return 6578;
	if (val < 273) return 6535;
	if (val < 278) return 6493;
	if (val < 283) return 6451;
	if (val < 288) return 6410;
	if (val < 294) return 6369;
	if (val < 299) return 6329;
	if (val < 304) return 6289;
	if (val < 309) return 6250;
	if (val < 314) return 6211;
	if (val < 319) return 6172;
	if (val < 324) return 6134;
	if (val < 329) return 6097;
	if (val < 334) return 6060;
	if (val < 339) return 6024;
	if (val < 344) return 5988;
	if (val < 349) return 5952;
	if (val < 354) return 5917;
	if (val < 359) return 5882;
	if (val < 364) return 5847;
	if (val < 370) return 5813;
	if (val < 375) return 5780;
	if (val < 380) return 5747;
	if (val < 385) return 5714;
	if (val < 390) return 5681;
	if (val < 395) return 5649;
	if (val < 400) return 5617;
	if (val < 405) return 5586;
	if (val < 410) return 5555;
	if (val < 415) return 5524;
	if (val < 420) return 5494;
	if (val < 425) return 5464;
	if (val < 430) return 5434;
	if (val < 435) return 5405;
	if (val < 441) return 5376;
	if (val < 446) return 5347;
	if (val < 451) return 5319;
	if (val < 456) return 5291;
	if (val < 461) return 5263;
	if (val < 466) return 5235;
	if (val < 471) return 5208;
	if (val < 476) return 5181;
	if (val < 481) return 5154;
	if (val < 486) return 5128;
	if (val < 491) return 5102;
	if (val < 496) return 5076;
	if (val < 501) return 5050;
	if (val < 506) return 5025;
	if (val < 512) return 5000;
	if (val < 517) return 4975;
	if (val < 522) return 4950;
	if (val < 527) return 4926;
	if (val < 532) return 4901;
	if (val < 537) return 4878;
	if (val < 542) return 4854;
	if (val < 547) return 4830;
	if (val < 552) return 4807;
	if (val < 558) return 4784;
	if (val < 563) return 4761;
	if (val < 568) return 4739;
	if (val < 573) return 4716;
	if (val < 578) return 4694;
	if (val < 583) return 4672;
	if (val < 588) return 4651;
	if (val < 593) return 4629;
	if (val < 599) return 4608;
	if (val < 604) return 4587;
	if (val < 609) return 4566;
	if (val < 614) return 4545;
	if (val < 619) return 4524;
	if (val < 624) return 4504;
	if (val < 629) return 4484;
	if (val < 634) return 4464;
	if (val < 640) return 4444;
	if (val < 645) return 4424;
	if (val < 650) return 4405;
	if (val < 655) return 4385;
	if (val < 660) return 4366;
	if (val < 665) return 4347;
	if (val < 670) return 4329;
	if (val < 675) return 4310;
	if (val < 680) return 4291;
	if (val < 686) return 4273;
	if (val < 691) return 4255;
	if (val < 696) return 4237;
	if (val < 701) return 4219;
	if (val < 706) return 4201;
	if (val < 711) return 4184;
	if (val < 716) return 4166;
	if (val < 721) return 4149;
	if (val < 727) return 4132;
	if (val < 732) return 4115;
	if (val < 737) return 4098;
	if (val < 742) return 4081;
	if (val < 747) return 4065;
	if (val < 752) return 4048;
	if (val < 757) return 4032;
	if (val < 762) return 4016;
	if (val < 768) return 4000;
	if (val < 773) return 3984;
	if (val < 778) return 3968;
	if (val < 783) return 3952;
	if (val < 788) return 3937;
	if (val < 793) return 3921;
	if (val < 798) return 3906;
	if (val < 803) return 3891;
	if (val < 808) return 3875;
	if (val < 814) return 3861;
	if (val < 819) return 3846;
	if (val < 824) return 3831;
	if (val < 829) return 3816;
	if (val < 834) return 3802;
	if (val < 839) return 3787;
	if (val < 844) return 3773;
	if (val < 849) return 3759;
	if (val < 855) return 3745;
	if (val < 860) return 3731;
	if (val < 865) return 3717;
	if (val < 870) return 3703;
	if (val < 875) return 3690;
	if (val < 880) return 3676;
	if (val < 885) return 3663;
	if (val < 890) return 3649;
	if (val < 896) return 3636;
	if (val < 901) return 3623;
	if (val < 906) return 3610;
	if (val < 911) return 3597;
	if (val < 916) return 3584;
	if (val < 921) return 3571;
	if (val < 926) return 3558;
	if (val < 931) return 3546;
	if (val < 936) return 3533;
	if (val < 942) return 3521;
	if (val < 947) return 3508;
	if (val < 952) return 3496;
	if (val < 957) return 3484;
	if (val < 962) return 3472;
	if (val < 967) return 3460;
	if (val < 972) return 3448;
	if (val < 977) return 3436;
	if (val < 983) return 3424;
	if (val < 988) return 3412;
	if (val < 993) return 3401;
	if (val < 998) return 3389;
	if (val < 1003) return 3378;
	if (val < 1008) return 3367;
	if (val < 1013) return 3355;
	if (val < 1018) return 3344;
	if (val < 1024) return 3333;
}

Схема:
Скалярный частотник для однофазного асинхронного двигателя - 3
В общем-то почти классик, но собранная из 5-и разных схем. Диоды по «высоким» плечам в общем-то при применении IGBT транзисторов и не обязательны, но я сначала сделал, а потом только подумал

В итоге: оно работает. Многое ещё стоит доделать, например выносной «пультик», тормоз. Может стоит поэкспериментировать с длительностью импульсов или сделать таки полноценный ШИМ имитирующий синус, а не постоянную скважность. Но пока это так. Может кому пригодится.
И в окончание, хотелось бы спросить: вместо балласта я поставил дроссель, «зажатый» диодами. На сколько я не прав в таком решении? Дроссель, я даже не знаю какой индуктивности. Взят из БП ATX, где он стоял в блоке компенсации реактивной мощности.

Ну а опыт… Опыт очень интересный. Я никогда б не подумал что это может быть столь сложно. И что 30В и 300В это огромная разница. Очень зауважал людей, которые такие вещи проектируют.
А это цена моих ошибок:
Скалярный частотник для однофазного асинхронного двигателя - 4

Видео всего процесса можно посмотреть тут: 1 2 3

Вопросы более знающим, ответы хотелось бы в комментариях:
1) Дроссель что стоит по цепи +310. Стоит ли мне от него избавляться? Я поставил его в надежде что нарастание тока, в случае сквозного тока, будет медленней и драйвер успеет уйти в защиту по току.
2) У меня получаются импульсы одинаковой скважности. Критически ли это важно? Оставить, или всё ж таки делать нечто «синусозависимое» по скважности?

Автор: Skiffrusspb

Источник

Поделиться новостью

  1. Алексей:

    На часах время 2.10 ночи, я просмотрел все видео — было не оторваться. На физиономии улыбка до ушей — вот автор тоже любит это дело — пожечь мосфеты и поморщить мозг над импедансом. Ух!! Спасибо!! Вопрос с дросселем удалось победить?

* - обязательные к заполнению поля