start9 霍尔双相编码器

一、编码器

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(); // 可选:防止编译器过度优化
    }
}
相关推荐
兆龙电子单片机设计2 小时前
【STM32项目开源】STM32单片机语音分类垃圾桶-蓝牙APP
stm32·单片机·嵌入式硬件·物联网·开源·自动化·毕业设计
零涂毕业设计3 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
ye150127774554 小时前
220V转5V1A隔离芯片WT5602
单片机·嵌入式硬件·其他·硬件工程
ye150127774556 小时前
DC24V-100V转3.3V500mA隔离芯片WT5602
单片机·嵌入式硬件·其他·硬件工程
Lumos6fly6 小时前
51单片机从零到实战(15)——数字时钟温度计
单片机·嵌入式硬件·51单片机
碎光拾影10 小时前
I2C总线通信全解析:从原理到实战
单片机·嵌入式硬件
愈心凌10 小时前
SSI 绝对值编码器通信 - 单片机TM32Cubemax配置教程
单片机·嵌入式硬件
科芯创展10 小时前
1.5A,5.5VIN,单灯,XZ4059A/D,4.2V/4.34V
单片机
江哥工作室11 小时前
几分钱的 LM358 采集心电
单片机·lm358·心电·小信号