目录
直流有刷电机位置环控制实现
硬件设计
直流电机位置环控制-位置式PID实现
编程要点
配置基本定时器6产生定时中断来执行PID运算
配置定时器1输出PWM控制电机
配置定时器3读取编码器的计数值
编写位置式PID算法
主体功能
直流电机位置环控制-增量式PID实现
编程要点
配置基本定时器6产生定时中断来执行PID运算
配置定时器1输出PWM控制电机
配置定时器3读取编码器的计数值
编写增量式PID算法
主体功能
直流有刷电机位置环控制实现
可以以开始为参考,记录正转多少圈或反转多少圈。如一圈脉冲为1920,刚开始目标值为0,输入目标值脉冲为1920后正转一圈,再输入目标值脉冲为1920*2后正转一圈,然后输入目标值脉冲为0即可反转两圈。
硬件设计
可选:L298N电机驱动板、野火MOS搭建的驱动板。
直流电机位置环控制-位置式PID实现
编程要点
配置基本定时器产生定时中断来执行PID运算
配置定时器输出PWM控制电机
配置定时器读取编码器的计数值
编写位置式PID算法
编写速度控制函数
增加上位机曲线观察相关代码
编写按键控制代码
配置基本定时器6产生定时中断来执行PID运算
TIM_HandleTypeDef TIM_TimeBaseStructure;
/**
* @brief 初始化基本定时器定时,默认50ms产生一次中断
* @param 无
* @retval 无
*/
void TIMx_Configuration(void)
{
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
__TIM6_CLK_ENABLE();
TIM_TimeBaseStructure.Instance = TIM6;
TIM_TimeBaseStructure.Init.Period = 50 * 50 - 1;
TIM_TimeBaseStructure.Init.Prescaler = 1680 - 1;
TIM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TIM_TimeBaseStructure);
// 开启定时器更新中断
HAL_TIM_Base_Start_IT(&TIM_TimeBaseStructure);
uint32_t temp = (__HAL_TIM_GET_AUTORELOAD(&TIM_TimeBaseStructure) + 1) / 50.0; // 计算周期,单位ms
set_computer_value(SEND_PERIOD_CMD, CURVES_CH1, &temp, 1); // 给通道 1 发送目标值
}
/**
* @brief 定时器更新事件回调函数
* @param 无
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&TIM_TimeBaseStructure))
{
motor_pid_control(); // 每50ms执行一次PID运算
}
}
配置定时器1输出PWM控制电机
TIM_HandleTypeDef DCM_TimeBaseStructure;
/**
* @brief 初始化控制通用定时器
* @param 无
* @retval 无
*/
void Motor_TIMx_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_OC_InitTypeDef TIM_OCInitStructure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__TIM1_CLK_ENABLE();
// PA8--PWM_TIM_CH1
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = PWM_TIM_GPIO_AF;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// PA9--PWM_TIM_CH2
GPIO_InitStruct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// TIM1 66.7us一次周期
DCM_TimeBaseStructure.Instance = TIM1;
DCM_TimeBaseStructure.Init.Period = 5600 - 1;
DCM_TimeBaseStructure.Init.Prescaler = 1 - 1;
DCM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
DCM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&DCM_TimeBaseStructure);
/*PWM模式配置*/
TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;
TIM_OCInitStructure.Pulse = 0;
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM_OCInitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
TIM_OCInitStructure.OCIdleState = TIM_OCIDLESTATE_SET;
TIM_OCInitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET;
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_1);
/*开始输出PWM*/
HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_1);
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_2);
/*开始输出PWM*/
HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_2);
}
/**
* @brief 设置TIM通道的占空比
* @param channel 通道 (1,2,3,4)
* @param compare 占空比
* @note 无
* @retval 无
*/
void TIM1_SetPWM_pulse(uint32_t channel, int compare)
{
switch (channel)
{
case TIM_CHANNEL_1:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_1, compare);
break;
case TIM_CHANNEL_2:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_2, compare);
break;
case TIM_CHANNEL_3:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_3, compare);
break;
case TIM_CHANNEL_4:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_4, compare);
break;
}
}
配置定时器3读取编码器的计数值
TIM_HandleTypeDef DCM_TimeBaseStructure;
/**
* @brief 初始化控制通用定时器
* @param 无
* @retval 无
*/
void Motor_TIMx_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_OC_InitTypeDef TIM_OCInitStructure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__TIM1_CLK_ENABLE();
// PA8--PWM_TIM_CH1
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = PWM_TIM_GPIO_AF;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// PA9--PWM_TIM_CH2
GPIO_InitStruct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// TIM1 66.7us一次周期
DCM_TimeBaseStructure.Instance = TIM1;
DCM_TimeBaseStructure.Init.Period = 5600 - 1;
DCM_TimeBaseStructure.Init.Prescaler = 1 - 1;
DCM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
DCM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&DCM_TimeBaseStructure);
/*PWM模式配置*/
TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;
TIM_OCInitStructure.Pulse = 0;
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM_OCInitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
TIM_OCInitStructure.OCIdleState = TIM_OCIDLESTATE_SET;
TIM_OCInitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET;
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_1);
/*开始输出PWM*/
HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_1);
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_2);
/*开始输出PWM*/
HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_2);
}
/**
* @brief 设置TIM通道的占空比
* @param channel 通道 (1,2,3,4)
* @param compare 占空比
* @note 无
* @retval 无
*/
void TIM1_SetPWM_pulse(uint32_t channel, int compare)
{
switch (channel)
{
case TIM_CHANNEL_1:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_1, compare);
break;
case TIM_CHANNEL_2:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_2, compare);
break;
case TIM_CHANNEL_3:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_3, compare);
break;
case TIM_CHANNEL_4:
__HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_4, compare);
break;
}
}
编写位置式PID算法
typedef struct
{
float target_val; // 目标值
float actual_val; // 实际值
float err; // 定义偏差值
float err_last; // 定义上一个偏差值
float Kp,Ki,Kd; // 定义比例、积分、微分系数
float integral; // 定义积分值
}_pid; // 位置式PID
_pid pid;
/**
* @brief PID参数初始化
* @note 无
* @retval 无
*/
void PID_param_init(void)
{
/* 初始化参数 */
pid.target_val = 100.0;
pid.actual_val = 0.0;
pid.err = 0.0;
pid.err_last = 0.0;
pid.integral = 0.0;
pid.Kp = 5.0;
pid.Ki = 2.0;
pid.Kd = 0.0;
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 值
}
/**
* @brief 设置目标值
* @param val 目标值
* @note 无
* @retval 无
*/
void set_pid_target(float temp_val)
{
pid.target_val = temp_val; // 设置当前的目标值
}
/**
* @brief 获取目标值
* @param 无
* @note 无
* @retval 目标值
*/
float get_pid_target(void)
{
return pid.target_val; // 设置当前的目标值
}
/**
* @brief 设置比例、积分、微分系数
* @param p:比例系数 P
* @param i:积分系数 i
* @param d:微分系数 d
* @note 无
* @retval 无
*/
void set_p_i_d(float p, float i, float d)
{
pid.Kp = p; // 设置比例系数 P
pid.Ki = i; // 设置积分系数 I
pid.Kd = d; // 设置微分系数 D
}
/**
* @brief PID算法实现
* @param actual_val:实际值
* @note 无
* @retval 通过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 motor_pid_control(void)
{
if (is_motor_en == 1) // 电机在使能状态下才进行控制处理
{
float cont_val = 0; // 当前控制值
int32_t Capture_Count = 0; // 当前时刻总计数值
/* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD */
Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * 65535);
cont_val = PID_realize(Capture_Count); // 进行 PID 计算
if (cont_val > 0) // 判断电机方向
{
set_motor_direction(MOTOR_FWD);
}
else
{
cont_val = -cont_val;
set_motor_direction(MOTOR_REV);
}
cont_val = (cont_val > 5500 * 0.48) ? 5500 * 0.48 : cont_val; // 速度上限处理
set_motor_speed(cont_val); // 设置 PWM 占空比
set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Capture_Count, 1); // 给通道 1 发送实际值
}
}
主体功能
#define CIRCLE_PULSES (16 * 4 * 30) // 编码器一圈可以捕获的脉冲,4倍物理分辨率 * 减速电机减速比
int main(void)
{
int32_t target_location = CIRCLE_PULSES;
初始化
set_computer_value(SEND_STOP_CMD, CURVES_CH1, NULL, 0); // 同步上位机的停止按钮状态
set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &target_location, 1); // 给通道 1 发送目标值
while (1)
{
/* 接收数据处理 */
receiving_process();
/* 扫描KEY1 */
if (Key_Scan(KEY1_GPIO_PORT, KEY1_PIN) == KEY_ON)
{
set_computer_value(SEND_START_CMD, CURVES_CH1, NULL, 0); // 同步上位机的启动按钮状态
set_pid_target(target_location); // 设置目标值
set_motor_enable(); // 使能电机
}
/* 扫描KEY2 */
if (Key_Scan(KEY2_GPIO_PORT, KEY2_PIN) == KEY_ON)
{
set_motor_disable(); // 停止电机
set_computer_value(SEND_STOP_CMD, CURVES_CH1, NULL, 0); // 同步上位机的停止按钮状态
}
/* 扫描KEY3 */
if (Key_Scan(KEY3_GPIO_PORT, KEY3_PIN) == KEY_ON)
{
/* 增加一圈 */
target_location += CIRCLE_PULSES;
set_pid_target(target_location);
set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &target_location, 1); // 给通道 1 发送目标值
}
/* 扫描KEY4 */
if (Key_Scan(KEY4_GPIO_PORT, KEY4_PIN) == KEY_ON)
{
/* 减少一圈 */
target_location -= CIRCLE_PULSES;
set_pid_target(target_location);
set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &target_location, 1); // 给通道 1 发送目标值
}
}
}
直流电机位置环控制-增量式PID实现
编程要点
配置基本定时器产生定时中断来执行PID运算
配置定时器输出PWM控制电机
配置定时器读取编码器的计数值
编写增量式PID算法
编写速度控制函数
增加上位机曲线观察相关代码
编写按键控制代码
配置基本定时器6产生定时中断来执行PID运算
同上。
配置定时器1输出PWM控制电机
同上。
配置定时器3读取编码器的计数值
同上。
编写增量式PID算法
typedef struct
{
float target_val; // 目标值
float actual_val; // 实际值
float err; // 定义当前偏差值
float err_next; // 定义下一个偏差值
float err_last; // 定义最后一个偏差值
float Kp, Ki, Kd; // 定义比例、积分、微分系数
}_pid; // 速度环PID
_pid pid;
/**
* @brief PID参数初始化
* @note 无
* @retval 无
*/
void PID_param_init()
{
/* 初始化参数 */
pid.target_val = 100;
pid.actual_val = 0.0;
pid.err = 0.0;
pid.err_last = 0.0;
pid.err_next = 0.0;
pid.Kp = 6;
pid.Ki = 2.5;
pid.Kd = 0;
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 值
}
/**
* @brief 设置目标值
* @param val 目标值
* @note 无
* @retval 无
*/
void set_pid_target(float temp_val)
{
pid.target_val = temp_val; // 设置当前的目标值
}
/**
* @brief 获取目标值
* @param 无
* @note 无
* @retval 目标值
*/
float get_pid_target(void)
{
return pid.target_val; // 设置当前的目标值
}
/**
* @brief 设置比例、积分、微分系数
* @param p:比例系数 P
* @param i:积分系数 i
* @param d:微分系数 d
* @note 无
* @retval 无
*/
void set_p_i_d(float p, float i, float d)
{
pid.Kp = p; // 设置比例系数 P
pid.Ki = i; // 设置积分系数 I
pid.Kd = d; // 设置微分系数 D
}
/**
* @brief PID算法实现
* @param actual_val:实际值
* @note 无
* @retval 通过PID计算后的输出
*/
float PID_realize(float actual_val)
{
/*计算目标值与实际值的误差*/
pid.err = pid.target_val - actual_val;
/*PID算法实现*/
pid.actual_val += pid.Kp * (pid.err - pid.err_next) + pid.Ki * pid.err + pid.Kd * (pid.err - 2 * pid.err_next + pid.err_last);
/*传递误差*/
pid.err_last = pid.err_next;
pid.err_next = pid.err;
/*返回当前实际值*/
return pid.actual_val;
}
/**
* @brief 电机增量式 PID 控制实现(定时调用)
* @param 无
* @retval 无
*/
void motor_pid_control(void)
{
if (is_motor_en == 1) // 电机在使能状态下才进行控制处理
{
float cont_val = 0; // 当前控制值
int32_t Capture_Count = 0; // 当前时刻总计数值
/* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD */
Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * 65535);
cont_val = PID_realize(Capture_Count); // 进行 PID 计算
if (cont_val > 0) // 判断电机方向
{
set_motor_direction(MOTOR_FWD);
}
else
{
cont_val = -cont_val;
set_motor_direction(MOTOR_REV);
}
cont_val = (cont_val > 5500 * 0.48) ? 5500 * 0.48: cont_val; // 速度上限处理
set_motor_speed(cont_val); // 设置 PWM 占空比
set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Capture_Count, 1); // 给通道 1 发送实际值
}
}
主体功能
同上。