c 控制代码
由于本项目电机负载较小,步进电机的开环控制已经可以满足性能要求,下面以开环步进电机控制进行说明。
电机驱动器根据PWM波脉冲频率控制步进电机转速,根据脉冲数控制转角度数,根据方向电平控制电机转向,根据使能电平控制电机的启停,这就要求控制器能够提供频率和脉冲数可变的PWM波,高低电平可控的方向电平和使能电平。下图为单片机产生PWM波和高低电平的方法分类:
控制系统要求:四路脉冲数频率可控的PWM输出、四路方向电平、接收到控制信号后输出指定脉冲数后停止
比较各种方法优缺点后,最后选择双定时器生成PWM波,基本IO口输出模式生成方向电平。双定时器方法原理:定时器a产生PWM输出(可输出频率相同的多路PWM),定时器b控制时间,定时器a输出打开(占空比设为50%)同时定时器b开始计时,定时器b溢出中断时关闭定时器a的PWM输出(占空比设为0%),脉冲数计算公式如下:
脉
冲
数
=
P
W
M
频
率
(
k
H
Z
)
∗
时
间
基
准
(
m
s
)
脉冲数 = PWM频率(kHZ)*时间基准(ms)
脉冲数=PWM频率(kHZ)∗时间基准(ms)
如果我们需要1s输出10000个脉冲,那就应该设置PWM频率为10kHZ,时间基准为1000ms。
具体方案:采用高级定时器TIM8产生4路PWM波,TIM7和TIM3提供时间基准,四个基本IO口配置为输出模式作为方向电平的输出。STM32F407ZGT6芯片引脚复用情况如下表:
TIM8 CH1 | TIM8 CH2 | TIM8 CH3 | TIM8 CH4 | motorA dir | motorB dir | motorC dir | motorD dir |
---|---|---|---|---|---|---|---|
GPIOC_Pin_6(PC6) | PC7 | PC8 | PC9 | PC2 | PC3 | PB12 | PB13 |
.h代码如下:
#ifndef STEP_MOTOR_H_
#define STEP_MOTOR_H_
#include "sys.h"
/***********定时器参数宏定义*******************/
//主要修改这四个参数实现输出指定脉冲数pwm波
//设置定时器中断时间640ms
//设置定时器中断时间921.6ms
//使用两个时间基准定时器是为了控制两个所需脉冲数不同的步进电机
#define TIM3_set_arr 6400-1
#define TIM3_set_psc 8400-1
#define TIM7_set_arr 9216-1
#define TIM7_set_psc 8400-1
//设置pwm频率为10KHZ 就使1s产生10000个pwm脉冲
#define TIM8_set_arr 2100-1 //重载值不易设置过小,实际调试时当值小于400时就会出现PWM波形输出错误
#define TIM8_set_psc 8-1
/***********电机方向控制IO 宏定义*******************/
#define Moter_A_1 PCout(2)
#define Moter_B_1 PCout(3)
#define Moter_C_1 PBout(12)
#define Moter_D_1 PBout(13)
/***********定占空比*******************/
#define Step_Motor_duty 500
void Motor_Pamar_Init(void);
void Set_Target_Angle_A0(void);
void Set_Target_Angle_A1(void);
void Set_Target_Angle_B0(void);
void Set_Target_Angle_B1(void);
void Set_Target_Angle_C0(void);
void Set_Target_Angle_C1(void);
void Set_Target_Angle_D0(void);
void Set_Target_Angle_D1(void);
/*************提供时间基准的定时器初始化函数*******************/
void TIM3_Int_Init(u16 arr,u16 psc);
void TIM7_Int_Init(u16 arr,u16 psc);
/*************PWM输出定时器初始化***************/
void TIM8_PWM_Init(u16 arr,u16 psc); // 电机的PWM信号初始化函数
/*************方向电平IO口初始化***************/
void Moter_IOs_Init(void); // 电机方向控制IO初始化
/*************步进电机底层初始化***************/
void Moter_Init(void); //包含全部初始化内容,在使用步进电机底层驱动时必须先调用该函数
#endif
.c代码如下:
/*********************************************步进电机开环控制双定时器方法代码*******************************************************/
#include "Step_Motor.h"
#include "usart.h"
#include "delay.h"
int Moter_Count[4];
void Moter_Init(void)
{
Moter_IOs_Init();
TIM8_PWM_Init(TIM8_set_arr,TIM8_set_psc);
TIM3_Int_Init(TIM3_set_arr,TIM3_set_psc);
TIM7_Int_Init(TIM7_set_arr,TIM7_set_psc);
Motor_Pamar_Init();
}
void Motor_Pamar_Init(void)
{
Moter_Count[0] = 0;
Moter_Count[1] = 0;
Moter_Count[2] = 0;
Moter_Count[3] = 0;
}
/* 以下8个函数功能是执行一次发送指定数量的PWM脉冲(大概)从而使步进电机正/反转一定的角度
* 同时设置了软限位防止到达边界后按按键还会移动的情况发生 if语句判断是否超出
* 正转按钮 首先计数-- 判断是否超出限制位置 然后对应方向电平写0 然后使能TIM7使其再指定时间后关闭pwm输出 再设置pwm占空比为50(类似使能pwm输出的作用)
* 反转按钮 首先计数++ 判断是否超出限制位置 然后对应方向电平写1 然后使能TIM7使其再指定时间后关闭pwm输出 再设置pwm占空比为50(类似使能pwm输出的作用)
* 写成无参函数的形式主要是为了再上位机界面函数直接调用方便
*/
/* 执行一次电机A反转1圈 */
void Set_Target_Angle_A0(void)
{
Moter_Count[0]--; //反转次数记录
if (Moter_Count[0] <= 5 && Moter_Count[0] >= -5) //软件限位
{
Moter_A_1 = 0; //PC2输出低电平
TIM_Cmd(TIM7,ENABLE); //使能定时器7
TIM_SetCompare1(TIM8,Step_Motor_duty); //设置占空比为定值,同时也起到打开PWM输出的作用
}
}
/* 执行一次电机A正转1圈 */
void Set_Target_Angle_A1(void)
{
Moter_Count[0]++;
if (Moter_Count[0] <= 5 && Moter_Count[0] >= -5)
{
Moter_A_1 = 1;
TIM_Cmd(TIM7,ENABLE); //使能定时器7
TIM_SetCompare1(TIM8,Step_Motor_duty);
}
}
/* 执行一次电机B反转1圈 */
void Set_Target_Angle_B0(void)
{
Moter_Count[1]--;
if (Moter_Count[1] <= 5 && Moter_Count[1] >= -5)
{
Moter_B_1 = 0;
TIM_Cmd(TIM7,ENABLE); //使能定时器7
TIM_SetCompare2(TIM8,Step_Motor_duty);;
}
}
/* 执行一次电机B正转1圈 */
void Set_Target_Angle_B1(void)
{
Moter_Count[1]++;
if (Moter_Count[1] <= 5 && Moter_Count[1] >= -5)
{
Moter_B_1 = 1;
TIM_Cmd(TIM7,ENABLE); //使能定时器7
TIM_SetCompare2(TIM8,Step_Motor_duty);
}
}
/* 执行一次电机C反转1圈 */
void Set_Target_Angle_C0(void)
{
Moter_Count[2]--;
if (Moter_Count[2] <= 5 && Moter_Count[2] >= -5)
{
Moter_C_1 = 0;
TIM_Cmd(TIM7,ENABLE); //使能定时器7
TIM_SetCompare3(TIM8,Step_Motor_duty);
}
}
/* 执行一次电机C正转1圈 */
void Set_Target_Angle_C1(void)
{
Moter_Count[2]++;
if (Moter_Count[2] <= 5 && Moter_Count[2] >= -5)
{
Moter_C_1 = 1;
TIM_Cmd(TIM7,ENABLE); //使能定时器7
TIM_SetCompare3(TIM8,Step_Motor_duty);
}
}
/* 执行一次电机D反转1圈 */
void Set_Target_Angle_D0(void)
{
Moter_Count[3]--;
if (Moter_Count[3] <= 5 && Moter_Count[3] >= -5)
{
Moter_D_1 = 0;
TIM_Cmd(TIM3,ENABLE); //使能定时器3
TIM_SetCompare4(TIM8,Step_Motor_duty);
}
}
/* 执行一次电机D正转1圈 */
void Set_Target_Angle_D1(void)
{
Moter_Count[3]++;
if (Moter_Count[3] <= 5 && Moter_Count[3] >= -5)
{
Moter_D_1 = 1;
TIM_Cmd(TIM3,ENABLE); //使能定时器3
TIM_SetCompare4(TIM8,Step_Motor_duty);
}
}
/* 步进电机转向控制电平初始化配置
* 主要对要用到的四个方向电平的IO复用和输出模式进行设置
* 默认设置IO输出 默认低电平
* 为避免io冲突,最终选择PC2 PC3 PB12 PB13这四个
*/
void Moter_IOs_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_12;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
}
/* 高级定时器TIM8配置成PWM模式输出四路不同的PWM的使用方法 默认是使能的,占空比默认设为0
* TIM8_PWM_Init函数主要对TIM8定时器进行IO口复用 计数周期频率 PWM模式等进行初始化设置
* 配置如下:时钟线APB1 不分频时为84MHZ 向上计数 开启中断
* 参数arr:为定时器每次装载的值可自己设置
* 参数psc:为总线时钟分频设置,即分频值,通过改变分频值可以改变计一次数的时间 如84 -1那么计一次数就时1/1000s
* 中断时间计算方法 Tout= ((arr+1)*(psc+1))/Tclk
* Tout:一个脉冲的(ms)
* arr:自动重装值
* psc:时钟预分频值
* Tclk:TIM8的输入时钟频率(KHZ)一般根据定时器所挂载的时钟线查找,固定值 TIM8挂载在 APB2 之下 84000KHZ
* 四路pwm输出io我们根据电路原理图,查找可以复用为TIM8 CH1-CH4的io,为避免io冲突,最终选择PC6 PC7 PC8 PC9这四个
*/
void TIM8_PWM_Init(u16 arr,u16 psc) // 电机的PWM信号初始化函数
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure; // 要用到的一些结构体的声明
//io配置
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //开启 IO口的时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // IO复用的相关参数配置
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7| GPIO_Pin_8 | GPIO_Pin_9; //这里是配置了4个 IO 的 PWM输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 实际比赛中我们也是这样用的 一个定时器 生成四路
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 占空比不同的 PWM信号 这四路信号占用一个定时器的资源
GPIO_Init(GPIOC,&GPIO_InitStructure); // 四路信号的频率是一致的
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6 ,GPIO_AF_TIM8); //GPIOC6 复用为定时器TIM8 IO复用
GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_TIM8);
//定时器属性配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE); //开启定时器的时钟
//Timer clock = sysclock /(TIM_Prescaler+1) = 168M
//Period = (TIM counter clock / TIM output clock) - 1 = 20K
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //定时器基本参数设置
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInit(TIM8,&TIM_TimeBaseInitStructure); //根据指定的参数初始化TIM8
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择PWM模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OC1Init(TIM8,&TIM_OCInitStructure); // 输出通道1 初始化
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC2Init(TIM8,&TIM_OCInitStructure); // 输出通道2 初始化
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC3Init(TIM8,&TIM_OCInitStructure); // 输出通道3 初始化
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC4Init(TIM8,&TIM_OCInitStructure); // 输出通道4 初始化
TIM_Cmd(TIM8,ENABLE);
TIM_CtrlPWMOutputs(TIM8,ENABLE);
}
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
TIM_SetCompare4(TIM8,0);
}
TIM_Cmd(TIM3,DISABLE); //关闭定时器3
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
/* TIM7定时器配置成定时器中断的使用方法 这里把使能开关放在了外边且每次中断完成会关闭使能, 默认是不使能的 且只能进行一次中断触发
* TIM7_Int_Init函数主要对TIM7定时器进行计数周期频率及中断等进行初始化设置
* 配置如下:时钟线APB1 不分频时为84MHZ 向上计数 开启中断
* 参数arr:为定时器每次装载的值可自己设置,比如1000-1 就是计数器计到1000时触发中断,并自动重新装载该值
* 参数psc:为总线时钟分频设置,即分频值,通过改变分频值可以改变计一次数的时间 如84 -1那么计一次数就时1/1000s
* 中断时间计算方法 Tout= ((arr+1)*(psc+1))/Tclk
* Tout:中断溢出时间(ms)
* arr:自动重装值
* psc:时钟预分频值
* Tclk:TIM7的输入时钟频率(KHZ)一般根据定时器所挂载的时钟线查找,固定值 TIM7挂载在 APB1 之下 84000KHZ
*/
void TIM7_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7,ENABLE); ///使能TIM7时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM7,&TIM_TimeBaseInitStructure);//初始化TIM7
TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE); //允许定时器7更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM7_IRQn; //定时器7中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/* TIM7定时器的中断处理函数,当定时器开启中断,且计数器达到装载值溢出时触发中断执行该函数 */
void TIM7_IRQHandler(void)
{
if(TIM_GetITStatus(TIM7,TIM_IT_Update)==SET) // 判断是溢出中断
{
//这里设置三路PWM占空比为0即不在输出pwm波,如果需要四路PWM输出脉冲个数不同要用多个定时器分别关闭PWM1,PWM2,PWM3,PWM4
TIM_SetCompare1(TIM8,0); //修改比较值,修改占空比 800对应占空比为80% 2对应占空比0.2%
TIM_SetCompare2(TIM8,0);
TIM_SetCompare3(TIM8,0);
}
TIM_Cmd(TIM7,DISABLE); //关闭定时器7,这样设置是让定时器7执行一次中断后便关闭等到要再次使用的时候再通过TIM_Cmd(TIM7,ENABLE)开始新一轮的定时
TIM_ClearITPendingBit(TIM7,TIM_IT_Update); // 清除中断标志位
}