本帖最后由 xld0932 于 2025-4-16 11:56 编辑
1.前言
通过学习正点原子实现步进电机进行加减速控制,实现对滑台的运动控制,使用光电编码器实现步进电机闭环系统(位置环)
2.步进电机闭环系统组成(《摘录于DMF407电机控制专题教程》)
同直流有刷电机的闭环系统类似,步进电机的闭环系统的组成由: 步进电机+编码器构成,编码器反馈步进电机的实际旋转位置给控制器,这样就可以控制器知晓是否有按要求到达指定目标位置,当有偏差就可及时纠偏了。接着来看总结的一些闭环系统与开环系统之间的优缺点:
3.步进电机闭环系统原理(《摘录于DMF407电机控制专题教程》)
上图为步进电机闭环系统的整体控制流程,其中编码器作为反馈通道, 反馈步进电机的实际位置, 系统将实际位置与目标位置进行比较, 计算差值,再把偏差值代入到 PID 控制器中,之后控制器输出期望值,最后作用于步进电机使其旋转至指定位置停止!这就是位置闭环控制。
4.步进电机位置闭环控制实现(增量式/位置式)
4.1.步进电机初始化
void TIM_OC1PreloadConfig(TIM_TypeDef *TIMx, uint16_t TIM_OCPreload)
{
uint16_t tmpccmr1 = 0;
tmpccmr1 = TIMx->CCMR1;
tmpccmr1 &= (uint16_t) ~((uint16_t)0x0008);
tmpccmr1 |= TIM_OCPreload;
TIMx->CCMR1 = tmpccmr1;
}
void stepper_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef GPIO_InitStruct;
System_Module_Enable(EN_GPIOCD);
/* Direction */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_0;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_WriteBit(GPIOC, GPIO_PIN_9, Bit_SET);
/* Limit Switch */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_0;
GPIO_Init(GPIOC, &GPIO_InitStruct);
TIM_Base_InitTypeDef TIM_Base_InitStruct;
TIM_OC_InitTypeDef TIM_OC_InitStruct;
uint32_t TIM_Clock;
System_Module_Enable(EN_GPIOCD);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_2;
GPIO_Init(GPIOC, &GPIO_InitStruct);
System_Module_Enable(EN_TIM1);
if (System_Get_SystemClock() == System_Get_APBClock())
{
TIM_Clock = System_Get_APBClock();
}
else
{
TIM_Clock = System_Get_APBClock() * 2;
}
TIM_Base_InitStruct.Prescaler = psc;
TIM_Base_InitStruct.Period = arr;
TIM_Base_InitStruct.RepetitionCounter = 0;
TIM_Base_InitStruct.CounterMode = TIM_COUNTERMODE_UP;
TIM_Base_InitStruct.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_TimeBase_Init(TIM1, &TIM_Base_InitStruct);
TIM_OC_InitStruct.OCMode = OUTPUT_MODE_MATCH_TOGGLE;
TIM_OC_InitStruct.Pulse = 0;
TIM_OC_InitStruct.OCPolarity = OUTPUT_POL_ACTIVE_HIGH;
TIM_OC_InitStruct.OCNPolarity = OUTPUT_POL_ACTIVE_HIGH;
TIM_OC_InitStruct.OCFastMode = OUTPUT_FAST_MODE_DISABLE;
TIM_OC_InitStruct.OCIdleState = OUTPUT_IDLE_STATE_0;
TIM_OC_InitStruct.OCNIdleState = OUTPUT_IDLE_STATE_0;
TIM_OC1Init(TIM1, &TIM_OC_InitStruct);
TIM_OC1PreloadConfig(TIM1, 0);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
4.2.编码器初始化
void bsp_EncoderInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_Base_InitTypeDef TIM_Base_InitStruct;
TIM_IC_InitTypeDef TIM_IC_InitStruct;
System_Module_Enable(EN_GPIOAB);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_3;
GPIO_Init(GPIOA, &GPIO_InitStruct);
System_Module_Enable(EN_TIM2);
TIM_Base_InitStruct.Prescaler = 0;
TIM_Base_InitStruct.Period = 0xFFFFFFFF;
TIM_Base_InitStruct.RepetitionCounter = 0;
TIM_Base_InitStruct.CounterMode = TIM_COUNTERMODE_UP;
TIM_Base_InitStruct.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_TimeBase_Init(TIM2, &TIM_Base_InitStruct);
TIM_SelectSlaveMode(TIM2, TIM_SLAVE_MODE_ENC1);
TIM_TI1FP1_ConfigInputStage(TIM2, TIM_SLAVE_CAPTURE_ACTIVE_FALLING, TIM_TI1_FILTER_LVL(6));
TIM_IC_InitStruct.ICPolarity = TIM_SLAVE_CAPTURE_ACTIVE_FALLING;
TIM_IC_InitStruct.ICSelection = TIM_ICSELECTION_DIRECTTI;
TIM_IC_InitStruct.ICPrescaler = TIM_IC1_PRESCALER_1;
TIM_IC_InitStruct.TIFilter = TIM_TI1_FILTER_LVL(6);
TIM_ICInit(TIM2, &TIM_IC_InitStruct, TIM_CHANNEL_1);
TIM2->CNT = 0;
TIM_Cmd(TIM2, ENABLE);
}
4.3.步进电机及编码器参数定义
#ifndef __STEPPER_H
#define __STEPPER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fxx_std.h"
enum STEPPER_DIRECTION
{
CCW = 0, /* 逆时针 */
CW = 1 /* 顺时针 */
};
#define PULSE_REV 400 /* 每圈脉冲数 */
#define TIM_FREQ 180000000U /* 定时器主频 */
#define T1_FREQ (TIM_FREQ/180) /* 频率ft值 */
#define FREQ_UINT (T1_FREQ/(SECOND/SAMPLING_PERIOD)) /* 对定时器的频率做单位换算,避免数值太大溢出 */
#define ENCODER_SPR (float)(600*4) /* 编码器单圈线数*4倍频(根据编码器线数) */
#define MPR 8 /* 步进电机旋转一圈,丝杠的距离;单位:mm/r */
#define PPM (ENCODER_SPR/MPR) /* 每mm内编码器的脉冲数;单位:Pules/mm */
#define MPP ((float)(MPR)/ENCODER_SPR) /* 编码器单步步进距离 */
#define FEEDBACK_CONST (float)(PULSE_REV/ENCODER_SPR) /* 编码器和步进电机驱动器的比值(表示每个编码器输出线数对应的步进电机脉冲数) */
void stepper_init(uint16_t arr, uint16_t psc); /* 步进电机接口初始化 */
void stepper_motion_ctrl(uint8_t dir, uint16_t location_m); /* 步进电机位置运动控制函数 */
#ifdef __cplusplus
}
#endif
#endif
4.4.PID控制参数定义
#ifndef __PID_H
#define __PID_H
#ifdef __cplusplus
extern "C" {
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fxx_std.h"
#define INCR_LOCT_SELECT 0 /* 0:选择位置式 1:增量式控制 */
#if INCR_LOCT_SELECT
#define L_KP 0.5f /* P参数 */
#define L_KI 0.05f /* I参数 */
#define L_KD 0.00f /* D参数 */
#define SMAPLSE_PID_SPEED 20 /* 采样率 单位ms */
#else
#define L_KP 0.5f /* P参数 */
#define L_KI 0.05f /* I参数 */
#define L_KD 0.00f /* D参数 */
#define SMAPLSE_PID_SPEED 20 /* 采样率 单位ms */
#endif
typedef struct
{
__IO float SetPoint; /* 设定目标 */
__IO float ActualValue; /* 实际值 */
__IO float SumError; /* 误差累计 */
__IO float Proportion; /* 比例常数 P */
__IO float Integral; /* 积分常数 I */
__IO float Derivative; /* 微分常数 D */
__IO float Error; /* Error[-1] */
__IO float LastError; /* Error[-1] */
__IO float PrevError; /* Error[-2] */
__IO float IngMin; /* 积分限制 */
__IO float IngMax;
__IO float OutMin; /* 输出限制 */
__IO float OutMax;
} PID_TypeDef;
void pid_init(void); /* PID初始化 */
int32_t increment_pid_ctrl(PID_TypeDef *PID, float Feedback_value); /* PID控制算法 */
#ifdef __cplusplus
}
#endif
#endif
4.5.PID控制
#include "pid.h"
#include "stepper.h"
PID_TypeDef g_location_pid; /* 位置PID参数结构体*/
void pid_init(void)
{
/*位置环初始化*/
g_location_pid.SetPoint = (float)(50 * PPM); /* 设定目标Desired Value*/
g_location_pid.ActualValue = 0.0; /* 期望值*/
g_location_pid.SumError = 0.0; /* 积分值*/
g_location_pid.Error = 0.0; /* Error[1]*/
g_location_pid.LastError = 0.0; /* Error[-1]*/
g_location_pid.PrevError = 0.0; /* Error[-2]*/
g_location_pid.Proportion = L_KP; /* 比例常数 Proportional Const*/
g_location_pid.Integral = L_KI; /* 积分常数 Integral Const*/
g_location_pid.Derivative = L_KD; /* 微分常数 Derivative Const*/
g_location_pid.IngMax = 20;
g_location_pid.IngMin = -20;
g_location_pid.OutMax = 150; /* 输出限制 */
g_location_pid.OutMin = -150;
}
int32_t increment_pid_ctrl(PID_TypeDef *PID, float Feedback_value)
{
PID->Error = (float)(PID->SetPoint - Feedback_value); /* 偏差 */
#if INCR_LOCT_SELECT
PID->ActualValue += (PID->Proportion * (PID->Error - PID->LastError)) /* E[k]项 */
+ (PID->Integral * PID->Error) /* E[k-1]项 */
+ (PID->Derivative * (PID->Error - 2 * PID->LastError + PID->PrevError)); /* E[k-2]项 */
PID->PrevError = PID->LastError; /* 存储误差,用于下次计算 */
PID->LastError = PID->Error;
#else
PID->SumError += PID->Error;
if (PID->SumError > PID->IngMax)
{
PID->SumError = PID->IngMax;
}
else if (PID->SumError < PID->IngMin)
{
PID->SumError = PID->IngMin;
}
PID->ActualValue = (PID->Proportion * PID->Error) /* E[k]项 */
+ (PID->Integral * PID->SumError) /* E[k-1]项 */
+ (PID->Derivative * (PID->Error - PID->LastError)); /* E[k-2]项 */
PID->LastError = PID->Error;
#endif
if (PID->ActualValue > PID->OutMax)
{
PID->ActualValue = PID->OutMax;
}
else if (PID->ActualValue < PID->OutMin)
{
PID->ActualValue = PID->OutMin;
}
return ((int32_t)(PID->ActualValue)); /* 返回实际控制数值 */
}
4.6.步进电机运动控制
void stepper_motion_ctrl(uint8_t dir, uint16_t location_m) /* location_m如果这个值设置100,那就是20ms的采样频率内要输出100个脉冲信号 */
{
float step_delay = 0.0; /* 步进延时 */
if (location_m == 0)
{
TIM_CCxCmd(TIM1, TIM_CHANNEL_1, TIM_CCx_Disable); /* 当期望值与目标值一致时,代表已到指定位置,停止输出 */
g_step_angle = 0; /* 清空数据 */
}
else
{
if (dir == CW) /* 设置旋转方向 */
{
GPIO_WriteBit(GPIOC, GPIO_PIN_9, Bit_SET);
}
else
{
GPIO_WriteBit(GPIOC, GPIO_PIN_9, Bit_RESET);
}
/* 经过PID计算得到的结果是编码器的输出期望值,
将其转换为步进电机所需的脉冲数( 编码器期望值 * (步进电机一圈所需脉冲数 / 编码器一圈计数值))*/
step_delay = (float)(SMAPLSE_PID_SPEED / (location_m * FEEDBACK_CONST)); /* 单个脉冲的时间宽度,单位: ms */
/* ms转成s step_delay = step_delay/1000 */
/*
T : 单个脉冲所需的时间
c : 需要求解的比较值
f : 定时器计数频率
T = c *(1/f)
step_delay/1000 = c*1/1000000 (单位是s)
c = step_delay*1000 求出整一个脉冲的计数值,需要除以2 因为一个完整的脉冲需要翻转2次
所要的计数值:c/2 = Sstep_delay*1000/2 = step_delay*500
*/
g_step_angle = (uint16_t)(step_delay * 500); /* 算出来的结果是周期,这里除以2,半周期翻转一次 */
TIM_CCxCmd(TIM1, TIM_CHANNEL_1, TIM_CCx_Enable);
}
}
5.附件