接触过实际项目后,才发现实际运用的步进电机的控制并不是采用初学单片机时采用高低电平延时输出相序控制(当然这种方法并未使用专门的步进电机驱动器),也不是采用PWM波输出的模式(这里使用了专门的步进电机驱动器,PWM输出模式只适用于电机一直转,不适合精准控制脉冲个数和精准角度以及做步进电机梯形加速和S型加速等算法),个人觉得使用单片机做控制的话,无疑比较输出模式是最好的策略(当然还是比较推荐采用PLC控制器)。stm32比较输出模式效果还是不错,实际控制9个步进电机还是比较容易。
首先先介绍stm32比较输出模式以及配置过程(这里我采用的控制芯片是STM32F103ZET6)
1.stm32比较输出模式简介以及配置原理
此项功能是用来控制一个输出波形,或者指示一段给定的的时间已经到时。当计数器与捕获
/
比较寄存器的内容相同时,输出比较功能做如下操作:
● 将输出比较模式 (TIMx_CCMRx 寄存器中的 OCxM 位 ) 和输出极性 (TIMx_CCER 寄存器中的 CCxP 位 ) 定义的值输出到对应的引脚上。在比较匹配时,输出引脚可以保持它的电平 (OCxM=000) 、被设置成有效电平 (OCxM=001) 、被设置成无效电平 (OCxM=010) 或进行翻转 (OCxM=011) 。
● 设置中断状态寄存器中的标志位 (TIMx_SR 寄存器中的 CCxIF 位 ) 。
● 若设置了相应的中断屏蔽 (TIMx_DIER 寄存器中的 CCxIE 位 ) ,则产生一个中断。
● 若设置了相应的使能位 (TIMx_DIER 寄存器中的 CCxDE 位, TIMx_CR2 寄存器中的 CCDS 位选择 DMA 请求功能 ) ,则产生一个 DMA 请求。
TIMx_CCMRx 中的 OCxPE 位选择 TIMx_CCRx 寄存器是否需要使用预装载寄存器。
在输出比较模式下,更新事件 UEV 对 OCxREF 和 OCx 输出没有影响。
同步的精度可以达到计数器的一个计数周期。输出比较模式 ( 在单脉冲模式下 ) 也能用来输出一个单脉 冲。
输出比较模式的配置步骤:
1. 选择计数器时钟 ( 内部,外部,预分频器 )
2. 将相应的数据写入 TIMx_ARR 和 TIMx_CCRx 寄存器中
3. 如果要产生一个中断请求和 / 或一个 DMA 请求,设置 CCxIE 位和 / 或 CCxDE 位。
4. 选择输出模式,例如当计数器 CNT 与 CCRx 匹配时翻转 OCx 的输出引脚, CCRx 预装载未用,开启 OCx 输出且高电平有效,则必须设置 OCxM=’011’ 、 OCxPE=’0’ 、 CCxP=’0’ 和 CCxE=’1’ 。
5. 设置 TIMx_CR1 寄存器的 CEN 位启动计数器 TIMx_CCRx 寄存器能够在任何时候通过软件进行更新以控制输出波形(这里就是我们采用输出比较模式的关键),条件是未使用预装载寄存器 (OCxPE=’0’ ,否则 TIMx_CCRx 影子寄存器只能在发生下一次更新事件时被更新 ) 。下图给出了一个例子。
● 将输出比较模式 (TIMx_CCMRx 寄存器中的 OCxM 位 ) 和输出极性 (TIMx_CCER 寄存器中的 CCxP 位 ) 定义的值输出到对应的引脚上。在比较匹配时,输出引脚可以保持它的电平 (OCxM=000) 、被设置成有效电平 (OCxM=001) 、被设置成无效电平 (OCxM=010) 或进行翻转 (OCxM=011) 。
● 设置中断状态寄存器中的标志位 (TIMx_SR 寄存器中的 CCxIF 位 ) 。
● 若设置了相应的中断屏蔽 (TIMx_DIER 寄存器中的 CCxIE 位 ) ,则产生一个中断。
● 若设置了相应的使能位 (TIMx_DIER 寄存器中的 CCxDE 位, TIMx_CR2 寄存器中的 CCDS 位选择 DMA 请求功能 ) ,则产生一个 DMA 请求。
TIMx_CCMRx 中的 OCxPE 位选择 TIMx_CCRx 寄存器是否需要使用预装载寄存器。
在输出比较模式下,更新事件 UEV 对 OCxREF 和 OCx 输出没有影响。
同步的精度可以达到计数器的一个计数周期。输出比较模式 ( 在单脉冲模式下 ) 也能用来输出一个单脉 冲。
输出比较模式的配置步骤:
1. 选择计数器时钟 ( 内部,外部,预分频器 )
2. 将相应的数据写入 TIMx_ARR 和 TIMx_CCRx 寄存器中
3. 如果要产生一个中断请求和 / 或一个 DMA 请求,设置 CCxIE 位和 / 或 CCxDE 位。
4. 选择输出模式,例如当计数器 CNT 与 CCRx 匹配时翻转 OCx 的输出引脚, CCRx 预装载未用,开启 OCx 输出且高电平有效,则必须设置 OCxM=’011’ 、 OCxPE=’0’ 、 CCxP=’0’ 和 CCxE=’1’ 。
5. 设置 TIMx_CR1 寄存器的 CEN 位启动计数器 TIMx_CCRx 寄存器能够在任何时候通过软件进行更新以控制输出波形(这里就是我们采用输出比较模式的关键),条件是未使用预装载寄存器 (OCxPE=’0’ ,否则 TIMx_CCRx 影子寄存器只能在发生下一次更新事件时被更新 ) 。下图给出了一个例子。
简单的来说假设我们开启了定时器向上计数,再设置CCRX的值。当定时器计数值CNT<CCRX的值时输出为低电平,当为高大于时输出为高电平。当然这里小于时输出为高电平还是低电平还是与设置有关。这些在源码中都会给出。
2.stm32比较输出配置代码
//TIM2 CH3 PB10 比较输出模式 完全重映射
//逆时针转72° 电机 86GYG250D-156 驱动器 HB860 匀速
//需要做开漏输出5V强上拉
//电机2 400细分数 23次252脉冲 4次251脉冲 依次一个循环
void TIM2_Compare_output(u16 arr,u16 psc) //比较输出
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE); //Timer2完全重映射 TIM2_CH3->PB10
//设置该引脚为复用输出功能,输出TIM2_CH3的PWM脉冲波形 GPIOB10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //复用开漏输出 外部记住外接上拉5v 不然用示波器看不到PWM输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//初始化TIM2
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 0XFFFF这里并没有用到
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM2 Channel3
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; //选择翻转模式
TIM_OCInitStructure.TIM_Pulse = 500; //指定要加载到捕获比较寄存器中的脉冲值
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC3
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable); //禁止TIM3在CCR2上的预装载寄存器 这里很重要不要使能啊
TIM_ITConfig(TIM2, TIM_IT_CC3, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占式优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //设置子优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE); //使能TIM2
/* TIM主输出使能 */
TIM_CtrlPWMOutputs(TIM2, ENABLE);
}
//TIM2 CH3 PB10比较输出 完全重映射
//输出 160个脉冲
u16 tt_2=0;
u16 pulse2_num=0; //输出脉冲数
void TIM2_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
{
tt_2++;
TIM_ClearITPendingBit(TIM2, TIM_IT_CC3); //清除TIMx的中断待处理位:TIM 中断源
if(tt_2==2) //每两次输出一个完整脉冲
{
tt_2=0;
pulse2_num++;
TIM2->ARR=Motor2_frequency; //设置重装载值 改变这个可以改变频率
TIM_SetCompare3(TIM2,Motor2_frequency); //设置比较值
if(pulse2_num==Motor2_Step) //完成226°
{
pulse2_num=0;
TIM_Cmd(TIM2,DISABLE); //关闭定时器2
}
}
}
}
这里需要注意以上几点:
1.
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //复用开漏输出 外部记住外接上拉5v 不然用示波器看不到PWM输出
这里我因为实际需要做了外部上拉,如果不接外部上拉到5V的话,没有输出。当然你可以改为
复用开漏输出模式
2.
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
这里就是比较输出模式下,先是输出高电平还是低电平。
3
.TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable); //禁止TIM3在CCR2上的预装载寄存器 这里很重要不要使能啊
这里也是很重要的一点,CCR2预装载寄存器不能使能,否者不会比较输出高低电平
4.还有一点需要注意的是其实是每进入两次中断才算输出一个完整的pwm脉冲
如果需要做梯形加减速的话,只需要每次进入中断修改比较值即可,我的其他博文有简介基本思想和用法。步进电机驱动的使用,以及硬件的接法运用,我将在下一篇博文进行较为详细的说明。