1. 正交编码器(QEI)核心原理与设计思路拆解
在嵌入式运动控制领域,无论是驱动一个精密的机械臂关节,还是控制一台3D打印机的步进电机,我们都需要一个“眼睛”来告诉控制器:“电机轴现在转到了哪里?它转得有多快?” 这个“眼睛”就是正交编码器。它不是我们常见的摄像头,而是一种将机械旋转的模拟量转换为数字脉冲信号的传感器。而正交编码器接口,则是微控制器内部专门用来“读懂”这双眼睛的硬件模块。
你可能用过简单的按键或旋转编码器,它们通常只有一个信号通道,只能告诉你“有转动”,但无法分辨是顺时针还是逆时针。正交编码器的精妙之处在于,它输出了两个相位差90度的方波信号,通常标记为通道A和通道B。想象一下两个人并排走路,他们的步伐总是错开半步。通过观察谁先迈步(哪个通道的边沿先出现),以及他们迈步的顺序,我们就能精确判断行走的方向和步数。这就是正交编码的基本思想:利用两个信号的相位关系来编码方向和位移。
在硬件层面,QEI模块的价值在于将这种边沿检测、方向判断和脉冲计数的繁重任务从软件中剥离,交由专用硬件电路完成。CPU只需要在需要的时候去读取一个位置计数器和一个速度寄存器即可,这极大地解放了CPU资源,使得系统能够实现高实时性、高精度的闭环控制。尤其是在处理高速旋转时,如果靠软件中断来计数每一个脉冲,CPU很可能被“淹死”,而硬件QEI模块则能轻松应对。
Stellaris(现属于TI的Tiva系列)微控制器的QEI模块设计得非常周全。它支持两种工作模式,以适应不同类型的编码器传感器:
- 正交相位模式:这就是我们上面提到的经典模式,编码器直接输出A、B两路相位差90度的方波。模块可以配置为仅对A通道的边沿计数,或者对A、B两个通道的边沿都计数。后者被称为“4倍频”模式,因为在一个完整的信号周期内(A和B各经历一次上升沿和下降沿),可以产生4个计数脉冲,从而将编码器的物理分辨率提高4倍。这对于提升系统精度至关重要。
- 时钟/方向模式:有些编码器或运动控制器会直接输出两路信号:一路是脉冲时钟,每个脉冲代表一个步进;另一路是方向电平信号,高电平代表正转,低电平代表反转。QEI模块也能很好地适配这种信号。
此外,模块还提供了一个索引信号输入。你可以把它想象成旋转圆盘上的一个“零位”标记。每当轴旋转一圈,索引信号会产生一个脉冲。QEI模块可以利用这个脉冲将位置计数器复位,从而实现绝对位置的跟踪。如果没有索引信号,位置计数器只会从0开始累加或递减,我们只知道相对于某个起点的位移,这就是相对位置模式。
速度测量则是通过一个巧妙的“定时采样”方法实现的。模块内部有一个定时器,每隔一段固定的时间(比如1ms),它就会“拍一张快照”,记录下在这段时间内累计的编码器脉冲数。这个脉冲数直接反映了在该时间段内的平均速度。读取这个值,再结合编码器每转的脉冲数,就能轻松换算出转速。
2. QEI模块核心功能与API函数深度解析
理解了原理,我们来看如何用代码驾驭它。Stellaris的驱动库提供了一组以ROM_QEI开头的函数,它们实际上是固化在芯片ROM中的优化过的驱动代码,执行效率比Flash中的代码更高。下面我们逐一拆解这些核心API,并解释其背后的硬件行为。
2.1 模块的配置与使能:ROM_QEIConfigure与ROM_QEIEnable
任何外设的使用,配置都是第一步。ROM_QEIConfigure函数就是QEI模块的“总设计师”。
void ROM_QEIConfigure(unsigned long ulBase, unsigned long ulConfig, unsigned long ulMaxPosition);ulBase: QEI模块的基地址,例如QEI0_BASE或QEI1_BASE。这告诉函数你要操作哪个具体的QEI模块。ulConfig: 这是一个位掩码参数,通过逻辑或操作组合多个配置选项,决定了模块的“性格”。- 信号捕获模式:
QEI_CONFIG_CAPTURE_A: 只捕获通道A的边沿。这是最基础的配置,分辨率等于编码器的线数。QEI_CONFIG_CAPTURE_A_B: 捕获通道A和B的所有边沿(上升沿和下降沿)。这就是实现4倍频的关键配置,将分辨率提升至编码器线数的4倍。例如,一个100线的编码器,在此模式下每转可产生400个计数。
- 索引信号行为:
QEI_CONFIG_NO_RESET: 检测到索引脉冲时,位置计数器不复位。模块工作在相对位置模式。QEI_CONFIG_RESET_IDX: 检测到索引脉冲时,位置计数器复位。这用于建立绝对位置坐标系。例如,在打印机初始化时,会让字车移动到“原点”传感器处,此时索引信号触发,位置计数器归零,之后的所有位置都是相对于这个原点的绝对位置。
- 工作模式:
QEI_CONFIG_QUADRATURE: 正交相位模式,接收A、B两路相位信号。QEI_CONFIG_CLOCK_DIR: 时钟/方向模式,接收时钟和方向两路信号。
- 信号交换:
QEI_CONFIG_NO_SWAP: A、B通道信号不交换。QEI_CONFIG_SWAP: 交换A、B通道的输入。这个功能非常实用!当你在画PCB板时不小心把A、B信号线接反了,导致电机转向与预期相反,你不需要重新焊接,只需要在软件中启用这个选项,就能纠正方向。
- 信号捕获模式:
ulMaxPosition: 位置计数器的最大值。当位置计数器达到此值并继续增加时,它会自动翻转到0。更重要的是,在索引复位模式下反向旋转时,计数器会复位到这个值。这通常设置为编码器在4倍频模式下一整圈的计数值减一(例如,100线编码器在4倍频下,ulMaxPosition = 100*4 - 1 = 399),这样位置值总是在[0, ulMaxPosition]的范围内循环,便于处理。
配置心得:在电机控制中,我强烈建议使用
QEI_CONFIG_CAPTURE_A_B以获得最高分辨率。ulMaxPosition的设置需要与你的机械系统匹配。如果你的运动范围小于一圈,可以将其设为一个很大的数(如0xFFFFFFFF),让计数器自由累加。如果运动是多圈的,并且需要索引信号来校正累积误差,那么QEI_CONFIG_RESET_IDX就非常关键。
配置完成后,必须调用ROM_QEIEnable来启动模块。硬件计数器开始工作,但速度捕获功能需要单独使能。
2.2 位置与方向的获取:ROM_QEIPositionGet与ROM_QEIDirectionGet
这是最常用的两个函数,用于读取实时状态。
unsigned long ROM_QEIPositionGet(unsigned long ulBase); long ROM_QEIDirectionGet(unsigned long ulBase);ROM_QEIPositionGet直接返回位置计数器的当前值。这个值是一个无符号长整型。在相对位置模式下,它可能是一个很大的数(连续旋转的结果)。在绝对位置模式下,它会在0到ulMaxPosition之间循环。ROM_QEIDirectionGet返回一个长整型,其值为1(正向)或-1(反向)。这里有个细节需要注意:文档说明它返回的是“最近检测到的”方向。这意味着即使电机此刻已停止,这个函数返回的仍是它停止前最后一刻的运动方向。这对于判断电机的最后运动趋势很有用。
2.3 速度捕获的配置与读取:ROM_QEIVelocityConfigure,ROM_QEIVelocityEnable,ROM_QEIVelocityGet
速度测量是闭环控制(如PID速度环)的核心反馈。QEI模块的速度捕获功能需要三步走:配置、使能、读取。
void ROM_QEIVelocityConfigure(unsigned long ulBase, unsigned long ulPreDiv, unsigned long ulPeriod); void ROM_QEIVelocityEnable(unsigned long ulBase); unsigned long ROM_QEIVelocityGet(unsigned long ulBase);ROM_QEIVelocityConfigure:这是速度测量的“节拍器”。ulPreDiv: 预分频系数。编码器原始脉冲会先经过这个分频器。可选QEI_VELDIV_1, 2, 4, 8, 16, 32, 64, 128。如果你的编码器分辨率很高(比如每转几千个脉冲),电机高速旋转时脉冲频率会非常快,可能导致计数器溢出。这时就需要预分频,例如设为QEI_VELDIV_4,表示每4个脉冲才计1个数,降低了计数频率。ulPeriod: 采样周期,单位是系统时钟周期数。这是速度测量的“时间窗口”长度。例如,系统时钟为50MHz,设置ulPeriod = 50000,那么采样时间窗口就是50000 / 50,000,000 Hz = 1 ms。模块会在这个1ms窗口内,累计经过预分频后的脉冲数。
ROM_QEIVelocityEnable:配置好后,调用此函数启动速度捕获定时器。注意:速度捕获功能依赖于位置捕获功能,因此必须先使能QEI模块 (ROM_QEIEnable)。ROM_QEIVelocityGet:读取速度值。这个函数返回的是上一个完整采样周期内累计的脉冲数。这是一个稳定的值。模块内部采用双缓冲机制:一个计数器用于当前周期的累计,另一个寄存器锁存上一个周期的结果。当你调用ROM_QEIVelocityGet时,读取的是锁存器的值,不会影响当前正在进行的计数。
速度计算示例: 假设编码器为1000线(每转1000个脉冲),使用4倍频模式,预分频QEI_VELDIV_1,采样周期ulPeriod配置为在50MHz系统时钟下产生10ms窗口。
- 物理分辨率:
1000 线/转 * 4 = 4000 计数/转。 - 如果
ROM_QEIVelocityGet返回值为120。 - 这意味着在过去的10ms内,累计了120个计数。
- 转速 =
(120 计数 / 10ms) * (1000 ms/s) / (4000 计数/转) = (12000 计数/s) / (4000 计数/转) = 3 转/秒 (RPS)。 - 换算成常用单位 RPM(转/分钟):
3 RPS * 60 = 180 RPM。
避坑指南:速度测量的精度和响应速度是一对矛盾。
ulPeriod设得越长,对单个采样周期内的噪声(如抖动)越不敏感,测量值越平滑,但速度更新的频率越低,系统响应变慢。设得太短,则更新快,但单个采样值可能因脉冲计数少而波动大。通常需要在控制环路周期(如PID的1ms)和测量稳定性之间折衷。一个技巧是:在软件中做滑动平均滤波,用短周期采样,然后在软件中对连续几个采样值求平均。
2.4 中断管理:状态、使能与清除
QEI模块可以在特定事件发生时产生中断,让CPU及时响应,而不是一味地轮询。
unsigned long ROM_QEIIntStatus(unsigned long ulBase, tBoolean bMasked); void ROM_QEIIntEnable(unsigned long ulBase, unsigned long ulIntFlags); void ROM_QEIIntDisable(unsigned long ulBase, unsigned long ulIntFlags); void ROM_QEIIntClear(unsigned long ulBase, unsigned long ulIntFlags);- 中断源:
QEI_INTINDEX: 检测到索引脉冲。用于绝对位置归零或圈数计数。QEI_INTTIMER: 速度捕获定时器到期。可以用于定期读取速度值。QEI_INTDIR: 旋转方向发生变化。这在某些需要检测反转的应用中很有用。QEI_INTERROR: 相位错误。当A、B两通道在同一时刻发生跳变时触发,通常表明编码器信号受到严重干扰或硬件故障。
ROM_QEIIntStatus:查询中断状态。bMasked参数为true时,返回已使能且正在发生的中断;为false时,返回所有硬件中断源的状态,无论是否使能。ROM_QEIIntEnable/Disable:使能或禁用特定的中断源。通常,我们只关心索引中断和定时器中断。ROM_QEIIntClear:至关重要!在中断服务函数中,处理完中断事件后,必须调用此函数清除对应的中断标志位。否则,CPU会认为中断一直存在,导致中断服务函数被连续不断地调用,系统卡死。文档特别提醒,由于Cortex-M处理器的写缓冲,清除操作可能需要几个时钟周期,建议在中断服务函数的开头就进行清除。
2.5 辅助函数:错误获取与位置设置
tBoolean ROM_QEIErrorGet(unsigned long ulBase): 读取相位错误状态。如果返回true,表明曾发生过A、B信号同时跳变的错误。这个错误标志需要手动清除(通过ROM_QEIIntClear(QEI_INTERROR))或复位模块。void ROM_QEIPositionSet(unsigned long ulBase, unsigned long ulPosition): 手动设置位置计数器的值。这在系统初始化、手动校准或位置预设时非常有用。例如,在已知机械原点的情况下,可以直接将位置设为0。
3. 从零搭建:一个完整的QEI应用实战流程
理论说再多,不如动手做一遍。下面我将以一个典型的直流有刷伺服电机控制项目为例,展示如何从硬件连接到软件驱动,完整地实现QEI功能。
3.1 硬件连接与注意事项
首先,将正交编码器的输出线连接到微控制器。通常编码器会有5根线:
- VCC(3.3V或5V) -> 连接到MCU的相同电压电源,并确保共地。
- GND-> MCU的GND。
- 通道A-> 连接到MCU指定的QEIxA引脚(如
PE4)。 - 通道B-> 连接到MCU指定的QEIxB引脚(如
PE5)。 - 索引Z-> 连接到MCU指定的QEIIx引脚(如
PE6)。如果不需要绝对位置,此引脚可以不接。
硬件经验:
- 上拉电阻:编码器输出通常是集电极开路或推挽输出。为确保信号稳定,特别是当编码器线缆较长时,建议在MCU端的A、B、Z信号线上各接一个4.7kΩ - 10kΩ的上拉电阻到VCC。
- 噪声滤波:在工业环境或电机附近,电磁干扰严重。可以在信号线上串联一个几十欧姆的电阻,并在MCU引脚到地之间接一个100pF的小电容,构成一个简单的RC低通滤波器,滤除高频毛刺。
- 电源隔离:如果电机功率较大,强烈建议将编码器的电源与MCU的电源通过磁珠或DC-DC隔离模块进行隔离,防止电机启停时的浪涌电流干扰数字电路。
3.2 软件驱动层实现
我们基于TI的TivaWare库进行开发。首先,创建一个qei_driver.c/h文件来封装底层操作。
qei_driver.h(头文件定义)
#ifndef QEI_DRIVER_H_ #define QEI_DRIVER_H_ #include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "driverlib/rom.h" #include "driverlib/qei.h" #include "driverlib/sysctl.h" // QEI模块配置结构体,便于管理参数 typedef struct { uint32_t base; // QEI模块基地址,如 QEI0_BASE uint32_t config; // 配置掩码 uint32_t maxPosition; // 位置计数器最大值 uint32_t velPreDiv; // 速度预分频 uint32_t velPeriod; // 速度采样周期(系统时钟数) void (*indexIsr)(void); // 索引中断服务函数指针 void (*timerIsr)(void); // 速度定时器中断服���函数指针 } QEI_Config_t; // 初始化函数 bool QEI_Init(const QEI_Config_t *config); // 获取位置(返回有符号长整型,便于处理溢出和方向) int32_t QEI_GetPosition(void); // 获取方向 int32_t QEI_GetDirection(void); // 获取速度(脉冲数/采样周期) uint32_t QEI_GetVelocity(void); // 设置位置(用于校准) void QEI_SetPosition(int32_t position); // 检查错误 bool QEI_IsError(void); #endif /* QEI_DRIVER_H_ */qei_driver.c(驱动实现)
#include "qei_driver.h" static QEI_Config_t s_qeiConfig; bool QEI_Init(const QEI_Config_t *config) { if (config == NULL) return false; // 1. 保存配置 s_qeiConfig = *config; // 2. 使能QEI模块的时钟和外设(以QEI0为例) SysCtlPeripheralEnable(SYSCTL_PERIPH_QEI0); // 等待外设就绪(良好习惯) while(!SysCtlPeripheralReady(SYSCTL_PERIPH_QEI0)) {} // 3. 配置QEI引脚(需要根据具体芯片数据手册映射) // 假设PE4, PE5, PE6用作QEI0 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); GPIOPinTypeQEI(GPIO_PORTE_BASE, GPIO_PIN_4 | GPIO_PIN_5); // A, B通道 GPIOPinTypeQEI(GPIO_PORTE_BASE, GPIO_PIN_6); // 索引通道 // 4. 配置QEI模块核心参数 ROM_QEIConfigure(s_qeiConfig.base, s_qeiConfig.config, s_qeiConfig.maxPosition); // 5. 配置速度捕获 ROM_QEIVelocityConfigure(s_qeiConfig.base, s_qeiConfig.velPreDiv, s_qeiConfig.velPeriod); ROM_QEIVelocityEnable(s_qeiConfig.base); // 6. 配置中断(如果提供了中断服务函数) if (s_qeiConfig.indexIsr != NULL) { ROM_QEIIntEnable(s_qeiConfig.base, QEI_INTINDEX); // 这里需要将s_qeiConfig.indexIsr注册到向量表,具体方法取决于你的RTOS或裸机框架 // 例如:IntRegister(INT_QEI0, s_qeiConfig.indexIsr); // IntEnable(INT_QEI0); } if (s_qeiConfig.timerIsr != NULL) { ROM_QEIIntEnable(s_qeiConfig.base, QEI_INTTIMER); // 注册定时器中断... } // 7. 最后,使能QEI模块 ROM_QEIEnable(s_qeiConfig.base); return true; } int32_t QEI_GetPosition(void) { // 直接读取硬件计数器 return (int32_t)ROM_QEIPositionGet(s_qeiConfig.base); } int32_t QEI_GetDirection(void) { return ROM_QEIDirectionGet(s_qeiConfig.base); } uint32_t QEI_GetVelocity(void) { return ROM_QEIVelocityGet(s_qeiConfig.base); } void QEI_SetPosition(int32_t position) { // 注意:ulPosition参数是无符号的,但我们的接口是有符号的。 // 这里需要进行转换,确保设置的值在[0, maxPosition]范围内。 uint32_t posToSet; if (position < 0) { // 处理负位置:通常转换为相对于maxPosition的补码形式 posToSet = s_qeiConfig.maxPosition + 1 + position; // 假设maxPosition是最大值 } else { posToSet = (uint32_t)position; } ROM_QEIPositionSet(s_qeiConfig.base, posToSet); } bool QEI_IsError(void) { return ROM_QEIErrorGet(s_qeiConfig.base); } // 示例中断服务函数 void QEI0_Index_ISR(void) { // 1. 清除中断标志(尽早清除) ROM_QEIIntClear(QEI0_BASE, QEI_INTINDEX); // 2. 处理索引事件 // 例如:记录圈数,或执行归零动作 // g_encoderRevolutions++; // 圈数加一 // 或者,如果配置了索引复位,位置已经自动归零,这里可以记录一个“归零完成”标志 // g_homeFlag = true; // 3. 其他处理... }3.3 应用层:一个简单的速度PID控制循环
假设我们有一个电机驱动任务,运行在1kHz(1ms周期)的频率下。我们需要用QEI反馈的速度进行PID控制。
#include "qei_driver.h" #include "pid_controller.h" // 假设有一个PID库 // 全局变量 PID_Controller_t speedPid; int32_t g_targetSpeedRPM = 100; // 目标速度:100 RPM int32_t g_currentSpeedRPM = 0; // 当前速度 uint32_t g_encoderPulsesPerRev = 4000; // 编码器分辨率:1000线 * 4倍频 uint32_t g_velocitySamplePeriodMs = 10; // QEI速度采样周期:10ms void MotorControlTask_1ms(void) { static uint32_t lastSpeedUpdateTime = 0; uint32_t currentTime = SysCtlMillisecondGet(); // 获取系统时间 // 每10ms更新一次速度反馈(与QEI采样周期同步) if ((currentTime - lastSpeedUpdateTime) >= g_velocitySamplePeriodMs) { lastSpeedUpdateTime = currentTime; // 1. 读取原始速度(过去10ms内的脉冲数) uint32_t rawPulseCount = QEI_GetVelocity(); // 2. 转换为转速 (RPM) // 公式: RPM = (rawPulseCount / SampleTime_s) * (60 sec/min) / (PulsesPerRev) // SampleTime_s = g_velocitySamplePeriodMs / 1000.0 // 所以: RPM = rawPulseCount * (60 * 1000) / (g_velocitySamplePeriodMs * g_encoderPulsesPerRev) // 为了避免浮点运算,使用定点数或整数运算 uint32_t numerator = rawPulseCount * 60000UL; // 60 * 1000 uint32_t denominator = g_velocitySamplePeriodMs * g_encoderPulsesPerRev; if (denominator != 0) { g_currentSpeedRPM = (int32_t)(numerator / denominator); } // 3. 执行PID计算 int32_t pidOutput = PID_Calculate(&speedPid, g_targetSpeedRPM, g_currentSpeedRPM); // 4. 将PID输出转换为PWM占空比,驱动电机 // SetMotorPwmDuty(pidOutput); } // 其他1ms周期的任务,如位置环控制、通信等 // 位置环可以读取 QEI_GetPosition() 进行更慢速的控制(如每10ms或100ms一次) }4. 调试、排错与性能优化实战经验
在实际项目中,QEI接口的调试往往是最耗时的一环。下面是我总结的常见问题排查清单和优化技巧。
4.1 常见问题速查表
| 现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 位置读数始终为0或不变化 | 1. QEI模块未使能。 2. 引脚复用功能未正确配置。 3. 编码器电源未接通或损坏。 4. 信号线连接错误或断路。 | 1. 检查ROM_QEIEnable是否被调用。2. 使用 GPIOPinTypeQEI或GPIOPinConfigure确认引脚已配置为QEI功能。3. 用示波器或逻辑分析仪探测编码器A、B通道输出。电机手动旋转时应有方波。 4. 检查硬件连接,确认A、B、Z线对应到MCU的正确引脚。 |
| 位置计数方向与电机实际转向相反 | A、B通道信号线接反。 | 软件解决:在ROM_QEIConfigure的ulConfig参数中加入QEI_CONFIG_SWAP标志,交换A、B通道。硬件解决:调换编码器A、B信号线的连接。 |
| 位置值跳跃、不准或偶尔回零 | 1. 信号噪声干扰。 2. 索引信号误触发(在相对位置模式下)。 3. 计数器溢出处理不当。 | 1. 增加硬件滤波(RC电路),检查接地,使用屏蔽线缆。 2. 如果不使用索引功能,确保索引引脚悬空或接地,并在配置中禁用索引复位 ( QEI_CONFIG_NO_RESET)。3. 如果位移范围可能超过 ulMaxPosition,需要在软件中处理溢出。例如,使用有符号64位变量来累加位置变化量。 |
| 速度读数波动非常大 | 1. 速度采样周期 (ulPeriod) 太短。2. 电机转速过低,导致单个采样周期内脉冲数太少。 3. 机械传动存在间隙或抖动。 | 1. 增加ulPeriod值,延长采样时间窗口,平滑测量值。2. 降低预分频 ulPreDiv,或者考虑在极低速时切换到基于位置差分计算速度的软件方法。3. 在软件中对速度值进行滤波,如滑动平均滤波或一阶低通滤波。 |
| 索引中断不触发 | 1. 索引中断未使能 (ROM_QEIIntEnable)。2. 全局中断未开启。 3. 索引信号脉宽太窄,可能被硬件滤除。 4. 索引信号电气特性不满足要求。 | 1. 检查ROM_QEIIntEnable调用,确认传入了QEI_INTINDEX。2. 调用 IntMasterEnable()开启总中断。3. 检查编码器手册,确保索引信号是标准的脉冲。有些编码器索引是低电平有效,需要确认MCU端是否支持或需要反相。 4. 用示波器观察索引信号波形。 |
| 系统进入QEI中断后卡死 | 中断标志未清除。这是最常见的原因。 | 确保在中断服务函数开头就调用ROM_QEIIntClear清除对应的中断标志。例如:ROM_QEIIntClear(QEI0_BASE, QEI_INTINDEX | QEI_INTTIMER); |
4.2 高级技巧与性能优化
32位计数器的溢出处理:QEI位置计数器是32位的。在4倍频下,一个10000线的编码器,转一圈就是40000个计数。32位无符号数最大值约42.9亿。这意味着电机可以连续旋转超过10万圈才会溢出。对于大多数应用这足够了。但如果需要无限位数的绝对位置(如超长行程的直线电机),就需要在软件中扩展。可以在索引中断里记录圈数,或者在一个更高频率的定时器中断里,通过比较前后两次位置值的变化来累计一个64位的位置。
volatile int64_t g_extendedPosition = 0; uint32_t g_lastRawPosition = 0; void Timer_ISR_ForPositionExtend(void) { uint32_t currentRaw = ROM_QEIPositionGet(QEI0_BASE); int32_t delta = (int32_t)(currentRaw - g_lastRawPosition); // 处理可能因计数器翻转(接近ulMaxPosition时)导致的大delta if (delta > (int32_t)(s_qeiConfig.maxPosition / 2)) { delta -= (s_qeiConfig.maxPosition + 1); } else if (delta < -(int32_t)(s_qeiConfig.maxPosition / 2)) { delta += (s_qeiConfig.maxPosition + 1); } g_extendedPosition += delta; g_lastRawPosition = currentRaw; }低速测量的精度提升:当电机转速极低时,速度采样周期内可能只捕获到几个甚至零个脉冲,导致速度计算量化误差很大。此时,可以关闭硬件速度捕获,改用软件计算:记录两个时间点
t1,t2的位置p1,p2,速度 =(p2 - p1) / (t2 - t1)。这种方法在低速时分辨率更高,但会消耗更多CPU资源。利用DMA减轻CPU负担:在一些高端应用中,如果需要以极高频率记录位置轨迹,频繁读取
ROM_QEIPositionGet会占用大量CPU。如果MCU支持,可以配置DMA在定时器触发下自动将QEI位置寄存器的值搬运到内存中的数组,实现无CPU干预的高速数据记录。多圈绝对编码器的模拟:廉价的增量编码器没有多圈记忆功能。我们可以利用索引信号和电池备份的RAM或FRAM来模拟多圈绝对编码器。思路是:上电后,先读取存储的上次断电时的圈数和位置偏移;然后使能QEI并等待第一个索引信号;当索引中断发生时,将硬件位置计数器复位,并以此为零点,结合存储的圈数信息,计算出当前的绝对多圈位置。这为低成本系统提供了绝对位置反馈的可能性。
正交编码器接口是现代运动控制系统的基石。从简单的转速测量到复杂的多轴协同,其稳定可靠的工作是这一切的前提。希望这篇结合了原理、API剖析和实战经验的分享,能帮助你驯服这双精密的“眼睛”,让你在嵌入式运动控制的世界里看得更清,走得更稳。记住,硬件是骨架,软件是灵魂,而深入理解其原理,则是连接两者的神经网络。