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

news2025/2/24 8:31:11

基于开源项目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.0

float 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 cycles
  float 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 voltage
    float angle_el = 0;  // Test value for angle

    // Test values across a full circle
    for (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>

#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到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);

}

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 cycles
  float 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_SERIAL

    printf("%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频率为10000Hz

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

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的占空比
*/
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);

}

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 cycles
  float 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_SERIAL
    rtc_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; 

在这里插入图片描述

  • 🌿修改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;

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1719965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

http协议及httpd安装组成

文章目录 一、http协议http协议通信过程http相关技术网站访问量HTTP工作机制HTTP协议版本HTTP请求访问的完整过程HTTP报文头部响应报文 二、httpd安装组成apache介绍和特点工作模式&#xff08; MPM multi-processing module &#xff09;Http相关文件Http编译安装httpd常见配置…

SqlServer还原系统库步骤及问题解决

还原master 需要切换到binn目录 Cd C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\Binn 关闭服务 用单用户模式启动 SQL Server 默认实例 sqlservr.exe -m 直接单用户登录 恢复master备份文件 RESTORE DATABASE master FROM DISK E:\dbbak\txic_ke…

【SpringMVC】_设置响应状态码与Header

目录 1. 设置响应状态码 2. 设置响应Header 2.1 设置Content-Type 2.1.1 不使用RequestMapping的produce属性 2.1.2 使用RequestMapping的produce属性 2.2 设置/新增其他Header 1. 设置响应状态码 Spring是基于servlet实现的&#xff0c;设置HTTP响应的状态码可以通过se…

精武杯复现(服务器部分)

起镜像连ssh 这里是raid5重组&#xff0c;123组成一个数据盘&#xff0c;4是系统盘&#xff0c;仿真的时候记得全选 第一步就是先配网&#xff0c;在etc/sysconfig/network-script里边&#xff0c;cat ifcfg-ens33 发现是dhcp&#xff0c;并且没有启动 直接ifup ifcfg-ens3…

蓝桥杯单片机第五届国赛题目

前言&#xff1a;针对串口的练手&#xff0c;此处只作代码记录&#xff0c;不进行分析和展示 目录 题目代码底层驱动主程序核心代码 题目 代码 注&#xff1a;EEPROM的五组后丢弃用一个记录次数的变量进行循环即可&#xff0c;我没有写这一部分代码。 底层驱动 IIC unsign…

低代码开发与人工智能技术在商品推荐系统中的应用

引言 低代码开发和人工智能技术的背景和重要性 随着数字化转型的深入&#xff0c;企业在信息技术领域面临着前所未有的挑战和机遇。快速变化的市场需求、日益复杂的技术环境以及高度竞争的商业环境&#xff0c;迫使企业不断寻求高效的开发和运营解决方案。低代码开发平台应运而…

【新能源大巴BMS结构与乘用车的区别】

新能源大巴BMS结构与乘用车的区别 这篇文章主要介绍新能源大巴的电池和BMS的结构与乘用车的区别。 主要有&#xff0c;新能源大巴行业、新能源电池系统结构和新能源大巴的BMS系统。 第一部分 新能源大巴行业 其实数数全球的商用车(大巴卡车)&#xff0c;大致的方向还是沿着就…

动态路由协议实验——RIP

动态路由协议实验——RIP 什么是RIP ​ RIP(Routing Information Protocol,路由信息协议&#xff09;是一种内部网关协议&#xff08;IGP&#xff09;&#xff0c;是一种动态路由选择协议&#xff0c;用于自治系统&#xff08;AS&#xff09;内的路由信息的传递。RIP协议基于…

瑞吉外卖项目学习笔记(二)后台系统的员工管理业务开发

一、完善登录功能 1.1 问题分析 1.2 代码实现 package com.itheima.reggie.filter;//这是一个过滤器类 //登录检查过滤器import com.alibaba.fastjson.JSON; import com.itheima.reggie.common.R; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf…

LAMMPS - 分子动力学模拟器

本文翻译自&#xff1a;https://www.lammps.org/ 文章目录 一、关于 LAMMPS下载作者R&D 100 二、LAMMPS 亮点毛细血管中的血流 一、关于 LAMMPS 官网&#xff1a; https://www.lammps.org/ github &#xff1a;https://github.com/lammps/lammps LAMMPS 分子动力学模拟器…

智能制造案例专题|与MongoDB一起解锁工业4.0转型与增长的无限潜力!

MongoDB 智能制造 数字化技术的洪流在各个产业链的主干和枝节涌现。在工业制造领域&#xff0c;能否通过数字化技术实现各生产要素、生产环节之间的紧密配合&#xff0c;高效规划、管理整个生产流程&#xff0c;是企业提升韧性、赢得竞争的关键。随着工业4.0的深入发展和智能…

易语言推箱子游戏(附带源码)

易语言推箱子游戏 易语言易语言的安装易语言功能特色易语言安装步骤易语言常见问题 导入游戏源码部分源码领取源码下期更新预报 易语言 易语言&#xff08;EPL&#xff09;是一门以中文作为程序代码编程语言&#xff0c;其以“易”著称&#xff0c;创始人为吴涛。易语言早期版…

记 Codes 开源免费研发管理平台 —— 日报与工时融合集中式填报的创新实现

继上一回合生成式全局看板的创新实现后&#xff0c;本篇我们来讲一讲日报与工时融合集中式填报的创新实现。 市面上所有的研发管理软件&#xff0c;大多都有工时相关功能&#xff0c;但是却没有日报功能&#xff0c;好像也没什么问题&#xff0c;但是在使用过程中体验非常不…

【设计模式】JAVA Design Patterns——Facade(外观模式)

&#x1f50d;目的 为一个子系统中的一系列接口提供一个统一的接口。外观定义了一个更高级别的接口以便子系统更容易使用。 &#x1f50d;解释 真实世界例子 一个金矿是怎么工作的&#xff1f;“嗯&#xff0c;矿工下去然后挖金子&#xff01;”你说。这是你所相信的因为你在使…

先进的无人机GPS/GNSS模块解决方案

由于多星座支持和增强的信号接收能力&#xff0c;先进的GNSS模块提供了更高的精度和可靠性。集成传感器融合补偿信号中断&#xff0c;实现无缝导航。内置实时运动学(RTK)支持提供厘米级的定位精度。这些模块还优先考虑低功耗和紧凑的尺寸&#xff0c;确保更长的飞行时间和对无人…

第四范式Q1业务进展:驰而不息 用科技锻造不朽价值

5月28日&#xff0c;第四范式发布今年前三个月的核心业务进展&#xff0c;公司坚持科技创新&#xff0c;业务稳步拓展&#xff0c;用人工智能为千行万业贡献价值。 今年前三个月&#xff0c;公司总收入人民币8.3亿元&#xff0c;同比增长28.5%&#xff0c;毛利润人民币3.4亿元&…

Vue3项目练习详细步骤(第四部分:文章管理页面模块)

文章列表查询 页面主体结构 接口文档 接口调用 添加文章列表 添加组件 富文本编辑器 封面图片上传 接口文档 接口调用 编辑文章列表 结构调整 数据回显 接口文档 绑定请求数据 删除文章列表 接口文档 绑定请求数据 文章列表查询 页面主体结构 在ArticleManag…

太速科技-基于3U VPX 4核8线程I7 X86主板

基于3U VPX 4核8线程I7 X86主板 一、产品概述 该产品是一款基于第六代Intel i7四核八线程处理器的高性能3U VPX刀片式计算机。产品提供了4个x4 PCIe 3.0总线接口&#xff0c;其中2个x4 PCIe 3.0接口可配置为1个x8 PCIe3.0接口&#xff0c;另外2个x4 PCIe 3.0接口可灵活配置…

微信小程序教程DAY3

box标签 第二种方法 绿色第一种 第一种更好 效果一样 完成这个项目 先写循环

Git基本使用教程(学习记录)

参考文章链接&#xff1a; Git教程&#xff08;超详细&#xff0c;一文秒懂&#xff09; RUNOOB Git教程 Git学习记录 1Git概述 1.1版本控制软件功能 版本管理&#xff1a;更新或回退到历史上任何版本&#xff0c;数据备份共享代码&#xff1a;团队间共享代码&#xff0c;…