基于開源項目ESP32 SVPWM驅動無刷電機開環速度測試
- ?本篇硬件電路和代碼來源于此開源項目:
https://github.com/MengYang-x/STM3F401-FOC/tree/main
- 📍硬件電路和項目介紹,立創開源廣場:
https://oshwhub.com/shadow27/tai-yang-neng-wu-ren-chuan
- 🥕相關篇《基于開源項目HAL STM32F4 +DSP庫跑SVPWM開環速度測試》
- 🔖代碼基于Arduino平臺。
- 🌼 ESP32 SVPWM開環測試效果:
- ?無刷電機運行正常運作過程中,代碼二的測試波形效果:
- 🍁驅動電路參考:
?如果是通過6路信號來驅動無刷電機的不支持。如果需要測試6路信號驅動的可以參考我上面的相關內容,有關STM32 通過高級定時器3路互補輸出來實現SVPWM驅動無刷電機。
?🔬模擬仿真測試
- 📍ESP32在線SVPWM模擬仿真測試地址:
https://wokwi.com/projects/396507548266030081
- 📝仿真代碼
#include <Arduino.h>
#include <math.h>#define PI 3.14159265359
#define PI_2 1.57079632679
#define PI_3 1.0471975512
#define _SQRT3 1.73205080757
#define voltage_power_supply 12.0float normalizeAngle(float angle) {float a = fmod(angle, 2 * PI);return a >= 0 ? a : (a + 2 * PI);
}void setPwm(float Ua, float Ub, float Uc) {Serial.print(Ua);Serial.print(",");Serial.print(Ub);Serial.print(",");Serial.println(Uc);
}void setTorque(float Uq, float angle_el) {if (Uq < 0)angle_el += PI;Uq = abs(Uq);angle_el = normalizeAngle(angle_el + PI_2);int sector = floor(angle_el / PI_3) + 1;// calculate the duty cyclesfloat T1 = _SQRT3 * sin(sector * PI_3 - angle_el) * Uq / voltage_power_supply;float T2 = _SQRT3 * sin(angle_el - (sector - 1.0) * PI_3) * Uq / voltage_power_supply;float T0 = 1 - T1 - T2;float Ta, Tb, Tc;switch (sector){case 1:Ta = T1 + T2 + T0 / 2;Tb = T2 + T0 / 2;Tc = T0 / 2;break;case 2:Ta = T1 + T0 / 2;Tb = T1 + T2 + T0 / 2;Tc = T0 / 2;break;case 3:Ta = T0 / 2;Tb = T1 + T2 + T0 / 2;Tc = T2 + T0 / 2;break;case 4:Ta = T0 / 2;Tb = T1 + T0 / 2;Tc = T1 + T2 + T0 / 2;break;case 5:Ta = T2 + T0 / 2;Tb = T0 / 2;Tc = T1 + T2 + T0 / 2;break;case 6:Ta = T1 + T2 + T0 / 2;Tb = T0 / 2;Tc = T1 + T0 / 2;break;default:Ta = 0;Tb = 0;Tc = 0;}float Ua = Ta * voltage_power_supply;float Ub = Tb * voltage_power_supply;float Uc = Tc * voltage_power_supply;setPwm(Ua, Ub, Uc);
}void setup() {Serial.begin(115200); // Make sure to match the baud rate with Serial Monitor
}void loop() {float Uq = 1/_SQRT3; // Test value for voltagefloat angle_el = 0; // Test value for angle// Test values across a full circlefor (int i = 0; i < 360; i++) {angle_el = i * PI / 180;setTorque(Uq, angle_el);delay(20); // Delay for visibility in plotter}
}
📙驅動測試代碼 一
- ?此代碼直接驅動無刷電機轉動沒有問題,但是開啟Vofa+波形就不正常了,打印函數太占用時間,驅動無刷電機對SVPWM要求實時連續性很高,因任務執行所消耗的時間,一個loop循環下來,運行時間大大超出了預期值。波形輸出的直接變成了正弦波,而不是馬鞍波,導致電機不能轉動,
- 🥕不開啟打印,一個loop循環下來。大概就是60us左右,也就是代碼中
velocityOpenloop(2.5f);
執行一遍的時間。- 🧨開啟打印,如果波特率設置比較低,打印3個浮點類型數據,消耗的時間會超過1ms。
- 🎉如果需要查看波形,串口通訊波特率盡可能的設置高一些,給定的預設的角度值大一些。
- 👉在驅動無刷電機前,調試前期,可以直接通過查看3路波形,即可預測驅動電機的實際效果。一定要是SVPWM波形(馬鞍波),才能正常轉起來。
/** 日期:2023.7.22* 開環速度控制代碼* 使用vofa+ 進行串口調試,波特率需要設置為57600* 電機參數 A2212/15T的極對數:7**/
#include <Arduino.h>
#include <math.h>const int poles = 7; // 電機的極對數// PWM輸出引腳定義
// 定義LEDC通道、GPIO引腳和分辨率
#define LEDC_CHANNEL1 0
#define LEDC_CHANNEL2 1
#define LEDC_CHANNEL3 2#define LEDC_GPIO1 5
#define LEDC_GPIO2 18
#define LEDC_GPIO3 19#define LEDC_RESOLUTION 10 // 設置分辨率為10位
#define PWM_FREQ 15000 // 設置PWM頻率為15000Hz// const char pwmA = 5;
// const char pwmB = 18;
// const char pwmC = 19;const float voltagePowerSupply = 12.0;
float open_loop_timestamp = 0;
float shaft_angle = 0; // 機械角度
float zero_electric_angle = 0;
float Ualpha, Ubeta = 0;
float Ua = 0, Ub = 0, Uc = 0;
float dc_a = 0, dc_b = 0, dc_c = 0;void setup()
{Serial.begin(57600);// PWM設置pinMode(LEDC_GPIO1, OUTPUT);pinMode(LEDC_GPIO2, OUTPUT);pinMode(LEDC_GPIO3, OUTPUT);ledcSetup(LEDC_CHANNEL1, PWM_FREQ, LEDC_RESOLUTION); // pwm通道(1-16), 頻率, 精度(0-14)ledcAttachPin(LEDC_GPIO1, 0); // 將GPIO引腳與LEDC通道關聯,這樣才能讓LEDC信號輸出到這個引腳ledcSetup(LEDC_CHANNEL2, PWM_FREQ, LEDC_RESOLUTION); // pwm通道, 頻率, 精度ledcAttachPin(LEDC_GPIO2, 1); // 將GPIO引腳與LEDC通道關聯ledcSetup(LEDC_CHANNEL3, PWM_FREQ, LEDC_RESOLUTION); // pwm通道, 頻率, 精度ledcAttachPin(LEDC_GPIO3, 2); // 將GPIO引腳與LEDC通道關聯Serial.println("完成PWM初始化設置");delay(3000);
}/** 電角度 = 機械角度 * 極對數* @brief 電角度計算函數* @param shaft_angle 機械角度* @param pole_pairs 電機的極對數
*/
float _electricalAngle(float shaft_angle, int pole_pairs)
{return (shaft_angle * pole_pairs);
}/**角度歸一化到[0, 2pi],把輸入的角度限制在[0, 2pi]* @brief 角度歸一化函數* @param angle 輸入的角度* @return 歸一化后的角度* 例如:_normalizeAngle(3.1415926) 返回 0
*/
float _normalizeAngle(float angle)
{float a = fmod(angle, 2 * PI); // 取余,結果可能為負值return a >= 0 ? a : (a + 2 * PI);
}/**設置PWM輸出* @brief 設置PWM輸出* @param Ua 電機A的占空比* @param Ub 電機B的占空比* @param Uc 電機C的占空比
*/
void setPwm(float Ua, float Ub, float Uc)
{// 計算占空比,并使用constrain()函數限制相電壓的范圍0到1dc_a = constrain(Ua / voltagePowerSupply, 0.0f, 1.0f);dc_b = constrain(Ub / voltagePowerSupply, 0.0f, 1.0f);dc_c = constrain(Uc / voltagePowerSupply, 0.0f, 1.0f);// 寫入PWM到PWM 0 1 2 通道ledcWrite(0, static_cast<uint32_t>(dc_a * 1023)); //使用10位分辨率計算占空比值ledcWrite(1, static_cast<uint32_t>(dc_b * 1023));ledcWrite(2, static_cast<uint32_t>(dc_c * 1023));}/*** @brief 設置相位電壓* @param Uq 電流值* @param Ud 電壓值* @param angle_el 電機的電角度,單位 rad* 電角度 = 機械角度 * 極對數* 機械角度 = 電角度 / 極對數
*/
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 + voltagePowerSupply / 2;Ub = (sqrt(3) * Ubeta - Ualpha) / 2 + voltagePowerSupply / 2;Uc = (-Ualpha - sqrt(3) * Ubeta) / 2 + voltagePowerSupply / 2;setPwm(Ua, Ub, Uc);
}/** 開環速度函數,Uq和電角度生成器* @brief 開環速度控制函數* @param target_velocity 目標速度,單位 rad/s* @return 返回Uq值,用于控制電機轉速*/
float velocityOpenloop(float target_velocity)
{// unsigned long now_us = micros(); // 獲取從開啟芯片以來的微秒數,它的精度是 4 微秒。 micros() 返回的是一個無符號長整型(unsigned long)的值static float deltaT = 6.5e-5f; // 給定一個固定的開環運行時間間隔// 計算當前每個Loop的運行時間間隔// float Ts = (now_us - open_loop_timestamp) * 1e-6f;// 由于 micros() 函數返回的時間戳會在大約 70 分鐘之后重新開始計數,在由70分鐘跳變到0時,TS會出現異常,因此需要進行修正。如果時間間隔小于等于零或大于 0.5 秒,則將其設置為一個較小的默認值,即 1e-3f// if (Ts <= 0 || Ts > 0.5f)// Ts = 6.5e-5f;// 通過乘以時間間隔和目標速度來計算需要轉動的機械角度,存儲在 shaft_angle 變量中。在此之前,還需要對軸角度進行歸一化,以確保其值在 0 到 2π 之間。// shaft_angle = _normalizeAngle(shaft_angle + target_velocity * Ts);shaft_angle = _normalizeAngle(shaft_angle + target_velocity * deltaT);// 以目標速度為 10 rad/s 為例,如果時間間隔是 1 秒,則在每個循環中需要增加 10 * 1 = 10 弧度的角度變化量,才能使電機轉動到目標速度。// 如果時間間隔是 0.1 秒,那么在每個循環中需要增加的角度變化量就是 10 * 0.1 = 1 弧度,才能實現相同的目標速度。因此,電機軸的轉動角度取決于目標速度和時間間隔的乘積。// 設置的voltage_power_supply的1/3作為Uq值,這個值會直接影響輸出力矩// 最大只能設置為Uq = voltage_power_supply/2,否則ua,ub,uc會超出供電電壓限幅float Uq = voltagePowerSupply / 24;setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, poles)); // 極對數可以設置為常量// open_loop_timestamp = now_us; // 用于計算下一個時間間隔return Uq;
}
/*** @brief 調試函數,用于輸出PWM占空比* @return 無*/
void debug()
{Serial.printf("%f,%f,%f\n", dc_a, dc_b, dc_c);
}void loop()
{velocityOpenloop(2.5f);// debug();
}
📙驅動測試代碼二
?代碼中換算采用的是上面仿真中的算法,在開啟
VOFA+
串口波形查看時,務必將波特率盡可能設置高一些,以減少打印信息執行的時間。
- 🌼波形效果:
- 📑說明:
-
🔖力矩大小影響因素:
setTorque(0.3f, _electricalAngle(shaft_angle, poles));//Uq影響振幅,力矩大小
-
🔖轉速影響因素:
velocityOpenloop(6.0f);//數值越大
和變量deltaT
/** 日期:2023.7.22* 開環速度控制代碼* 進行串口調試,波特率需要設置為576000* 電機參數 A2212/15T的極對數:7**/
#include <Arduino.h>
#include <math.h>#define VOFA_SERIAL // 使用vofa+串口調試器查看馬鞍波波形
const int poles = 7; // 電機的極對數// PWM輸出引腳定義
// 定義LEDC通道、GPIO引腳和分辨率
#define LEDC_CHANNEL1 0
#define LEDC_CHANNEL2 1
#define LEDC_CHANNEL3 2#define LEDC_GPIO1 5
#define LEDC_GPIO2 18
#define LEDC_GPIO3 19#define LEDC_RESOLUTION 10 // 設置分辨率為10位
#define PWM_FREQ 15000 // 設置PWM頻率為10000Hz// const char pwmA = 5;
// const char pwmB = 18;
// const char pwmC = 19;const float voltagePowerSupply = 12.0;
float open_loop_timestamp = 0;
float shaft_angle = 0; // 機械角度
float zero_electric_angle = 0;
float Ualpha, Ubeta = 0;
float Ua = 0, Ub = 0, Uc = 0;
float dc_a = 0, dc_b = 0, dc_c = 0;//#define PI 3.14159265359
#define PI_2 1.57079632679
#define PI_3 1.0471975512
#define _SQRT3 1.73205080757/** 電角度 = 機械角度 * 極對數* @brief 電角度計算函數* @param shaft_angle 機械角度* @param pole_pairs 電機的極對數
*/
float _electricalAngle(float shaft_angle, int pole_pairs)
{return (shaft_angle * pole_pairs);
}/**角度歸一化到[0, 2pi],把輸入的角度限制在[0, 2pi]* @brief 角度歸一化函數* @param angle 輸入的角度* @return 歸一化后的角度* 例如:_normalizeAngle(3.1415926) 返回 0
*/
float _normalizeAngle(float angle)
{float a = fmod(angle, 2 * PI); // 取余,結果可能為負值return a >= 0 ? a : (a + 2 * PI);
}/**設置PWM輸出* @brief 設置PWM輸出* @param Ua 電機A的占空比* @param Ub 電機B的占空比* @param Uc 電機C的占空比
*/
void setPwm(float Ua, float Ub, float Uc)
{// 計算占空比,并使用constrain()函數限制相電壓的范圍0到1dc_a = constrain(Ua / voltagePowerSupply, 0.0f, 1.0f);dc_b = constrain(Ub / voltagePowerSupply, 0.0f, 1.0f);dc_c = constrain(Uc / voltagePowerSupply, 0.0f, 1.0f);// 寫入PWM到PWM 0 1 2 通道ledcWrite(0, static_cast<uint32_t>(dc_a * 1023)); //使用10位分辨率計算占空比值ledcWrite(1, static_cast<uint32_t>(dc_b * 1023));ledcWrite(2, static_cast<uint32_t>(dc_c * 1023));}/*** @brief 設置相位電壓* @param Uq 電流值* @param Ud 電壓值* @param angle_el 電機的電角度,單位 rad* 電角度 = 機械角度 * 極對數* 機械角度 = 電角度 / 極對數
*/
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 + voltagePowerSupply / 2;Ub = (sqrt(3) * Ubeta - Ualpha) / 2 + voltagePowerSupply / 2;Uc = (-Ualpha - sqrt(3) * Ubeta) / 2 + voltagePowerSupply / 2;setPwm(Ua, Ub, Uc);}void setTorque(float Uq, float angle_el) {if (Uq < 0)angle_el += PI;Uq = abs(Uq);angle_el = _normalizeAngle(angle_el + PI_2);int sector = floor(angle_el / PI_3) + 1;// calculate the duty cyclesfloat T1 = _SQRT3 * sin(sector * PI_3 - angle_el) * Uq / voltagePowerSupply;float T2 = _SQRT3 * sin(angle_el - (sector - 1.0) * PI_3) * Uq / voltagePowerSupply;float T0 = 1 - T1 - T2;float Ta, Tb, Tc;switch (sector){case 1:Ta = T1 + T2 + T0 / 2;Tb = T2 + T0 / 2;Tc = T0 / 2;break;case 2:Ta = T1 + T0 / 2;Tb = T1 + T2 + T0 / 2;Tc = T0 / 2;break;case 3:Ta = T0 / 2;Tb = T1 + T2 + T0 / 2;Tc = T2 + T0 / 2;break;case 4:Ta = T0 / 2;Tb = T1 + T0 / 2;Tc = T1 + T2 + T0 / 2;break;case 5:Ta = T2 + T0 / 2;Tb = T0 / 2;Tc = T1 + T2 + T0 / 2;break;case 6:Ta = T1 + T2 + T0 / 2;Tb = T0 / 2;Tc = T1 + T0 / 2;break;default:Ta = 0;Tb = 0;Tc = 0;}float Ua = Ta * voltagePowerSupply;float Ub = Tb * voltagePowerSupply;float Uc = Tc * voltagePowerSupply;setPwm(Ua, Ub, Uc);}/** 開環速度函數,Uq和電角度生成器* @brief 開環速度控制函數* @param target_velocity 目標速度,單位 rad/s* @return 返回Uq值,用于控制電機轉速*/
float velocityOpenloop(float target_velocity)
{// unsigned long now_us = micros(); // 獲取從開啟芯片以來的微秒數,它的精度是 4 微秒。 micros() 返回的是一個無符號長整型(unsigned long)的值//影響T周期float deltaT = 4.2e-4f; // 給定一個固定的開環運行時間間隔6.5e-5f 8.4e-5f 8.4e-3f 1.7e-2f// 計算當前每個Loop的運行時間間隔
//unsigned long mid_value = now_us - open_loop_timestamp;
//Serial.println(mid_value);// 計算當前每個Loop的運行時間間隔// float deltaT = mid_value * 1e-6f;// 計算電機軸的機械角度// 計算電機軸的電角度// float Ts = (now_us - open_loop_timestamp) * 1e-6f;//float Ts = mid_value * 1e-6f;// 由于 micros() 函數返回的時間戳會在大約 70 分鐘之后重新開始計數,在由70分鐘跳變到0時,TS會出現異常,因此需要進行修正。如果時間間隔小于等于零或大于 0.5 秒,則將其設置為一個較小的默認值,即 1e-3f
// if (Ts <= 0 || Ts > 0.5f)
// Ts = 6.5e-5f;// 通過乘以時間間隔和目標速度來計算需要轉動的機械角度,存儲在 shaft_angle 變量中。在此之前,還需要對軸角度進行歸一化,以確保其值在 0 到 2π 之間。// shaft_angle = _normalizeAngle(shaft_angle + target_velocity * Ts);shaft_angle = _normalizeAngle(shaft_angle + target_velocity * deltaT);// 以目標速度為 10 rad/s 為例,如果時間間隔是 1 秒,則在每個循環中需要增加 10 * 1 = 10 弧度的角度變化量,才能使電機轉動到目標速度。// 如果時間間隔是 0.1 秒,那么在每個循環中需要增加的角度變化量就是 10 * 0.1 = 1 弧度,才能實現相同的目標速度。因此,電機軸的轉動角度取決于目標速度和時間間隔的乘積。// 設置的voltage_power_supply的1/3作為Uq值,這個值會直接影響輸出力矩// 最大只能設置為Uq = voltage_power_supply/2,否則ua,ub,uc會超出供電電壓限幅float Uq = voltagePowerSupply / 24;// setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, poles)); // 極對數可以設置為常量setTorque(0.3f, _electricalAngle(shaft_angle, poles));//Uq影響振幅,力矩大小// open_loop_timestamp = now_us; // 用于計算下一個時間間隔return Uq;
}}
void setup()
{Serial.begin(576000);// PWM設置pinMode(LEDC_GPIO1, OUTPUT);pinMode(LEDC_GPIO2, OUTPUT);pinMode(LEDC_GPIO3, OUTPUT);ledcSetup(LEDC_CHANNEL1, PWM_FREQ, LEDC_RESOLUTION); // pwm通道(1-16), 頻率, 精度(0-14)ledcAttachPin(LEDC_GPIO1, 0); // 將GPIO引腳與LEDC通道關聯,這樣才能讓LEDC信號輸出到這個引腳ledcSetup(LEDC_CHANNEL2, PWM_FREQ, LEDC_RESOLUTION); // pwm通道, 頻率, 精度ledcAttachPin(LEDC_GPIO2, 1); // 將GPIO引腳與LEDC通道關聯ledcSetup(LEDC_CHANNEL3, PWM_FREQ, LEDC_RESOLUTION); // pwm通道, 頻率, 精度ledcAttachPin(LEDC_GPIO3, 2); // 將GPIO引腳與LEDC通道關聯Serial.println("完成PWM初始化設置");delay(3000);
}void loop()
{velocityOpenloop(6.0f);//數值越大,電機旋轉的速度越快 。(Limited:0 到 2π 之間)#ifdef VOFA_SERIALprintf("%f,%f,%f\n", dc_a, dc_b, dc_c);#endif}
?解決上面程序中的痛點問題,引入雙核心多線程任務運行方案
?在Arduino平臺,esp32程序默認運行在核心1上的,引入雙核心多線程任務運行,將串口打印和SVPWM計算分別運行在核心0和核心1上,來保證任務執行的實時性。由于不同線程間的任務執行,任務的執行時間差異,需要及時給rtc看門狗,進行喂狗操作,否則,每執行一段時間,esp32就會產生看門狗復位的動作。
/** 日期:2024.5.31更新* 開環速度控制代碼* 進行串口調試,波特率需要設置為576000* 電機參數 2204-1400KV-12N14P 的極對數:7**/
#include <Arduino.h>
#include <math.h>
#include "soc/rtc_wdt.h" //設置看門狗用
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/ledc.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_err.h"
#include "esp_task_wdt.h"#define VOFA_SERIAL // 使用vofa+串口調試器查看馬鞍波波形
const int poles = 7; // 電機的極對數// PWM輸出引腳定義
// 定義LEDC通道、GPIO引腳和分辨率
#define LEDC_CHANNEL1 0
#define LEDC_CHANNEL2 1
#define LEDC_CHANNEL3 2#define LEDC_GPIO1 5
#define LEDC_GPIO2 18
#define LEDC_GPIO3 19#define LEDC_RESOLUTION 10 // 設置分辨率為10位
#define PWM_FREQ 15000 // 設置PWM頻率為10000Hzconst float voltagePowerSupply = 12.0;
float open_loop_timestamp = 0;
float shaft_angle = 0; // 機械角度
float zero_electric_angle = 0;
float Ualpha, Ubeta = 0;
float Ua = 0, Ub = 0, Uc = 0;
float dc_a = 0, dc_b = 0, dc_c = 0;//#define PI 3.14159265359
#define PI_2 1.57079632679
#define PI_3 1.0471975512
#define _SQRT3 1.73205080757TaskHandle_t th_p[2];// 任務句柄,對xTaskCreate的調用返回。可用作參數到vTaskDelete以刪除任務。/** 電角度 = 機械角度 * 極對數* @brief 電角度計算函數* @param shaft_angle 機械角度* @param pole_pairs 電機的極對數
*/
float _electricalAngle(float shaft_angle, int pole_pairs)
{return (shaft_angle * pole_pairs);
}/**角度歸一化到[0, 2pi],把輸入的角度限制在[0, 2pi]* @brief 角度歸一化函數* @param angle 輸入的角度* @return 歸一化后的角度* 例如:_normalizeAngle(3.1415926) 返回 0
*/
float _normalizeAngle(float angle)
{float a = fmod(angle, 2 * PI); // 取余,結果可能為負值return a >= 0 ? a : (a + 2 * PI);
}/**設置PWM輸出* @brief 設置PWM輸出* @param Ua 電機A的占空比* @param Ub 電機B的占空比* @param Uc 電機C的占空比
*/
void setPwm(float Ua, float Ub, float Uc)
{// 計算占空比,并使用constrain()函數限制相電壓的范圍0到1dc_a = constrain(Ua / voltagePowerSupply, 0.0f, 1.0f);dc_b = constrain(Ub / voltagePowerSupply, 0.0f, 1.0f);dc_c = constrain(Uc / voltagePowerSupply, 0.0f, 1.0f);// 寫入PWM到PWM 0 1 2 通道ledcWrite(0, static_cast<uint32_t>(dc_a * 1023)); //使用10位分辨率計算占空比值ledcWrite(1, static_cast<uint32_t>(dc_b * 1023));ledcWrite(2, static_cast<uint32_t>(dc_c * 1023));}/*** @brief 設置相位電壓* @param Uq 電流值* @param Ud 電壓值* @param angle_el 電機的電角度,單位 rad* 電角度 = 機械角度 * 極對數* 機械角度 = 電角度 / 極對數
*/
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 + voltagePowerSupply / 2;Ub = (sqrt(3) * Ubeta - Ualpha) / 2 + voltagePowerSupply / 2;Uc = (-Ualpha - sqrt(3) * Ubeta) / 2 + voltagePowerSupply / 2;setPwm(Ua, Ub, Uc);}void setTorque(float Uq, float angle_el) {if (Uq < 0)angle_el += PI;Uq = abs(Uq);angle_el = _normalizeAngle(angle_el + PI_2);int sector = floor(angle_el / PI_3) + 1;// calculate the duty cyclesfloat T1 = _SQRT3 * sin(sector * PI_3 - angle_el) * Uq / voltagePowerSupply;float T2 = _SQRT3 * sin(angle_el - (sector - 1.0) * PI_3) * Uq / voltagePowerSupply;float T0 = 1 - T1 - T2;float Ta, Tb, Tc;switch (sector){case 1:Ta = T1 + T2 + T0 / 2;Tb = T2 + T0 / 2;Tc = T0 / 2;break;case 2:Ta = T1 + T0 / 2;Tb = T1 + T2 + T0 / 2;Tc = T0 / 2;break;case 3:Ta = T0 / 2;Tb = T1 + T2 + T0 / 2;Tc = T2 + T0 / 2;break;case 4:Ta = T0 / 2;Tb = T1 + T0 / 2;Tc = T1 + T2 + T0 / 2;break;case 5:Ta = T2 + T0 / 2;Tb = T0 / 2;Tc = T1 + T2 + T0 / 2;break;case 6:Ta = T1 + T2 + T0 / 2;Tb = T0 / 2;Tc = T1 + T0 / 2;break;default:Ta = 0;Tb = 0;Tc = 0;}float Ua = Ta * voltagePowerSupply;float Ub = Tb * voltagePowerSupply;float Uc = Tc * voltagePowerSupply;setPwm(Ua, Ub, Uc);}/** 開環速度函數,Uq和電角度生成器* @brief 開環速度控制函數* @param target_velocity 目標速度,單位 rad/s* @return 返回Uq值,用于控制電機轉速*/
float velocityOpenloop(float target_velocity)
{// unsigned long now_us = micros(); // 獲取從開啟芯片以來的微秒數,它的精度是 4 微秒。 micros() 返回的是一個無符號長整型(unsigned long)的值//影響T周期float deltaT = 4.2e-4f; // 給定一個固定的開環運行時間間隔6.5e-5f 8.4e-5f 8.4e-3f 1.7e-2f// 計算當前每個Loop的運行時間間隔
//unsigned long mid_value = now_us - open_loop_timestamp;
//Serial.println(mid_value);// 計算當前每個Loop的運行時間間隔// float deltaT = mid_value * 1e-6f;// 計算電機軸的機械角度// 計算電機軸的電角度// float Ts = (now_us - open_loop_timestamp) * 1e-6f;//float Ts = mid_value * 1e-6f;// 由于 micros() 函數返回的時間戳會在大約 70 分鐘之后重新開始計數,在由70分鐘跳變到0時,TS會出現異常,因此需要進行修正。如果時間間隔小于等于零或大于 0.5 秒,則將其設置為一個較小的默認值,即 1e-3f
// if (Ts <= 0 || Ts > 0.5f)
// Ts = 6.5e-5f;// 通過乘以時間間隔和目標速度來計算需要轉動的機械角度,存儲在 shaft_angle 變量中。在此之前,還需要對軸角度進行歸一化,以確保其值在 0 到 2π 之間。// shaft_angle = _normalizeAngle(shaft_angle + target_velocity * Ts);shaft_angle = _normalizeAngle(shaft_angle + target_velocity * deltaT);// 以目標速度為 10 rad/s 為例,如果時間間隔是 1 秒,則在每個循環中需要增加 10 * 1 = 10 弧度的角度變化量,才能使電機轉動到目標速度。// 如果時間間隔是 0.1 秒,那么在每個循環中需要增加的角度變化量就是 10 * 0.1 = 1 弧度,才能實現相同的目標速度。因此,電機軸的轉動角度取決于目標速度和時間間隔的乘積。// 設置的voltage_power_supply的1/3作為Uq值,這個值會直接影響輸出力矩// 最大只能設置為Uq = voltage_power_supply/2,否則ua,ub,uc會超出供電電壓限幅float Uq = voltagePowerSupply / 24;// setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, poles)); // 極對數可以設置為常量setTorque(0.35f, _electricalAngle(shaft_angle, poles));//Uq影響振幅,力矩大小// open_loop_timestamp = now_us; // 用于計算下一個時間間隔return Uq;
}void Core0task(void *args) {while(1){ // 多線程中必須使用一個死循環#ifdef VOFA_SERIALrtc_wdt_feed(); //喂狗函數Serial.printf("%f,%f,%f\n", dc_a, dc_b, dc_c);vTaskDelay(1);//1MS
// delayMicroseconds(150);//以微秒為單位時間
// yield();#endif
}}void Core1task(void *args) {while(1){ // 多線程中必須使用一個死循環rtc_wdt_feed(); //喂狗函數velocityOpenloop(6.0f);//數值越大,電機旋轉的速度越快 。(Limited:0 到 2π 之間)vTaskDelay(1);//1MS// delayMicroseconds(150);// yield();}
}void setup()
{Serial.begin(576000);rtc_wdt_protect_off(); //看門狗寫保護關閉 關閉后可以喂狗//rtc_wdt_protect_on(); //看門狗寫保護打開 打開后不能喂狗//rtc_wdt_disable(); //禁用看門狗rtc_wdt_enable(); //啟用看門狗rtc_wdt_set_time(RTC_WDT_STAGE0,1000); //看門狗超時時間設置為1秒// PWM設置pinMode(LEDC_GPIO1, OUTPUT);pinMode(LEDC_GPIO2, OUTPUT);pinMode(LEDC_GPIO3, OUTPUT);ledcSetup(LEDC_CHANNEL1, PWM_FREQ, LEDC_RESOLUTION); // pwm通道(1-16), 頻率, 精度(0-14)ledcAttachPin(LEDC_GPIO1, 0); // 將GPIO引腳與LEDC通道關聯,這樣才能讓LEDC信號輸出到這個引腳ledcSetup(LEDC_CHANNEL2, PWM_FREQ, LEDC_RESOLUTION); // pwm通道, 頻率, 精度ledcAttachPin(LEDC_GPIO2, 1); // 將GPIO引腳與LEDC通道關聯ledcSetup(LEDC_CHANNEL3, PWM_FREQ, LEDC_RESOLUTION); // pwm通道, 頻率, 精度ledcAttachPin(LEDC_GPIO3, 2); // 將GPIO引腳與LEDC通道關聯// 創建兩個任務xTaskCreatePinnedToCore(Core0task, "Core0task", 4096, NULL, 3, &th_p[0], 0);xTaskCreatePinnedToCore(Core1task, "Core1task", 4096, NULL, 4, &th_p[1], 1);Serial.println("完成PWM初始化設置");delay(3000);
}void loop()
{
}
🔬調參測試過程,記錄分析參考
- 🌿3.2過度到6.0f波形變化
velocityOpenloop(6.0f);//3.2過度到6.0f
float deltaT = 3.4e-3f;
- 🌿修改
deltaT
:3.4e-3f;
到3.4e-4f
波形變化。
velocityOpenloop(6.0f);
float deltaT = 3.4e-4f;
- 🌿修改形參
6.0f
到12.0f
變化:
velocityOpenloop(12.0f);float deltaT = 3.4e-4f;
- 🌿修改
deltaT
,3.4e-4f
到6.5e-4f
變化:
velocityOpenloop(12.0f);float deltaT = 6.5e-4f;