1. 编码电机的整体结构
-
有刷直流电机(DC Motor)
-
定子产生恒定磁场(永久磁铁)
-
转子绕组通电产生电磁力矩,使电机旋转
-
轴端通常固定一块多极磁铁,用于编码检测
-
-
减速箱(Gearbox)
-
通过齿轮传动降低输出转速、提升扭矩
-
减速比越大 → 输出轴更慢 → 扭矩越大
-
-
霍尔编码器(Hall Encoder)
-
位于电机尾部
-
一块圆形多极磁钢固定在电机轴上
-
两个霍尔开关器件间隔 90° 电角度布置
-
随着旋转产生两路方波输出(A、B相)
-
2. 编码器内部结构
(1) 磁钢
电机后端的磁钢经过多级磁化,如:
➡ N S N S N S ...(通常 6、8、12 极)
每转过一个极性,霍尔传感器的输出会发生一次跳变(高/低转换)。
(2) 霍尔传感器对(A、B 相)
两个霍尔开关之间的中心位置错开 90° 电角度(不是几何角度)。
- 当磁钢旋转时,A 相、B 相分别输出一系列方波
- 由于位置错开 90°,两个信号呈**正交(Quadrature)**关系
3. 90° 相位差的意义:方向判断
➤ 正转时
A 相领先 B 相(A 在前,B 在后)
➤ 反转时
B 相领先 A 相
4. 位置与速度的测量原理
(1) 位置(位移)测量
每一个 A/B 相的跳变称为 脉冲。
例:
-
磁钢 = 6 对磁极(12 个高低变化)
-
经过减速箱 1:30
-
则输出轴每转产生:
12×30=360 脉冲12 \times 30 = 360 \text{ 脉冲}12×30=360 脉冲
各厂家会给出一个 PPR(Pulse Per Revolution),表示编码器每转的脉冲数。
(2) 速度测量
编码器本质上测"位移",但通过定时读取脉冲数即可转换为速度。
两种常见测速方式:
方法 A:定时计数法
每隔 T 秒读取一次过去的脉冲数 N:

举例:设置一个1ms的定时器中断,在中断里读取编码器的值,读完后清零。在主函数里除0.001即可。
方法 B:周期测量法
测量相邻脉冲间时间 Δt:

高速电机一般用周期法,更准确。
5. 为什么加减速箱会提升测量精度?
编码器装在电机轴上,而不是输出轴上。
减速箱让输出轴转一圈时,电机轴其实转了很多圈。
例:
减速比 1:50
编码器原本每转 12 脉冲
输出轴的有效分辨率:

➡ 分辨率显著提高,位置控制更精准
6. 编码电机工作流程总结
-
电机轴带动磁钢旋转
-
两个霍尔传感器检测磁场变化
-
输出两路方波 A/B,相位相差 90°
-
MCU 通过计数 A/B 的上升沿/下降沿得到转角
-
通过 A/B 先后顺序判断方向
-
通过计时统计脉冲数得到速度
所以编码电机能实现:
-
速度反馈(闭环)
-
位置控制
-
正反转识别
-
距离测量
七.编码器驱动编写(基于stm32)
1. Encoder_Init:初始化编码器接口
① 开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
-
给 TIM3 和 GPIOA 开时钟
-
编码器信号 A/B 接在:PA6 / PA7 → TIM3_CH1 / TIM3_CH2
② GPIO 配置为上拉输入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 输入上拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // PA6, PA7
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
-
编码器 A/B 相一般是 开集电极/推挽输出+上拉电阻 ,所以 MCU 端设为 上拉输入 很合适
-
只做输入捕获,不用复用推挽
③ 定时器基本参数(计数范围)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; // 0~65535
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; // 预分频=0
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
-
这里设置 CNT 计数范围为 16 位 (0 ~ 65535)
-
预分频为 0,即编码器每来一个有效沿,定时器就+1 或 -1(不被分频)
注意:虽然这里写了
TIM_CounterMode_Up,但进了编码器模式后,方向是由 A/B 相相位关系自动控制的,硬件会自动 Up/Down,不用你管。
④ 输入捕获通道(滤波)
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
-
给 CH1/CH2(也就是 A/B 相)配置输入捕获
-
TIM_ICFilter = 0xF:开启最大数字滤波,能滤掉抖动和毛刺(对机械式编码器很有用)
⑤ 关键一步:编码器接口模式
TIM_EncoderInterfaceConfig(TIM3,
TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Falling);
-
TIM_EncoderMode_TI12:使用 TI1 和 TI2 两路信号(正交编码器 A/B) -
后两个参数是两路输入的极性:
-
CH1:上升沿有效
-
CH2:下降沿有效
-
一般常见写法是两个都用 TIM_ICPolarity_Rising,你这个写法也可以工作,只是对边沿的选择方式不同。
作用:
硬件自动根据 A/B 相相位关系判断转向(正反转)
自动对 CNT 做 加/减计数
⑥ 使能定时器
TIM_Cmd(TIM3, ENABLE);
至此,TIM3 已经变成一个硬件正交编码器计数器:
-
A/B 相接进来 → CNT 自动 +1 / -1
-
不用中断、不用软件判断方向
2. Encoder_Get:获取编码器"增量"
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
return Temp;
}
这段非常常见,是一个 "读增量+清零" 的写法。
机制解释:
-
TIM_GetCounter(TIM3)读取的是 当前 CNT 值(无符号 16 位) -
然后立刻执行
TIM_SetCounter(TIM3, 0)归零 -
你把这个无符号的值放进
int16_t,产生一个小技巧:
✅ 情况 1:编码器正向转动,CNT 比较小
比如 CNT = 100:
-
读到 100,cast 成
int16_t→ 100 -
清零 → 下次从 0 继续计
→ 表示这段时间 向前转了 +100 个脉冲
✅ 情况 2:编码器反向转动(CNT 会往下数)
在定时器内部是 0~65535 环形:
-
假设你从 0 开始往反方向动了一点,CNT 可能变成 65535、65534、...
-
例如 CNT = 65535,读出来放进
int16_t:-
65535 的二进制是
0xFFFF -
解释为
int16_t就是 -1→ 表示这段时间 向后转了 -1 个脉冲
-
只要确保两次读取之间的脉冲数小于 32768,就能正确区分正负方向。
所以 Encoder_Get() 函数的含义是:
"从上一次调用到现在,编码器增量的脉冲数(有符号,正负表示方向)"
你要算总位移,就累加它:
int32_t Position = 0;
void Loop(void)
{
int16_t delta = Encoder_Get();
Position += delta;
// Position 就是当前位置对应的总脉冲数
}