STM32驱动28BYJ-48步进电机(八拍精确控制角度)

文章目录

    • 一、概要
    • 二、28BYJ-48步进电机简介
    • 三、八拍驱动模式
    • 四、误差累计分析
    • 五、程序源码

一、概要

        先说明一下写此文章的缘由,Up最近做东西的过程中用到28BYJ-48步进电机,主控为STM32F103C8T6单片机,看网上关于28BYJ-48步进电机程序基本以四拍为主,而且控制参数直接按照理论值来做的。Up在测试的过程中发现,随着转动圈数的不断增加,误差会不断累积,导致偏差原来越大。Up针对此问题花时间做了一些研究,发现导致误差累积的原因可能是步进电机本身的工艺误差造成的,即实际的减速比并不是说明书上严格的64:1。经过实际确认减速比,结合八拍驱动模式,控制精度有了明显的提高。由于Up技术水平有限,还请各位看官理性观看,各取所需!

二、28BYJ-48步进电机简介

        28BYJ-48是四相永磁式减速步进电机,采用四相五线制接法,拆解之后外壳内有8个齿,且每个齿上面都有漆包线绕组,正对着的两个漆包线绕组是串联在一起的,因此8个齿就形成了四相。而五线除了四相的信号线,外加一根电源线,这就是四相五线的定义。28BYJ-48内部定子绕组采用共阳极接法。即公共线接电源,四相信号线给低电平时,对应的绕组通电产生磁力,吸引转子转动到相对位置。

        由于单片机本身的引脚驱动能力有限,因此驱动步进电机需要功率放大电路,Up采用网上最常用的组件ULN2003驱动板进行测试。

三、八拍驱动模式

        说八拍之前,我先唠唠四拍是怎么回事儿,假如按照逆时针方向进行旋转。此时接通A相绕组,B、C、D三相断开,此时A相绕组产生磁力吸引转子1、4号齿正对A相绕组;以此类推,紧接着A相绕组断开,B相绕组导通;B相绕组断开,C相绕组导通;C相绕组断开,D相绕组导通;D相绕组断开,A相绕组导通。

        当完成一个A-B-C-D的轮番导通后,转子的1、4号齿将从正对A相绕组转动到正对B相绕组,即转过一个定子的角度。由此可以算出每次导通转过的角度为360/8/4 = 11.25度,这个角度就是28BYJ-48减速电机的步进角度。一个轮番换序四次,即A-B-C-D,也就是四拍,转子转过一个定子的角度,电机转动一圈需要转过8个定子的角度,即4x8 = 32拍。软件上可用for或者while循环进行递增或递减的操作进行控制,以for循环为例,外层循环8次,内层循环4次(逆向A-B-C-D,正向D-C-B-A),即可实现转子旋转一圈。

        理解四拍之后,八拍模式就简单多了,即在四拍的基础上每相之间插入一拍,也就是原先四拍一个轮番(A-B-C-D)转过一个定子的角度,现在变成了八拍一个轮番(A-AB-B-BC-C-CD-D-DA)转过一个定子的角度,电机转子转动一圈需要8x8 = 64拍,步进角度为360/8/8= 5.625度,精度增加了一倍。软件控制与四拍一致,同样以for循环为例,外层循环64次,内循环8次(A-AB-B-BC-C-CD-D-DA),即可实现转子旋转一圈。

        当然这里说的转子转一圈,并不是指步进电机的轴旋转一圈,而是内部的小电机转子旋转一圈,中间转轴与内部小电机齿轮之间存在4级减速,即减速比的概念就引出来了。  28BYJ-48减速步进电机说明书上标注减速比为64:1,即转轴旋转一圈,内部小电机需要旋转64圈。因此以八拍为例,转轴想要旋转一圈,需要轮番(A-AB-B-BC-C-CD-D-DA)64x64=4096次。此外想要电机转起来需要满足电机启动频率要求,即F>550 P.P.S即550Hz,1.8ms维持时间。

       

四、误差累积分析

  Up在测试的过程中发现,按照厂家说明书给的减速比64:1进行参数配置,随着圈数增大,转轴的起始位置会出现偏差,经过分析导致这一现象的可能原因,就出在减速比上。拆开步进电机实测减速比约为63.684:1,实际的误差角度为0.0049度,因此实际转轴转一圈所需要的节拍数为64x63.684 (约等于) 4076。虽然不能完全的消除误差,但是相当于原先的误差,精度有了明显提高。

五、程序源码

Stepper.h

#ifndef __STEPPER_SERVO_H
#define __STEPPER_SERVO_H

typedef enum
{
	Forward  = 0,
	Reversal   = 1
}Sepper_Servo_Dir;

#define		Stepper_Servo_CLK			RCC_APB2Periph_GPIOB
#define		Stepper_Servo_GPIO	    	GPIOB
#define 	Stepper_Servo_A				GPIO_Pin_12
#define 	Stepper_Servo_B				GPIO_Pin_13
#define 	Stepper_Servo_C				GPIO_Pin_14
#define 	Stepper_Servo_D				GPIO_Pin_15

void Timer2_Init(void);
void Sepper_Servo_Init(void);
void Sepper_Servo_Stop(void);
void Stepper_Servo_Angle(Sepper_Servo_Dir direction , uint32_t angle);
#endif

Stepper.c

#include "stm32f10x.h"                  // Device header
#include "Stepper.h"
#include "Delay.h"
static uint32_t Beats;//总的节拍数
static uint8_t Dir;
static uint8_t StepNum = 0;
extern uint8_t Stepper_flage;

/**
  * @brief  定时器初始化函数
  * @param  无
  * @retval 无
  */
void Timer2_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	
	TIM_TimeBaseInitStructure.TIM_Period = 2000 - 1;				
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						
																
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				
																														
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			
	NVIC_Init(&NVIC_InitStructure);								
	
	/*TIM使能*/
	//TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * @brief  步进电机GPIO初始化函数
  * @param  无
  * @retval 无
  */
void Sepper_Servo_Init(void)
{
	Timer2_Init();
	RCC_APB2PeriphClockCmd(Stepper_Servo_CLK, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;	// 推挽输出
	GPIO_InitStruct.GPIO_Pin = Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_C | Stepper_Servo_D;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(Stepper_Servo_GPIO, &GPIO_InitStruct);
	
	GPIO_ResetBits(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_C | Stepper_Servo_D);
}

/**
  * @brief  电机停转函数
  * @param  无
  * @retval 无
  */
void Sepper_Servo_Stop(void)
{
	GPIO_ResetBits(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_C | Stepper_Servo_D);
}

/**
  * @brief  步进电机按整数角度旋转
  * @param  Sepper_Servo_Dir	旋转方向,Forward(正传)Reversal(反转)
  * @param  angle			    电机转轴旋转角度
  * @retval 
  */


void Stepper_Servo_Angle(Sepper_Servo_Dir direction , uint32_t angle)
{
	Dir = direction;
	TIM_Cmd(TIM2, DISABLE);//关定时器
	Beats = (angle * 4076) / 360; //计算angle角度对应总的节拍数
	TIM_Cmd(TIM2, ENABLE);//开定时器
	Stepper_flage = 0;
}

/**
  * @brief  定时器2中断刷新节拍
  * @param  无
  * @retval 无
  */

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		if(Beats != 0)
		{
			if(Dir == Forward)
			{
					if(StepNum == 9)
					{
						StepNum = 1;
					}
					switch(StepNum)
					{
						case 1:		// A
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A, Bit_SET);
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B | Stepper_Servo_C | Stepper_Servo_D, Bit_RESET);
						break;
						
						case 2:		// AB
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_C | Stepper_Servo_D, Bit_RESET);
						break;			
						
						case 3:		// B
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_C | Stepper_Servo_D, Bit_RESET);
						break;	

						case 4:		// BC
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B | Stepper_Servo_C, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_D, Bit_RESET);
						break;	
						
						case 5:		// C
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_C, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_D, Bit_RESET);
						break;
						
						case 6:		// CD
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_C | Stepper_Servo_D, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B, Bit_RESET);
						break;		
						
						case 7:		// D
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_D, Bit_SET);
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_C, Bit_RESET);
						break;
						
						case 8:		// DA
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_D | Stepper_Servo_A, Bit_SET);
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B | Stepper_Servo_C, Bit_RESET);
						break;		
						
						default: break;
					}
					StepNum++;
					
				}
			else if(Dir == Reversal)
			{
					if(StepNum == 0)
					{
						StepNum = 8;
					}
					switch(StepNum)
					{
						case 1:		// A
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A, Bit_SET);
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B | Stepper_Servo_C | Stepper_Servo_D, Bit_RESET);
						break;
						
						case 2:		// AB
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_C | Stepper_Servo_D, Bit_RESET);
						break;			
						
						case 3:		// B
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_C | Stepper_Servo_D, Bit_RESET);
						break;	

						case 4:		// BC
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B | Stepper_Servo_C, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_D, Bit_RESET);
						break;	
						
						case 5:		// C
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_C, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_D, Bit_RESET);
						break;
						
						case 6:		// CD
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_C | Stepper_Servo_D, Bit_SET);	
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B, Bit_RESET);
						break;		
						
						case 7:		// D
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_D, Bit_SET);
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_A | Stepper_Servo_B | Stepper_Servo_C, Bit_RESET);
						break;
						
						case 8:		// DA
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_D | Stepper_Servo_A, Bit_SET);
							GPIO_WriteBit(Stepper_Servo_GPIO, Stepper_Servo_B | Stepper_Servo_C, Bit_RESET);
						break;		
						
						default: break;
					}
					StepNum --;
					
			}
			
			Beats--;
		}
		else
		{
			Sepper_Servo_Stop();
			TIM_Cmd(TIM2, DISABLE);//关定时器中断
			
		}
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	
   }
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Stepper.h"
int main(void)
{
	Sepper_Servo_Init();//步进电机初始化	
    Stepper_Servo_Angle(Forward,720);    //正传720度
    Delay_ms(1000);                      //延时1000ms
    Stepper_Servo_Angle(Reversal,720);   //反传720度
    Delay_ms(1000);                      //延时1000ms
	while(1)
	{}
}

        最后在絮叨两句,Up采用定时器2中断进行节拍刷新(中断每2ms刷新一次),而并未放在主循环中,主要原因是步进电机具有启动频率要求,主循环中东西比较多的时候,对刷新频率会有影响。最后预祝各位看官,项目顺顺利利!

  • 27
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值