介绍
实验一:使用比较输出PWM模式方法实现控制步进电机的正反转以及控制转速
实验二:使用比较输出翻转模式方法实现控制步进电机的正反转以及控制转速
实验三:实现步进电机的定位
硬件资源:
1、F4电机开发板的TIM8_CH1(定时器输出通道), DIR( 正反转) , EN( 脱机引脚)
2、步进电机驱动器
3、步进电机
4、电源
实验一
#include "./BSP/STEPPER_MOTOR/stepper_motor.h"
#include "./BSP/TIMER/stepper_tim.h"
/**
* @brief 初始化步进电机相关IO口, 并使能时钟
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void stepper_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
STEPPER_DIR1_GPIO_CLK_ENABLE(); /* DIR1时钟使能 */
STEPPER_DIR2_GPIO_CLK_ENABLE(); /* DIR2时钟使能 */
STEPPER_EN1_GPIO_CLK_ENABLE(); /* EN1时钟使能 */
STEPPER_EN2_GPIO_CLK_ENABLE(); /* EN2时钟使能 */
gpio_init_struct.Pin = STEPPER_DIR1_GPIO_PIN; /* DIR1引脚PF9 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速 */
HAL_GPIO_Init(STEPPER_DIR1_GPIO_PORT, &gpio_init_struct); /* 初始化DIR1引脚 */
gpio_init_struct.Pin = STEPPER_DIR2_GPIO_PIN; /* DIR2引脚 */
HAL_GPIO_Init(STEPPER_DIR2_GPIO_PORT, &gpio_init_struct); /* 初始化DIR2引脚 */
/* 脱机引脚初始化 */
gpio_init_struct.Pin = STEPPER_EN1_GPIO_PIN; /* EN1引脚 */
HAL_GPIO_Init(STEPPER_EN1_GPIO_PORT, &gpio_init_struct); /* 初始化EN1引脚 */
gpio_init_struct.Pin = STEPPER_EN2_GPIO_PIN; /* EN2引脚 */
HAL_GPIO_Init(STEPPER_EN2_GPIO_PORT, &gpio_init_struct); /* 初始化EN2引脚 */
atim_timx_oc_chy_init(arr, psc); /* 初始化PUL引脚,以及脉冲模式等 */
}//这里针对ST1的EN和DIR做了管脚约束和配置
/**
* @brief 开启步进电机
* @param motor_num: 步进电机接口序号
* @retval 无
*/
void stepper_star(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH1); /* 开启对应PWM通道 */
break;
}
case STEPPER_MOTOR_2 :
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH2); /* 开启对应PWM通道 */
break;
}
default : break;
}
}
/**
* @brief 关闭步进电机
* @param motor_num: 步进电机接口序号
* @retval 无
*/
void stepper_stop(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH1); /* 关闭对应PWM通道 */
break;
}
case STEPPER_MOTOR_2 :
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH2); /* 关闭对应PWM通道 */
break;
}
default : break;
}
}
步进电机初始化代码,这里针对gpio做配置。
定时器代码
/**
* @brief 高级定时器TIMX PWM 初始化函数
* @note
* 高级定时器的时钟来自APB2, 而PCLK2 = 170Mhz, 我们设置PPRE2不分频, 因此
* 高级定时器时钟 = 170Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void atim_timx_oc_chy_init(uint16_t arr, uint16_t psc)
{
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_atimx_handle.Instance = ATIM_TIMX_PWM; /* 定时器x */
g_atimx_handle.Init.Prescaler = psc; /* 定时器分频 */
g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_atimx_handle.Init.Period = arr; /* 自动重装载值 */
g_atimx_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; /* 分频因子 */
g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; /*使能TIMx_ARR进行缓冲*/
g_atimx_handle.Init.RepetitionCounter = 0; /* 开始时不计数*/
HAL_TIM_PWM_Init(&g_atimx_handle); /* 初始化PWM */
g_atimx_oc_chy_handle.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
g_atimx_oc_chy_handle.Pulse = arr/2;
g_atimx_oc_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
g_atimx_oc_chy_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH; /*设置互补输出比较极性,通常用于高级定时器。*/
g_atimx_oc_chy_handle.OCFastMode = TIM_OCFAST_DISABLE; /*禁用快速模式,这影响PWM信号的输出速度。
*/
g_atimx_oc_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET; /*设置输出比较通道和互补输出比较通道在空闲时的状态。*/
g_atimx_oc_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH1); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH2); /* 配置TIMx通道y */
}
/**
* @brief 定时器底层驱动,时钟使能,引脚配置
此函数会被HAL_TIM_PWM_Init()调用
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == ATIM_TIMX_PWM)
{
GPIO_InitTypeDef gpio_init_struct;
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* 开启通道y的GPIO时钟 */
ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE(); /* IO时钟使能 */
ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE(); /* IO时钟使能 */
gpio_init_struct.Pin = ATIM_TIMX_PWM_CH1_GPIO_PIN; /* 通道y的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = ATIM_TIMX_PWM_CHY_GPIO_AF; /* 端口复用 */
HAL_GPIO_Init(ATIM_TIMX_PWM_CH1_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_PWM_CH2_GPIO_PIN; /* 通道x的GPIO口 */
HAL_GPIO_Init(ATIM_TIMX_PWM_CH2_GPIO_PORT, &gpio_init_struct);
}
}
实验二
使用比较输出翻转模式方法实现控制步进电机的正反转以及控制转速。
/**
* @brief 开启步进电机
* @param motor_num: 步进电机接口序号
* @retval 无
*/
void stepper_star(uint8_t motor_num)
{
switch(motor_num)
{
/* 开启对应PWM通道 */
case STEPPER_MOTOR_1 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
break;
}
case STEPPER_MOTOR_2 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
break;
}
default : break;
}
}
在代码中根据定时器初始化的输出模式设置选择pwm输出还是翻转比较输出模式,这里我们配置为翻转比较输出,另外我们可以看到在定时器输出配置里包括msp函数里都没有开启定时器中断,是因为在这里开启的。
/**
* @brief 关闭步进电机
* @param motor_num: 步进电机接口序号
* @retval 无
*/
void stepper_stop(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
/* 关闭对应PWM通道 */
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
break;
}
case STEPPER_MOTOR_2 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
break;
}
default : break;
}
}
同上,关闭翻转比较输出。
/**
* @brief 定时器比较中断
* @param htim:定时器句柄指针
* @note 无
* @retval 无
*/
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
/*获取当前计数*/
g_count_val = __HAL_TIM_GET_COUNTER(&g_atimx_handle);
/*设置比较数值*/
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH1, (g_count_val + 500)%0XFFFF);//这里取余防止超过最大重装载值
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH2, (g_count_val + 500)%0XFFFF);
}
在中断中修改比较值,让它的翻转值改变。
实验三
实验三:实现步进电机的定位
首先依旧是对定时器配置,以及配置为翻转输出等,然后是gpio的配置。
接下来是中断回调函数。
/**
* @brief 定时器比较中断回调函数
* @param htim:定时器句柄指针
* @note 无
* @retval 无
*/
uint8_t i = 0;
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
i++;
if(i % 2 == 0)//意思就是因为只要达到比较值电平就会翻转,一个周期会翻转两次,取余也就是代表一个脉冲
{
i = 0;
g_run_flag = 1;
g_stepper.pulse_count --; /* 每一个完整的脉冲计数值就-- */
if(g_stepper.dir == CW) /* 正转 */
{
g_stepper.add_pulse_count++; /* 每来一个完整脉冲,绝对位置++ */
}else
{
g_stepper.add_pulse_count--; /* 绝对位置-- */
}
if(g_stepper.pulse_count <= 0) /* 脉冲数是转动角度除以单位步距角1.8°,当脉冲数等于0的时候 代表需要发送的脉冲个数已完成,即转动的一定角度需要多少脉冲数耗尽,停止定时器输出 */
{
printf("累计旋转的角度:%d\r\n", (int)(g_stepper.add_pulse_count*MAX_STEP_ANGLE)); /* 打印累计转动了多少角度 */
stepper_stop(STEPPER_MOTOR_1); /* 停止接口一输出 */
g_run_flag = 0;
}
}
/*获取当前计数*/
g_count_val = __HAL_TIM_GET_COUNTER(&g_atimx_handle);
/*设置比较数值*/
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH1, (g_count_val + g_ccr_val)%0XFFFF);//句柄加通道,然后是比较翻转值是固定的g_ccr_val,注意在定时器输出配置里是翻转模式,比较初始也为0
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH2, (g_count_val + g_ccr_val)%0XFFFF);
}
再将角度转化为脉冲数
/**
* @brief 将需要转动的角度转换成脉冲数
* @param angle : 需要转动的角度值
* @param dir : 旋转方向
* @param motor_num: 步进电机接口序号
* @retval 无
*/
void stepper_set_angle(uint16_t angle, uint8_t dir, uint8_t motor_num)
{
g_stepper.pulse_count = angle / MAX_STEP_ANGLE;
if(g_stepper.pulse_count == 0)
{
stepper_stop(motor_num);//耗尽停转
}
else stepper_star(motor_num, dir);
}
总结
整个例程就是,先初始化配置,翻转模式;然后在main里按键启动,如增加角度angle来调用函数计算出对应的脉冲个数,并且启动电机转动开启输出通道和中断使能,然后每两次跳变为一个脉冲,当脉冲个数耗尽即认为电机已经转动到对应的角度然后停止电机。
如果同样的功能将定时器输出配置为pwm模式,区别就在HAL_TIM_OC_DelayElapsedCallback这个中断回调函数中不需要每次都去设置比较值,只需要在最开始配置就好了,因为一个重装载值就是一个周期一个脉冲,而翻转模式在一个重装载值中可能有多个脉冲,当然pwm模式也可以设置手动再次设置比较值的代码实现。