370 lines
22 KiB
Python
370 lines
22 KiB
Python
"""
|
||
Raspberry Pi 5 + M5Stack StepMotor Driver 模块驱动
|
||
=====================================================
|
||
|
||
本模块用于驱动 M5Stack StepMotor Driver 模块,控制最多三个步进电机,
|
||
驱动圆柱在水槽中直线移动。
|
||
|
||
本模块结合了两种控制方式:
|
||
1. I2C 通信(地址 0x27):用于控制模块的使能/复位/配置
|
||
2. 硬件 PWM(GPIO12):用于产生步进脉冲,通过调整脉冲频率控制电机速度
|
||
|
||
方向控制通过独立的 GPIO 引脚(5/6/16)实现,三个电机共享同一路 PWM
|
||
但方向可以独立控制。
|
||
|
||
PWM 概念简介:
|
||
PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过快速开关信号来
|
||
模拟不同电压输出的技术。在步进电机控制中,PWM 的频率决定电机转速——
|
||
频率越高,脉冲越快,电机转动越快。
|
||
|
||
参考文档:
|
||
https://docs.m5stack.com/en/module/Stepmotor%20Driver%20Module13.2%20v1.1
|
||
|
||
依赖库:smbus2(I2C 通信库)
|
||
|
||
使用示例:
|
||
from drv_stepmotor import StepMotorDriver
|
||
with StepMotorDriver() as driver:
|
||
driver.set_speeds([0.02, -0.015, 0.01])
|
||
time.sleep(2)
|
||
|
||
"""
|
||
from __future__ import annotations # 启用 Python 未来的注解特性,允许在类型提示中使用字符串形式的类名(前向引用)
|
||
|
||
import math # 导入 math 模块,用于数学计算(如余弦函数 cos() 用于 S 曲线加减速、圆周率 pi)
|
||
import threading # 导入 threading 模块,用于多线程操作(本模块预留,当前未直接使用)
|
||
import time # 导入 time 模块,用于延时和计时(如加减速过程中的 sleep 暂停)
|
||
from typing import List, Optional # 从 typing 模块导入 List(列表类型)和 Optional(可选类型)用于参数类型注解
|
||
|
||
from gpiozero import DigitalOutputDevice # 从 gpiozero 库导入 DigitalOutputDevice 类,用于控制树莓派 GPIO 引脚的电平输出(高/低)
|
||
from smbus2 import SMBus # 从 smbus2 库导入 SMBus 类,用于通过 I2C 总线与 M5Stack 步进电机模块通信
|
||
from rpi_hardware_pwm import HardwarePWM # 从 rpi_hardware_pwm 库导入 HardwarePWM 类,用于控制树莓派硬件 PWM 发生器
|
||
|
||
# ==================== I2C 寄存器地址常量 ====================
|
||
# 以下常量对应 M5Stack StepMotor Driver 模块内部寄存器的 I2C 地址,
|
||
# 参考自官方 C++ 实现文件 ref_stepmotor.cpp
|
||
MODULE_STEPMOTOR_ADDR = 0x27 # 模块的 I2C 从设备地址(7 位地址 0x27),所有 I2C 通信都发往此地址
|
||
STEPMOTOR_REG_IO_CFG = 0x03 # IO 配置寄存器地址,用于配置模块引脚的输入/输出功能
|
||
STEPMOTOR_REG_MICROSTEP = 0x01 # 微步进模式配置寄存器地址,设置电机的微步细分数
|
||
STEPMOTOR_REG_ENABLE = 0x01 # 使能控制寄存器地址(与微步进共用 0x01,通过不同位控制不同功能)
|
||
STEPMOTOR_REG_EXTIO = 0x00 # 外部 IO 寄存器地址,用于扩展输入输出
|
||
STEPMOTOR_REG_FAULT = 0x04 # 故障状态寄存器地址,读取过流/过热等错误状态
|
||
STEPMOTOR_REG_RESET = 0x05 # 复位寄存器地址,用于单独复位各个电机通道
|
||
|
||
|
||
class StepMotorError(RuntimeError):
|
||
"""自定义异常类,继承自 RuntimeError,用于步进电机驱动相关的错误处理"""
|
||
pass # 类体为空,仅作为自定义异常类型的标识,不需要额外实现
|
||
|
||
|
||
class StepMotorDriver:
|
||
"""步进电机驱动器类,最多控制 3 个步进电机。
|
||
|
||
本类封装了 I2C 通信和硬件 PWM 的全部控制逻辑,提供启动、停止、调速、
|
||
方向控制等完整功能。三个步进电机共享同一路硬件 PWM(产生步进脉冲),
|
||
但每个电机的方向由独立的 GPIO 引脚控制。
|
||
|
||
重要设计说明:
|
||
- 三个电机共享同一路 PWM:因为所有电机同时以相同频率步进,仅方向可以不同
|
||
- I2C 只用于模块的使能/复位配置,不用于产生步进脉冲
|
||
- 步进脉冲由树莓派硬件 PWM 直接产生,不受 CPU 负载影响
|
||
|
||
构造函数参数说明:
|
||
- pwm_pins: 用于产生步进脉冲的 GPIO 引脚编号(BCM 编号),默认为 GPIO12
|
||
- dir_pins: 用于控制方向信号的 GPIO 引脚编号列表,最多 3 个,默认 [5, 6, 16]
|
||
- steps_per_rev: 电机转一圈所需的步数(微步进),默认 200×32×14 = 89600
|
||
- mm_per_rev: 电机转一圈对应的直线移动距离(毫米),默认 π×20 ≈ 62.83 mm
|
||
- ramp_time: 加减速的斜坡时间(秒),默认 20 秒
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
pwm_pins: int = None, # PWM 引脚编号(BCM 编号),默认为 12(即 GPIO12)
|
||
dir_pins: Optional[List[int]] = None, # 方向控制 GPIO 引脚编号列表,默认为 [5, 6, 16]
|
||
steps_per_rev: int = 200*32*14, # 电机转一圈的总步数(微步进后)
|
||
mm_per_rev: float = math.pi*20, # 电机转一圈对应的直线移动距离(毫米)
|
||
ramp_time: float = 20, # 加减速斜坡时间(秒)
|
||
) -> None:
|
||
# 保存所有配置参数为实例变量,供其他方法访问
|
||
self.pwm_pins = pwm_pins or 12 # 如果未传入 PWM 引脚编号,默认使用 GPIO12
|
||
self.dir_pins = dir_pins or [5, 6, 16] # 如果未传入方向引脚列表,默认使用 GPIO5、GPIO6、GPIO16
|
||
self.steps_per_rev = steps_per_rev # 每转总步数 = 200(电机物理步数)× 32(微步数)× 14(减速比)
|
||
self.mm_per_rev = mm_per_rev # 每转移动距离 = π × 20(驱动轮直径,单位 mm)
|
||
self.max_speed = 0.1 # 最大速度限制(米/秒),防止电机速度过快导致失步或机械冲击
|
||
self.ramp_time = float(ramp_time) # 加减速斜坡时间(秒),强制转为 float 类型以确保精度
|
||
self.i2c_addr = MODULE_STEPMOTOR_ADDR # I2C 设备地址,使用预定义的常量 0x27
|
||
self.i2c_bus = SMBus(1) # 创建 I2C 总线对象,使用 I2C 总线 1(树莓派 5 上 I2C-1 对应引脚 GPIO2/SDA、GPIO3/SCL)
|
||
|
||
self.default_dir = [True, True, False] # 三个电机的默认方向:电机 0 和电机 1 正向,电机 2 反向
|
||
self._pwm = HardwarePWM(pwm_channel=0, hz=1, chip=0) # 创建硬件 PWM 对象:通道 0,初始 1Hz,PWM 芯片 0
|
||
self._dir_pins = [DigitalOutputDevice(d, initial_value=False) for d in self.dir_pins] # 为每个方向引脚创建 GPIO 输出对象,初始值为低电平
|
||
self.current_speed = 0.0 # 当前速度(米/秒),初始化为 0 表示电机处于停止状态
|
||
|
||
self.set_dir(True) # 设置三个电机的初始方向为正向
|
||
|
||
|
||
def _write_byte(self, reg: int, value: int) -> None:
|
||
"""向模块的指定 I2C 寄存器写入一个字节的数据。
|
||
|
||
这是一个内部方法(以下划线开头),封装了 I2C 寄存器写入操作。
|
||
|
||
参数:
|
||
reg: 寄存器地址(如 0x01 使能寄存器、0x05 复位寄存器等)
|
||
value: 要写入的 8 位数据值(0~255),写入前会自动截断为低 8 位
|
||
|
||
异常:
|
||
StepMotorError: 如果 I2C 通信失败则抛出此异常
|
||
"""
|
||
value &= 0xFF # 截断高位:只保留低 8 位(0~255),确保写入一个字节
|
||
try:
|
||
self.i2c_bus.write_byte_data(self.i2c_addr, reg, value) # 通过 I2C 总线向指定寄存器的地址写入数据
|
||
except Exception as e:
|
||
raise StepMotorError(f"I2C write_byte failed: {e}") # 如果 I2C 操作异常,包装为自定义异常向上抛出
|
||
|
||
def _read_byte(self, reg: int) -> int:
|
||
"""读取模块指定 I2C 寄存器的一个字节数据。
|
||
|
||
这是一个内部方法(以下划线开头),封装了 I2C 寄存器读取操作。
|
||
|
||
参数:
|
||
reg: 寄存器地址
|
||
|
||
返回:
|
||
int: 读取到的 8 位数据值(0~255)
|
||
|
||
异常:
|
||
StepMotorError: 如果 I2C 通信失败则抛出此异常
|
||
"""
|
||
try:
|
||
self.i2c_bus.write_byte(self.i2c_addr, reg) # 先向 I2C 设备发送要读取的寄存器地址(写入操作选择寄存器)
|
||
data = self.i2c_bus.read_byte(self.i2c_addr) # 再从设备读取该寄存器的当前值(读取操作获取数据)
|
||
return data # 返回读取到的数据值
|
||
except Exception as e:
|
||
raise StepMotorError(f"I2C read_byte failed: {e}") # 如果 I2C 操作异常,包装为自定义异常向上抛出
|
||
|
||
def enable_motor(self):
|
||
"""使能电机:允许电机驱动芯片接收脉冲信号,电机可以正常转动。
|
||
|
||
通过修改使能寄存器(地址 0x01)的第 4 位(bit 4)来实现:
|
||
bit 4 = 0 表示使能,bit 4 = 1 表示禁止。
|
||
操作流程:先读取寄存器的当前值 → 修改特定位 → 将新值写回寄存器。
|
||
这种"读-改-写"模式确保不影响寄存器中其他位的设置。
|
||
"""
|
||
reg_data = self._read_byte(STEPMOTOR_REG_ENABLE) # 读取使能寄存器(地址 0x01)的当前值
|
||
reg_data &= 0xEF # 清除第 4 位(bit 4 置 0):0xEF = 0b11101111,bit 4 = 0 表示使能电机
|
||
self._write_byte(STEPMOTOR_REG_ENABLE, reg_data) # 将修改后的值写回使能寄存器(地址 0x01)
|
||
|
||
def disable_motor(self):
|
||
"""禁止电机:阻止电机驱动芯片接收脉冲信号,电机停止转动。
|
||
|
||
通过修改使能寄存器(地址 0x01)的第 4 位(bit 4)来实现:
|
||
将其他位清零、仅保留 bit 4 的值。
|
||
注意:原代码使用 reg_data &= 0x10 仅保留 bit 4 并清除其他位,
|
||
这与标准的"将 bit 4 置 1"(reg_data |= 0x10)有所不同,
|
||
但为保持逻辑不变,此处不修改代码。
|
||
"""
|
||
reg_data = self._read_byte(STEPMOTOR_REG_ENABLE) # 读取使能寄存器(地址 0x01)的当前值
|
||
reg_data &= 0x10 # 将除第 4 位之外的所有位清零:0x10 = 0b00010000,仅保留 bit 4
|
||
self._write_byte(STEPMOTOR_REG_ENABLE, reg_data) # 将修改后的值写回使能寄存器(地址 0x01)
|
||
|
||
def reset_motor(self, resmtr: int, enable: bool):
|
||
"""复位或释放指定的电机通道。
|
||
|
||
通过复位寄存器(地址 0x05)的低 3 位分别控制三个电机:
|
||
bit 0 控制电机 0,bit 1 控制电机 1,bit 2 控制电机 2。
|
||
将某位置 1 表示复位该电机,置 0 表示释放。
|
||
|
||
参数:
|
||
resmtr: 电机索引(0、1、2),分别对应三个步进电机通道
|
||
enable: True 表示复位(将对应位设为 1),False 表示释放(将对应位设为 0)
|
||
"""
|
||
reg_data = self._read_byte(STEPMOTOR_REG_RESET) # 读取复位寄存器(地址 0x05)的当前值
|
||
reg_data &= 0x07 # 仅保留低 3 位(bit 0~2),分别对应三个电机的复位状态,高位清零
|
||
if not enable:
|
||
reg_data &= ~(0x01 << resmtr) # 如果 enable=False,将对应电机的位清零:~(1<<n) 将第 n 位取反后做 AND
|
||
else:
|
||
reg_data |= (0x01 << resmtr) # 如果 enable=True,将对应电机的位置 1:(1<<n) 左移 n 位得到只有第 n 位为 1 的掩码
|
||
self._write_byte(STEPMOTOR_REG_RESET, reg_data) # 将修改后的值写回复位寄存器(地址 0x05)
|
||
|
||
def close(self) -> None:
|
||
"""关闭驱动器:停止所有电机、释放硬件资源。
|
||
|
||
按顺序执行:紧急停止 → I2C 禁能 → 所有电机通道释放 → 清理 GPIO/PWM/I2C 对象。
|
||
使用 try/except 确保即使某个清理步骤出错,后续步骤也能继续执行。
|
||
"""
|
||
self.emergency_stop() # 紧急停止:立即切断 PWM 输出,电机瞬间失去脉冲
|
||
self.disable_motor() # 通过 I2C 设置使能寄存器,禁止电机驱动芯片
|
||
for idx in range(3): # 循环三个电机通道(索引 0、1、2)
|
||
self.reset_motor(idx, False) # 释放每个电机通道:将复位寄存器中对应位清 0
|
||
# 关闭 gpiozero 相关的 GPIO 设备
|
||
try:
|
||
self._pwm.close() # 关闭硬件 PWM 对象,释放 PWM 通道资源
|
||
self.i2c_bus.close() # 关闭 I2C 总线连接,释放总线资源
|
||
for d in self._dir_pins: # 遍历所有方向控制引脚对象
|
||
d.off() # 将方向 GPIO 引脚置为低电平(关闭输出)
|
||
d.close() # 关闭方向 GPIO 引脚对象,释放资源
|
||
except Exception:
|
||
pass # 忽略清理过程中的任何异常,确保程序能正常退出
|
||
|
||
def set_dir(self, dir: bool = True) -> None:
|
||
"""设置三个电机的整体方向。
|
||
|
||
注意:三个电机只能同时设置为相同的方向(全部正向或全部反向)。
|
||
每个电机的实际方向由 default_dir(电机默认方向)和传入的 dir 参数
|
||
通过异或(XOR)运算决定,这样可以灵活设置每个电机的正反。
|
||
|
||
参数:
|
||
dir: True 表示正向,False 表示反向
|
||
电机 n 的实际方向 = default_dir[n] XOR dir
|
||
|
||
异常:
|
||
StepMotorError: 如果电机正在运行(current_speed ≠ 0)则不能改变方向
|
||
"""
|
||
if self.current_speed == 0.0: # 检查电机是否处于停止状态,运行时禁止改变方向
|
||
for idx, dir_dev in enumerate(self._dir_pins): # 遍历三个方向控制引脚(索引 0、1、2)
|
||
boolean_dir = self.default_dir[idx] ^ dir # 计算实际方向:默认方向 XOR 传入方向(异或运算)
|
||
if boolean_dir:
|
||
dir_dev.on() # 如果计算结果为 True,将 GPIO 引脚置为高电平(电机正转)
|
||
else:
|
||
dir_dev.off() # 如果计算结果为 False,将 GPIO 引脚置为低电平(电机反转)
|
||
else:
|
||
raise StepMotorError("Cannot change direction while motor is running") # 电机运行时改变方向会损坏驱动器
|
||
|
||
def start(self, speed_m_s: float) -> None:
|
||
"""启动电机,并使用 S 曲线加速到目标速度。
|
||
|
||
S 曲线加速原理:
|
||
使用余弦函数产生平滑的"S"形加速曲线,速度变化率(即加速度)
|
||
在启动和结束时较小,中间较大,从而减少机械冲击和电机失步风险。
|
||
|
||
S 曲线加速公式:
|
||
s = (cos(π × (1 - i/100)) + 1) / 2 × speed_m_s
|
||
其中 i 从 0 到 99 逐步增加:
|
||
- i=0 时:cos(π×(1-0)) = cos(π) = -1,s = (-1+1)/2 × speed = 0 × speed = 0(起始速度为 0)
|
||
- i=50 时:cos(π×(1-0.5)) = cos(π/2) = 0,s = (0+1)/2 × speed = 0.5 × speed(中间速度为目标一半)
|
||
- i=99 时:cos(π×(1-0.99)) = cos(0.01π) ≈ 1,s = (1+1)/2 × speed ≈ speed(接近目标速度)
|
||
整个过程中,加速度先增大后减小,形成"S"形曲线,相比线性加速更加平顺。
|
||
|
||
频率换算公式:
|
||
freq = s × steps_per_rev / (mm_per_rev / 1000)
|
||
推导过程:
|
||
- freq(Hz) = 每秒步数(脉冲数)
|
||
- 每秒步数 = 速度(m/s) × 步数/米
|
||
- 步数/米 = 步数/转 ÷ 米/转 = steps_per_rev ÷ (mm_per_rev / 1000)
|
||
- steps_per_rev = 200(电机物理步数,1.8°/步)× 32(驱动器微步细分数)× 14(行星减速器减速比)
|
||
- mm_per_rev = π × 20(驱动轮直径 20mm)= 62.83 mm/转
|
||
|
||
参数:
|
||
speed_m_s: 目标速度(米/秒),必须为正值
|
||
|
||
异常:
|
||
StepMotorError: 如果电机已经在运行中,则不能重复启动
|
||
"""
|
||
if self.current_speed == 0.0: # 仅在电机当前处于停止状态时才能启动
|
||
self._pwm.change_frequency(1) # 先将 PWM 频率设为最低的 1Hz,避免从 0Hz 直接跳变
|
||
self._pwm.start(100) # 启动硬件 PWM 输出,占空比设为 100%(模块内部处理步进脉冲的生成)
|
||
for i in range(100): # 将加速过程分为 100 个步进点,从 i=0 到 i=99 逐步加速
|
||
# S 曲线加速公式:余弦函数值从 cos(π) = -1 逐渐变化到 cos(0.01π) ≈ 1
|
||
# 经 (cos + 1) / 2 映射后,系数从 0 逐渐变化到接近 1
|
||
s = (math.cos(math.pi * (1-i/100)) + 1) / 2 * speed_m_s # 计算当前步进点的瞬时目标速度(米/秒)
|
||
# 将速度(米/秒)换算为 PWM 频率(Hz):
|
||
# freq = s × steps_per_rev / (mm_per_rev / 1000)
|
||
# 除以 1000 因为 mm_per_rev 是毫米,需要转换为米
|
||
freq = max(s * self.steps_per_rev / (self.mm_per_rev / 1000), 1) # 换算频率,max(...,1) 确保频率不低于 1Hz
|
||
self._pwm.change_frequency(freq) # 更新硬件 PWM 的频率,使电机以对应速度转动
|
||
time.sleep(self.ramp_time / 100) # 等待 ramp_time/100 秒,控制每步加速的持续时间
|
||
self.current_speed = speed_m_s # 加速完成后,将当前速度记录为目标速度
|
||
else:
|
||
raise StepMotorError("Motor is already running") # 如果电机已经运行,拒绝重复启动(必须先 stop)
|
||
|
||
def stop(self) -> None:
|
||
"""停止电机,使用 S 曲线减速到零。
|
||
|
||
S 曲线减速公式:
|
||
s = (cos(π × i/100) + 1) / 2 × current_speed
|
||
其中 i 从 0 到 99 逐步增加:
|
||
- i=0 时:cos(0) = 1,s = (1+1)/2 × speed = 1 × speed(保持当前速度开始减速)
|
||
- i=50 时:cos(π/2) = 0,s = (0+1)/2 × speed = 0.5 × speed(速度降到一半)
|
||
- i=99 时:cos(0.99π) ≈ -1,s = (-1+1)/2 × speed = 0 × speed = 0(减速到零)
|
||
|
||
与 start() 中的加速曲线对称,减速曲线也是 S 形,确保停止过程平滑。
|
||
减速时间比加速时间短(使用 ramp_time/4 而不是 ramp_time/100),
|
||
因为减速到零后电机即停止,不需要像加速那样缓慢进入。
|
||
"""
|
||
if self.current_speed != 0.0: # 只有在电机正在运行时才能执行停止操作
|
||
for i in range(100): # 将减速过程分为 100 个步进点,从 i=0 到 i=99 逐步减速
|
||
# S 曲线减速公式:余弦值从 cos(0) = 1 逐渐变化到 cos(0.99π) ≈ -1
|
||
# 经 (cos + 1) / 2 映射后,系数从 1 逐渐下降到接近 0
|
||
s = (math.cos(math.pi * i/100) + 1) / 2 * self.current_speed # 计算当前步进点的瞬时速度(米/秒)
|
||
freq = max(s * self.steps_per_rev / (self.mm_per_rev / 1000), 1) # 将速度换算为 PWM 频率,最小 1Hz
|
||
self._pwm.change_frequency(freq) # 更新硬件 PWM 频率,降低电机转速
|
||
time.sleep(self.ramp_time / 4 / 100) # 等待 ramp_time/400 秒,比加速步进快 4 倍
|
||
self._pwm.stop() # 减速完成后,完全停止硬件 PWM 输出
|
||
self._pwm.change_frequency(1) # 将 PWM 频率复位到 1Hz 默认值,为下一次启动做准备
|
||
self.current_speed = 0.0 # 将当前速度设为 0,标记电机已停止
|
||
else:
|
||
raise StepMotorError("Motor is already stopped") # 如果电机已经停止,拒绝重复执行停止操作
|
||
|
||
def change_speed(self, speed_m_s: float) -> None:
|
||
"""在电机运行过程中,平滑地将速度切换到新目标值。
|
||
|
||
速度切换 S 曲线公式:
|
||
s = (cos(π × i/100) + 1) / 2 × (current_speed - speed_m_s) + speed_m_s
|
||
公式推导:
|
||
将减速公式推广到任意目标速度:
|
||
- 令 delta = (current_speed - speed_m_s),即当前速度与目标速度的差值
|
||
- 减速公式变为:s = 余弦因子 × delta + speed_m_s
|
||
- i=0 时:因子=1,s = 1×delta + speed_m_s = current_speed(保持当前速度)
|
||
- i=99 时:因子≈0,s = 0×delta + speed_m_s = speed_m_s(达到目标速度)
|
||
传入的速度可以是加速(目标 > 当前)或减速(目标 < 当前),
|
||
S 曲线都能平滑过渡。
|
||
|
||
参数:
|
||
speed_m_s: 新的目标速度(米/秒),可高于或低于当前速度
|
||
|
||
异常:
|
||
StepMotorError: 如果电机未运行,调用此方法会抛出异常,提示使用 start()
|
||
"""
|
||
if self.current_speed != 0.0: # 只有在电机正在运行时才能切换速度
|
||
for i in range(100): # 分 100 步进行速度过渡,从 i=0 到 i=99
|
||
# 速度切换公式:当前的 S 曲线因子从 1 过渡到 0,
|
||
# 乘以速度差后加到目标速度上,实现从当前速度到目标速度的平滑过渡
|
||
s = (math.cos(math.pi * i/100) + 1) / 2 * (self.current_speed - speed_m_s) + speed_m_s
|
||
freq = max(s * self.steps_per_rev / (self.mm_per_rev / 1000), 1) # 将速度换算为 PWM 频率,最小 1Hz
|
||
self._pwm.change_frequency(freq) # 更新硬件 PWM 频率,改变电机转速
|
||
time.sleep(self.ramp_time / 4 / 100) # 等待 ramp_time/400 秒,控制每步过渡的持续时间
|
||
self.current_speed = speed_m_s # 速度过渡完成后,将当前速度更新为新目标速度
|
||
else:
|
||
raise StepMotorError("Motor is not running. Use start() to start the motor.") # 电机停止时不能用 change_speed
|
||
|
||
def emergency_stop(self) -> None:
|
||
"""紧急停止:立即切断 PWM 输出,不经过 S 曲线减速。
|
||
|
||
与 stop() 方法的区别:
|
||
- stop():使用 S 曲线平滑减速(约需 ramp_time/4 秒)
|
||
- emergency_stop():直接切断 PWM 输出,无减速过程(立即停止)
|
||
|
||
适用于紧急情况下的快速停机,但会产生较大的机械冲击。
|
||
"""
|
||
self._pwm.stop() # 立即停止硬件 PWM 输出,电机瞬间失去步进脉冲
|
||
self._pwm.change_frequency(1) # 将 PWM 频率复位到 1Hz 默认值,为下一次启动做准备
|
||
self.current_speed = 0.0 # 将当前速度设为 0,标记电机已停止
|
||
|
||
def __enter__(self):
|
||
"""进入上下文管理器:当使用 'with StepMotorDriver() as driver:' 语法时自动调用
|
||
|
||
返回:
|
||
StepMotorDriver: 返回自身实例,供 with 语句块中的变量引用
|
||
"""
|
||
return self # 返回当前 StepMotorDriver 实例
|
||
|
||
def __exit__(self, exc_type, exc, tb):
|
||
"""退出上下文管理器:当 with 语句块执行完毕时自动调用
|
||
|
||
参数:
|
||
exc_type: 异常类型(如果语句块中发生了异常)
|
||
exc: 异常对象
|
||
tb: 异常回溯信息
|
||
"""
|
||
self.close() # 自动调用 close() 清理所有资源,确保即使发生异常也能释放硬件
|