代码
首先是PID的结构体参数
typedef struct{float target_val; //目标值float actual_val; //实际值float err; //定义偏差值float err_last; //定义上一个偏差值float Kp,Ki,Kd; //定义比例、积分、微分系数float integral; //定义积分值}_pid;
然后对这些参数进行初始化
void PID_param_init(){/* 初始化参数 */pid.target_val=0.0;pid.actual_val=0.0;pid.err=0.0;pid.err_last=0.0;pid.integral=0.0;pid.Kp = 0.3;//24pid.Ki = 0.2;pid.Kd = 0.0;#if defined(PID_ASSISTANT_EN)float pid_temp[3] = {pid.Kp, pid.Ki, pid.Kd};set_computer_value(SEND_P_I_D_CMD, CURVES_CH1, pid_temp, 3); // 给通道 1 发送 P I D 值#endif}
PID算法的具体实现
float PID_realize(float actual_val){/*计算目标值与实际值的误差*/pid.err = pid.target_val - actual_val;pid.integral += pid.err;/*PID算法实现*/pid.actual_val = pid.Kp * pid.err +pid.Ki * pid.integral +pid.Kd * (pid.err - pid.err_last);/*误差传递*/pid.err_last = pid.err;/*返回当前实际值*/return pid.actual_val;}
在速度控制函数中的调用
/*** @brief 电机位置式 PID 控制实现(定时调用)* @param 无* @retval 无*/void bldcm_pid_control(void){int32_t speed_actual = get_motor_speed(); // 电机旋转的当前速度if (bldcm_data.is_enable){float cont_val = 0; // 当前控制值cont_val = PID_realize(speed_actual);if (cont_val < 0){cont_val = -cont_val;bldcm_data.direction = MOTOR_REV;}else{bldcm_data.direction = MOTOR_FWD;}cont_val = (cont_val > PWM_PERIOD_COUNT) ? PWM_PERIOD_COUNT : cont_val; // 上限处理set_bldcm_speed(cont_val);#ifdef PID_ASSISTANT_ENset_computer_value(SEND_FACT_CMD, CURVES_CH1, &speed_actual, 1); // 给通道 1 发送实际值#elseprintf("实际值:%d, 目标值:%.0f,控制值: %.0f\n", speed_actual, get_pid_target(), cont_val);#endif}}
在定时回调函数中的调用
/*** @brief 定时器更新中断回调函数* @param htim:定时器句柄* @retval 无*/void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim == (&htimx_hall)){if (motor_drive.timeout++ > 100) // 有一次在产生更新中断前霍尔传感器没有捕获到值{printf("堵转超时\r\n");motor_drive.timeout = 0;LED1_ON; // 点亮LED1表示堵转超时停止/* 堵转超时停止 PWM 输出 */hall_disable(); // 禁用霍尔传感器接口stop_pwm_output(); // 停止 PWM 输出set_bldcm_disable();motor_drive.speed = 0;}}else if(htim == (&TIM_TimeBaseStructure)){bldcm_pid_control();}}
这个函数主要在定时器中断中调用,定时器配置为每 20ms 中断一次,PID 算法每 20ms 执行一次,这也就是算法的周期。
串口打印结果

在调大P之后很明显,收敛更快,近似于直线
