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