编码器驱动
本节介绍
📝本节您将学习如何使用 MSPM0G3507 的 keil 环境通过电机上的编码器,检测电机的速度。
🏆本章⽬标
1️⃣明白编码器的测速原理;
2️⃣明白如何通过配置引脚的外部中断外设,获取编码器的数据;
编码器原理
该项目的电机编码器使用的是 霍尔编码器
。霍尔编码器由码盘和霍尔元件组成。霍尔码盘与电机主轴同轴,码盘上等分的分布有多个磁极,电机转动时,霍尔元件会输出若干个脉冲信号
,我们正是利用这些脉冲信号实现电机的测速和电机转向的判断。
![]() | ![]() |
---|---|
如何实现测速
"电机转动时,霍尔元件会输出若干个脉冲信号
"
- 当电机转的慢的时候,这个脉冲的输出速度就慢;
- 当电机转的快的时候,这个脉冲的输出速度就快;
那么我们就可以取固定时间内脉冲数的变化,来确定电机速度。
举个例子
我实时获取着编码器的脉冲数。然后,我每隔 1S 才取出这个脉冲数,取出后清零脉冲数。
我第一次 1S 的速度是 100 个脉冲,第二次 1S 的速度是 150 个脉冲,第三次 1S 的速度是 200 个脉冲。
这样的变化就可以知道速度是在变快的了。单位是 脉冲/S。
如何判断转向
编码器的 A相 和 B相 相位差 90°,就可以用于判断旋转方向(通过哪个信号先跳变)。比如我设置了 A相 的上升沿中断。
- 当A相上升沿触发时,如果 B相 当前处于
低
电平,则是顺时针旋转
; - 当A相上升沿触发时,如果 B相 当前处于
高
电平,则是逆时针旋转
;
![]() |
---|
这里仅为举例,电机具体的旋转方向那一边是顺那一边是逆,是由你写代码的说的算。如果要更精准的检测,那就A相B相都开启中断检测,双倍脉冲值。
硬件连接
编码器与开发板的引脚连接表:
编码器 | 开发板 |
---|---|
GND | GND |
A | B00 |
B | B01 |
VCC | 3V3 |
![]() | ![]() |
---|---|
原理图中的 U13 是为了兼容其他电机的编码器线序,不可将本案例的电机编码器接到 U13 接口。
按照 编码器原理
小节的说明,我们要实现测速和方向判断那就需要实现引脚的外部中断 + 定时器功能。
驱动方法
工程创建
复制粘贴上一个章节的工程,并重新命名为 05_encoder_driver
。
![]() |
---|
在新工程下的 harware 文件夹中,新建两个文件 hw_encoder.c 和 hw_encoder.h 。然后打开该工程,往工程下的 hardware 虚拟文件夹添加新建的 .c 文件。
![]() | ![]() |
---|---|
在工程下的 middle 文件夹中,新建两个文件 mid_timer.c 和 mid_timer.h 。然后在keil中往工程下的 middle 虚拟文件夹添加新建的 .c 文件。
![]() | ![]() |
---|---|
更新工程的头文件路径为该工程的路径。
![]() |
---|
引脚配置
打开工程的 .syscfg 文件,再打开图形化代码生成工具,配置编码器对应的 A相 和 B相 引脚为输入模式,并都开启上升沿中断,中断优先级为最高。
![]() | ![]() |
---|---|
![]() | ![]() |
---|---|
再配置一个定时器用于测速。设置为周期的计数模式,开启 20ms 的中断,中断优先级为最低。
![]() |
---|
获取编码器脉冲
往 hw_encoder.c 文件中加入以下hw_encoder.c选项页的代码,往 hw_encoder.h 文件中加入以下hw_encoder.h选项页的代码:
以下代码实现了编码器的脉冲计数 + 方向判断。
#include "hw_encoder.h"
static ENCODER_RES motor_encoder;
//编码器初始化
void encoder_init(void)
{
//编码器引脚外部中断
NVIC_ClearPendingIRQ(GPIOB_INT_IRQn);
NVIC_EnableIRQ(GPIOB_INT_IRQn);
}
//获取编码器的值
int get_encoder_count(void)
{
return motor_encoder.count;
}
//获取编码器的方向
ENCODER_DIR get_encoder_dir(void)
{
return motor_encoder.dir;
}
//编码器数据更新
//请间隔一定时间更新
void encoder_update(void)
{
motor_encoder.count = motor_encoder.temp_count;
//确定方向
motor_encoder.dir = ( motor_encoder.count >= 0 ) ? FORWARD : REVERSAL;
motor_encoder.temp_count = 0;//编码器计数值清零
}
//外部中断处理函数
void GROUP1_IRQHandler(void)
{
uint32_t gpio_status;
//获取中断信号情况
gpio_status = DL_GPIO_getEnabledInterruptStatus(GPIO_ENCODER_PORT, GPIO_ENCODER_PIN_A_PIN | GPIO_ENCODER_PIN_B_PIN);
//编码器A相上升沿触发
if((gpio_status & GPIO_ENCODER_PIN_A_PIN) == GPIO_ENCODER_PIN_A_PIN)
{
//如果在A相上升沿下,B相为低电平
if(!DL_GPIO_readPins(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_B_PIN))
{
motor_encoder.temp_count--;
}
else
{
motor_encoder.temp_count++;
}
}//编码器B相上升沿触发
else if((gpio_status & GPIO_ENCODER_PIN_B_PIN)==GPIO_ENCODER_PIN_B_PIN)
{
//如果在B相上升沿下,A相为低电平
if(!DL_GPIO_readPins(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_A_PIN))
{
motor_encoder.temp_count++;
}
else
{
motor_encoder.temp_count--;
}
}
//清除状态
DL_GPIO_clearInterruptStatus(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_A_PIN|GPIO_ENCODER_PIN_B_PIN);
}
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
#ifndef _HW_ENCODER_H_
#define _HW_ENCODER_H_
#include "ti_msp_dl_config.h"
typedef enum {
FORWARD, // 正向
REVERSAL // 反向
} ENCODER_DIR;
typedef struct {
volatile long long temp_count; //保存实时计数值
int count; //根据定时器时间更新的计数值
ENCODER_DIR dir; //旋转方向
} ENCODER_RES;
void encoder_init(void);
int get_encoder_count(void);
ENCODER_DIR get_encoder_dir(void);
void encoder_update(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
往 hw_timer.c 文件中加入以下hw_timer.c选项页的代码,往 hw_timer.h 文件中加入以下hw_timer.h选项页的代码:
以下代码实现了编码器的固定时间内的速度记录,并将按键扫描的代码从 main 函数中放到了这里,更加方便按键的事件管理。
#include "mid_timer.h"
#include "hw_encoder.h"
#include "mid_button.h"
void timer_init(void)
{
//定时器中断
NVIC_ClearPendingIRQ(TIMER_TICK_INST_INT_IRQN);
NVIC_EnableIRQ(TIMER_TICK_INST_INT_IRQN);
}
//电机编码器脉冲计数
void TIMER_TICK_INST_IRQHandler(void)
{
//20ms归零中断触发
if( DL_TimerA_getPendingInterrupt(TIMER_TICK_INST) == DL_TIMER_IIDX_ZERO )
{
//编码器更新
encoder_update();
//按键扫描+事件管理
flex_button_scan();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef __MID_TIMER_H__
#define __MID_TIMER_H__
#include "ti_msp_dl_config.h"
void timer_init(void);
#endif
2
3
4
5
6
7
8
9
编码器驱动验证
往 empty.c 文件写入以下代码:
#include "ti_msp_dl_config.h"
#include "mid_debug_led.h"
#include "mid_debug_uart.h"
#include "string.h"
#include "stdio.h"
#include "hw_lcd.h"
#include "mid_button.h"
#include "app_key_task.h"
#include "hw_encoder.h"
#include "mid_timer.h"
void ui_home_page(void);// 首页页面初始化
void ui_encoder_value_update(void);//编码器的数据显示
int main(void)
{
int sys_time = 0;
SYSCFG_DL_init();
//DEBUG串口初始化
debug_uart_init();
//按键任务初始化
user_button_init();
//编码器初始化
encoder_init();
//定时器初始化
timer_init();
//LCD初始化
lcd_init();
//LCD显示UI
ui_home_page();
while (1)
{
sys_time++;
if( sys_time % 20 == 0 )//2*20=40ms刷新一次屏幕
{
ui_encoder_value_update();
}
delay_cycles(CPUCLK_FREQ / 1000 * 2);//时间基准2ms
}
}
/*
功能说明:显示居中字符串函数用于显示居中的文字,计算文字的居中坐标
参数说明: x=屏幕中心x坐标
w=屏幕宽度
y=屏幕中心y坐标
h=屏幕高度
str_len=字符串长度
sizey=字体大小
*str=需要显示的字符串
color背景颜色
备注:GRAYBLUE 浅蓝色
DARKBLUE 深蓝色
*/
void disp_x_center(int y, int str_len, uint16_t bc, unsigned char sizey, unsigned char* str)
{
int str_center_x = (sizey * str_len) / 2;//字符串x=字体大小*字符串长度/2
int str_center_y = sizey / 2;//字符串y=字体大小/2
//显示居中坐标的文字
LCD_ArcRect(screen_center_x - str_center_x - 10, y, screen_center_x + str_center_x + 10, sizey+y, bc);
LCD_ShowChinese(screen_center_x - str_center_x,y,str,WHITE,bc,sizey,1);
}
/*
功能说明:显示字符串矩形函数用于显示矩形的文字,计算文字的居中坐标
参数说明: x=矩形起始x坐标
w=矩形宽度
y=矩形起始y坐标
h=矩形高度
str_len=字符串长度
sizey=字体大小
*str=需要显示的字符串
color背景颜色
备注:GRAYBLUE 浅蓝色
DARKBLUE 深蓝色
*/
void disp_string_rect(int x, int w, int y, int h, int str_len, int sizey, unsigned char* str, int color)
{
int str_center_x = ((sizey/2) * str_len) / 2; //字符串x = 字体大小*字符串长度/2
int rect_center_x = x + (w / 2); //矩形中心x
int str_center_y = sizey / 2; //字符串y=字体大小/2
int rect_center_y = y + (h / 2); //矩形中心y
//显示背景矩形
LCD_ArcRect(x, y, x + w, y + h, color);
//显示字符串
LCD_ShowChinese(rect_center_x - str_center_x, rect_center_y - str_center_y,str,WHITE,color,sizey,1);
}
//首页页面初始化
void ui_home_page(void)
{
//关闭背光
LCD_BLK_Clr();
//显示全屏背景颜色
LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
//显示标题
disp_x_center(3,5,BLUE,16,(unsigned char *)"立创开发板");
//显示副标题
disp_x_center(3+16+3,8,BLUE,16,(unsigned char *)"简易PID入门套件");
int x = 40;
int x_offset = 90;
int y = 65;
int y_offset = 25;
//显示speed
disp_string_rect(x, x_offset, y, y_offset, 5, 24, "speed", BLUE);
int x2 = 170;
int x2_offset = 120;
int y2 = 65;
int y2_offset = 25;
//显示direction
disp_string_rect(x2, x2_offset, y2, y2_offset, 9, 24, "direction", BLUE);
//打开背光
LCD_BLK_Set();
}
//编码器的速度+方向显示
void ui_encoder_value_update(void)
{
static int last_encoder_count = 255;
static int last_encoder_dir = 255;
char* disp_buff[50]={0};
if( last_encoder_count != get_encoder_count() )
{
last_encoder_count = get_encoder_count();
sprintf(disp_buff, "%d", get_encoder_count() );
int x = 30;
int x_offset = 110;
int y = 65+30;
int y_offset = 25;
disp_string_rect(x, x_offset, y, y_offset, strlen(disp_buff), 24, disp_buff, BLACK);
}
if( last_encoder_dir != get_encoder_dir() )
{
last_encoder_dir = get_encoder_dir();
int x2 = 170;
int x2_offset = 120;
int y2 = 65+30;
int y2_offset = 25;
sprintf(disp_buff, "%s", (get_encoder_dir() == FORWARD) ? "FORWARD" : "REVERSAL" );
disp_string_rect(x2, x2_offset, y2, y2_offset, strlen(disp_buff), 24, disp_buff, BLACK);
}
}
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
162
163
效果: