251204
This commit is contained in:
parent
9b54c10c4a
commit
b7f7ab60bc
|
|
@ -1,9 +1,4 @@
|
|||
import utime
|
||||
from utils import WS2812
|
||||
from utils.servo import SG90
|
||||
|
||||
rgb = WS2812()
|
||||
|
||||
while True:
|
||||
rgb.iridesce()
|
||||
|
||||
utime.sleep_ms(20)
|
||||
print(1)
|
||||
|
|
@ -1,401 +0,0 @@
|
|||
import utime
|
||||
from machine import Pin, PWM
|
||||
from neopixel import NeoPixel
|
||||
|
||||
|
||||
class MTS102:
|
||||
"""MTS102(三脚二档钮子开关)"""
|
||||
|
||||
def __init__(self, pin, pull_down=False):
|
||||
"""
|
||||
:param pin: 引脚编号
|
||||
:param pull_down: 引脚下拉电阻(断开为低电平,接通为高电平),默认为False。若使用下拉电阻,因钮子接通后断开可能存留电荷/漏电故需在引脚和GND并联1K~10K欧电阻
|
||||
"""
|
||||
import utime
|
||||
from machine import Pin
|
||||
|
||||
if not isinstance(pull_down, bool):
|
||||
raise TypeError("pull_down数据类型应为布尔")
|
||||
|
||||
self.pull_down = pull_down
|
||||
|
||||
# 尝试初始化引脚
|
||||
try:
|
||||
self.pin = Pin(
|
||||
pin, Pin.IN, Pin.PULL_DOWN if self.pull_down else Pin.PULL_UP
|
||||
)
|
||||
except Exception as exception:
|
||||
raise RuntimeError(f"初始化引脚发生异常,{str(exception)}") from exception
|
||||
|
||||
# 初始化上一次检测时间
|
||||
self._last_time = utime.ticks_ms()
|
||||
# 初始化上一次检测状态
|
||||
self._last_state = self._get_state()
|
||||
|
||||
def _get_state(self):
|
||||
"""获取状态:0为断开,1为接通"""
|
||||
return self.pin.value() ^ (not self.pull_down)
|
||||
|
||||
def _debounce(self):
|
||||
"""去抖"""
|
||||
current_time = utime.ticks_ms()
|
||||
current_state = self._get_state()
|
||||
|
||||
# 若当前与上一次检测间隔大于等于防抖时间则更新时间,又若状态不同则更新状态
|
||||
if utime.ticks_diff(current_time, self._last_time) >= 20:
|
||||
if current_state != self._last_state:
|
||||
self._last_time = current_time
|
||||
self._last_state = current_state
|
||||
|
||||
return self._last_state
|
||||
|
||||
@property
|
||||
def switched(self):
|
||||
"""是否接通"""
|
||||
return self._debounce() == 1
|
||||
|
||||
|
||||
"""
|
||||
使用示例
|
||||
|
||||
引脚0→LED→220R→GND
|
||||
引脚1→MST102→GND
|
||||
|
||||
import utime
|
||||
from machine import Pin
|
||||
from utils import MTS102
|
||||
|
||||
led = Pin(0, Pin.OUT)
|
||||
toggle = MTS102(pin=1)
|
||||
|
||||
while True:
|
||||
if toggle.switched:
|
||||
led.on()
|
||||
else:
|
||||
led.off()
|
||||
|
||||
utime.sleep_ms(200)
|
||||
"""
|
||||
|
||||
|
||||
class WS2812:
|
||||
"""WS2812(可编程 RGB LED)"""
|
||||
|
||||
def __init__(self, pin=16, led_beads=1):
|
||||
"""
|
||||
:param pin: 引脚编号,RP2350ZERO板载WS2812使用引脚16
|
||||
:param led_beads: LED灯珠数
|
||||
"""
|
||||
import utime
|
||||
from machine import Pin
|
||||
from neopixel import NeoPixel
|
||||
|
||||
if not isinstance(led_beads, int):
|
||||
raise TypeError("led_beads数据类型应为整数")
|
||||
|
||||
if not led_beads >= 1:
|
||||
raise ValueError("led_beads应大于等于1")
|
||||
|
||||
self.led_beads = led_beads
|
||||
|
||||
# 尝试初始化LED
|
||||
try:
|
||||
self.led = NeoPixel(Pin(pin, Pin.OUT), self.led_beads)
|
||||
except Exception as exception:
|
||||
raise RuntimeError(f"初始化LED发生异常,{str(exception)}") from exception
|
||||
|
||||
def set_colors(self, colors):
|
||||
"""
|
||||
设置灯珠颜色
|
||||
:param colors: 包含每颗灯珠RGB的颜色列表
|
||||
"""
|
||||
if not isinstance(colors, list):
|
||||
raise TypeError("colors数据类型应为列表")
|
||||
|
||||
if len(colors) != self.led_beads:
|
||||
raise ValueError("colors列表长度应与LED灯珠数一致")
|
||||
|
||||
for bead_idx, bead_color in enumerate(colors):
|
||||
if not isinstance(bead_color, tuple):
|
||||
raise TypeError("灯珠颜色数据类型应为元组")
|
||||
|
||||
if len(bead_color) != 3:
|
||||
raise ValueError("灯珠颜色长度应为3")
|
||||
|
||||
for channel_idx, channel_value in enumerate(bead_color):
|
||||
if not isinstance(channel_value, int):
|
||||
raise TypeError("通道数据数据类型应为整数")
|
||||
|
||||
if not 0 <= channel_value <= 255:
|
||||
raise ValueError("通道数据应在0~255范围内")
|
||||
|
||||
for bead_idx, bead_color in enumerate(colors):
|
||||
# 将RGB转为GRB并设置灯珠颜色
|
||||
self.led[bead_idx] = (bead_color[1], bead_color[0], bead_color[2])
|
||||
|
||||
# LED刷新
|
||||
self.led.write()
|
||||
|
||||
def iridesce(self, hue_offset=20):
|
||||
"""通过HSV颜色空间生成渐变炫彩效果
|
||||
:param hue_offset: 色相偏移角度
|
||||
"""
|
||||
if not isinstance(hue_offset, int):
|
||||
raise TypeError("hue_offset数据类型应为整数")
|
||||
|
||||
if not 0 <= hue_offset <= 60:
|
||||
raise ValueError("hue_offset应在0~60范围内")
|
||||
|
||||
# 初始化HSV颜色空间参数,其中hue为色相,saturation为饱和度,value为亮度
|
||||
hue, saturation, value = 0, 255, 128
|
||||
|
||||
while True:
|
||||
# 初始化颜色列表
|
||||
colors = []
|
||||
|
||||
for bead_idx in range(self.led_beads):
|
||||
# 色相
|
||||
hue = (hue + bead_idx * hue_offset) % 360
|
||||
# 色域
|
||||
region = hue // 60
|
||||
# 色域内相对位置
|
||||
remainder = (hue % 60) * 255 // 60
|
||||
# RGB颜色通道中的最小值分量
|
||||
primary = (value * (255 - saturation)) // 255
|
||||
# RGB颜色通道中的过渡分量
|
||||
tertiary = (value * remainder) // 255
|
||||
|
||||
# 根据色域将HSV转为RGB
|
||||
if region == 0:
|
||||
color = (value, tertiary, primary) # 由红色渐变为黄色
|
||||
elif region == 1:
|
||||
color = (value - tertiary, value, primary) # 由黄色渐变为绿色
|
||||
elif region == 2:
|
||||
color = (primary, value, tertiary) # 由绿色渐变为青色
|
||||
elif region == 3:
|
||||
color = (primary, value - tertiary, value) # 由青色渐变为蓝色
|
||||
elif region == 4:
|
||||
color = (tertiary, primary, value) # 由蓝色渐变为洋红色
|
||||
else:
|
||||
color = (value, primary, value - tertiary) # 由洋红色渐变为红色
|
||||
|
||||
colors.append(color)
|
||||
|
||||
self.set_colors(colors)
|
||||
|
||||
# 根据色域动态调整色相步长
|
||||
hue = hue + [1, 3, 3, 3, 3, 1][region]
|
||||
|
||||
utime.sleep_ms(20)
|
||||
|
||||
|
||||
"""
|
||||
使用示例
|
||||
|
||||
RP2350ZERP 使用板载WS2812,其占用引脚16
|
||||
|
||||
import utime
|
||||
from utils import WS2812
|
||||
|
||||
rgb = WS2812()
|
||||
|
||||
while True:
|
||||
rgb.iridesce()
|
||||
|
||||
utime.sleep_ms(20)
|
||||
"""
|
||||
|
||||
|
||||
class Servo:
|
||||
"""舵机基类"""
|
||||
|
||||
|
||||
class EasingObj:
|
||||
"""缓动曲线对象,用于封装缓动曲线函数、转动单位角度对应的步数和平均速率"""
|
||||
def __init__(self, function, steps_per_degree):
|
||||
"""
|
||||
:param function: 缓动曲线函数,X轴为进程、Y轴为速率
|
||||
:param steps_per_degree: 转动单位角度对应的步数,单位为步
|
||||
"""
|
||||
self.function = function
|
||||
self.steps_per_degree = steps_per_degree
|
||||
# 平均速率
|
||||
self.average_speed = self._calculate_average_speed()
|
||||
|
||||
def _calculate_average_speed(self, intervals=100):
|
||||
"""
|
||||
基于复核中点矩形法计算平均速率
|
||||
:param intervals: 积分间隔数
|
||||
"""
|
||||
return sum(self.function((2 * i + 1) / (2 * intervals))[1] for i in range(intervals)) / intervals
|
||||
|
||||
|
||||
# 缓动曲线
|
||||
EASINGS = {
|
||||
"uniform": EasingObj(lambda x: (x, 1), 1.0), # 匀速
|
||||
"ease_in": EasingObj(lambda x: (x ** 2, 2 * x), 1.5), # 慢→快
|
||||
"ease_out": EasingObj(lambda x: (1 - (1 - x) ** 2, 2 * (1 - x)), 1.5), # 快→慢
|
||||
"ease_in_out": EasingObj(lambda x: (3 * x ** 2 - 2 * x ** 3, 6 * x * (1 - x)), 1.65), # 慢→快→慢
|
||||
}
|
||||
|
||||
def __init__(self, pin, frequency=50, min_angle=0, max_angle=180, min_pulsewidth=500, max_pulsewidth=2500, dead_zone=8, duration_per_degree=0.13):
|
||||
"""
|
||||
:param pin: 引脚编号
|
||||
:param frequency: 频率,单位为赫兹
|
||||
:param min_angle: 最小转动角度,单位为度
|
||||
:param max_angle: 最大转动角度,单位为度
|
||||
:param min_pulsewidth: 最小转动角度对应的脉宽,单位为微秒
|
||||
:param max_pulsewidth: 最大转动角度对应的脉宽,单位为微秒
|
||||
:param dead_zone: 死区,单位为微秒
|
||||
:param duration_per_degree: 转动单位角度对应的时长,单位为秒/六十度
|
||||
"""
|
||||
import utime
|
||||
from machine import Pin, PWM
|
||||
|
||||
# 尝试初始化PWM
|
||||
try:
|
||||
self.pwm = PWM(Pin(pin, Pin.OUT))
|
||||
# 设置频率
|
||||
self.pwm.freq(frequency)
|
||||
self.pwm.duty_ns(0) # 关闭PWM信号输出
|
||||
except Exception as exception:
|
||||
raise RuntimeError(f"初始化PWM发生异常,{str(exception)}") from exception
|
||||
|
||||
self.min_angle, self.max_angle = min_angle, max_angle
|
||||
# 脉宽单位由微秒转为纳秒(默认脉宽单位为纳秒)
|
||||
self.min_pulsewidth, self.max_pulsewidth = min_pulsewidth * 1000, max_pulsewidth * 1000
|
||||
# 转动单位角度对应的脉宽,单位为纳秒/度
|
||||
self.pulsewidth_per_degree = (self.max_pulsewidth - self.min_pulsewidth) / (self.max_angle - self.min_angle)
|
||||
|
||||
# 死区单位由微秒转为纳秒
|
||||
self.dead_zone = dead_zone * 1000
|
||||
|
||||
# 转动单位角度对应的时长单位由秒/六十度转为毫秒/度(默认转动、延迟单位为毫秒)
|
||||
self.duration_per_degree = duration_per_degree * 1000 / 60
|
||||
|
||||
# 初始化标记为停止转动
|
||||
self.rotating = False
|
||||
# 初始化目标转动角度
|
||||
self.target_angle = self.min_angle
|
||||
# 初始化目标转动角度对应的脉宽
|
||||
self.target_pulsewidth = self._angle_to_pulsewidth(self.target_angle)
|
||||
# 初始化当前转动角度
|
||||
self.current_angle = self.min_angle
|
||||
# 初始化当前转动角度对应的脉宽
|
||||
self.current_pulsewidth = self._angle_to_pulsewidth(self.current_angle)
|
||||
# 初始化缓动曲线
|
||||
self.easing = None
|
||||
# 初始化转动步骤生成器
|
||||
self.rotation_steps = None
|
||||
|
||||
def set_angle(self, target_angle, easing="ease_in_out"):
|
||||
"""
|
||||
设置旋转角度
|
||||
:param target_angle: 目标转动角度,单位为度
|
||||
:param easing: 缓动函数名称
|
||||
"""
|
||||
if not isinstance(target_angle, (int, float)):
|
||||
raise TypeError("target_angle数据类型应为整数或浮点")
|
||||
|
||||
# 限制目标转动角度在最小转动角度至最大转动角度范围之内
|
||||
target_angle = max(self.min_angle, min(self.max_angle, target_angle))
|
||||
|
||||
if not isinstance(easing, str):
|
||||
raise TypeError("easing数据类型应为字符")
|
||||
|
||||
# 限制缓动曲线在指定范围之内
|
||||
easing = self.EASINGS.get(easing.lower(), self.EASINGS["ease_in_out"])
|
||||
|
||||
# 若为旋转中则返回设置旋转角度失败
|
||||
if self.rotating:
|
||||
return False
|
||||
|
||||
# 目标转动角度对应的脉宽
|
||||
target_pulsewidth = self._angle_to_pulsewidth(target_angle)
|
||||
|
||||
# 若目标转动角度对应的脉宽和当前转动角度对应的脉宽之差小于死区则返回设置旋转角度成功
|
||||
if abs(target_pulsewidth - self.current_pulsewidth) < self.dead_zone:
|
||||
# 更新目标转动角度
|
||||
self.target_angle = target_angle
|
||||
# 更新目标转动角度对应的脉宽
|
||||
self.target_pulsewidth = target_pulsewidth
|
||||
return True
|
||||
|
||||
# 标记为旋转中
|
||||
self.rotating = True
|
||||
# 更新目标转动角度
|
||||
self.target_angle = target_angle
|
||||
# 更新目标转动角度对应的脉宽
|
||||
self.target_pulsewidth = target_pulsewidth
|
||||
# 更新缓动曲线
|
||||
self.easing = easing
|
||||
# 初始化转动步骤生成器
|
||||
self.rotation_steps = self._generate_rotation_steps()
|
||||
return True
|
||||
|
||||
def _generate_rotation_steps(self):
|
||||
"""生成转动步骤"""
|
||||
# 起始转动角度
|
||||
start_angle = self.current_angle
|
||||
# 目标转动角度和起始转动角度之差
|
||||
angle_difference = self.target_angle - start_angle
|
||||
|
||||
# 转动步数,单位为步
|
||||
steps = max(int(round(abs(angle_difference) * self.easing.steps_per_degree)), max(10, int(round(abs(angle_difference) / 2))))
|
||||
# 每步延迟,单位为毫秒
|
||||
step_delay = max(5, min(50, int(round((abs(angle_difference) * self.duration_per_degree / self.easing.average_speed) / steps))))
|
||||
|
||||
for step in range(steps + 1):
|
||||
# 当前转动角度
|
||||
self.current_angle = start_angle + angle_difference * self.easing.function(step / steps)[0]
|
||||
# 当前转动角度对应的脉宽
|
||||
self.current_pulsewidth = self._angle_to_pulsewidth(self.current_angle)
|
||||
|
||||
# 输出PWM信号,更新当前转动角度
|
||||
self.pwm.duty_ns(self.current_pulsewidth)
|
||||
|
||||
if step < steps: # 最后一步无需等待
|
||||
yield
|
||||
utime.sleep_ms(step_delay)
|
||||
|
||||
# 兜底:确保最后一步达到目标转动角度对应的脉宽
|
||||
self.current_angle = self.target_angle
|
||||
self.current_pulsewidth = self.target_pulsewidth
|
||||
self.pwm.duty_ns(self.current_pulsewidth)
|
||||
|
||||
self._stop()
|
||||
yield
|
||||
|
||||
def _angle_to_pulsewidth(self, angle):
|
||||
"""将转动角度转为脉宽"""
|
||||
pulsewidth = self.min_pulsewidth + (angle - self.min_angle) * self.pulsewidth_per_degree
|
||||
return max(self.min_pulsewidth, min(self.max_pulsewidth, int(round(pulsewidth))))
|
||||
|
||||
def rotate(self):
|
||||
"""转动:需在主循环调用,执行转动步骤"""
|
||||
if self.rotating and self.rotation_steps:
|
||||
try:
|
||||
next(self.rotation_steps)
|
||||
# 生成器耗尽,正常停止
|
||||
except StopIteration:
|
||||
self._stop()
|
||||
except Exception as exception:
|
||||
self._stop()
|
||||
raise RuntimeError(f"转动时发生异常,{str(exception)}") from exception
|
||||
|
||||
def _stop(self):
|
||||
"""停止转动"""
|
||||
# 标记为停止转动
|
||||
self.rotating = False
|
||||
self.rotation_steps = None
|
||||
self.pwm.duty_ns(0)
|
||||
|
||||
|
||||
class SG90(Servo):
|
||||
"""SG90(适用于SG90和MG90S等舵机)"""
|
||||
def __init__(self, pin):
|
||||
super().__init__(
|
||||
pin=pin,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import utime
|
||||
from machine import Pin
|
||||
from neopixel import NeoPixel
|
||||
|
||||
class WS2812:
|
||||
"""WS2812(可编程 RGB LED)"""
|
||||
|
||||
def __init__(self, pin=16, led_beads=1):
|
||||
"""
|
||||
:param pin: 引脚编号,RP2350ZERO板载WS2812使用引脚16
|
||||
:param led_beads: LED灯珠数
|
||||
"""
|
||||
if not isinstance(led_beads, int):
|
||||
raise TypeError("led_beads数据类型应为整数")
|
||||
|
||||
if not led_beads >= 1:
|
||||
raise ValueError("led_beads应大于等于1")
|
||||
|
||||
self.led_beads = led_beads
|
||||
|
||||
# 尝试初始化LED
|
||||
try:
|
||||
self.led = NeoPixel(Pin(pin, Pin.OUT), self.led_beads)
|
||||
except Exception as exception:
|
||||
raise RuntimeError(f"初始化LED发生异常,{str(exception)}") from exception
|
||||
|
||||
def set_colors(self, colors):
|
||||
"""
|
||||
设置灯珠颜色
|
||||
:param colors: 包含每颗灯珠RGB的颜色列表
|
||||
"""
|
||||
if not isinstance(colors, list):
|
||||
raise TypeError("colors数据类型应为列表")
|
||||
|
||||
if len(colors) != self.led_beads:
|
||||
raise ValueError("colors列表长度应与LED灯珠数一致")
|
||||
|
||||
for bead_idx, bead_color in enumerate(colors):
|
||||
if not isinstance(bead_color, tuple):
|
||||
raise TypeError("灯珠颜色数据类型应为元组")
|
||||
|
||||
if len(bead_color) != 3:
|
||||
raise ValueError("灯珠颜色长度应为3")
|
||||
|
||||
for channel_idx, channel_value in enumerate(bead_color):
|
||||
if not isinstance(channel_value, int):
|
||||
raise TypeError("通道数据数据类型应为整数")
|
||||
|
||||
if not 0 <= channel_value <= 255:
|
||||
raise ValueError("通道数据应在0~255范围内")
|
||||
|
||||
for bead_idx, bead_color in enumerate(colors):
|
||||
# 将RGB转为GRB并设置灯珠颜色
|
||||
self.led[bead_idx] = (bead_color[1], bead_color[0], bead_color[2])
|
||||
|
||||
# LED刷新
|
||||
self.led.write()
|
||||
|
||||
def iridesce(self, hue_offset=20):
|
||||
"""通过HSV颜色空间生成渐变炫彩效果
|
||||
:param hue_offset: 色相偏移角度
|
||||
"""
|
||||
if not isinstance(hue_offset, int):
|
||||
raise TypeError("hue_offset数据类型应为整数")
|
||||
|
||||
if not 0 <= hue_offset <= 60:
|
||||
raise ValueError("hue_offset应在0~60范围内")
|
||||
|
||||
# 初始化HSV颜色空间参数,其中hue为色相,saturation为饱和度,value为亮度
|
||||
hue, saturation, value = 0, 255, 128
|
||||
|
||||
while True:
|
||||
# 初始化颜色列表
|
||||
colors = []
|
||||
|
||||
for bead_idx in range(self.led_beads):
|
||||
# 色相
|
||||
hue = (hue + bead_idx * hue_offset) % 360
|
||||
# 色域
|
||||
region = hue // 60
|
||||
# 色域内相对位置
|
||||
remainder = (hue % 60) * 255 // 60
|
||||
# RGB颜色通道中的最小值分量
|
||||
primary = (value * (255 - saturation)) // 255
|
||||
# RGB颜色通道中的过渡分量
|
||||
tertiary = (value * remainder) // 255
|
||||
|
||||
# 根据色域将HSV转为RGB
|
||||
if region == 0:
|
||||
color = (value, tertiary, primary) # 由红色渐变为黄色
|
||||
elif region == 1:
|
||||
color = (value - tertiary, value, primary) # 由黄色渐变为绿色
|
||||
elif region == 2:
|
||||
color = (primary, value, tertiary) # 由绿色渐变为青色
|
||||
elif region == 3:
|
||||
color = (primary, value - tertiary, value) # 由青色渐变为蓝色
|
||||
elif region == 4:
|
||||
color = (tertiary, primary, value) # 由蓝色渐变为洋红色
|
||||
else:
|
||||
color = (value, primary, value - tertiary) # 由洋红色渐变为红色
|
||||
|
||||
colors.append(color)
|
||||
|
||||
self.set_colors(colors)
|
||||
|
||||
# 根据色域动态调整色相步长
|
||||
hue = hue + [1, 3, 3, 3, 3, 1][region]
|
||||
|
||||
utime.sleep_ms(20)
|
||||
|
||||
|
||||
"""
|
||||
使用示例
|
||||
|
||||
RP2350ZERP 使用板载WS2812,其占用引脚16
|
||||
|
||||
import utime
|
||||
from utils.led import WS2812
|
||||
|
||||
rgb = WS2812()
|
||||
|
||||
while True:
|
||||
rgb.iridesce()
|
||||
|
||||
utime.sleep_ms(20)
|
||||
"""
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
import utime
|
||||
from machine import Pin, PWM
|
||||
|
||||
|
||||
class ServoMotor:
|
||||
"""微型伺服电机"""
|
||||
|
||||
def __init__(self, pin, frequency=50, min_angle=0, max_angle=180, min_pulsewidth=500, max_pulsewidth=2500, deadband_pulsewidth=8, duration_per_60deg=0.13):
|
||||
"""
|
||||
:param pin: 绑定引脚
|
||||
:param frequency: PWM信号输出频率,单位为赫兹
|
||||
:param min_angle: 最小转动角度,单位为度
|
||||
:param max_angle: 最大转动角度,单位为度
|
||||
:param min_pulsewidth: 最小转动角度对应的脉宽,单位为微秒
|
||||
:param max_pulsewidth: 最大转动角度对应的脉宽,单位为微秒
|
||||
:param deadband_pulsewidth: 死区对应的脉宽,单位为微秒
|
||||
:param duration_per_60deg: 转动六十度对应的时长,单位为秒/六十度
|
||||
"""
|
||||
print()
|
||||
print(f"正在初始化ServoMotor(绑定引脚{pin:2d})...", end="")
|
||||
|
||||
# 初始化最小转动角度和最大转动角度,单位为度
|
||||
self.min_angle, self.max_angle = min_angle, max_angle
|
||||
# 初始化最小转动角度对应的脉宽和最大转动角度对应的脉宽,单位为纳秒
|
||||
self.min_pulsewidth, self.max_pulsewidth = min_pulsewidth * 1000, max_pulsewidth * 1000
|
||||
# 初始化死区对应的脉宽,单位为纳秒
|
||||
self.deadband_pulsewidth = deadband_pulsewidth * 1000
|
||||
# 初始化单位角度对应的脉宽,单位为纳秒/度
|
||||
self.pulsewidth_per_degree = (self.max_pulsewidth - self.min_pulsewidth) / (self.max_angle - self.min_angle)
|
||||
# 初始化单位时长对应的脉宽,单位为纳秒/毫秒
|
||||
self.pulsewidth_per_millisecond = self.pulsewidth_per_degree * 60 / duration_per_60deg / 1000
|
||||
|
||||
# 初始化目标角度对应的脉宽,单位为纳秒
|
||||
self.target_pulsewidth = self.min_pulsewidth
|
||||
# 初始化开始角度对应的脉宽和当前角度对应的脉宽,单位为纳秒
|
||||
self.start_pulsewidth = self.current_pulsewidth = self.min_pulsewidth
|
||||
# 初始化转动脉宽差,单位为纳秒
|
||||
self.pulsewidth_difference = 0
|
||||
|
||||
# 初始化转动时长,单位为毫秒
|
||||
self.duration = 1000
|
||||
# 初始化转动步骤间延迟
|
||||
self.delay = int(round(1000 / frequency)) + 10
|
||||
# 初始化开始角度对应的时间戳和当前角度对应的时间戳,单位为毫秒
|
||||
self.start_time = self.current_time = utime.ticks_ms()
|
||||
# 初始化转动步骤迭代器
|
||||
self.iterator = None
|
||||
|
||||
try:
|
||||
self.pwm = PWM(Pin(pin, Pin.OUT)) # 初始化PWM
|
||||
self.pwm.freq(frequency) # 设置频率
|
||||
except Exception as exception:
|
||||
raise RuntimeError(f"初始化发生异常:{str(exception)}")
|
||||
|
||||
# 打开PWM信号输出,最小转动角度对应的脉宽
|
||||
self.pwm.duty_ns(self.min_pulsewidth)
|
||||
utime.sleep_ms(self.duration)
|
||||
print("已完成")
|
||||
|
||||
def rotate(self, target_angle, duration=1000):
|
||||
"""
|
||||
将设置目标角度和执行转动步骤迭代器封装为转动接口
|
||||
"""
|
||||
print(f"由{self._pulsewidth_to_angle(self.current_pulsewidth):3d}转动至{target_angle:3d},预期转动{duration:4d}毫秒...", end="")
|
||||
|
||||
if self._set_target_angle(target_angle, duration):
|
||||
while self._execute():
|
||||
pass
|
||||
print(f"已完成,实际转动{utime.ticks_diff(self.current_time, self.start_time):4d}毫秒")
|
||||
return True
|
||||
|
||||
print("设置目标角度失败:微型伺服电机正在转动")
|
||||
return False
|
||||
|
||||
def _set_target_angle(self, target_angle, duration):
|
||||
"""
|
||||
设置目标角度
|
||||
:param target_angle: 目标角度,单位为度
|
||||
:param duration: 转动时长,单位为毫秒
|
||||
"""
|
||||
if not isinstance(target_angle, int):
|
||||
raise TypeError("target_angle数据类型应为整数")
|
||||
|
||||
if not isinstance(duration, int):
|
||||
raise TypeError("duration数据类型应为整数")
|
||||
|
||||
# 若正在转动则返回失败
|
||||
if self._rotating:
|
||||
return False
|
||||
|
||||
# 更新目标角度对应的脉宽
|
||||
self.target_pulsewidth = self._angle_to_pulsewidth(target_angle)
|
||||
# 更新开始角度对应的脉宽
|
||||
self.start_pulsewidth = self.current_pulsewidth
|
||||
# 更新转动脉宽差
|
||||
self.pulsewidth_difference = self.target_pulsewidth - self.start_pulsewidth
|
||||
|
||||
# 若转动脉宽差的绝对值小于死区对应的脉宽则返回成功
|
||||
if abs(self.pulsewidth_difference) < self.deadband_pulsewidth:
|
||||
return True
|
||||
|
||||
# 更新转动时长
|
||||
self.duration = max(
|
||||
self._estimate_min_duration(self.pulsewidth_difference),
|
||||
duration
|
||||
)
|
||||
# 更新当前角度对应的时间戳
|
||||
self.current_time = utime.ticks_ms()
|
||||
# 更新开始角度对应的时间戳
|
||||
self.start_time = self.current_time
|
||||
# 更新转动步骤迭代器
|
||||
self.iterator = self._generate_iterator()
|
||||
return True
|
||||
|
||||
@property
|
||||
def _rotating(self):
|
||||
"""是否正在转动"""
|
||||
return self.iterator is not None
|
||||
|
||||
def _generate_iterator(self):
|
||||
"""创建转动迭代器"""
|
||||
while True:
|
||||
# 当前角度对应的时间戳
|
||||
current_time = utime.ticks_ms()
|
||||
# 当前角度对应的进程
|
||||
process = max(
|
||||
0,
|
||||
min(
|
||||
1,
|
||||
utime.ticks_diff(current_time, self.start_time) / self.duration
|
||||
)
|
||||
)
|
||||
|
||||
# 若当前角度对应的时间戳和上一次当前角度对应的时间戳大于等于转动步骤间延迟则继续,否则跳过
|
||||
if utime.ticks_diff(current_time, self.current_time) >= self.delay:
|
||||
# 当前角度对应的脉宽
|
||||
current_pulsewidth = max(
|
||||
self.min_pulsewidth,
|
||||
min(
|
||||
self.max_pulsewidth,
|
||||
int(round(self.start_pulsewidth + self.pulsewidth_difference * self._easing(process)))
|
||||
)
|
||||
)
|
||||
|
||||
# 若转动脉宽差的绝对值大于等于死区对应的脉宽则继续
|
||||
if abs(current_pulsewidth - self.current_pulsewidth) >= self.deadband_pulsewidth:
|
||||
# 打开PWM信号输出,当前角度对应的脉宽
|
||||
self.pwm.duty_ns(current_pulsewidth)
|
||||
# 更新当前角度对应的脉宽
|
||||
self.current_pulsewidth = current_pulsewidth
|
||||
# 更新当前角度对应的时间戳
|
||||
self.current_time = current_time
|
||||
|
||||
# 若当前角度对应的进程大于等于1则继续
|
||||
if process >= 1:
|
||||
# 打开PWM信号输出,当前角度对应的脉宽
|
||||
self.pwm.duty_ns(self.target_pulsewidth)
|
||||
# 更新当前角度对应的脉宽
|
||||
self.current_pulsewidth = self.target_pulsewidth
|
||||
# 更新当前角度对应的时间戳
|
||||
self.current_time = utime.ticks_ms()
|
||||
yield
|
||||
break
|
||||
|
||||
yield
|
||||
|
||||
@staticmethod
|
||||
def _easing(process):
|
||||
"""
|
||||
缓动函数:根据当前角度对应的进程计算当前角度
|
||||
:param process: 当前角度对应的进程
|
||||
"""
|
||||
return 3 * process ** 2 - 2 * process ** 3
|
||||
|
||||
def _execute(self):
|
||||
"""执行转动步骤迭代器"""
|
||||
if self._rotating:
|
||||
try:
|
||||
next(self.iterator)
|
||||
return True
|
||||
except StopIteration:
|
||||
self._stop()
|
||||
return False
|
||||
|
||||
def _stop(self):
|
||||
"""停止转动"""
|
||||
self.iterator = None
|
||||
|
||||
def _estimate_min_duration(self, pulsewidth_difference):
|
||||
"""
|
||||
估计最小转动时长
|
||||
:param pulsewidth_difference: 转动脉宽差,单位为纳秒
|
||||
"""
|
||||
min_duration = int(round(abs(pulsewidth_difference) / self.pulsewidth_per_millisecond))
|
||||
return min_duration
|
||||
|
||||
def _angle_to_pulsewidth(self, angle):
|
||||
"""
|
||||
将角度转为角度对应的脉宽
|
||||
:param angle: 角度,单位为度
|
||||
"""
|
||||
pulsewidth = max(
|
||||
self.min_pulsewidth,
|
||||
min(
|
||||
self.max_pulsewidth,
|
||||
int(round(self.min_pulsewidth + (angle - self.min_angle) * self.pulsewidth_per_degree))
|
||||
)
|
||||
)
|
||||
return pulsewidth
|
||||
|
||||
def _pulsewidth_to_angle(self, pulsewidth):
|
||||
"""
|
||||
将角度对应的脉宽转为角度
|
||||
:param pulsewidth: 角度对应的脉宽,单位为纳秒
|
||||
"""
|
||||
angle = max(
|
||||
self.min_angle,
|
||||
min(
|
||||
self.max_angle,
|
||||
int(round(self.min_angle + (pulsewidth - self.min_pulsewidth) / self.pulsewidth_per_degree))
|
||||
)
|
||||
)
|
||||
return angle
|
||||
|
||||
|
||||
class SG90(ServoMotor):
|
||||
"""SG90(适用于SG90和MG90S等舵机)"""
|
||||
def __init__(self, pin):
|
||||
super().__init__(
|
||||
pin=pin,
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
使用示例
|
||||
引脚0→舵机信号引脚
|
||||
|
||||
import utime
|
||||
from utils.servo import SG90
|
||||
|
||||
servo = SG90(pin=0)
|
||||
servo.rotate(target_angle=180, duration=1000)
|
||||
"""
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import utime
|
||||
from machine import Pin
|
||||
|
||||
|
||||
class MTS102:
|
||||
"""MTS102(三脚二档钮子开关)"""
|
||||
|
||||
def __init__(self, pin, pull_down=False):
|
||||
"""
|
||||
:param pin: 引脚编号
|
||||
:param pull_down: 引脚下拉电阻(断开为低电平,接通为高电平),默认为False。若使用下拉电阻,因钮子接通后断开可能存留电荷/漏电故需在引脚和GND并联1K~10K欧电阻
|
||||
"""
|
||||
if not isinstance(pull_down, bool):
|
||||
raise TypeError("pull_down数据类型应为布尔")
|
||||
|
||||
self.pull_down = pull_down
|
||||
|
||||
# 尝试初始化引脚
|
||||
try:
|
||||
self.pin = Pin(
|
||||
pin, Pin.IN, Pin.PULL_DOWN if self.pull_down else Pin.PULL_UP
|
||||
)
|
||||
except Exception as exception:
|
||||
raise RuntimeError(f"初始化引脚发生异常,{str(exception)}") from exception
|
||||
|
||||
# 初始化上一次检测时间
|
||||
self._last_time = utime.ticks_ms()
|
||||
# 初始化上一次检测状态
|
||||
self._last_state = self._get_state()
|
||||
|
||||
def _get_state(self):
|
||||
"""获取状态:0为断开,1为接通"""
|
||||
return self.pin.value() ^ (not self.pull_down)
|
||||
|
||||
def _debounce(self):
|
||||
"""去抖"""
|
||||
current_time = utime.ticks_ms()
|
||||
current_state = self._get_state()
|
||||
|
||||
# 若当前与上一次检测间隔大于等于防抖时间则更新时间,又若状态不同则更新状态
|
||||
if utime.ticks_diff(current_time, self._last_time) >= 20:
|
||||
if current_state != self._last_state:
|
||||
self._last_time = current_time
|
||||
self._last_state = current_state
|
||||
|
||||
return self._last_state
|
||||
|
||||
@property
|
||||
def switched(self):
|
||||
"""是否接通"""
|
||||
return self._debounce() == 1
|
||||
|
||||
|
||||
"""
|
||||
使用示例
|
||||
|
||||
引脚0→LED→220R→GND
|
||||
引脚1→MTS102→GND
|
||||
|
||||
import utime
|
||||
from machine import Pin
|
||||
from utils.switch import MTS102
|
||||
|
||||
led = Pin(0, Pin.OUT)
|
||||
toggle = MTS102(pin=1)
|
||||
|
||||
while True:
|
||||
if toggle.switched:
|
||||
led.on()
|
||||
else:
|
||||
led.off()
|
||||
|
||||
utime.sleep_ms(200)
|
||||
"""
|
||||
Loading…
Reference in New Issue