树莓派4开环系统下霍尔编码器无法绘制RPM曲线问题求助
树莓派4霍尔编码器X1解码开环系统故障排查&修复方案
我仔细梳理了你的问题和代码,你现在卡在X1解码的霍尔编码器在开环系统里没法正常运行,但PID模式却能正常工作这个点上,而且已经尝试了低通滤波还是没解决。结合你的代码细节,我从几个核心方向帮你排查问题,再给出修复后的完整方案:
核心问题拆解
编码器计数逻辑的致命问题
- 你在循环里反复注册
GPIO.add_event_detect,这会导致同一个引脚绑定N个回调函数,计数直接乱套——回调应该只注册一次! - 你的
counterup回调里多了个if GPIO.input(channel) == 1的判断,完全没必要:既然是用GPIO.RISING触发的回调,触发时引脚肯定是高电平,这个判断纯粹是多余的,还可能导致计数丢步。 - 最关键的:你代码里写的
encoderCount = 55,但实际PPR是160!这直接导致转速计算完全错误,开环系统根本拿不到正确的转速反馈,自然没法按预期运行。
- 你在循环里反复注册
开环控制逻辑完全混乱
- 你在循环里每次都重置
count = 0,然后固定给50%的PWM占空比,这根本不是开环控制——开环的核心是根据目标转速输出对应的固定PWM值,而不是每次循环都重启PWM、固定占空比。 - 时间处理部分的
simulationTime = int(1000 / 24 * simulationTime)逻辑模糊,而且和后面np.linspace(0,20)的时间轴完全不匹配,采样和时间同步彻底乱了。
- 你在循环里每次都重置
资源管理的低级错误
- 你在循环里反复调用
pwmOut.start(50),这会导致PWM模块多次重启,电机转速根本稳定不下来。
- 你在循环里反复调用
修复后的完整代码
下面是调整后的代码,修复了所有上述问题,适配X1解码逻辑、正确的PPR设置,还有合理的开环控制流程:
import RPi.GPIO as GPIO import numpy as np import time import matplotlib.pyplot as plt from time import sleep import sensormotion as sm from motor_model import motor from scipy.integrate import odeint # 定义GPIO引脚 GPIO.setmode(GPIO.BCM) GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 编码器A相输入(X1解码只需要A相) GPIO.setup(18, GPIO.OUT) # PWM输出引脚 GPIO.setup(27, GPIO.OUT) # 电机左转方向引脚 GPIO.setup(22, GPIO.OUT) # 电机右转方向引脚 # 系统核心参数(按实际硬件修改) TARGET_RPM = 500 # 开环控制的目标转速 PPR = 160 # 编码器实际脉冲数/转(你之前写的55是错误的!) SAMPLING_TIME = 0.01 # 采样间隔(10ms) PWM_FREQ = 100 # PWM工作频率(树莓派推荐100Hz,避免高频干扰) MAX_PWM = 100 # PWM最大占空比(0-100) # 全局变量 encoder_count = 0 # 编码器脉冲计数 speed_history = np.array([]) time_history = np.array([]) # 编码器计数回调(X1解码:仅检测A相上升沿) def count_encoder_pulse(channel): global encoder_count encoder_count += 1 # 上升沿触发时直接计数,无需额外判断 # 转速转换:脉冲计数转RPM def count_to_rpm(count, sampling_time, ppr): # 每秒脉冲数 = 采样周期内计数 / 采样时间 # RPM = (每秒脉冲数 * 60秒) / 每转脉冲数 return (count / sampling_time) * 60 / ppr # 电机方向控制函数 def set_motor_dir(direction): if direction == 'cw': # 顺时针转动 GPIO.output(27, 0) GPIO.output(22, 1) elif direction == 'ccw': # 逆时针转动 GPIO.output(27, 1) GPIO.output(22, 0) else: # 停止电机 GPIO.output(27, 0) GPIO.output(22, 0) # 初始化PWM模块 pwm_out = GPIO.PWM(18, PWM_FREQ) pwm_out.start(0) # 先以0占空比启动,避免电机突然启动 try: # 仅注册一次编码器事件检测! GPIO.add_event_detect(17, GPIO.RISING, callback=count_encoder_pulse) set_motor_dir('cw') # 设置电机顺时针转动 # 开环控制核心:根据目标转速设置固定PWM占空比 # 注意:这个PWM值需要你实际测试校准——不同电机的电压-转速特性不同 # 比如你测试后发现60%占空比能达到500RPM,就填60 OPEN_LOOP_PWM = 60 pwm_out.ChangeDutyCycle(OPEN_LOOP_PWM) # 数据采样循环(运行20秒) start_time = time.perf_counter() # 用高精度计时函数 while time.perf_counter() - start_time < 20: current_time = time.perf_counter() - start_time encoder_count = 0 # 重置计数,准备下一次采样 sleep(SAMPLING_TIME) # 计算当前转速 current_rpm = count_to_rpm(encoder_count, SAMPLING_TIME, PPR) # 保存数据到历史数组 speed_history = np.append(speed_history, current_rpm) time_history = np.append(time_history, current_time) print(f"当前转速: {current_rpm:.2f} RPM") # 停止电机并清理PWM set_motor_dir('stop') pwm_out.stop() # 绘制转速曲线 plt.figure(figsize=(10, 6)) plt.plot(time_history, speed_history, 'k-', linewidth=0.8, label='编码器实测转速') plt.axhline(y=TARGET_RPM, color='r', linestyle='--', label='目标转速') plt.title("开环控制下电机转速曲线") plt.xlabel('时间 [s]') plt.ylabel('转速 [RPM]') plt.legend(loc='upper right') plt.grid(True) plt.show() except KeyboardInterrupt: print("程序被用户中断") finally: GPIO.remove_event_detect(17) # 移除编码器事件检测 GPIO.cleanup() # 清理所有GPIO资源
额外优化建议
- 开环PWM值校准:你必须实际测试你的电机,找到对应目标转速的PWM占空比——不同电机的KV值、负载情况不同,固定PWM值需要根据实际场景调整。
- 编码器硬件验证:先单独测试编码器计数,手动转动电机一圈,看计数是否刚好是160次,确保硬件连接(比如接线、上拉电阻)和计数逻辑正确。
- 噪声滤波优化:如果编码器信号有噪声,推荐用滑动平均滤波,比Butterworth滤波更简单适合嵌入式场景:
# 滑动平均滤波示例(窗口大小为5) def moving_average(data, window_size=5): return np.convolve(data, np.ones(window_size)/window_size, mode='valid') - 计时精度优化:用
time.perf_counter()替代time.time(),能获得更高精度的计时,适合高速采样场景。
内容的提问来源于stack exchange,提问作者Nimra Ghazal
相关产品推荐
相关产品推荐

