电机应用开发-直流有刷电机速度环控制实现

目录

直流有刷电机速度环控制实现

硬件设计

直流电机速度环控制-位置式PID实现

编程要点

配置基本定时器6产生定时中断来执行PID运算

配置定时器1输出PWM控制电机

配置定时器3读取编码器的计数值

编写位置式PID算法

直流电机速度环控制-增量式PID实现

编程要点

配置基本定时器6产生定时中断来执行PID运算

配置定时器1输出PWM控制电机

配置定时器3读取编码器的计数值

编写增量式PID算法


直流有刷电机速度环控制实现

硬件设计

可选: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读取编码器的计数值
/* 定时器溢出次数 */
__IO int16_t Encoder_Overflow_Count = 0;

TIM_HandleTypeDef TIM_EncoderHandle;

/**
  * @brief  编码器接口初始化
  * @param  无
  * @retval 无
  */
void Encoder_Init(void)
{
    GPIO_InitTypeDef 		GPIO_InitStruct = {0};
	TIM_Encoder_InitTypeDef Encoder_ConfigStructure;

    __HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_TIM3_CLK_ENABLE();

	// PC6--TIM3_CH1
    GPIO_InitStruct.Pin 		= GPIO_PIN_6;
    GPIO_InitStruct.Mode 		= GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull 		= GPIO_PULLUP;
    GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate 	= GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

	// PC7--TIM3_CH2
    GPIO_InitStruct.Pin 		= GPIO_PIN_7;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	
	// TIM3 0.78ms一个周期
    TIM_EncoderHandle.Instance 					= TIM3;
    TIM_EncoderHandle.Init.Prescaler 			= 0;
    TIM_EncoderHandle.Init.CounterMode 			= TIM_COUNTERMODE_UP;
    TIM_EncoderHandle.Init.Period 				= 65535;
    TIM_EncoderHandle.Init.ClockDivision 		= TIM_CLOCKDIVISION_DIV1;
    TIM_EncoderHandle.Init.AutoReloadPreload 	= TIM_AUTORELOAD_PRELOAD_DISABLE;

    /* 设置编码器倍频数 */
    Encoder_ConfigStructure.EncoderMode 		= TIM_ENCODERMODE_TI12;
    Encoder_ConfigStructure.IC1Polarity 		= TIM_ICPOLARITY_RISING;
    Encoder_ConfigStructure.IC1Selection 		= TIM_ICSELECTION_DIRECTTI;
    Encoder_ConfigStructure.IC1Prescaler 		= TIM_ICPSC_DIV1;
    Encoder_ConfigStructure.IC1Filter 			= 0;
    Encoder_ConfigStructure.IC2Polarity 		= TIM_ICPOLARITY_RISING;
    Encoder_ConfigStructure.IC2Selection		= TIM_ICSELECTION_DIRECTTI;
    Encoder_ConfigStructure.IC2Prescaler		= TIM_ICPSC_DIV1;
    Encoder_ConfigStructure.IC2Filter 			= 0;
    HAL_TIM_Encoder_Init(&TIM_EncoderHandle, &Encoder_ConfigStructure);

    /* 清零计数器 */
    __HAL_TIM_SET_COUNTER(&TIM_EncoderHandle, 0);

    /* 清零中断标志位 */
    __HAL_TIM_CLEAR_IT(&TIM_EncoderHandle, TIM_IT_UPDATE);
    /* 使能定时器的更新事件中断 */
    __HAL_TIM_ENABLE_IT(&TIM_EncoderHandle, TIM_IT_UPDATE);
    /* 设置更新事件请求源为:计数器溢出 */
    __HAL_TIM_URS_ENABLE(&TIM_EncoderHandle);

    /* 设置中断优先级 */
    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 1);
    /* 使能定时器中断 */
    HAL_NVIC_EnableIRQ(TIM3_IRQn);

    /* 使能编码器接口 */
    HAL_TIM_Encoder_Start(&TIM_EncoderHandle, TIM_CHANNEL_ALL);
}

/**
  * @brief  定时器更新事件回调函数
  * @param  无
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM_EncoderHandle))
    {
        /* 判断当前计数器计数方向 */
        if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&TIM_EncoderHandle)) 
        {	/* 下溢 */
            Encoder_Overflow_Count--;
        }
        else    
        {	/* 上溢 */
            Encoder_Overflow_Count++;
        }
    }
}

编写位置式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;                     // 当前控制值
        static __IO int32_t Capture_Count = 0;  // 当前时刻总计数值
        static __IO int32_t Last_Count = 0;     // 上一时刻总计数值
        int32_t actual_speed = 0;               // 实际测得速度

        /* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD  */
        Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * 65535);

		// 编码器物理分辨率为16,初始化配置后为4倍,即16*4
		// 减速电机减速比为30,50为基本定时器周期
        // 转速 = 里程/时间,单位为多少转/min。
        /* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数  */
        actual_speed = ((float)(Capture_Count - Last_Count) / 16 * 4 / 30) / (50 / 1000.0 / 60.0);

        /* 记录当前总计数值,供下一时刻计算使用 */
        Last_Count = Capture_Count;

        cont_val = PID_realize(actual_speed);    // 进行 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) ? 5500: cont_val;    	                                // 速度上限处理
        set_motor_speed(cont_val);                                                 			// 设置 PWM 占空比

        set_computer_value(SEND_FACT_CMD, CURVES_CH1, &actual_speed, 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)     					// 电机在使能状态下才进行控制处理
    {
        static float cont_val = 0;              // 当前控制值
        static __IO int32_t Capture_Count = 0;  // 当前时刻总计数值
        static __IO int32_t Last_Count = 0;     // 上一时刻总计数值
        int32_t actual_speed = 0;               // 实际测得速度

        /* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD  */
        Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * 65535);

        // 编码器物理分辨率为16,初始化配置后为4倍,即16*4
		// 减速电机减速比为30,50为基本定时器周期
        // 转速 = 里程/时间,单位为多少转/min。
        /* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数  */
        actual_speed = ((float)(Capture_Count - Last_Count) / 16 * 4  / 30) / (GET_BASIC_TIM_PERIOD() / 1000.0 / 60.0);

        /* 记录当前总计数值,供下一时刻计算使用 */
        Last_Count = Capture_Count;

        cont_val = PID_realize(actual_speed);    // 进行 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) ? 5500: cont_val;                                  // 速度上限处理
        set_motor_speed(cont_val);                                                 		// 设置 PWM 占空比

        set_computer_value(SEND_FACT_CMD, CURVES_CH1, &actual_speed, 1);                // 给通道 1 发送实际值
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值