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刷新一次),而并未放在主循环中,主要原因是步进电机具有启动频率要求,主循环中东西比较多的时候,对刷新频率会有影响。最后预祝各位看官,项目顺顺利利!

### STM32 控制 28BYJ48 步进电机 示例代码 为了实现STM32F103C8T6对28BYJ48步进电机控制,可以采用如下方法。该过程涉及初始化GPIO端口以及编写用于设置方向和执行步进操作的功能函数。 #### 初始化 GPIO 端口配置 首先定义并初始化与步进电机连接的相关引脚: ```c #include "stm32f1xx_hal.h" #define MOTOR_STEP_PIN GPIO_PIN_8 #define MOTOR_DIR_PIN GPIO_PIN_9 #define MOTOR_PORT GPIOA void Motor_GPIO_Init(void){ __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; // 配置MOTOR_STEP_PIN 和 MOTOR_DIR_PIN为推挽输出模式 GPIO_InitStruct.Pin = MOTOR_STEP_PIN | MOTOR_DIR_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(MOTOR_PORT, &GPIO_InitStruct); } ``` 此部分完成了必要的硬件接口准备工作[^1]。 #### 设置转动方向及步进动作 接着创建两个辅助函数来设定运动的方向(正向或反向),并通过循环调用来完成指定数量的步数移动: ```c // 定义常量表示每圈所需脉冲数目(取决于具体型号) const uint16_t STEPS_PER_REVOLUTION = 512; void SetMotorDirection(uint8_t direction){ if(direction == 1){ HAL_GPIO_WritePin(MOTOR_PORT,MOTOR_DIR_PIN,GPIO_PIN_SET); }else{ HAL_GPIO_WritePin(MOTOR_PORT,MOTOR_DIR_PIN,GPIO_PIN_RESET); } } void StepOnce(){ HAL_GPIO_TogglePin(MOTOR_PORT,MOTOR_STEP_PIN); HAL_Delay(2); // 延迟时间可根据实际需求调整 } ``` 上述代码片段实现了基本的操作逻辑,其中`SetMotorDirection()`负责切换转向而`StepOnce()`则发出单步步进信号。 #### 主程序结构 最后,在主函数中通过调用这些功能模块来进行完整的测试流程: ```c int main(void){ HAL_Init(); SystemClock_Config(); // 系统时钟配置省略 Motor_GPIO_Init(); // 初始化电机IO口 while (1) { SetMotorDirection(1); // 设定为顺时针 for(int i=0;i<STEPS_PER_REVOLUTION;i++){ StepOnce(); } HAL_Delay(1000); SetMotorDirection(0); // 改变为逆时针 for(int i=0;i<STEPS_PER_REVOLUTION;i++){ StepOnce(); } HAL_Delay(1000); } } ``` 这段主程序会使得电机先沿一个方向旋转一圈再反转回来,形成连续往复的动作效果。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值