要求:点击正方向时电机正运行,点击负方向时电机负运行,松开时电机停止,电机梯形加减速。实现四组电机控制
已知量:给出电机的角速度(每秒多少转),一圈需要多少个脉冲,加速时间,减速时间。
方案
这里使用定时器输出比较进行配置。
则电机的运行模式如下:
注:图片来自西门子 S7-200 SMART 系统手册
-
max_speed:设定的最大速度
-
基底速度
-
加速度运行时间
-
加速度运行时间
运动场景
-
点动保持一段时间再停止
停止-加速运动-匀速运动-减速运动-停止
-
点动后就停止
停止-加速运动-减速运动-停止
开关状态及运动状态
点动时s1=1
-
加速
-
匀速
放开时s1=0
-
减速
速度参数求解
已知量:
-
max_speed,设定的最大速度,每秒多少转r/s。
-
ss_speed,基底速度,每秒多少转r/s。在电机能力范围内输入一个数值,以 便以较低的速度驱动负载。 如果ss_speed 数值过低,电机和负载在运动的开始和结束时可能会摇摆或颤动。 如果ss_speed 数值过高,电机会在启动时丢失脉冲, 并且负载在试图 停止时会使电机超速。设定电机的基底速度为最大速度的10%(通常为5%-15%)。这里为了便于更改10%,以及便于计算,改为最大速度是基底 速度的a倍。则 ss_speed=max_speed/a 公式1
-
accel_time 加速时间 电机从 ss_speed 加速至 max_speed 所需要的时间,默认值 = 0.5s。
-
decel_time 减速时间 电机从 max_speed 减速至 ss_speed 所需要的时间。 默认值 = 0.5s。 电机的加速和减速时间要经过测试来确定。 开始时, 应输入一个较大的值。 然后逐渐减小这个时间值,直到电 机开始失速,这样可以优化应用中的这些设置。
-
PulsePerRevolution pulse_per_rev 旋转一圈需要的脉冲数。
过程参数
-
在最大速度时,电机每秒旋转多少脉冲 pulse_per_sec?
pulse_per_sec = max_speed* pulse_per_rev 公式2
-
当基底速度时,每秒旋转多少脉冲 ss_pulse_per_sec?
ss_pulse_per_sec = ss_speed * pulse_per_rev 公式3
ss_pulse_per_sec = pulse_per_sec / a 公式4
单片机内部参数:
使用定时器的输出比较,需要设定定时器时钟预分频psc,计数器反转次数c(反转两次为一个脉冲)。
单片机时钟为 F Hz,预分频后的频率f, f= F/psc 公式5
需要求解:加速过程中,输出多少个脉冲acc_n?,输出比较值在每个脉冲后减少的计数次数acc_c
减速过程中,输出多少个脉冲dec_n?输出比较在每脉冲后增加的计数次数dec_c
-
列表项基底速度一个脉冲需要的计数器次数C_0
C_0=f/ss_pulse_per_sec
C_0=fa/max_speed* pulse_per_rev 公式6
-
设定最大速度一个脉冲需要的计数器次数C_s
C_s=f / pulse_per_sec
C_s=f / max_speed * pulse_per_rev 公式7
-
加速阶段,计数器总共计数次数C_acc
C_acc = accel_time * f 公式8
-
C0与Cs以及C_acc的关系为
C_s = C_0 + acc_n * acc_c 公式9
C_acc = C_0 + C_0 + acc_c + C_0+2acc_c + … + C_0 + acc_n * acc_c 公式10
-
则根据公式8、10可得
C_0+C_0+acc_c+C_0+2acc_c+…+C_0+acc_n * acc_c = accel_time * f
化简为
acc_n = 2 * accel_time * f / (C_s+C_0)-1 公式11
acc_c = (C_s-C_0) / acc_n
因为加速阶段Cs>C0,acc_c为负数。为了便于计算,acc_c取正,则
acc_c=(C_0-C_s) / acc_n 公式12
-
减速阶段,初始脉冲需要的计数器次数为电机当前速度一个脉冲所需计数器次数CurrentCounts,该值可能为匀速阶段的C_s,也可能为加速阶段的一个值。在此时,
dec_n = 2* decel_time * f / (CurrentCounts + C_0) - 1 公式13
dec_c = ( C_0 - CurrentCounts) / dec_n 公式14
参与计算的公式
C_0 = f * a / max_speed * pulse_per_rev 公式6 基础速度一个脉冲需要的计数器次数
C_s = f / max_speed * pulse_per_rev 公式7 最大速度一个脉冲需要的计数器次数
acc_n = 2 * accel_time * f / (C_s + C_0) - 1 公式11 加速过程中,有多少个脉冲
acc_c = (C_0 - C_s) / acc_n 公式12 加速过程中,输出比较在每脉冲后减少的计数次数
dec_n = 2* decel_time * f / (CurrentCounts + C_0) - 1 公式13 减速过程中,有多少个脉冲
dec_c = ( C_0 - CurrentCounts) / dec_n 公式14 减速过程中,输出比较在每脉冲后增加的计数次数
操作流程
通过触摸屏点击按钮1+,电机正向运行,松开时,电机停止。
通过触摸屏点击按钮1- ,电机反向运行,松开时,电机停止。
代码实现逻辑
-
点击时发送 M1 01 00(电机1 运行 正向 ) ,松开时发送M1 00 00(电机1 停止 正向) 。通过在main中while循环语句,轮询查询电机x是否启动运行。
-
将已知的电机参数传递到motor_control_start()函数中,求出加速过程中acc_n,acc_c。将电机的控制状态改为ACCEL状态并开启定时器比较输出通道,输出电机脉冲。
-
在中断函数中,每输出一个脉冲,脉冲的计数次数减acc_c,加速脉冲总数acc_n减一,当输出acc_n为0或者电机速度等与最大速度,电机状态改为CONSTANT,匀速状态运行
-
当接受到电机停止命令,电机状态改为ACCEL,从当前的一个脉冲所需计数器次数CurrentCounts开始减速。每输出一个脉冲,脉冲的计数次数加dec_c,减速脉冲总数dec_n减一,当输出dec_n为0或者电机速度等于基底速度时,电机状态改为STOP,停止运行,失能定时器输出通道。
代码实现
头文件定义
-
宏定义
#define SYSCLK 72000000U /*系统时钟*/ #define TIM_Pres 36 /*定时器预分频值,需要与定时器设置的值一致*/ #define f (SYSCLK/TIM_Pres) /* StepTim_FRE 步进电机定时器的频率(分频之后的)*/ #define a 5 /* 最大速度与基底速度的倍数*/
-
电机脉冲、方向与使能引脚定义
脉冲采用定时器输出比较控制,需求需要四个电机,本项目选择使用一个定时器的四个通道。若需要多个定时器,则需要将定时器有常量变为变量,同时增加了代码的复杂程度。
-
电机参数结构体:
/*电机计算过程中的变量定义*/ typedef struct { __IO float C0; /*基底速度一个脉冲需要的计数器次数 */ __IO float Cs; /*最大速度一个脉冲需要的计数器次数*/ __IO float acc_n; /* 加速阶段需要的脉冲数 */ __IO float acc_c; /* 加速过程中,输出比较在每脉冲后减少的计数次数 */ __IO float dec_n; /* 减速阶段需要的脉冲数*/ __IO float dec_c; /* 减速过程中,输出比较在每脉冲后增加的计数次数 */ __IO float CurrentCounts; /* 当前一个脉冲需要的计数次数 */ __IO uint16_t CC; /* 输出比较重置值 临时值*/ __IO uint8_t i; /* 一个脉冲需要两次翻转,流程临时值 */ }motor_var;
-
电机控制
typedef struct { __IO uint8_t en; /* 电机使能*/ __IO uint8_t dir; /* 电机旋转方向 */ __IO uint8_t state; /* 电机旋转状态 */ __IO uint8_t mode; /* 电机操作模式 */ }motor_control; enum MODE { MAN, AUTO }; enum STA { STOP = 0, /* 加减速曲线状态:停止*/ ACCEL, /* 加减速曲线状态:加速阶段*/ DECEL, /* 加减速曲线状态:减速阶段*/ CONSTANT /* 加减速曲线状态:匀速阶段*/ }; enum DIR { CCW = 0, /* 逆时针 */ CW /* 顺时针 */ }; enum EN { EN_ON = 0, /* 低电平有效,使能引脚 */ EN_OFF /* 失能,电机停止出力 */ };
.c文件
-
初始化电机比较输出脉冲、方向、使能引脚。
-
点动梯形加减速函数
/********************************************点动梯形加减速***********************************************/ /* * @brief 生成点动梯形运动控制参数 * @param max_speed 运行最大速度设置,单位 r/s * @param accel_time 加速时间,单位 s * @param decel_time 减速时间,单位 s * @param pulse_per_rev 电机转一圈需要多少个脉冲 * @param *mx_ctrl 电机控制结构体,x可以为1~4 * @param *mx_var 电机参数结构体,x可以为1~4 * @param TIM_IT_CCx 输出通道设置,x可以为1~4 * @param *TIM_SetComparex() 回调函数,选择对应的TIM_SetComparex(),TIM_IT_CCx中x为几,TIM_SetComparex中的x为几。 * @retval 无 */ void motor_control_start(uint16_t max_speed, float accel_time,uint16_t pulse_per_rev, motor_control *mx_ctrl, motor_var *mx_var, uint16_t TIM_IT_CCx,void (*TIM_SetComparex)(TIM_TypeDef*, uint16_t)) { mx_var->C0 = f*a/(max_speed*pulse_per_rev); mx_var->Cs = f/(max_speed*pulse_per_rev); mx_var->acc_n =2*accel_time*f/(mx_var->C0+mx_var->Cs)-1; /*加速需要的脉冲数 */ mx_var->acc_c = (mx_var->C0 - mx_var->Cs) / mx_var->acc_n; /*加速过程中,输出比较在每个脉冲后减少的计数次数 */ mx_ctrl->state = ACCEL; /*电机状态改为加速状态*/ mx_var->CurrentCounts = mx_var->C0; mx_var->CC = mx_var->CurrentCounts/2; /*初始输出比较值*/ TIM_SetComparex(MOTOR_TIMX, mx_var->CC + TIM_GetCounter(MOTOR_TIMX)); TIM_ITConfig(MOTOR_TIMX, TIM_IT_CCx, ENABLE); TIM_Cmd(MOTOR_TIMX,ENABLE); } void motor_man_stop(float decel_time,motor_control *mx_ctrl, motor_var *mx_var) { mx_var->dec_n =2*decel_time*f/(mx_var->CurrentCounts+mx_var->C0)-1; mx_var->dec_c = (mx_var->C0- mx_var->CurrentCounts)/mx_var->dec_n; mx_ctrl->state = DECEL; } /********************************************电机控制***********************************************/ /* * @brief 点动梯形运动控制参数 * @param max_speed 运行最大速度设置,单位 r/s * @param accel_time 加速时间,单位 s * @param decel_time 减速时间,单位 s * @param pulse_per_rev 电机转一圈需要多少个脉冲 * @retval 无 */ motor_control m1_ctrl; motor_var m1_var; void motor1_man(uint16_t max_speed, float accel_time,uint16_t pulse_per_rev) { motor_control_start(max_speed, accel_time,pulse_per_rev, &m1_ctrl, &m1_var,TIM_IT_CC1,TIM_SetCompare1); TIM_CCxCmd(MOTOR_TIMX,TIM_Channel_1,TIM_CCx_Enable); } motor_control m2_ctrl; motor_var m2_var; void motor2_man(uint16_t max_speed, float accel_time,uint16_t pulse_per_rev) { motor_control_start(max_speed, accel_time,pulse_per_rev, &m2_ctrl, &m2_var,TIM_IT_CC2,TIM_SetCompare2); TIM_CCxCmd(MOTOR_TIMX,TIM_Channel_2,TIM_CCx_Enable); } motor_control m3_ctrl; motor_var m3_var; void motor3_man(uint16_t max_speed, float accel_time,uint16_t pulse_per_rev) { motor_control_start(max_speed, accel_time, pulse_per_rev, &m3_ctrl, &m3_var, TIM_IT_CC3,TIM_SetCompare3); TIM_CCxCmd(MOTOR_TIMX,TIM_Channel_3,TIM_CCx_Enable); } motor_control m4_ctrl; motor_var m4_var; void motor4_man(uint16_t max_speed, float accel_time, uint16_t pulse_per_rev) { motor_control_start(max_speed, accel_time, pulse_per_rev, &m4_ctrl, &m4_var, TIM_IT_CC4,TIM_SetCompare4); TIM_CCxCmd(MOTOR_TIMX,TIM_Channel_4,TIM_CCx_Enable); }
-
中断运动控制
/********************************************中断运动控制***********************************************/ /* * @brief 生成点动梯形运动控制参数 * @param *CCRx 指定电机比较输出寄存器MOTOR_TIMX->CCRx,x可以为1~4 * @param *mx_ctrl 电机控制结构体,x可以为1~4 * @param *mx_var 电机参数结构体,x可以为1~4 * @param *i ax[i],i可以为0~3 * @param TIM_IT_CCx 输出通道设置,x可以为1~4 * @retval 无 * TIM_OC_DelayElapsedCallback(&MOTOR_TIMX->CCR1,&m1_var,&mx_ctrl,&ac[1], TIM_IT_CC1); */ void TIM_OC_DelayElapsedCallback(__IO uint16_t *CCRx,motor_var *mx_var,motor_control *mx_ctrl,uint16_t TIM_IT_CCx,uint16_t TIM_Channel) { uint32_t tim_count = 0; tim_count = (*CCRx); //将选定的输出比较当前值赋给tim_count mx_var->CC = tim_count + mx_var->CurrentCounts/2; //新的输出比较值 (*CCRx) = mx_var->CC; //重置输出比较值,相当于TIM_SetComparex()函数 mx_var->i++; if(mx_var->i == 2) { mx_var->i = 0; switch(mx_ctrl->state) /* 加减速曲线阶段 */ { case ACCEL: mx_var->CurrentCounts = mx_var->CurrentCounts - mx_var->acc_c; mx_var->acc_n--; if(mx_var->acc_n <= 0 || mx_var->CurrentCounts <= mx_var->Cs) { mx_var->acc_n = 0; mx_ctrl->state = CONSTANT; } break; case CONSTANT: break; case DECEL: mx_var->CurrentCounts = mx_var->CurrentCounts + mx_var->dec_c; mx_var->dec_n--; if(mx_var->dec_n <= 0 || mx_var->CurrentCounts >= mx_var->C0) { mx_var->dec_n = 0; mx_var->CurrentCounts = mx_var->C0; mx_ctrl->state = STOP; } break; case STOP: TIM_CCxCmd(MOTOR_TIMX,TIM_Channel,TIM_CCx_Disable); TIM_ITConfig(MOTOR_TIMX, TIM_IT_CCx, DISABLE); break; default:break; } } } /********************************************实例化中断通道***********************************************/ void tim_oc1_callback(void) { TIM_OC_DelayElapsedCallback(&MOTOR_TIMX->CCR1,&m1_var,&m1_ctrl,TIM_IT_CC1,TIM_Channel_1); } void tim_oc2_callback(void) { TIM_OC_DelayElapsedCallback(&MOTOR_TIMX->CCR2,&m2_var,&m2_ctrl,TIM_IT_CC2,TIM_Channel_2); } void tim_oc3_callback(void) { TIM_OC_DelayElapsedCallback(&MOTOR_TIMX->CCR3,&m3_var,&m3_ctrl,TIM_IT_CC3,TIM_Channel_3); } void tim_oc4_callback(void) { TIM_OC_DelayElapsedCallback(&MOTOR_TIMX->CCR4,&m4_var,&m4_ctrl,TIM_IT_CC4,TIM_Channel_4); }
-
实例化电机启停
/********************************************实例化电机启停***********************************************/ /*每秒10转,加速时间0.2 s,减速时间0.2 s 电机转动1圈需要1600个脉冲*/ void motor1_man_start(void) { motor1_man(10,0.2,1600); } void motor1_man_stop(void) { motor_man_stop(0.2,&m1_ctrl, &m1_var); } void motor2_man_start(void) { motor2_man(10,0.2,1600); } void motor2_man_stop(void) { motor_man_stop(0.2,&m2_ctrl, &m2_var); } void motor3_man_start(void) { motor3_man(10,0.2,1600); } void motor3_man_stop(void) { motor_man_stop(0.2,&m3_ctrl, &m3_var); } void motor4_man_start(void) { motor4_man(10,0.2,1600); } void motor4_man_stop(void) { motor_man_stop(0.2,&m4_ctrl, &m4_var); }
调试
初始调试四个输出通道仿真如下
解决
上述问题主要是由于初始化定时器比较输出时,对IO端口设置为翻转模式并进行了使能。这导致开启定时器后,四个通道的定时器CNT开始计时,当CNT的值与CCRx值相等时,IO进行翻转,状态寄存器CCXIF置位。相关函数如下:
-
CCER比较输出通道使能(CCxE配置)
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx)
-
输出比较模式选择(OCxM)
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode); 此函数的使用需要先关闭对应的通道,模式配置完成后,再开启通道。
-
获取CNT值 uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);获取CNT的,电机的初始比较值为电机初始脉冲的计数器值/2+当前CNT的
优化后如下
加减速细节如下:
完
2023.08.09