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-59-14即通用定时器、高级定时器2.配置通道为编码器模式3.设置滤波参数和极性4.使能定时器和中断优势硬件自动计数不占用CPU自动方向识别支持4倍频提高分辨率。6.信号质量与抗干扰7.思考题二、代码实操stm32实例代码// 不确定是否启用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); #检查编码器是否正在运行#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 编码器IDENCODER_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 } } }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(); // 可选防止编译器过度优化 } }

相关新闻

tech-summary.skill

tech-summary.skill

name: tech-summary description: | 对编程语言、框架、工具等做深度技术总结,输出结构化 Markdown 知识文档。 当用户请求"总结/梳理/整理"某技术知识点、准备技术面试、系统化学习某主题时使用; 简单问答、代码调试、单点概念解释不使用本技能。 disable-model-inv…

2026/10/25 22:11:58 阅读更多 →
为什么 Hermes 上手容易,团队协作时却成了“Bug 制造机”?

为什么 Hermes 上手容易,团队协作时却成了“Bug 制造机”?

聊《Hermes真能提效吗?先看流程里最慢的那一步》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要前两周,我带着团队里的几个初级开发试水了 Hermes。Demo 阶段确实惊艳:输入自…

2026/10/31 16:46:53 阅读更多 →
ELK+FileBeat 7.14.0版本安装、部署及使用

ELK+FileBeat 7.14.0版本安装、部署及使用

框架 简介 作用 Elasticsearch 开源分布式搜索引擎,提供存储、分析、搜索功能。特点:分布式、基于reasful风格、支持海量高并发的准实时搜索场景、稳定、可靠、快速、使用方便等。 接收搜集的海量结构化日志数据,并提供给Kibana查询分析。 Lo…

2026/10/29 13:01:49 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/31 0:19:11 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/31 10:20:19 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/31 10:20:23 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/31 0:59:28 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/31 10:30:19 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/31 9:29:43 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/30 17:32:24 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 11:00:40 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 0:09:01 阅读更多 →