二:使用正点原子的直流无刷驱动板自写FOC控制永磁同步(PMSM)电机(位置闭环)

在上一篇博客中配置了UVW三相PWM的定时器,在此基础上增加配置ABZ编码器定时器

启用一个定时器中断,用于PID处理

 代码如下

// 常量定义
#define PI 3.14159265359f
#define PWM_RESOLUTION 5250.0f  // PWM分辨率
#define CIRCLE_RESOLUTION 1000  // 圆周分辨率
#define MOTOR_PP  4             // 电机极对数
#define MAX_MOTORS 2            // 最大电机数量
#define MAX_ANGULAR_VELOCITY 2.0f  // 定义最大电角速度

// 电机参数结构体
typedef struct {
    float voltage_power_supply;     // 电机电压
    float zero_electric_angle;      // 零电角位置
    float dc_a, dc_b, dc_c;         // 三相PWM占空比
    float Ualpha, Ubeta, Ua, Ub, Uc; // 坐标变换后的电压
    int32_t cur_pos;                // 当前编码器位置
    float rad_per_num;              // 每个脉冲对应的电角度变化
    uint8_t direction;              // 电机转向
    int32_t circle_count;           // 圆周计数

    // PI控制参数
    float prev_angle_error, integral_angle_error;  // 电角度PI控制误差
    float prev_angular_velocity_error, integral_angular_velocity_error;  // 电角速度PI控制误差
    float Kp_angular_velocity, Ki_angular_velocity; // 电角速度PI控制增益
    float outP_angular_velocity, outI_angular_velocity; // 电角速度PI控制输出

    // Uq控制PI参数
    float Kp_Uq, Ki_Uq;

    float max_Uq;               // Uq最大值

    // 目标角度和当前角度
    float set_angle, cur_angle, tar_angle;

    // PWM和编码器的定时器
    TIM_HandleTypeDef *pwm_timer;  // PWM输出定时器
    TIM_HandleTypeDef *enc_timer_ab; // 编码器A和B信号的定时器
    TIM_HandleTypeDef *enc_timer_z;  // 编码器Z信号的定时器
} Motor;

// 全局电机实例数组
Motor motors[MAX_MOTORS];

// 函数原型声明
void setPwm(Motor *motor, float Ua, float Ub, float Uc);
float getCurrentAngle(Motor *motor);
float wrapAngle(float angle, float period);
void moveToPosition(Motor *motor);
void initFocMotor(int motor_idx, TIM_HandleTypeDef *pwm_timer, TIM_HandleTypeDef *enc_timer_ab, TIM_HandleTypeDef *enc_timer_z);
void setPhaseVoltage(Motor *motor, float Uq, float Ud, float angle_el);

// 约束函数:限制输入值在[min, max]范围内
float _constrain(float x, float min, float max) {
    return (x < min) ? min : (x > max) ? max : x;
}

// 电角度归一化函数:将角度归一化到[0, 2PI]区间
float _normalizeAngle(float angle) {
    float a = fmod(angle, 2 * PI);  // 模运算
    return a >= 0 ? a : (a + 2 * PI);  // 如果角度为负,调整为正
}

// 设置PWM输出函数:将电压转换为PWM占空比并输出
void setPwm(Motor *motor, float Ua, float Ub, float Uc) {
    // 根据电压计算PWM占空比,并约束在[0, 1]范围内
    motor->dc_a = _constrain(Ua / motor->voltage_power_supply, 0.0f, 1.0f);
    motor->dc_b = _constrain(Ub / motor->voltage_power_supply, 0.0f, 1.0f);
    motor->dc_c = _constrain(Uc / motor->voltage_power_supply, 0.0f, 1.0f);

    // 将PWM占空比转换为实际的PWM值
    uint32_t pwm_a = (uint32_t)(motor->dc_a * PWM_RESOLUTION);
    uint32_t pwm_b = (uint32_t)(motor->dc_b * PWM_RESOLUTION);
    uint32_t pwm_c = (uint32_t)(motor->dc_c * PWM_RESOLUTION);

    // 使用电机的PWM定时器设置PWM值
    __HAL_TIM_SET_COMPARE(motor->pwm_timer, TIM_CHANNEL_1, pwm_a);
    __HAL_TIM_SET_COMPARE(motor->pwm_timer, TIM_CHANNEL_2, pwm_b);
    __HAL_TIM_SET_COMPARE(motor->pwm_timer, TIM_CHANNEL_3, pwm_c);
}

// 获取电机当前位置函数:根据编码器计数值计算电机位置
int32_t calculatePosition(Motor *motor) {
    int32_t count = __HAL_TIM_GET_COUNTER(motor->enc_timer_ab) / 4;
    int32_t position = count + (motor->circle_count * CIRCLE_RESOLUTION);
    return position;
}

// 获取当前电角度:根据编码器计数值计算电角度
float getCurrentAngle(Motor *motor) {
    // 获取编码器A信号的计数值并计算电角度
    int32_t tim_count = __HAL_TIM_GET_COUNTER(motor->enc_timer_ab) / 4;
    return (float)(motor->rad_per_num * tim_count);  // 每个计数对应的电角度
}

void moveToPosition(Motor *motor) {
    motor->cur_angle = getCurrentAngle(motor);  // 获取当前电角度
    float angle_error = motor->tar_angle - motor->cur_angle;  // 计算角度误差

    // 电角速度PI控制
    motor->outP_angular_velocity = motor->Kp_angular_velocity * angle_error;
    motor->outP_angular_velocity = _constrain(motor->outP_angular_velocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);  // 限制PI输出

    motor->outI_angular_velocity += motor->Ki_angular_velocity * angle_error;
    motor->outI_angular_velocity = _constrain(motor->outI_angular_velocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);  // 限制PI积分输出

    // 计算期望电角速度,并限制在[-MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY]范围内
    float desired_angular_velocity = motor->outP_angular_velocity + motor->outI_angular_velocity;
    desired_angular_velocity = _constrain(desired_angular_velocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);

    // 控制Uq(磁场方向电压)
    float Uq = motor->Kp_Uq * angle_error + motor->Ki_Uq * motor->integral_angle_error;
    Uq = _constrain(Uq, -motor->max_Uq, motor->max_Uq);  // 使用Uq最大值限制
    Uq = fabs(Uq);  // 保证Uq为正值

    motor->set_angle = motor->cur_angle + desired_angular_velocity;  // 设定目标电角度
    setPhaseVoltage(motor, Uq, 0, motor->set_angle);  // 设置相电压
}

// 设置相电压:根据Uq和电角度计算相电压并输出PWM信号
void setPhaseVoltage(Motor *motor, float Uq, float Ud, float angle_el) {
    angle_el = _normalizeAngle(angle_el + motor->zero_electric_angle);  // 归一化电角度

    // 逆Park变换
    motor->Ualpha = -Uq * sin(angle_el);
    motor->Ubeta = Uq * cos(angle_el);

    // 逆Clarke变换
    motor->Ua = motor->Ualpha + motor->voltage_power_supply / 2.0f;
    motor->Ub = (sqrt(3.0f) * motor->Ubeta - motor->Ualpha) / 2.0f + motor->voltage_power_supply / 2.0f;
    motor->Uc = (-motor->Ualpha - sqrt(3.0f) * motor->Ubeta) / 2.0f + motor->voltage_power_supply / 2.0f;

    // 设置PWM输出
    setPwm(motor, motor->Ua, motor->Ub, motor->Uc);
}

// 初始化FOC控制:启动PWM定时器和编码器定时器
// 通过传入电机索引和定时器参数来指定初始化哪个电机,并配置相关定时器
void initFocMotor(int motor_idx, TIM_HandleTypeDef *pwm_timer, TIM_HandleTypeDef *enc_timer_ab, TIM_HandleTypeDef *enc_timer_z) {
    // 检查电机索引是否有效
    if (motor_idx >= MAX_MOTORS) {
        // 如果索引超出范围,返回错误
        Error_Handler();
    }

    // 初始化电机参数
    motors[motor_idx].max_Uq = 8;                 // 设置Uq最大值,改变此值可以改变速度,理论最大值为母线电压的一半,即24/2 = 12,但是实际设为12电机会卡住,具体设为多少根据电机以及负载而定
    motors[motor_idx].voltage_power_supply = 24.0f;    // 设置电机电压
    motors[motor_idx].zero_electric_angle = 0.0f;       // 设置零电角度
    motors[motor_idx].rad_per_num = MOTOR_PP * 2 * PI / CIRCLE_RESOLUTION * -1; // 每个脉冲对应的电角度变化

    // 设置PI控制器参数
    motors[motor_idx].Kp_angular_velocity = 1.0f;
    motors[motor_idx].Ki_angular_velocity = 0.01f;
    motors[motor_idx].Kp_Uq = 1.0f;
    motors[motor_idx].Ki_Uq = 0.0f;

    // 初始化定时器
    motors[motor_idx].pwm_timer = pwm_timer;
    motors[motor_idx].enc_timer_ab = enc_timer_ab;
    motors[motor_idx].enc_timer_z = enc_timer_z;

    // 启动PWM输出(主输出和互补输出)
    if (HAL_TIM_PWM_Start(motors[motor_idx].pwm_timer, TIM_CHANNEL_1) != HAL_OK)
        Error_Handler();
    if (HAL_TIMEx_PWMN_Start(motors[motor_idx].pwm_timer, TIM_CHANNEL_1) != HAL_OK)
        Error_Handler();
    if (HAL_TIM_PWM_Start(motors[motor_idx].pwm_timer, TIM_CHANNEL_2) != HAL_OK)
        Error_Handler();
    if (HAL_TIMEx_PWMN_Start(motors[motor_idx].pwm_timer, TIM_CHANNEL_2) != HAL_OK)
        Error_Handler();
    if (HAL_TIM_PWM_Start(motors[motor_idx].pwm_timer, TIM_CHANNEL_3) != HAL_OK)
        Error_Handler();
    if (HAL_TIMEx_PWMN_Start(motors[motor_idx].pwm_timer, TIM_CHANNEL_3) != HAL_OK)
        Error_Handler();

    // 启动编码器定时器AB(ENCA和ENCB)
    if (HAL_TIM_Encoder_Start(motors[motor_idx].enc_timer_ab, TIM_CHANNEL_ALL) != HAL_OK)
        Error_Handler();

    // 启动编码器定时器Z(ENCZ)
    if (HAL_TIM_IC_Start(motors[motor_idx].enc_timer_z, TIM_CHANNEL_2) != HAL_OK)
        Error_Handler();
}

// 编码器中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    // 获取电机1的编码器信息
    if (htim->Instance == motors[0].enc_timer_z->Instance && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
        // 获取电机当前的方向
        motors[0].direction = (htim3.Instance->CR1 & TIM_CR1_DIR) ? 0 : 1;

        // 根据方向更新转向次数
        if (motors[0].direction) {
            motors[0].direction++;
        } else {
            motors[0].direction--;
        }

        // 这里可以选择是否需要清零计数器
        // __HAL_TIM_SET_COUNTER(&htim3, 0); // 重置ENCA和ENCB的计时器
    }
}

// 定时器溢出回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    // 处理电机控制定时器溢出
    if (htim->Instance == TIM6) {  // 判断是否是TIM6中断,周期为1ms
        moveToPosition(&motors[0]);  // 调用电机控制函数
    }
}

int main(void)
{
    // 初始化电机0的FOC控制,传入对应的定时器
    initFocMotor(0, &htim1, &htim3, &htim9);

    // 强制将电角度保持在0
    setPhaseVoltage(&motors[0], 1.0f, 0.0f, 0.0f);
    osDelay(100);

    // 重置编码器计数器
    __HAL_TIM_SET_COUNTER(motors[0].enc_timer_ab, 0);

    // 启动FOC控制的定时器
    HAL_TIM_Base_Start_IT(&htim6);

    motors[0].tar_angle = -2 * PI;//设置电机到-2pi的电角度位置,如果目标电角度值在0到-8pi之间,处于位置模式。小于-8pi(一个机械周期,电机极对数为4,电角度 = 4 * 2pi)电机就会一直旋转。
    while(1)
    {
        
    }
}
View Code

按一下复位键移动-2PI电角度,效果如下

 电角度小于-8PI,就会旋转

 

posted @ 2024-12-18 14:49  阿坦  阅读(41)  评论(0编辑  收藏  举报