Skip to content

Kevinyym/Stepper-Motor-Control

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 

Repository files navigation

Stepper-Motor-Control

1 目标

  • 实现PWM控制步进电机
  • 实现输出精确脉冲数(定时器主从模式)
  • 实现步进电机梯形加减速控制
  • 实现编码器反馈速度
  • 实现串口上位机显示速度
  • 实现限位开关(home sensor)

2 步骤

2.1 实现步进电机的PWM控制(参考江科协代码)

  • 步进电机的PWM驱动函数能输入PWM的值作为参数
#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM2);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period = 100 - 1;      //ARR
	TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;    //PSC
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;      //CCR
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);
	
	TIM_Cmd(TIM2, ENABLE);
}

void PWM_SetCompare3(uint16_t Compare) //设置占空比
{
	TIM_SetCompare3(TIM2, Compare);
}

void PWM_PrescalerConfig(uint16_t Prescaler) //设置频率
{
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Update);
}

2.2 实现固定PWM输入,转动指定角度

  • 将2.1中的TIM2单独输出PWM,改成TIM1和TIM2进行定时器主从模式来精确输出脉冲数
  • MS1,MS2 低电平,1/8 microstep
  • 基本步距角0.9°,360/0.9=400 P/R,所以一圈就是3200 plus (400*8=3200 plus)
//PWM输出
/***定时器1主模式***/
void TIM1_config(u32 Cycle)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_TIM1 , ENABLE); 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;                   //TIM1_CH4 PA11
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;             //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_TimeBaseStructure.TIM_Period = Cycle-1;                                                   
    TIM_TimeBaseStructure.TIM_Prescaler =71;                    //设置用来作为TIMx时钟频率除数的预分频值                                                     
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //设置时钟分割:TDTS= Tck_tim            
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;            //重复计数,一定要=0!!!
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);                                       

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;          //选择定时器模式:TIM脉冲宽度调制模式1       
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    TIM_OCInitStructure.TIM_Pulse = Cycle/2-1;                    //设置待装入捕获寄存器的脉冲值                                   
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;      //输出极性       

    TIM_OC4Init(TIM1, &TIM_OCInitStructure);                                                         

    TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
    TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); 

    TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);                              
    TIM_ARRPreloadConfig(TIM1, ENABLE);                                                          
}
/***定时器2从模式***/
void TIM2_config(u32 PulseNum)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure; 
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_TimeBaseStructure.TIM_Period = PulseNum-1;   
    TIM_TimeBaseStructure.TIM_Prescaler =0;    
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);  

    TIM_SelectInputTrigger(TIM2, TIM_TS_ITR0);
    //TIM_InternalClockConfig(TIM2);
    TIM2->SMCR|=0x07;                                  //设置从模式寄存器 
    //TIM_ITRxExternalClockConfig(TIM2, TIM_TS_ITR0);

    //TIM_ARRPreloadConfig(TIM2, ENABLE);         
    TIM_ITConfig(TIM2,TIM_IT_Update,DISABLE);

   // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;        
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;     
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
    NVIC_Init(&NVIC_InitStructure);
}
void Pulse_output(u32 Cycle,u32 PulseNum)
{
    TIM2_config(PulseNum);
    TIM_Cmd(TIM2, ENABLE);
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
    TIM1_config(Cycle);
    
    TIM_Cmd(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1, ENABLE);   //高级定时器一定要加上,主输出使能
}

void TIM2_IRQHandler(void) 
{ 
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)     // TIM_IT_CC1
    { 
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志位 
        TIM_CtrlPWMOutputs(TIM1, DISABLE);  //主输出使能
        TIM_Cmd(TIM1, DISABLE); // 关闭定时器 
        TIM_Cmd(TIM2, DISABLE); // 关闭定时器 
        TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);   
    } 
}

2.3 实现步进电机梯形加减速控制

  • S型曲线加减速, 链接
  • Sigmoid函数是一个在生物学中常见的S型函数,也称为S型生长曲线。Sigmoid函数也叫Logistic函数,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用来做二分类。该S型函数有以下优缺点:优点是平滑,而缺点则是计算量大。
  • image
  • image
  • 在电机加减速控制上,电机频率越大,电机速度越快。因而,可以通过公式法求出每个加减速点的频率值,进而通过电机频率求出具体的脉冲周期,最后在间隔相同的时间内改变脉冲相关参数(分频、周期、占空比)即可达到加减速的效果。一般情况下,如步进电机、伺服电机等,分频(71)与占空比(50%)通常固定数值即可,这样在加减速过程仅需改变输出周期值即可。
#define Len 20 //宏定义S曲线的变速脉冲点
float Fre[Len];
u32 Period[Len]; 
void Calculate_S_Curve(float StartFre, float EndFre, float Flexible)
{
	u32 TIM_CLK = 72000000;
	u8 TIM_FRESCALER = 71;
    float melo;

    for(int i = 0; i < Len; i ++)
    {
        melo = Flexible * (i-Len/2.0) / (Len/2.0);
        Fre[i] = StartFre + (EndFre - StartFre) / (1 + expf(-melo));
        Period[i] = (u32)(TIM_CLK / TIM_FRESCALER / Fre[i]);       // TIM_CLK 定时器时钟   TIM_FRESCALER 定时器分频
	}
}
  • 同时,不同频率脉冲输出时也需要注意脉冲的连续性(即我们需要在当前脉冲完全输出之后才能改变电机频率),否则电机加减速过程就会出现丢步现象,在脉冲数严格要求的情况下造成累积误差。main函数中代码如下:先计算出脉冲频率变更点,然后for循环调用频率数组,while循环检查中断标志,确保在当前脉冲完全输出之后才能改变电机频率
Calculate_S_Curve(1000, 4000, 3); //公式计算S曲线
for (int i=0; i<Len; i++)
{
	Pulse_output(Period[i],80); //变速脉冲点的ARR每固定脉冲数后改变
	while (IRQ_Flag == 0)
		{
			OLED_ShowNum(4,5,i,2);
			OLED_ShowNum(4,9,Fre[i],5);
		}
	IRQ_Flag = 0;
}
Pulse_output(Period[(Len-1)],3200*3);
  • Calculate_S_Curve函数,用Sublime Text 输出具体脉冲频率值后,导入execel,如下
  • image
  • 用Freq单独做表,如下变化
  • image
  • 用Time-Freq做表,如下, Time是Freq的倒数,每个频率输出80个脉冲,所以Time*80
  • image
  • 遗留问题:加速过程电机发生低频振动。可能解决方案:从现有8细分改成更大的细分,比如64

2.4 实现编码器反馈速度

  • 编码器是ABZ型,此应用中只接A+/B+信号,相位差=P/4,所以4个信号代表1个脉冲
#include "stm32f10x.h"                  // Device header

void Encoder_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1;      //ARR
	TIM_TimeBaseInitStruct.TIM_Prescaler = 1 - 1;    //PSC
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
	
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);	
	
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

	TIM_Cmd(TIM3, ENABLE);
}

int16_t Encoder_Get(void)
{
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
 	TIM_SetCounter(TIM3, 0);
	return Temp;
}

2.5 串口通讯(参考江科协代码)

  • 串口输出转速,连接上位机图形化显示
  • 加速还需优化,考虑使用24V开关电源(电机额定24V,目前使用12v开关电源)
  • Serial_Printf("Speed:%f\n", Speed); //串口输出转速,换行打印 image

3 定时器表资源表:STM32F103C8T6定时器资源:TIM1,TIM2,TIM3,TIM4

	*功能*	  *定时器*	 *类型* 		*备注*
	PWM	   TIM1	   	高级定时器	步进电机控制
	脉冲计数	   TIM2    	通用定时器	输出精确脉冲数
	Encoder    TIM3    	通用定时器	编码器脉冲计数
	Timer	    TIM4    	通用定时器	监控按键

4 接线框图

  • 电机驱动TMC2209(RX,TX,CLK可以不接)注意:EN脚一定要接低电平,悬空电机不转,切记
  • 电容100uf,接在电机电源上
  • 开关电源12V(5V带不起来)
  • 步进电机PKP243MD15A-R2FL, 编码器分辨率400P/R,基本步距角0.9°. 接头Pin#:1248,分别对应GND/A+/B+/VCC(DC5V),从ST-Link直接取5V和GND),编码器输出AB相接在PA6和PA7两个GPIO口

image image image

注意:下图中的驱动器的pin脚是从正面看的,如果从pin脚一面看是反的,千万注意!!!⚠️

image image image image image image

5 参考资料

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published