4.18 二相四线步进电机
步进电机是将电脉冲信号,转变为角位移或线位移的开环控制电机,又称为脉冲电机。步进电机组成最主要的就是转子和定子部分。
- 定子,就是由电流控制磁场方向,通电时就会产生磁力;
- 转子,被定子环绕在中间受定子磁场变化产生转动(下方示意图中转动的指针)
通过给定子通电,产生磁力,将转子吸附过来,那转子就会转一小格;通过给定子连续的通电,就可以实现让转子转动。
在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数。当步进驱动器接收到一个脉冲信号时,它就可以驱动步进电机按设定的方向转动一个固定的角度。因此:
- 可以通过控制脉冲个数来控制角位移量,从而达到准确定位的目的;
- 可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的;
- 可以通过控制绕组通电顺序,达到控制电机正反转的目的。
4.18.1 模块来源
采购链接:https://item.taobao.com/item.htm?spm=a1z09.2.0.0.4be02e8dpoBqfX&id=642594293054&_u=72t4uge55e33
4.18.3 模块原理
二相四线步进电机,二相指的是有两个线圈,四线指的是每一个线圈有两根线。其中A+与A-为一相,B+与B-为一相。
要让它转动起来,需要给线圈连续通电。而转动方式有四拍方式、八拍方式。
四拍方式的转动顺序:【A+】->【B+】->【A-】->【B-】。
八拍方式的转动顺序:【A+】->【A+B+】->【B+】->【B+A-】->【A-】->【A-B-】->【B-】->【B-A+】。
使用磁性电机,电流越大,磁力越强。虽然直接使用开发板的 GPIO 去控制步进电机也可以,但是会有损坏开发板引脚的风险。因此我们需要考虑一个合适的步进电机驱动。
4.18.4 移植工程
4.18.4-1 引脚选择
| 步进电机 | 立创·梁山派 |
|---|---|
| A+ | PG12 |
| B+ | PB9 |
| A- | PG10 |
| B- | PB6 |
要想让步进电机动起来,我们需要先配置好引脚。引脚的配置直接配置为推挽输出模式即可。
#define AP_RCU RCU_GPIOG
#define AP_PORT GPIOG
#define AP_PIN GPIO_PIN_12
#define AM_RCU RCU_GPIOG
#define AM_PORT GPIOG
#define AM_PIN GPIO_PIN_10
#define BP_RCU RCU_GPIOB
#define BP_PORT GPIOB
#define BP_PIN GPIO_PIN_9
#define BM_RCU RCU_GPIOB
#define BM_PORT GPIOB
#define BM_PIN GPIO_PIN_6
#define AP(X) gpio_bit_write(AP_PORT, AP_PIN, X?SET:RESET)//A+
#define AM(X) gpio_bit_write(AM_PORT, AM_PIN, X?SET:RESET)//A-
#define BP(X) gpio_bit_write(BP_PORT, BP_PIN, X?SET:RESET)//B+
#define BM(X) gpio_bit_write(BM_PORT, BM_PIN, X?SET:RESET)//B-2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/******************************************************************
* 函 数 名 称:stepper_motor_config
* 函 数 说 明:对步进电机引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void stepper_motor_config(void)
{
rcu_periph_clock_enable(AP_RCU); // 开启时钟
rcu_periph_clock_enable(AM_RCU); // 开启时钟
rcu_periph_clock_enable(BP_RCU); // 开启时钟
rcu_periph_clock_enable(BM_RCU); // 开启时钟
/* 配置A+推挽输出模式 上拉模式 */
gpio_mode_set(AP_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,AP_PIN);
gpio_output_options_set(AP_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,AP_PIN);
/* 配置A-推挽输出模式 上拉模式 */
gpio_mode_set(AM_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,AM_PIN);
gpio_output_options_set(AM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,AM_PIN);
/* 配置B+推挽输出模式 上拉模式 */
gpio_mode_set(BP_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,BP_PIN);
gpio_output_options_set(BP_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BP_PIN);
/* 配置B-推挽输出模式 上拉模式 */
gpio_mode_set(BM_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,BM_PIN);
gpio_output_options_set(BM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BM_PIN);
AP(0);
BP(0);
AM(0);
BM(0);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
4.18.4-2 驱动方式
本案例使用的是八拍方式驱动步进电机,则顺时针的控制方式为:
| A+ | B+ | A- | B- | |
|---|---|---|---|---|
| 第一拍 | 1 | 0 | 0 | 0 |
| 第二拍 | 1 | 1 | 0 | 0 |
| 第三拍 | 0 | 1 | 0 | 0 |
| 第四拍 | 0 | 1 | 1 | 0 |
| 第五拍 | 0 | 0 | 1 | 0 |
| 第六拍 | 0 | 0 | 1 | 1 |
| 第七拍 | 0 | 0 | 0 | 1 |
| 第八拍 | 1 | 0 | 0 | 1 |
将以上控制方式的顺序,转为 16 进制,得到顺时针旋转的控制码。
uint8_t phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09};最终实现顺时针旋转的代码:
//顺时针,转动顺序:a+ b+ a- b-
void motor_cw(void)
{
static uint8_t i=0;
//开启了顺时针动作
if( motor_cw_flag == 1 )
{
AP ( ( phasecw[i] >> 3 ) & 0x01 );
BP ( ( phasecw[i] >> 2 ) & 0x01 );
AM ( ( phasecw[i] >> 1 ) & 0x01 );
BM ( ( phasecw[i] >> 0 ) & 0x01 );
//拍数增加
i = ( i + 1 ) % 8;
//记录当前步数
step_count++;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
逆时针旋转同理。
| A+ | B+ | A- | B- | |
|---|---|---|---|---|
| 第一拍 | 0 | 0 | 0 | 1 |
| 第二拍 | 0 | 0 | 1 | 1 |
| 第三拍 | 0 | 0 | 1 | 0 |
| 第四拍 | 0 | 1 | 1 | 0 |
| 第五拍 | 0 | 1 | 0 | 0 |
| 第六拍 | 1 | 1 | 0 | 0 |
| 第七拍 | 1 | 0 | 0 | 0 |
| 第八拍 | 1 | 0 | 0 | 1 |
uint8_t phaseccw[8] = {0x09, 0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08};
//逆时针,转动顺序:b- a- b+ a+
void motor_ccw( void )
{
static uint8_t i=0;
//如果开启了逆时针动作
if( motor_ccw_flag == 1 )
{
AP ( ( phaseccw[i] >> 3 ) & 0x01 );
BP ( ( phaseccw[i] >> 2 ) & 0x01 );
AM ( ( phaseccw[i] >> 1 ) & 0x01 );
BM ( ( phaseccw[i] >> 0 ) & 0x01 );
i=(i+1)%8;
//记录当前步数
if( step_count <= 1 ) step_count = 1;
step_count--;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
但是,此时我们还不能够将其应用,如果现在下载程序,会发现步进电机只是在震动,没有旋转。是因为我们给的速度太快了,给完一个脉冲后,直接给下一个脉冲,步进电机还没有被吸附过去,这个脉冲就结束了。
所以我们需要给脉冲一定的延时,等待转子被吸附过去之后,再启动下一次脉冲。本案例开启了定时器 5 计时中断,作为步进电机的脉冲频率,每隔 2 Ms,更新一次脉冲。
/******************************************************************
* 函 数 名 称:stepper_motor_timer_config
* 函 数 说 明:步进电机脉冲更新频率定时器初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:设置为2ms,即每2ms更新一次脉冲。速度太快只会震动,时间太慢速度也很慢
******************************************************************/
void stepper_motor_timer_config(void)
{
/* 一个周期的时间T = 1/f, 定时时间time = T * 周期
设预分频值位pre,周期位per
time = (pre + 1) * (per + 1) / psc_clk
*/
timer_parameter_struct timere_initpara; // 定义定时器结构体
/* 开启时钟 */
rcu_periph_clock_enable(RCU_TIMER5); // 开启定时器时钟
/* CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
timer_deinit(TIMER5); // 复位定时器
/* 配置定时器参数 */
timere_initpara.prescaler = 2000-1; // 时钟预分频值 0-65535
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = 200-1; // 周期
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(TIMER5,&timere_initpara); // 初始化定时器
/* 配置中断优先级 */
nvic_irq_enable(TIMER5_DAC_IRQn,1,2); // 设置中断优先级为 3,2
/* 使能中断 */
timer_interrupt_enable(TIMER5,TIMER_INT_UP); // 使能更新事件中断
/* 使能定时器 */
timer_enable(TIMER5);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
在定时器 5 中断中,将步进电机的动作放入,让其每隔 2ms 更新一次脉冲。
/************************************************
函数名称 : BSP_TIMER_IRQHandler
功 能 : 基本定时器中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void TIMER5_DAC_IRQHandler(void)
{
/* 这里是定时器中断 */
if(timer_interrupt_flag_get(TIMER5,TIMER_INT_FLAG_UP) == SET)
{
// 清除中断标志位
timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP);
//顺时针旋转
motor_cw();
//逆时针旋转
motor_ccw();
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可能会有朋友,在这里感到迷惑,为什么顺时针和逆时针放在一起,那不是就不动了吗?
我们在顺时针和逆时针函数中,加入了一个开启标志位,只有当对应标志位为 1 时,才会开启动作。
例如,我要让它顺时针旋转,那么可以这样写:
motor_cw_flag = 1; //开启顺时针旋转
motor_ccw_flag = 0; //关闭逆时针旋转2
而逆时针旋转,可以这样写:
motor_cw_flag = 0; //关闭顺时针旋转
motor_ccw_flag = 1; //开启逆时针旋转2
最终的完整代码如下:
bsp_stepper_motor.c
/********************************************************************************
* 文 件 名: bsp_stepper_motor.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2023年04月06日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "bsp_stepper_motor.h"
#include "systick.h"
#include "stdio.h"
#include "math.h"
uint8_t phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09};
uint8_t phaseccw[8] = {0x09, 0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08};
uint8_t motor_cw_flag = 0;
uint8_t motor_ccw_flag = 0;
uint16_t step_count = 0;
/******************************************************************
* 函 数 名 称:stepper_motor_timer_config
* 函 数 说 明:步进电机脉冲更新频率定时器初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:设置为2ms,即每2ms更新一次脉冲
******************************************************************/
void stepper_motor_timer_config(void)
{
/* 一个周期的时间T = 1/f, 定时时间time = T * 周期
设预分频值位pre,周期位per
time = (pre + 1) * (per + 1) / psc_clk
*/
timer_parameter_struct timere_initpara; // 定义定时器结构体
/* 开启时钟 */
rcu_periph_clock_enable(RCU_TIMER5); // 开启定时器时钟
/* CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
timer_deinit(TIMER5); // 复位定时器
/* 配置定时器参数 */
timere_initpara.prescaler = 2000-1; // 时钟预分频值 0-65535
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = 200-1; // 周期
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(TIMER5,&timere_initpara); // 初始化定时器
/* 配置中断优先级 */
nvic_irq_enable(TIMER5_DAC_IRQn,1,2); // 设置中断优先级为 3,2
/* 使能中断 */
timer_interrupt_enable(TIMER5,TIMER_INT_UP); // 使能更新事件中断
/* 使能定时器 */
timer_enable(TIMER5);
}
/******************************************************************
* 函 数 名 称:stepper_motor_config
* 函 数 说 明:对步进电机引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void stepper_motor_config(void)
{
rcu_periph_clock_enable(AP_RCU); // 开启时钟
rcu_periph_clock_enable(AM_RCU); // 开启时钟
rcu_periph_clock_enable(BP_RCU); // 开启时钟
rcu_periph_clock_enable(BM_RCU); // 开启时钟
/* 配置A+推挽输出模式 上拉模式 */
gpio_mode_set(AP_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,AP_PIN);
gpio_output_options_set(AP_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,AP_PIN);
/* 配置A-推挽输出模式 上拉模式 */
gpio_mode_set(AM_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,AM_PIN);
gpio_output_options_set(AM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,AM_PIN);
/* 配置B+推挽输出模式 上拉模式 */
gpio_mode_set(BP_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,BP_PIN);
gpio_output_options_set(BP_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BP_PIN);
/* 配置B-推挽输出模式 上拉模式 */
gpio_mode_set(BM_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,BM_PIN);
gpio_output_options_set(BM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BM_PIN);
AP(0);
BP(0);
AM(0);
BM(0);
}
//顺时针,转动顺序:a+ b+ a- b-
void motor_cw(void)
{
static uint8_t i=0;
//开启了顺时针动作
if( motor_cw_flag == 1 )
{
AP ( ( phasecw[i] >> 3 ) & 0x01 );
BP ( ( phasecw[i] >> 2 ) & 0x01 );
AM ( ( phasecw[i] >> 1 ) & 0x01 );
BM ( ( phasecw[i] >> 0 ) & 0x01 );
//拍数增加
i = ( i + 1 ) % 8;
//记录当前步数
step_count++;
}
}
//逆时针,转动顺序:b- a- b+ a+
void motor_ccw( void )
{
static uint8_t i=0;
//如果开启了逆时针动作
if( motor_ccw_flag == 1 )
{
AP ( ( phaseccw[i] >> 3 ) & 0x01 );
BP ( ( phaseccw[i] >> 2 ) & 0x01 );
AM ( ( phaseccw[i] >> 1 ) & 0x01 );
BM ( ( phaseccw[i] >> 0 ) & 0x01 );
i=(i+1)%8;
//记录当前步数
if( step_count <= 1 ) step_count = 1;
step_count--;
}
}
//获取当前行进步数
uint16_t get_step_count(void)
{
return step_count;
}
//设置当前行进步数
void set_step_count(uint16_t num)
{
step_count = num;
}
/************************************************
函数名称 : BSP_TIMER_IRQHandler
功 能 : 基本定时器中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void TIMER5_DAC_IRQHandler(void)
{
/* 这里是定时器中断 */
if(timer_interrupt_flag_get(TIMER5,TIMER_INT_FLAG_UP) == SET)
{
timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP); // 清除中断标志位
//顺时针旋转
motor_cw();
//逆时针旋转
motor_ccw();
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
bsp_stepper_motor.h
#ifndef _BSP_STEPPER_MOTOR_H_
#define _BSP_STEPPER_MOTOR_H_
#include "gd32f4xx.h"
#define AP_RCU RCU_GPIOG
#define AP_PORT GPIOG
#define AP_PIN GPIO_PIN_12
#define AM_RCU RCU_GPIOG
#define AM_PORT GPIOG
#define AM_PIN GPIO_PIN_10
#define BP_RCU RCU_GPIOB
#define BP_PORT GPIOB
#define BP_PIN GPIO_PIN_9
#define BM_RCU RCU_GPIOB
#define BM_PORT GPIOB
#define BM_PIN GPIO_PIN_6
#define AP(X) gpio_bit_write(AP_PORT, AP_PIN, X?SET:RESET)//A+
#define AM(X) gpio_bit_write(AM_PORT, AM_PIN, X?SET:RESET)//A-
#define BP(X) gpio_bit_write(BP_PORT, BP_PIN, X?SET:RESET)//B+
#define BM(X) gpio_bit_write(BM_PORT, BM_PIN, X?SET:RESET)//B-
extern uint8_t motor_cw_flag;
extern uint8_t motor_ccw_flag;
void stepper_motor_config(void);
void stepper_motor_timer_config(void);
uint16_t get_step_count(void);
void set_step_count(uint16_t num);
#endif2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
4.18.5 移植验证
在 main.c 中编写以下代码:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "bsp_usart.h"
#include "bsp_adc.h"
#include "bsp_stepper_motor.h"
int main(void)
{
uint8_t motor_flag = 0;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1ms
systick_config();
//串口0初始化 调试
usart_gpio_config(9600U);
//步进电机初始化
stepper_motor_config();
stepper_motor_timer_config();
while(1)
{
//状态每隔500ms,取反一次
motor_flag = !motor_flag;
//如果当前状态为1
if( motor_flag == 0 )
{
//开启顺时针旋转
motor_cw_flag = 1;
motor_ccw_flag = 0;
}
else//如果当前状态为0
{
//开启逆时针旋转
motor_cw_flag = 0;
motor_ccw_flag = 1;
}
delay_1ms(500);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
完整的示例代码见下方文件 。
通过网盘分享的文件:立创·梁山派GD32F470ZGT6开发板【模块移植手册代码】
链接: https://pan.baidu.com/s/1pp44yjD1Dhh7U9iZ2a11IA 提取码: LCKF