文章目录
- 前言
- 一、AB相编码器计数原理
- 四倍频
- 二、要用到的一些参数
- 编码器参数
- 电机参数
- 轮子参数
- 计算
- 三、代码如下(更契合原理的代码)
- 四倍频的代码(更新)
前言
详解编码器测速原理及实现
参考csdn
一、AB相编码器计数原理
适用于霍尔编码器和带光栅的光电编码器
编码器样子
让遮挡与透过形成高低电平的脉冲
此图为B 提前 A 90° 为反向旋转,相位差相差90°的波形就叫正交波形
当只有一个方波 只能测位置、速度,不能知道方向
而两个方波,就能一个测速,一个测方向
例:
A下降沿触发 并且B是低电平 判断反转
B下降沿触发 并且A是低电平 判断正转
四倍频
还有一种用法 叫四倍频,它是数 A相 和 B相 的上升和下降脉冲 4个信号来测速可以用来提高精度,因为一般只需要考虑A相一个上升或下降沿计数就行,这里数4个,所以叫四倍频。
stm32的编码器接口应该默认是四倍频的:
这里的大佬,说
手动转一圈的脉冲数 n:1560 个;
且用的是13线编码器 和 1:120转速比的电机,那么计算一圈的脉冲数则是
13 *(120)*4 = 1560;其中4应该就是一个周期数4个脉冲沿。这里计算后面会讲。
二、要用到的一些参数
编码器参数
编码器线数:线数就是编码器的分辨率,即转一圈发出的脉冲数
例:光电编码器:500ppr (500线)
霍尔编码器: 13ppr (13线)
电机参数
减速比 :输入转速/输出转速
一般减速比的表示方法是以1为分母,用“:”连接的输入转速和输出转速的比值,如输入转速为1500r/min,输出转速为25r/min,那么其减速比则为:i=60:1。
轮子参数
用直尺 或商家给的参数 得到
轮子直径 :65mm = 0.065m
计算
例 :500线的编码器 和30:1减速比的电机
也就是说 电机实际转30圈 但现实轮子只转一圈
那么轮子转一圈 编码器得到的脉冲数为 500*30 = 15000 (个)
速度计算思路
设 定时器设定时间为t,D为直径,则
e: 由程序得到
PI : 3.1415926
D : 0.065m
n :15000
另定时500ms
t = 0.5s
所以 speed = e * 0.00002722713 (m/s)
为了数据好显示 speed =1000* e * 0.00002722713 (mm/s)
三、代码如下(更契合原理的代码)
没有使用32的编码器接口
Enconder.c:
1
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203#include "stm32f10x.h" // Device header //-32768 ~ +32767 编码器范围 定时器是16位 //这里没用stm32自带的编码器接口(TIM_EncoderInterfaceConfig) , 而是用原理写出 int16_t Encoder_Count_Left;//带符号 int16_t Encoder_Count_Right;//带符号 // B接PB1 A接PB0 //A下降沿触发 并且B是低电平 判断反转 //B下降沿触发 并且A是低电平 判断正转 //左编码 E1B E1A // PB1 PB0 void Encoder_Init_Left(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //AFIO中断引脚选择 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//第0个线路拨到GPIOB上 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);// EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式 也有事件模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); } //单位时间内读取编码器计数 变化的值 int16_t Encoder_Get_Left(void) { int16_t Temp; Temp = Encoder_Count_Left; Encoder_Count_Left = 0; return Temp; } //如果是9-10 中断函数 只需要将两个if并列的放在一个函数里就行了 //A下降沿触发 并且B是低电平 判断反转 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) { Encoder_Count_Left --; } EXTI_ClearITPendingBit(EXTI_Line0); } } //B下降沿 A低电平 判断正转 void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { Encoder_Count_Left ++; } EXTI_ClearITPendingBit(EXTI_Line1);//清楚标志位 } } //右编码 E2B E2A // PB11 PB10 void Encoder_Init_Right(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //AFIO中断引脚选择 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);//第0个线路拨到GPIOB上 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11);// EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line10 | EXTI_Line11; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式 也有事件模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); } //变化的值 int16_t Encoder_Get_Right(void) { int16_t Temp; Temp = Encoder_Count_Right; Encoder_Count_Right = 0; return Temp; } //如果是10-15 中断函数 只需要将两个if并列的放在一个函数里就行了 //因为轮子是对称转 所以镜像 相反 //右编码 E2B E2A // PB11 PB10 //A下降沿触发 并且B是低电平 判断正转 void EXTI15_10_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line10) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) { Encoder_Count_Right ++; } EXTI_ClearITPendingBit(EXTI_Line10); } B下降沿 A低电平 判断反转 if (EXTI_GetITStatus(EXTI_Line11) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0) { Encoder_Count_Right --; } EXTI_ClearITPendingBit(EXTI_Line11);//清楚标志位 } } void Get_Motor_Speed(int *LSpeed,int *RSpeed) { static int LWheelEncoderNow = 0; static int RWheelEncoderNow = 0; static int LWheelEncoderLast = 0; static int RWheelEncoderLast = 0; //记录本次左右编码器数据 LWheelEncoderNow += Encoder_Get_Left(); RWheelEncoderNow += Encoder_Get_Right(); //500ms测速 ( *1000 )单位改为mm/s 变化显示更清楚 *LSpeed = (LWheelEncoderNow - LWheelEncoderLast)* 1000*0.00002722713; *RSpeed = (RWheelEncoderNow - RWheelEncoderLast)* 1000*0.00002722713; //记录上次编码器数据 LWheelEncoderLast = LWheelEncoderNow; RWheelEncoderLast = RWheelEncoderNow; } /* static int 不管在函数内还是函数外,都作为一个全局变量可以保存它被修改以后的值。 而 int 则没有这一功能,只有作为全局变量时能保存修改。放在函数内部时,每次调用都用的是一个新的数。 */
Encoder.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#ifndef __ENCODER_H #define __ENCODER_H //左编码 E1B E1A // PB1 PB0 void Encoder_Init_Left(void); int16_t Encoder_Get_Left(void); //右编码 E2B E1A // PB11 PB10 void Encoder_Init_Right(void); int16_t Encoder_Get_Right(void); void Get_Motor_Speed(int *LSpeed,int *RSpeed); #endif
Timer.c
1
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#include "stm32f10x.h" // Device header //extern uint16_t Num; //引用其他文件(主函数)的Num变量 void TIM4_Timer_Init(u16 arr,u16 psc) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//TIM2 APB1总线上的外设 TIM_InternalClockConfig(TIM4); //选择内部时钟 //CK_CNT_OV = CK_PSC/(PSC+1)/(ARR+1) //预分频给少点 自动重装给多点 以一个比较高的频率计比较多的数 //预分频给多点 自动重装给少点 以一个低的频率计比较少的数 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //配置时基单元 TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频 /1分频 影响不大 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数 TIM_TimeBaseInitStructure.TIM_Period = arr; //周期 ARR自动重装器的值 10000 - 1 TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //PSC预分频器的值 10k的计数频率 计10000的数 7200 - 1 TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值 //高级定时器才有 这里通用计时器 不用 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure); TIM_ClearFlag(TIM4, TIM_FLAG_Update);//手动把更新中断标志位清除一下 防止 reset直接到1 TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //使能中断 update 更新中断 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); //优先级分组 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //3通道 //f10x.h 255行 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM4, ENABLE); //启动定时器 } /* void TIM2_IRQHandler(void) //定时器2 的中断函数 { if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//看更新中断标志位 { Num++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } */
Timer.h
1
2
3
4
5
6
7
8#ifndef __TIMER_H #define __TIMER_H void TIM4_Timer_Init(u16 arr,u16 psc); #endif
main.c
1
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#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Motor.h" #include "Encoder.h" #include "Timer.h" uint8_t KeyNum; int8_t Speed; int16_t NumL; int16_t NumR; int LSpeedNow = 0; int RSpeedNow = 0; int main(void) { OLED_Init(); Motor_Init_Left(); Motor_Init_Right(); Encoder_Init_Left(); Encoder_Init_Right(); TIM4_Timer_Init(5000-1,7200-1);//500ms 定时 OLED_ShowString(1, 1, "VLeft:"); OLED_ShowString(2, 1, "VRight:"); OLED_ShowString(3, 1, "LSpd:"); OLED_ShowString(4, 1, "RSpd:"); while (1) { //KeyNum = Key_GetNum(); /* Delay_ms(1000); KeyNum = 1; if (KeyNum == 1) { Speed += 20; if (Speed > 100) { Speed = -100; } } */ Speed = 10; Motor_SetSpeed_Left(Speed); Motor_SetSpeed_Right(Speed); //NumL += Encoder_Get_Left();//调用这个函数的间隙里,旋转编码器产生的正负脉冲数 OLED_ShowSignedNum(3, 5, LSpeedNow, 3); //NumR += Encoder_Get_Right();//调用这个函数的间隙里,旋转编码器产生的正负脉冲数 OLED_ShowSignedNum(4, 5, RSpeedNow, 3); OLED_ShowSignedNum(1, 8, Speed, 3); OLED_ShowSignedNum(2, 8, Speed, 3); OLED_ShowSignedNum(4, 10, NumL, 2); } } void TIM4_IRQHandler(void) //定时器4 的中断函数 { if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)//看更新中断标志位 { Get_Motor_Speed(&LSpeedNow,&RSpeedNow); TIM_ClearITPendingBit(TIM4, TIM_IT_Update); } }
四倍频的代码(更新)
在原来基础上修改,这里写的四倍频不管向前还是向后转后会加加,理论上按照00 ,01 ,10,11 四倍频也能有方向,但觉得写的太烦,没写,下面的四倍频已实操过可以用来调pid,在本来的脉冲数上*4就行。
1
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//四倍频 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) == SET) { //if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //{ Encoder_Count_Left++; //} EXTI_ClearITPendingBit(EXTI_Line0); } } //B下降沿 A低电平 判断正转 void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) == SET) { //if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //{ Encoder_Count_Left++; //} EXTI_ClearITPendingBit(EXTI_Line1);//清楚标志位 } } void EXTI15_10_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line10) == SET) { //if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //{ Encoder_Count_Right++; //} EXTI_ClearITPendingBit(EXTI_Line10); } B下降沿 A低电平 判断反转 if (EXTI_GetITStatus(EXTI_Line11) == SET) { //if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0) //{ Encoder_Count_Right++; //} EXTI_ClearITPendingBit(EXTI_Line11);//清楚标志位 } }
至此就可以在oled上显示速度了。
最后
以上就是细心手机最近收集整理的关于【一文读懂】如何用编码器测速前言一、AB相编码器计数原理二、要用到的一些参数三、代码如下(更契合原理的代码)的全部内容,更多相关【一文读懂】如何用编码器测速前言一、AB相编码器计数原理二、要用到内容请搜索靠谱客的其他文章。
发表评论 取消回复