stm32:编码电机原理

1. 编码电机的整体结构

  1. 有刷直流电机(DC Motor)

    • 定子产生恒定磁场(永久磁铁)

    • 转子绕组通电产生电磁力矩,使电机旋转

    • 轴端通常固定一块多极磁铁,用于编码检测

  2. 减速箱(Gearbox)

    • 通过齿轮传动降低输出转速、提升扭矩

    • 减速比越大 → 输出轴更慢 → 扭矩越大

  3. 霍尔编码器(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. 编码电机工作流程总结

  1. 电机轴带动磁钢旋转

  2. 两个霍尔传感器检测磁场变化

  3. 输出两路方波 A/B,相位相差 90°

  4. MCU 通过计数 A/B 的上升沿/下降沿得到转角

  5. 通过 A/B 先后顺序判断方向

  6. 通过计时统计脉冲数得到速度

所以编码电机能实现:

  • 速度反馈(闭环)

  • 位置控制

  • 正反转识别

  • 距离测量

七.编码器驱动编写(基于stm32)

1. Encoder_Init:初始化编码器接口

① 开时钟

复制代码
​​RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  • TIM3GPIOA 开时钟

  • 编码器信号 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 就是当前位置对应的总脉冲数
}
相关推荐
Darken031 小时前
STM32中的主从触发模式是什么?
stm32·单片机·tim
编程墨客1 小时前
STM32 HAL库实战指南:从零开始构建嵌入式应用系统
stm32·单片机·嵌入式硬件
就是蠢啊2 小时前
51单片机——DS1302 时钟芯片(一)
单片机·嵌入式硬件·51单片机
淘晶驰AK2 小时前
电机控制是不是就绑死在单片机上了
单片机·嵌入式硬件
CS Beginner2 小时前
【单片机】orange prime pi开发板与单片机的区别
笔记·嵌入式硬件·学习
bubiyoushang88812 小时前
基于STM32F103与A3988驱动芯片的两相四线步进电机控制方案
stm32·单片机·嵌入式硬件
bai54593615 小时前
STM32 备份寄存器
stm32·单片机·嵌入式硬件
cold_Mirac15 小时前
stm32-freertos和逻辑编程下堆栈功能的区分
stm32·单片机·嵌入式硬件
youcans_16 小时前
【动手学STM32G4】(3)上位机实时显示多路波形
stm32·单片机·嵌入式硬件·上位机