一、编码器
1.什么是编码器

1.增量式编码器-"记步器"
核心:只记录"相对变化量"(走了多少步)
特点:断电后位置丢失,上电需回零。输出连续的脉冲串。
2.绝对式编码器-"坐标尺"
核心:直接读取"唯一位置值"(现在在哪儿)
特点:断电后位置保持,上电即知当前位置,输出特定编码(如二进制、格雷码)
3.混合式编码器-"二合一"
核心:内部是"绝对式"结构,同时输出"绝对位置"和"增量脉冲"两套信号
特点:兼具二者的优点,艮既方便高精度控制,又能与依赖增量信号的系统兼容。

A、B相霍尔编码器原理:磁圈与电机的轴相连,和电机转动速度相同,A、B相感应磁极的变化,将电机转动的信号转换成波形。
2.旋转编码器的原理

A、B两个相既可以记录旋转角度,也可以记录旋转方向。
3.编码器基本参数
分辨率:
指编码器能够分辨的最小单位。
对于增量式编码器,其分辨率表示为编码器转轴旋转一圈所产生的脉冲数,即脉冲数/转(PulsePerRevolution或PPR)。码盘上透分辨率光线槽的数目其实就等于分辨率,也叫多少线,较为常见的有5-6000线。
对于绝对式编码器,内部码盘所用的位数就是它的分辨率,单位是位(bit),具体还分单圈分辨率和多圈分辨率。
PPR:对于上图中的霍尔编码器指的是线圈的磁极数。

精度:
精度与分辨率是两个不同的概念。
精度是指编码器每个读数与转轴实际位置间的最大误差,通常用角度、角分或角秒来表示。精度·例如:±20",表示编码器输出的读数与转轴实际位置之间存在正负20角秒的误差,
精度由码盘刻线加工精度、转轴同心度、材料的温度特性、电路的响应时间等各方面因素共同决定。
最大响应频率:
指编码器每秒输出的脉冲数。单位是 Hz。
最大响应频率=分辨率*轴转速/60。
信号输出形式:
增量式编码器,每个通道的信号独立输出,输出电路形式通常有集电极开路输出、推挽输出、差分输出等。
绝对式编码器,由于是直接输出几十位的二进制数,为了确保传输速率和信号质量,一般采用串行输出或总线型输出,如同步串行接口(SSI)、RS485、CANopen或EtherCAT等,也有一部分是并行输出,输出电路形式与增量式编码器相同。
4.霍尔编码器接线
霍尔传感器三根引脚:VCC(一般接5V供电),GND(电源地),信号线
默认情况下该信号线是低电平的,当有磁场接近时(实际就是要求磁场强度达到一定值后)霍尔传感器的信号线就变为高电平;
如果此时把磁场移开,信号线又变为低电平。
磁环固定在直流电机转轴上的,与减速电机的输出轴是不一样。
编码器使用了铁氧体磁环、两个霍尔传感器
两个霍尔传感器位置与转轴的连线是相差90度的
直流电机轴旋转一圈在霍尔传感器引脚有13个周期的脉冲信号输出
5.编码器接口模式
STM32编码器接口模式,专用硬件解决方案:
配置步骤:
1.选择支持编码器模式的定时器(TIM2-5,9-14,即通用定时器、高级定时器)
2.配置通道为编码器模式
3.设置滤波参数和极性
4.使能定时器和中断
优势:硬件自动计数,不占用CPU;
自动方向识别;
支持4倍频提高分辨率。

6.信号质量与抗干扰

7.思考题

二、代码实操
stm32实例代码
cs
// 不确定是否启用htimX,所以弱定义
__weak TIM_HandleTypeDef htim3 = {0};
__weak TIM_HandleTypeDef htim2 = {0};
__weak TIM_HandleTypeDef htim6 = {0};
// 编码器状态结构体
typedef struct
{
int32_t overflow_count; // 溢出计数(处理16位定时器溢出)
int32_t last_count; // 上次计数值(用于计算速度差值)
int32_t total_count; // 总计数(停止时也保持,用于位置记忆)
float rpm; // 当前转速,单位:转/分钟
uint8_t running; // 编码器运行状态标志(1=运行中,0=已停止)
uint8_t available; // 编码器是否可用标志(1=可用,0=不可用)
TIM_HandleTypeDef *htim; // 对应的定时器句柄
} Encoder_t;
static Encoder_t encoders[2]; // PM1和PM2两个编码器
// 编码器配置
#define ENCODER_PPR 52 // 编码器每转脉冲数(4倍频后的值)
#define GEAR_RATIO 30.0f // 减速比
#define SPEED_UPDATE_MS 100 // 速度更新周期(ms)
// 编码器编号
typedef enum {
ENCODER_PM1 = 0, // PM1接口,使用TIM3
ENCODER_PM2 = 1, // PM2接口,使用TIM2
} Encoder_ID_t;
// 函数声明
void BSP_Encoder_Init(void); # 初始化编码器模块
void BSP_Encoder_Start(Encoder_ID_t encoder_id); #启动指定编码器计数
void BSP_Encoder_Stop(Encoder_ID_t encoder_id); #停止指定编码器计数
void BSP_Encoder_StartAll(void); #启动所有编码器
void BSP_Encoder_StopAll(void); #停止所有编码器
int32_t BSP_Encoder_GetCount(Encoder_ID_t encoder_id); #获取编码器计数
float BSP_Encoder_GetSpeedRPM(Encoder_ID_t encoder_id); #获取转速(RPM)
void BSP_Encoder_HandleOverflow(TIM_HandleTypeDef *htim); #处理编码器溢出
void BSP_Encoder_UpdateSpeed(void); #更新转速计算
void BSP_Encoder_Reset(Encoder_ID_t encoder_id); #重置编码器计数
uint8_t BSP_Encoder_IsAvailable(Encoder_ID_t encoder_id); #检查编码器是否可用
uint8_t BSP_Encoder_IsRunning(Encoder_ID_t encoder_id); #检查编码器是否正在运行
cs
#include "bsp_encoder.h"
#include <math.h>
/**
* @brief 检查定时器是否有效
* @param htim 定时器句柄
* @return 1表示定时器有效,0表示无效
* @note 定时器有效的条件是:句柄不为空,且定时器实例不为空
*/
static uint8_t IsTimerValid(TIM_HandleTypeDef *htim)
{
return (htim != NULL && htim->Instance != NULL);
}
/**
* @brief 初始化编码器模块
* @note 该函数将初始化PM1和PM2编码器,
* 并启动速度计算定时器(50ms中断)
* @return 无
*/
void BSP_Encoder_Init(void)
{
// 初始化PM1编码器
encoders[ENCODER_PM1].htim = &htim3; // 绑定定时器3
encoders[ENCODER_PM1].available = IsTimerValid(&htim3); // 检查定时器有效性
encoders[ENCODER_PM1].running = 0; // 初始状态为停止
encoders[ENCODER_PM1].total_count = 0; // 总计数清零
encoders[ENCODER_PM1].last_count = 0; // 上次计数值清零
encoders[ENCODER_PM1].overflow_count = 0; // 溢出计数清零
encoders[ENCODER_PM1].rpm = 0.0f; // 转速初始化为0
// 初始化PM2编码器
encoders[ENCODER_PM2].htim = &htim2; // 绑定定时器2
encoders[ENCODER_PM2].available = IsTimerValid(&htim2); // 检查定时器有效性
encoders[ENCODER_PM2].running = 0; // 初始状态为停止
encoders[ENCODER_PM2].total_count = 0; // 总计数清零
encoders[ENCODER_PM2].last_count = 0; // 上次计数值清零
encoders[ENCODER_PM2].overflow_count = 0; // 溢出计数清零
encoders[ENCODER_PM2].rpm = 0.0f; // 转速初始化为0
// 启动速度计算定时器(50ms中断)
if (IsTimerValid(&htim6))
{
HAL_TIM_Base_Start_IT(&htim6); // 启动定时器6的中断模式
}
}
/**
* @brief 检查编码器是否可用
* @param encoder_id 编码器ID(ENCODER_PM1或ENCODER_PM2)
* @return 1表示可用,0表示不可用
* @note 如果编码器ID无效,将返回0
*/
uint8_t BSP_Encoder_IsAvailable(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
return encoders[encoder_id].available; // 返回可用状态
}
return 0;
}
/**
* @brief 检查编码器是否正在运行
* @param encoder_id 编码器ID
* @return 1表示正在运行,0表示未运行
* @note 如果encoder_id无效,将返回0
*/
uint8_t BSP_Encoder_IsRunning(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
return encoders[encoder_id].running;
}
return 0;
} #####################两个同时启动要改代码
/**
* @brief 启动编码器
* @param encoder_id 编码器ID
* @note 如果encoder_id无效,不做任何操作
* @return 无
*/
void BSP_Encoder_Start(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
// 检查编码器是否可用且未运行
if (encoders[encoder_id].available && !encoders[encoder_id].running)
{
// 启动编码器定时器(所有通道)
HAL_StatusTypeDef status = HAL_TIM_Encoder_Start(encoders[encoder_id].htim, TIM_CHANNEL_ALL);
if (status == HAL_OK) // 启动成功
{
// 使能更新中断(用于处理溢出)
__HAL_TIM_ENABLE_IT(encoders[encoder_id].htim, TIM_IT_UPDATE);
encoders[encoder_id].running = 1; // 标记为运行状态
// 注意:这里不重置任何计数器!
// 计数器只在Init或显式调用Reset时重置
}
}
}
}
/**
* @brief 停止编码器
* @param encoder_id 编码器ID
* @note 如果encoder_id无效,不做任何操作
* @return 无
*/
void BSP_Encoder_Stop(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
if (encoders[encoder_id].available && encoders[encoder_id].running)
{
// 先更新总计数(保存当前计数值),再停止
encoders[encoder_id].total_count = BSP_Encoder_GetCount(encoder_id);
// 停止编码器定时器
HAL_TIM_Encoder_Stop(encoders[encoder_id].htim, TIM_CHANNEL_ALL);
// 禁用更新中断
__HAL_TIM_DISABLE_IT(encoders[encoder_id].htim, TIM_IT_UPDATE);
encoders[encoder_id].running = 0; // 标记为停止状态
}
}
}
/**
* @brief 启动所有编码器
* @note 该函数会依次启动PM1和PM2编码器
*/
void BSP_Encoder_StartAll(void)
{
for (Encoder_ID_t i = ENCODER_PM1; i <= ENCODER_PM2; i++)
{
BSP_Encoder_Start(i);
}
}
/**
* @brief 停止所有编码器
* @note 该函数会依次停止PM1和PM2编码器
*/
void BSP_Encoder_StopAll(void)
{
for (Encoder_ID_t i = ENCODER_PM1; i <= ENCODER_PM2; i++)
{
BSP_Encoder_Stop(i);
}
}
/**
* @brief 重置编码器,将指定编码器的计数器清零
* @param encoder_id 编码器ID
* @note 如果encoder_id无效,不做任何操作
* @return 无
*/
void BSP_Encoder_Reset(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
if (encoders[encoder_id].available)
{
// 如果编码器正在运行,直接清零硬件计数器
if (encoders[encoder_id].running)
{
__HAL_TIM_SET_COUNTER(encoders[encoder_id].htim, 0);
}
// 清零所有软件计数值
encoders[encoder_id].overflow_count = 0; // 溢出计数清零
encoders[encoder_id].last_count = 0; // 上次计数清零
encoders[encoder_id].total_count = 0; // 总计数清零
encoders[encoder_id].rpm = 0.0f; // 转速清零
}
}
}
/**
* @brief 获取编码器的总计数(支持溢出处理)
* @param encoder_id 编码器ID
* @return 编码器的总计数
* @note 如果encoder_id无效,将返回0
* @note 总计数 = 当前计数器值 + 溢出计数 × 65536
*/
int32_t BSP_Encoder_GetCount(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
if (encoders[encoder_id].available)
{
if (encoders[encoder_id].running)
{
// 运行中:读取当前计数值,加上溢出部分
int32_t current_count = __HAL_TIM_GET_COUNTER(encoders[encoder_id].htim);
return current_count + encoders[encoder_id].overflow_count * 65536;
}
else
{
// 停止状态:返回保存的总计数
return encoders[encoder_id].total_count;
}
}
}
return 0;
}
/**
* @brief 获取编码器的转速
* @param encoder_id 编码器ID
* @return 编码器的转速,单位:转/分钟(RPM)
* @note 如果encoder_id无效,将返回0
*/
float BSP_Encoder_GetSpeedRPM(Encoder_ID_t encoder_id)
{
if (encoder_id <= ENCODER_PM2)
{
// 无论是否运行,都返回计算出的转速
// 停止时rpm为0,这是正确的
return encoders[encoder_id].rpm;
}
return 0.0f;
}
/**
* @brief 处理编码器溢出中断
* @param htim 触发中断的编码器定时器
* @note 该函数在定时器更新中断中被调用
* @note 根据计数方向增加或减少溢出计数
*/
void BSP_Encoder_HandleOverflow(TIM_HandleTypeDef *htim)
{
// 遍历所有编码器,找到匹配的定时器
for (Encoder_ID_t i = ENCODER_PM1; i <= ENCODER_PM2; i++)
{
if (encoders[i].available && encoders[i].running &&
encoders[i].htim->Instance == htim->Instance)
{
// 判断计数方向:向下计数则溢出计数减1,向上计数则加1
if (__HAL_TIM_IS_TIM_COUNTING_DOWN(encoders[i].htim))
{
encoders[i].overflow_count--; // 向下溢出
}
else
{
encoders[i].overflow_count++; // 向上溢出
}
break; // 找到匹配的编码器后退出循环
}
}
}
/**
* @brief 更新编码器速度
* @note 本函数更新所有编码器的速度值,应在定时器中断中周期调用
* @note 速度计算公式:RPM = (脉冲数 × (60000/采样时间毫秒)) ÷ (减速比 × 每转脉冲数)
* @return 无
*/
void BSP_Encoder_UpdateSpeed(void)
{
for (Encoder_ID_t i = ENCODER_PM1; i <= ENCODER_PM2; i++)
{
if (encoders[i].available && encoders[i].running)
{
// 获取当前总计数
int32_t current_count = BSP_Encoder_GetCount(i);
// 计算与上次采样时的差值
int32_t delta_count = current_count - encoders[i].last_count;
if (delta_count != 0)
{
// 使用与正点原子相同的公式原理
// RPM = (脉冲数 × (60000/采样时间ms)) ÷ 减速比 ÷ PPR
// 60000/采样时间 = 60000/50 = 1200
encoders[i].rpm = (delta_count * (60000.0f / SPEED_UPDATE_MS)) / (GEAR_RATIO * ENCODER_PPR);
}
else
{
encoders[i].rpm = 0.0f; // 没有脉冲变化,转速为0
}
encoders[i].last_count = current_count; // 更新上次计数值
}
else
{
encoders[i].rpm = 0.0f; // 编码器不可用或未运行,转速为0
}
}
}
cs
void DWT_Init(void)
{
if ((CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk) == 0U)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
}
DWT->CYCCNT = 0U;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
/**
* @brief Microsecond delay using DWT cycle counter
* @param us: delay time in microseconds
* @note SystemCoreClock must be correctly configured
*/
void delay_us(uint32_t us)
{
uint32_t startTick = DWT->CYCCNT;
uint32_t delayTicks = us * (SystemCoreClock / 1000000U);
while ((DWT->CYCCNT - startTick) < delayTicks)
{
__NOP(); // 可选:防止编译器过度优化
}
}