基于开源项目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>#definePI3.14159265359#definePI_21.57079632679#definePI_31.0471975512#define_SQRT31.73205080757#definevoltage_power_supply12.0floatnormalizeAngle(float angle){float a =fmod(angle,2* PI);return a >=0? a :(a +2* PI);}voidsetPwm(float Ua,float Ub,float Uc){
Serial.print(Ua);
Serial.print(",");
Serial.print(Ub);
Serial.print(",");
Serial.println(Uc);}voidsetTorque(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){case1:
Ta = T1 + T2 + T0 /2;
Tb = T2 + T0 /2;
Tc = T0 /2;break;case2:
Ta = T1 + T0 /2;
Tb = T1 + T2 + T0 /2;
Tc = T0 /2;break;case3:
Ta = T0 /2;
Tb = T1 + T2 + T0 /2;
Tc = T2 + T0 /2;break;case4:
Ta = T0 /2;
Tb = T1 + T0 /2;
Tc = T1 + T2 + T0 /2;break;case5:
Ta = T2 + T0 /2;
Tb = T0 /2;
Tc = T1 + T2 + T0 /2;break;case6:
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);}voidsetup(){
Serial.begin(115200);// Make sure to match the baud rate with Serial Monitor}voidloop(){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到1
dc_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>#defineVOFA_SERIAL// 使用vofa+串口调试器查看马鞍波波形constint poles =7;// 电机的极对数// PWM输出引脚定义// 定义LEDC通道、GPIO引脚和分辨率#defineLEDC_CHANNEL10#defineLEDC_CHANNEL21#defineLEDC_CHANNEL32#defineLEDC_GPIO15#defineLEDC_GPIO218#defineLEDC_GPIO319#defineLEDC_RESOLUTION10// 设置分辨率为10位#definePWM_FREQ15000// 设置PWM频率为10000Hz// const char pwmA = 5;// const char pwmB = 18;// const char pwmC = 19;constfloat 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#definePI_21.57079632679#definePI_31.0471975512#define_SQRT31.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的占空比
*/voidsetPwm(float Ua,float Ub,float Uc){// 计算占空比,并使用constrain()函数限制相电压的范围0到1
dc_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
* 电角度 = 机械角度 * 极对数
* 机械角度 = 电角度 / 极对数
*/voidsetPhaseVoltage(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);}voidsetTorque(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){case1:
Ta = T1 + T2 + T0 /2;
Tb = T2 + T0 /2;
Tc = T0 /2;break;case2:
Ta = T1 + T0 /2;
Tb = T1 + T2 + T0 /2;
Tc = T0 /2;break;case3:
Ta = T0 /2;
Tb = T1 + T2 + T0 /2;
Tc = T2 + T0 /2;break;case4:
Ta = T0 /2;
Tb = T1 + T0 /2;
Tc = T1 + T2 + T0 /2;break;case5:
Ta = T2 + T0 /2;
Tb = T0 /2;
Tc = T1 + T2 + T0 /2;break;case6:
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值,用于控制电机转速
*/floatvelocityOpenloop(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;}}voidsetup(){
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);}voidloop(){velocityOpenloop(6.0f);//数值越大,电机旋转的速度越快 。(Limited:0 到 2π 之间)#ifdefVOFA_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"#defineVOFA_SERIAL// 使用vofa+串口调试器查看马鞍波波形constint poles =7;// 电机的极对数// PWM输出引脚定义// 定义LEDC通道、GPIO引脚和分辨率#defineLEDC_CHANNEL10#defineLEDC_CHANNEL21#defineLEDC_CHANNEL32#defineLEDC_GPIO15#defineLEDC_GPIO218#defineLEDC_GPIO319#defineLEDC_RESOLUTION10// 设置分辨率为10位#definePWM_FREQ15000// 设置PWM频率为10000Hzconstfloat 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#definePI_21.57079632679#definePI_31.0471975512#define_SQRT31.73205080757
TaskHandle_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的占空比
*/voidsetPwm(float Ua,float Ub,float Uc){// 计算占空比,并使用constrain()函数限制相电压的范围0到1
dc_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
* 电角度 = 机械角度 * 极对数
* 机械角度 = 电角度 / 极对数
*/voidsetPhaseVoltage(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);}voidsetTorque(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){case1:
Ta = T1 + T2 + T0 /2;
Tb = T2 + T0 /2;
Tc = T0 /2;break;case2:
Ta = T1 + T0 /2;
Tb = T1 + T2 + T0 /2;
Tc = T0 /2;break;case3:
Ta = T0 /2;
Tb = T1 + T2 + T0 /2;
Tc = T2 + T0 /2;break;case4:
Ta = T0 /2;
Tb = T1 + T0 /2;
Tc = T1 + T2 + T0 /2;break;case5:
Ta = T2 + T0 /2;
Tb = T0 /2;
Tc = T1 + T2 + T0 /2;break;case6:
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值,用于控制电机转速
*/floatvelocityOpenloop(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;}voidCore0task(void*args){while(1){// 多线程中必须使用一个死循环#ifdefVOFA_SERIALrtc_wdt_feed();//喂狗函数
Serial.printf("%f,%f,%f\n", dc_a, dc_b, dc_c);vTaskDelay(1);//1MS// delayMicroseconds(150);//以微秒为单位时间// yield();#endif}}voidCore1task(void*args){while(1){// 多线程中必须使用一个死循环rtc_wdt_feed();//喂狗函数velocityOpenloop(6.0f);//数值越大,电机旋转的速度越快 。(Limited:0 到 2π 之间)vTaskDelay(1);//1MS// delayMicroseconds(150);// yield();}}voidsetup(){
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);}voidloop(){}
🔬调参测试过程,记录分析参考
- 🌿3.2过度到6.0f波形变化
velocityOpenloop(6.0f);//3.2过度到6.0ffloat 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;
- 🍭SVPWM驱动模式下,给定速度
velocityOpenloop(75.0f);
赋值的大小需要和后面的延时vTaskDelay(1);
进行适配,个人电机测试时,不开调试输出,值运行电机函数的情况下,给定速度75的情况下,后面延时1ms,电机也是不会抖动,运转正常的。当给定速度120时,延时1ms,电机就会抖动运转不起来,将延时调整到2ms,电机可以恢复运转。
velocityOpenloop(75.0f);//数值越大,电机旋转的速度越快 。vTaskDelay(1);//1MS
📙基于SimpleFOC库速度开环测试
- 🌿
SimpleFOC
:https://github.com/simplefoc/Arduino-FOC
- 📍SimpleFOC调试参考:《SimpleFOC之ESP32(二)—— 开环控制》
✨首次测试时,需要注意,
motor.voltage_limit
的值不要设置过大,最好接可调电源,将电流限制在一定范围内。无刷电机控制要尽快操作,不要停留时间太久,时刻注意电机和驱动器的发热情况,一旦过热立即断电。
📢voltage_limit调参须知
开环控制尽快操作,注意电机发热情况,发热严重要立即断电 .
- 电机抖动转不起来把voltage_limit设置的大一点,
- 电机发热严重的把voltage_limit设置的小一点,
- voltage_limit设置越大,电机达到的最大转速越大,但是voltage_limit值越大电机发热就越严重.
- 电机能转就表示一切正常,记下当前的voltage_limit值,闭环控制中用于设置voltage_sensor_align,
// Open loop motor control example#include<SimpleFOC.h>// BLDC motor & driver instance// BLDCMotor motor = BLDCMotor(pole pair number);
BLDCMotor motor =BLDCMotor(7);// BLDCDriver3PWM driver = BLDCDriver3PWM(pwmA, pwmB, pwmC, Enable(optional));
BLDCDriver3PWM driver =BLDCDriver3PWM(18,5,19,23);// Stepper motor & driver instance//StepperMotor motor = StepperMotor(50);//StepperDriver4PWM driver = StepperDriver4PWM(9, 5, 10, 6, 8);//target variablefloat target_velocity =0;// instantiate the commander
Commander command =Commander(Serial);voiddoTarget(char* cmd){ command.scalar(&target_velocity, cmd);}voiddoLimit(char* cmd){ command.scalar(&motor.voltage_limit, cmd);}voidsetup(){// driver config// power supply voltage [V]
driver.voltage_power_supply =12;// limit the maximal dc voltage the driver can set// as a protection measure for the low-resistance motors// this value is fixed on startup
driver.voltage_limit =5;
driver.init();// link the motor and the driver
motor.linkDriver(&driver);// limiting motor movements// limit the voltage to be set to the motor// start very low for high resistance motors// current = voltage / resistance, so try to be well under 1Amp
motor.voltage_limit =3.2;// [V]// open loop control config
motor.controller = MotionControlType::velocity_openloop;// init motor hardware
motor.init();// add target command T
command.add('T', doTarget,"target velocity");
command.add('L', doLimit,"voltage limit");
Serial.begin(115200);
Serial.println("Motor ready!");
Serial.println("Set target velocity [rad/s]");_delay(1000);}voidloop(){// open loop velocity movement// using motor.voltage_limit and motor.velocity_limit// to turn the motor "backwards", just set a negative target_velocity
motor.move(target_velocity);// user communication
command.run();}
版权归原作者 perseverance52 所有, 如有侵权,请联系我们删除。