基于开源项目ESP32 SVPWM驱动无刷电机开环速度测试

2024-05-31 14:20

本文主要是介绍基于开源项目ESP32 SVPWM驱动无刷电机开环速度测试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基于开源项目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上,来保证任务执行的实时性。由于不同线程间的任务执行,任务的执行时间差异,需要及时给看门狗,进行喂狗操作,否则,每执行一段时间,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];/**  电角度 = 机械角度 * 极对数* @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);delayMicroseconds(150);//以微秒为单位时间yield();#endif
}}void Core1task(void *args) {while(1){ // 多线程中必须使用一个死循环rtc_wdt_feed();  //喂狗函数velocityOpenloop(6.0f);//数值越大,电机旋转的速度越快 。(Limited:0 到 2π 之间)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; 

在这里插入图片描述

  • 🌿修改deltaT3.4e-3f; 3.4e-4f波形变化。
velocityOpenloop(6.0f);
float deltaT = 3.4e-4f; 

在这里插入图片描述

  • 🌿修改形参6.0f12.0f变化:
 velocityOpenloop(12.0f);float deltaT = 3.4e-4f;

在这里插入图片描述

  • 🌿修改deltaT 3.4e-4f6.5e-4f变化:
 velocityOpenloop(12.0f);float deltaT = 6.5e-4f;

在这里插入图片描述

这篇关于基于开源项目ESP32 SVPWM驱动无刷电机开环速度测试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1018168

相关文章

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

配置springboot项目动静分离打包分离lib方式

《配置springboot项目动静分离打包分离lib方式》本文介绍了如何将SpringBoot工程中的静态资源和配置文件分离出来,以减少jar包大小,方便修改配置文件,通过在jar包同级目录创建co... 目录前言1、分离配置文件原理2、pom文件配置3、使用package命令打包4、总结前言默认情况下,

python实现简易SSL的项目实践

《python实现简易SSL的项目实践》本文主要介绍了python实现简易SSL的项目实践,包括CA.py、server.py和client.py三个模块,文中通过示例代码介绍的非常详细,对大家的学习... 目录运行环境运行前准备程序实现与流程说明运行截图代码CA.pyclient.pyserver.py参

IDEA运行spring项目时,控制台未出现的解决方案

《IDEA运行spring项目时,控制台未出现的解决方案》文章总结了在使用IDEA运行代码时,控制台未出现的问题和解决方案,问题可能是由于点击图标或重启IDEA后控制台仍未显示,解决方案提供了解决方法... 目录问题分析解决方案总结问题js使用IDEA,点击运行按钮,运行结束,但控制台未出现http://

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element