Skocz do zawartości

Kontroler silników bezszczotkowych (BLDC) z wykorzystaniem STM32G431 i DRV8316.


Pomocna odpowiedź

Znając twoją dbałość o szczegóły wiem że będzie to dopracowany projekt. Czy jest opcja udostępnienia kodu źródłowego? 

  • Lubię! 1
  • 2 tygodnie później...

Update z projektu:

1. Oprogramowanie - wstępna wersja na prototyp napisana i działa. Od ostatniego wpisu zoptymalizowałem funkcję liczącą FOC, oraz dodałem modulację SVPWM (poprzez dodanie 3ciej harmonicznej). Jest też funkcja automatycznej kalibracji. Wciąż brakuje kilku funkcji jak obsługa EEPROM, protokół CAN BUS, obsługa błędów. Ale to już będę dodawał na gotowym układzie.

2. Schemat - gotowy (jak ktoś chce przejrzeć to zrzuty poniżej - tylko zastrzegam że to wstępna wersja). Teraz czas na płytkę PCB. Pewnie w trakcie wyjdą jeszcze jakieś błedy i będę wprowadzał poprawki do schematu na bieżąco.

3. Montaż urządzenia - mam zamiar zamówić kilka płytek z usługą montażu z JLCPCB. Mam nadzieję że obejdzie się bez wpadek 🙂

 

schematic_00.thumb.png.5746e2ef191b24ecc2acca03837cbd11.pngschematic_01.thumb.png.992a68032e9217fe959e0aafb3e88940.pngschematic_02.thumb.png.90839fb3a0a3a5632b8497114a997e37.pngschematic_03.thumb.png.b49acf4b02d73cc42f4c4aae13fe1809.pngschematic_04.thumb.png.563dfbb6ee7eeb5d1affdab00775a327.png

 

Pozdrawiam,
Marek

 

 

  • Lubię! 2

Hej!

Ostatecznie zrezygnowałem z opcji enkodera na oddzielnej płytce. Sensor będzie dolutowany na dolnej warstwie. Uznałem że dla moich potrzeb takie rozwiązanie bardziej się sprawdzi.

 

Wizualizacja sterownika:

Na górze: wtyczka baterii, złącze CAN bus, dioda LED, przycisk CTRL, przycisk RESET.

Z boku: wtyczka do programatora

Na płytce: 3 pady lutownicze dla faz silnika. 2 pady dla czujnika temperatury uzwojeń silnika.

obraz.thumb.png.d194323c16bf4b525730c4a48f5f7d9c.png

Przycisk CTRL po dłuższym przytrzymaniu przełączy kontroler w tryb konfiguracji. W tym trybie będzie można przez złacze CAN wywołać auto-kalibrację, nadać ID oraz zmienić różne parametry sterownika.

Pozdrawiam,
Marek

  • Lubię! 2
  • 4 tygodnie później...
(edytowany)

Hej!

Projekt ukończony. Zamówiłem łącznie 5 płytek z montażem. Wszystko przyszło jeszcze przed świętami. Zmontowałem ostatecznie dwa zestawy z silnikiem.

Oto lista funkcji/możliwości:

1. Sterowanie FOC z modulacją SVPWM lub SPWM do wyboru
2. Automatyczna kalibracja do silnika
3. Zabezpieczenia:

- Przeciw odwrotnej polaryzacji zasilania
- Bezpiecznik topikowy 8A na wejściu zasilania
- Przeciwzwarciowe na fazach silnika
- Nadprądowe
- Podnapięciowe
- Termiczne na uzwojeniu silnika (NTC)
- Termiczne na układzie drivera
- Możliwość konfiguracji ostrzeżenia o napięciu zasilania, prądzie zasilania, temperaturze uzwojeń silnika
- W wypadku wykrycia urzędzenie przechodzi w tryb ERROR. Kolejne komendy są ignorowane do czasu potwierdzenia błedu.
- Zabezpieczenia ESD na wszystkich złączach, przyciskach oraz interfejscie FDCAN

4. Sterowanie przez FDCAN w następujących profilach prędkości:

- 500kbps bez BRS
- 1000kbps bez BRS
- 500kbps/2000kbps z BRS
- 1000kbps/5000kbps z BRS

5. Tryby sterowania silnika:

- Wartość prądu Iq, Id
- Kontrola prędkości (dana prędkość oraz przyspieszenie)
- Kontrola pozycji (podana pozycja absolutna, prędkość, przyspieszenie)

6. Obsługiwane jednostki:

- Opcja 1: Napięcie: Volty, Natężenie: Ampery, Moc: Waty, Temperatura: C, Prędkość kątowa: RPM, Przyspieszenie: RPM/s, Pozycja: Obroty
- Opcja 2: Napięcie: Volty, Natężenie: Ampery, Moc: Waty, Temperatura: C, Prędkość kątowa: rad/s, Przyspieszenie: rad/s2, Pozycja: radiany
- Opcja 3: Napięcie: Volty, Natężenie: Ampery, Moc: Waty, Temperatura: C, Prędkość kątowa: deg/s Przyspieszenie: deg/s2, Pozycja: deg

7. Przyciski, sygnalizacja LED:

- Przycisk reset
- Przycisk ctrl
- 2 kolorowy LED

Przycisk CTRL po wciśnięciu 5 sekund przestawia urządzenie w tryb konfiguracyjny, wtedy można np zmienić nr id oraz grupę albo dokonać kalibracji silnika. W trybie tym krótkie przyciśnięcie zmienia profil prędkości FDCAN.

W normalnym trybie krótkie przyciśnięcie przycisku CTRL powoduje potwierdzenie (skasowanie) błedu lub ostrzeżenia.

LED w trybie konfiguracji: G G R - 500kbps, G G R R - 1000kbps, G G R R R - 500/2000kbps, G G R R R R - 1000/5000kbps
LED w trybie pracy: G ciągły - normalna praca, GRGR... - niepotwierdzone ostrzeżenie, R ciągły - niepotwierdzony błąd

8. Konfiguracja jest zapisywana i odczytywana do pamięci EEPROM

9. Każdy sterownik ma przyporządkowany numer ID oraz numer grupy. Moża wysyłać polecenia do konkretnego urządzenia, do grupy, lub do wszystkich

Poniżej kilka fotek gotowego urządzenia:
20241230_145034.thumb.jpg.8dfd7ed0681c3130ab16e348cde58b65.jpg20241230_145040.thumb.jpg.abfe709b4c75926afe5fbc8f18b027a2.jpg20241230_145833.thumb.jpg.9dbad272af54940b3dec58d021506654.jpg20250117_183458.thumb.jpg.68dba8e2182a6e0bdb87642d013a9896.jpg20250117_183516.thumb.jpg.2361ea02d0ae4775b334ad43b3badc76.jpg20250117_183525.thumb.jpg.f9586c07342597a9b471cf47bddcc8ab.jpg

W załączniku też specyfikacja protokołu komunikacji, dla zainteresowanych.

Do komunikacji z komputerem korzystam z konwertera USB-CanBus - MKS CANable V2.0.

Kolejnym krokiem będzie napisanie biblioteki w pythonie do obsługi sterowników.

Co bym poprawił w kolejnej rewizji płytki:
- Wyprowadził jedno albo dwa dodatkowe złącza GPIO w postaci punktów lutowniczych
- Połączył linie informujące o błędzie z enkodera do uC, aby móc wykrywać błąd enkodera magnetycznego przerwaniem
- Zamontował większą pamięć EEPROM tak aby móc zmieścić tam tablicę LUT do kompensacji cogging torque. Od strony programowej mam to opracowane, ale muszę teraz wymienić pamięć, więc na razie nie korzystam z tej opcji.

Pozdrawiam,
Marek

 

 

Communication Protocol.pdf

Edytowano przez MR1979
  • Lubię! 2

łał super projekt. Sam buduje taki sterownik od kilku lat. Tylko jak już mam gotową płytkę to brakuje oprogramowania a potem pojawia się inny projekt i ten trafia do szuflady. Po pewnym czasie przypominam sobie o chęci budowy sterownika BLDC, wyciągam go z szuflady i zaczynam znowu od płytki bo jakieś są nowe lepsze rozwiązania:)

Gratuluję poświęconego czasu i profesjonalizmu.

Czy gdzieś można zobaczyć kod programu, jak do działa od środka?

  • Lubię! 1

@wojttar2 Na razie nie będę zamieszczać całego kodu ale poniżej masz najważniejsze elementy:

config.h

(...)

// This configuration structure to be stored/loaded to/from EEPROM
typedef struct {
	uint32_t 		PWM_Resolution;
	int32_t 		Angle_Offset;
	uint8_t			Phase_Swap;
	uint8_t			Pole_No;

	PID_Config_f32	pid_q;
	PID_Config_f32	pid_d;
	PID_Config_f32	pid_spd;

	uint8_t			DeviceName[12];
	uint8_t			Units;

	uint8_t			FDCAN_Profile;
	uint8_t			FDCAN_GroupId;
	uint8_t			FDCAN_DeviceId;

	uint8_t 		ENC_Dir;

	uint8_t			DRV_SlewRate;
	uint8_t			DRV_OVP;
	uint8_t			DRV_OCP;
	uint8_t			DRV_CSA_Gain;

	float32_t		Vdc_warn;
	float32_t		Vdc_max;
	float32_t		Vdc_min;
	float32_t		Idc_warn;
	float32_t		Idc_max;
	float32_t		Temp_warn;
	float32_t		Temp_max;

#ifdef FOC_COGGING_TORQUE_COMPENSATION
	//float32_t		CT[1024];
#endif

	uint32_t		CfgCRC;
} Config_TypeDef;

extern Config_TypeDef	Config;

(...)

 

foc.h

(...)

typedef enum { FOC_MODE_IDLE, FOC_MODE_CURRENT, FOC_MODE_SPEED, FOC_MODE_POSITION, FOC_MODE_ERROR, FOC_MODE_CAL, FOC_MODE_CALCT } _FOC_Mode;
typedef enum { FOC_MOD_SPWM, FOC_MOD_SVPWM } _FOC_Modulation;

// Fill-up with sensor data for every loop
typedef struct {
	uint16_t 	ENC_Angle;	// Present Absolute Encoder angle [16 bit: 0 - 65535]
	float32_t 	I_a;		// Electric current on phase A [A]
	float32_t 	I_b;		// Electric current on phase B [A]
	float32_t 	I_c;		// Electric current on phase C [A]
	float32_t	V_dc;		// Power supply voltage [V]
	float32_t	V_ref;		// Reference voltage for current sense
} _Input_f32;

// Command structure
typedef struct {
	_FOC_Mode			Mode;
	_FOC_Modulation		Modulation;		// PWM Modulation
	float32_t			I_q;			// Current setting on Quadrature Axis [A]
	float32_t			I_d;			// Current setting on Direct Axis [A]
	float32_t			SPD;			// Speed setting in Speed mode [Rounds/Minute]
	float32_t			ACC;			// Acceleration (positive number in Rounds/Minute2)
	int64_t				POS;			// Required position in Position mode [Degrees]
} _Command_f32;

// Output data for inverter and ctrl
typedef struct {
	uint32_t	PWM_Duty_a;	// PWM duty setting for phase A
	uint32_t	PWM_Duty_b;	// PWM duty setting for phase B
	uint32_t	PWM_Duty_c;	// PWM duty setting for phase C
	float32_t	SPD;		// Present RPM - measured [Rounds/Minute]
	int64_t		POS;		// Present Position (360 deg = 65536 => example 1.5 turns = 98304)
	float32_t	I_dc;		// Total current [A]
	float32_t	P_dc;		// Total power [W]
	float32_t	Temp;		// Temperature
	uint16_t	Fault;		// Error code
	uint8_t		Warning;	// Warning code
} _Output_f32;

// Fill up this structure before calling FOC_Init()
typedef struct {
	float32_t 	Update_Freq;	// Frequency of executing of FOC main loop
	float32_t	Ctrl_Freq;		// Frequency of executing of FOC control loop
} _Init_f32;

// State of FOC ctrl - variables for calculations
typedef struct {
	uint16_t				ENC_Angle;		// Corrected encoder angle
	uint16_t				ENC_PrvAngle;	// Previous readout of encoder
	PID_Instance_f32		pid_q;			// PI instance for I_q regulation
	PID_Instance_f32		pid_d;			// PI instance for I_d regulation
	PID_Instance_f32		pid_spd;		// PI instance for I_q regulation in constant speed mode
	float32_t				I_q;			// Measured Quadrature Current
	float32_t				I_d;			// Measured Direct Current
	float32_t				ElAngle;		// Electric angle in degrees
	float32_t				Diff;			// Average difference between present and previous encoder readout
	float32_t				SPDset;			// Angular velocity setpoint
	float32_t				SPDConv;		// Angular velocity conversion factor
	float32_t				ACCConv;		// Acceleration conversion factor
	float32_t				POSConv;		// Position conversion factor
	uint32_t				MaxPWMsense;	// Variable used to determine if current measurement is valid
	uint32_t				Cal_progress;	// Calibration progress
	uint32_t				Cal_delay;		// Calibration delay variable
	uint32_t				Cal_var0;		// Calibration temporary variable #0
	uint32_t				Cal_var1;		// Calibration temporary variable #1
	uint32_t				Cal_var2;		// Calibration temporary variable #2
	float32_t				Cal_var3;		// Calibration temporary variable #3
	uint32_t				Cal_pwm;		// Calibration max pwm duty
	uint16_t				Cal_Poles[64];	// Recording pole positions during calibration
} _State_f32;

// Main Instance of FOC ctrl
typedef struct {
	_Init_f32				Init;				// Initialization parameters
	_Command_f32			Command;			// Controlling FOC
	_Input_f32				Input;				// Input data for calculations
	_Output_f32				Output;				// Output data for inverter and user
	_State_f32				State;				// Internal variables
} FOC_Ctx_TypeDef;

(...)

 

foc.c

(...)

uint8_t FOC_Init(FOC_Ctx_TypeDef * foc) {

	// Initialize input variables
	memset(&foc->Input, 0, sizeof(foc->Input));

	// Initialize output variables
	memset(&foc->Output, 0, sizeof(foc->Output));

	// Initialize state variables
	memset(&foc->State, 0, sizeof(foc->State));

	// Initialize PIs
	pid_init_f32(&foc->State.pid_q);
	pid_init_f32(&foc->State.pid_d);
	pid_init_f32(&foc->State.pid_spd);

	// Initialize default mode
	foc->Command.Mode = FOC_MODE_IDLE;

	// Updating units types
	FOC_ChangeUnits(foc);

	// Calculating MaxPWM assuming ADC measurement of current lies takes 2us
	// 2[us] * ((PWM_Res * 2 * UpdateFreq) / 1000000)
	foc->State.MaxPWMsense = Config.PWM_Resolution - (uint32_t)(2 * ((Config.PWM_Resolution * foc->Init.Update_Freq) / 1000000));


	return FOC_OK;
}

uint8_t FOC_Update(FOC_Ctx_TypeDef * foc) {
	float32_t sinval;			// Sin value for given electric angle
	float32_t cosval;			// Cos value for given electric angle
	float32_t I_alpha;			// Current alpha in time-invariant system
	float32_t I_beta;			// Current beta in time-invariant system
	float32_t I_dc = 0;			// Momentary total current;
	float32_t U_q = 0;			// Control signal for I_q
	float32_t U_d = 0;			// Control signal for I_d
	float32_t U_0;
	float32_t U_alpha;			// Control signal in time-invariant system
	float32_t U_beta;			// Control signal in time-invariant system
	float32_t U_a;				// Control signal for phase A
	float32_t U_b;				// Control signal for phase B
	float32_t U_c;				// Control signal for phase C
	float32_t e3;				// Third harmonic
	int16_t	diff;				// Difference between present and previous position
	int64_t poserror;			// Position error

	// 0. Input measurements validation
	uint8_t sense_flag = 0;
	sense_flag |= ((foc->Output.PWM_Duty_a > foc->State.MaxPWMsense) << 0);
	sense_flag |= ((foc->Output.PWM_Duty_b > foc->State.MaxPWMsense) << 1);
	sense_flag |= ((foc->Output.PWM_Duty_c > foc->State.MaxPWMsense) << 2);

	switch (sense_flag) {
	case 0:		break;	// All measurements valid
	case 1:		foc->Input.I_a = -(foc->Input.I_b + foc->Input.I_c);	break; // I_a invalid
	case 2:		foc->Input.I_b = -(foc->Input.I_a + foc->Input.I_c);	break; // I_b invalid
	case 4:		foc->Input.I_c = -(foc->Input.I_a + foc->Input.I_b);	break; // I_c invalid
	default:	return FOC_OK;	// Skip loop
	}

	// 1. Calculating electric angle
	foc->State.ENC_Angle = foc->Input.ENC_Angle + Config.Angle_Offset;
	foc->State.ElAngle = ENC_TO_DEG * ((float32_t)(foc->State.ENC_Angle * Config.Pole_No));

	// 2. Clarke forward transform
	if (Config.Phase_Swap == 0) arm_clarke_f32(foc->Input.I_a, foc->Input.I_b, &I_alpha, &I_beta);
		else arm_clarke_f32(foc->Input.I_a, foc->Input.I_c, &I_alpha, &I_beta);

	// 3. Park forward transform
	arm_sin_cos_f32(foc->State.ElAngle, &sinval, &cosval);
	arm_park_f32(I_alpha, I_beta, &foc->State.I_d, &foc->State.I_q, sinval, cosval);

	// 4. Calculating FOC control signals depend on mode selected

	switch (foc->Command.Mode) {

	case FOC_MODE_IDLE:
		U_q = pid_update_f32(&foc->State.pid_q, &Config.pid_q, (-foc->State.I_q));
		U_d = pid_update_f32(&foc->State.pid_d, &Config.pid_d, (-foc->State.I_d));
		break;

	case FOC_MODE_CURRENT:
		U_q = pid_update_f32(&foc->State.pid_q, &Config.pid_q,(foc->Command.I_q - foc->State.I_q));
		U_d = pid_update_f32(&foc->State.pid_d, &Config.pid_d, (foc->Command.I_d - foc->State.I_d));
		break;

	case FOC_MODE_SPEED:
		if (foc->Command.SPD > foc->State.SPDset) foc->State.SPDset += foc->Command.ACC;
		if (foc->Command.SPD < foc->State.SPDset) foc->State.SPDset -= foc->Command.ACC;
		U_q = pid_update_f32(&foc->State.pid_spd, &Config.pid_spd, (foc->State.SPDset - foc->Output.SPD));
		U_d = pid_update_f32(&foc->State.pid_d, &Config.pid_d, (foc->Command.I_d - foc->State.I_d));
		break;

	case FOC_MODE_POSITION:
		poserror = foc->Command.POS - foc->Output.POS;
		if (poserror > 0){
			foc->State.SPDset += foc->Command.ACC;
			if (foc->State.SPDset > (poserror * foc->Command.ACC)) foc->State.SPDset = poserror * foc->Command.ACC;
			if (foc->State.SPDset > foc->Command.SPD) foc->State.SPDset = foc->Command.SPD;
		}
		if (poserror < 0) {
			foc->State.SPDset -= foc->Command.ACC;
			if (foc->State.SPDset < (poserror * foc->Command.ACC)) foc->State.SPDset = poserror * foc->Command.ACC;
			if (foc->State.SPDset < -foc->Command.SPD) foc->State.SPDset = -foc->Command.SPD;
		}
		U_q = pid_update_f32(&foc->State.pid_spd, &Config.pid_spd, (foc->State.SPDset - foc->Output.SPD));
		U_d = pid_update_f32(&foc->State.pid_d, &Config.pid_d, (foc->Command.I_d - foc->State.I_d));
		break;

	case FOC_MODE_ERROR:
		foc->Output.PWM_Duty_a = 0;
		foc->Output.PWM_Duty_b = 0;
		foc->Output.PWM_Duty_c = 0;
		return FOC_ERROR;

	case FOC_MODE_CAL:
		_calibration(foc);
		return FOC_CAL;

	case FOC_MODE_CALCT:

#ifdef FOC_COGGING_TORQUE_COMPENSATION

		_calibration_ct(foc);
		if (foc->Command.RPM > foc->State.RPMset) foc->State.RPMset += foc->Command.ACC;
		if (foc->Command.RPM < foc->State.RPMset) foc->State.RPMset -= foc->Command.ACC;
		U_q = pid_update_f32(&foc->State.pid_spd, &Config.pid_spd, (foc->State.RPMset - foc->Output.RPM));
		U_d = pid_update_f32(&foc->State.pid_d, &Config.pid_d, (foc->Command.I_d - I_d));

#else
		foc->Command.Mode = FOC_MODE_IDLE;
#endif

		break;
	}

#ifdef FOC_COGGING_TORQUE_COMPENSATION

	// 5. Cogging Torque compensation
	float32_t CT_corr = 0;
	if (foc->State.RPMset != 0) CT_corr = Config.CT[foc->State.ENC_Angle >> CT_LUT_SHIFT];
	U_q += CT_corr;

#endif

	// 6. Inverse Park transform
	arm_inv_park_f32(U_d, U_q, &U_alpha, &U_beta, sinval, cosval);

	arm_sqrt_f32((U_alpha * U_alpha) + (U_beta * U_beta), &U_0);

	if (U_0 > 1.0) {
		U_alpha /= U_0;
		U_beta /= U_0;
	}

	// 7. Inverse Clarke transform and correcting phase order

	if (Config.Phase_Swap == 0) {
		arm_inv_clarke_f32(U_alpha, U_beta, &U_a, &U_b);
		U_c = -U_a - U_b;
	} else {
		arm_inv_clarke_f32(U_alpha, U_beta, &U_a, &U_c);
		U_b = -U_a - U_c;
	}


	// 8. Calculating 3rd harmonic for SVPWM modulation (sign depend on rotation direction)
	if (foc->Command.Modulation == FOC_MOD_SVPWM) {
		e3 = SQRT3_1_9 * arm_sin_f32((foc->State.ElAngle * DEG_TO_RAD) * 3);
		e3 = (U_q > 0) ? -e3 : e3;
		e3 += 1.0;

		U_a *= SQRT3_2_3;
		U_a += e3;
		U_b *= SQRT3_2_3;
		U_b += e3;
		U_c *= SQRT3_2_3;
		U_c += e3;

	} else {

		U_a += 1.0;
		U_b += 1.0;
		U_c += 1.0;

	}

	// 9. Calculating PWM duty
	foc->Output.PWM_Duty_a = (uint32_t)(U_a * Config.PWM_Resolution) >> 1;
	foc->Output.PWM_Duty_b = (uint32_t)(U_b * Config.PWM_Resolution) >> 1;
	foc->Output.PWM_Duty_c = (uint32_t)(U_c * Config.PWM_Resolution) >> 1;

	// 10. Speed and position calculation
	diff = foc->State.ENC_Angle - foc->State.ENC_PrvAngle;
	foc->State.ENC_PrvAngle = foc->State.ENC_Angle;
	foc->State.Diff = 0.999 * foc->State.Diff + 0.001 * diff;
	foc->Output.SPD = foc->State.Diff;// * foc->State.RPMConv;
	foc->Output.POS += diff;

	// 11. Calculating total current and power
	I_dc += (foc->Input.I_a > 0) ? foc->Input.I_a : 0;
	I_dc += (foc->Input.I_b > 0) ? foc->Input.I_b : 0;
	I_dc += (foc->Input.I_c > 0) ? foc->Input.I_c : 0;

	foc->Output.I_dc = 0.999 * foc->Output.I_dc + 0.001 * I_dc;
	foc->Output.P_dc = foc->Output.I_dc * foc->Input.V_dc;

	return FOC_OK;
}

(...)

Pozdrawiam,
Marek

  • Lubię! 2

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...