這里就不過多的講解什么原理,公式的變換了,感興趣的可以看燈哥開源,講解的非常好的。當然,更細致的講解,也可以看b站其他教學。
我這里主要講解我對于開環部分的理解,以及stm32代碼的實現邏輯。可以看作是講解燈哥的代碼流程,畢竟是一致的。
本次stm32使用hal庫開發。
一、為什么會有克拉克變換、帕克變換
一個電機,我們如果想要它能夠在損失最小的情況下運動,那么就是我們希望力永遠垂直魚當前指向的方向。(這里,我們把電機看作一個指針,電機轉動,其實就是指針轉動。想要指針損失最小轉動,自然力就是垂直與指針,否則就會有損耗,高中物理嘛)。那么問題來,我們知道需要力,我們該如何設定電機輸入的uwv電壓,來設定這個力呢?
我們可以將力分解,分解為一個固定坐標系下的兩個方向的力。一般是x,y軸的方向。為什么呢?當然是好算,很多的表達式都可以不要再去計算偏移角度了。當然,這里就出現了一個問題,這個力是不斷變化的,我們怎么分解?你這個力現在指向左側,一會又指向左下角了,怎么做分解呢?所有這個當前的角度,我們是必須知道的。有了角度才好分解。ok,這個角度,我們可以使用傳感器來獲取。這個變換也就是帕克逆變換!
好了,我們現在就當我們得到水平固定坐標系下的兩個分解,那么如何得到uwv呢?這個很簡單了,我們直接將u或者其他,無所謂哪一個當作x軸,這樣就不需要算它了。x軸分解得到多大,就是它的值。借用燈哥的圖,這里的la=lα,非常簡便,減少了計算。(所以其實不是一定要這樣的,你可以根據自己喜愛去偏轉,只是麻煩而已,而且計算會多一個正余弦計算)。好了,我們再將lβ分解。角度都知道,自然也就得到了lb,lc。這就是克拉克逆變換。
雖然說是逆變換,實際上就是輸入輸出地位的變換而已。自然的,我們反過來再順一遍,就知道我們輸入uwv,這個力是哪個方向的力了。
二、代碼
我的stm32是當時不知道什么時候收藏的一個stm32f407zgt6板子。
主要也是燈哥的代碼思路。
首先,我們轉動起來,需要去配置一個定時器來輸出三路pwm。
void PWM_gpio_init()
{__HAL_RCC_TIM1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; // TIM1_CH1~CH3GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 復用推挽GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // TIM1 對應的 AFHAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void PWM_init(uint16_t arr, uint16_t psc)
{PWM_gpio_init();TIM_OC_InitTypeDef sConfigOC = {0};TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};/* 基本定時器配置 */htim1.Instance = TIM1;htim1.Init.Prescaler = 0; // 不分頻htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;htim1.Init.Period = MAX_PWM-1; // PWM頻率 = 168MHz / (8399+1) = 20kHzhtim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim1.Init.RepetitionCounter = 0;htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;HAL_TIM_PWM_Init(&htim1);sConfigOC.OCMode = TIM_OCMODE_PWM1;sConfigOC.Pulse = 1400; // 初始占空比0sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
}
這樣我們就得到了三個pwm輸出。
剩下的foc開環部分用到的代碼,講解一下這個流程。其實就是void velocityOpenloop(float target_velocity)這個函數。核心作用是在開環條件下,根據目標轉速更新電機相角并輸出對應的 PWM 電壓。開環的情況,一般就是我們不算角度,那么角度怎么來呢?
????????1.我們可以通過定時器來,比如定時1ms,加上我們需要的速度,那么時間乘以角速度,就是角度了。當然了,這種是不準確的。
? ? ? ? 2.我們可以通過讀取當前獲取的時間,也就是記錄上一次進入函數的時間,和本次進入的時間,這兩者之差就是運行時間。當然,首先你要有這一個時間獲取的方法。我這里是hal庫,如果是標準庫,或者寄存器,或者其他開發板,就得自己想辦法了。
????????得到角度之后,我們就知道我們當前的所在的角度。然后輸入電壓值,電壓的大小代表著力矩的大小,同樣的,這個電壓不能太大。如果太大,可能會燒電機,或者其他問題。根據當前的所在角度,所給力,我們就可以由上邊的克拉克,帕克等變換,得到uwv,這時候在給pwm配置,就完成了一次運動。我們將velocityOpenloop放到main函數的while里就可以了。當然,也可以再開一個定時器,把這個放到定時器的回調函數里,這里的時間就是固定的了。可以參考定時器調用開環函數。
float _normalizeAngle(float angle){float a = fmod(angle, 2*PI); //取余運算可以用于歸一化,列出特殊值例子算便知return a >= 0 ? a : (a + 2*PI);
}void setPhaseVoltage(float Uq,float Ud, float angle_el) {angle_el = _normalizeAngle(angle_el + zero_electric_angle);// 帕克逆變換Ualpha = -Uq*sin(angle_el); Ubeta = Uq*cos(angle_el); // 克拉克逆變換Ua = Ualpha + voltage_power_supply/2;Ub = (sqrt(3)*Ubeta-Ualpha)/2 + voltage_power_supply/2;Uc = (-Ualpha-sqrt(3)*Ubeta)/2 + voltage_power_supply/2;setPwm(Ua,Ub,Uc);
}void setPwm(float Ua, float Ub, float Uc) {// 計算占空比// 限制占空比從0到1dc_a = _constrain(Ua / voltage_power_supply, 0.0f , 1.0f );dc_b = _constrain(Ub / voltage_power_supply, 0.0f , 1.0f );dc_c = _constrain(Uc / voltage_power_supply, 0.0f , 1.0f );//寫入PWM到PWM 0 1 2 通道__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,dc_a*MAX_PWM);__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,dc_b*MAX_PWM);__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,dc_c*MAX_PWM);
}
static inline uint32_t micros(void) {return DWT->CYCCNT / (SystemCoreClock / 1000000);
}
void velocityOpenloop(float target_velocity){unsigned long now_us = micros();float Ts = (now_us - open_loop_timestamp) * 1e-6f;if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;shaft_angle = _normalizeAngle(shaft_angle + target_velocity*Ts);// 最大只能設置為Uq = voltage_power_supply/2,否則ua,ub,uc會超出供電電壓限幅float Uq = voltage_power_supply/8;setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, 7));open_loop_timestamp = now_us; //用于計算下一個時間間隔
}
float _electricalAngle(float shaft_angle, int pole_pairs) {return (shaft_angle * pole_pairs);
}
void DWT_Init(void) {CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWTDWT->CYCCNT = 0; // 清零計數DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 啟動
}
講解的還是過于粗糙了,如果錯誤,歡迎指正。
智能旋鈕(一)---foc開環控制