268 lines
10 KiB
Python
268 lines
10 KiB
Python
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
|
||
|
||
import utime
|
||
from machine import Pin
|
||
|
||
# 尝试初始化引脚
|
||
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
|
||
|
||
|
||
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
|
||
|
||
import utime
|
||
from machine import Pin
|
||
from neopixel import NeoPixel
|
||
|
||
# 尝试初始化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)
|
||
|
||
|
||
class Servo:
|
||
"""舵机(基类)"""
|
||
|
||
|
||
class Easing:
|
||
"""缓动曲线对象"""
|
||
def __init__(self, function, density):
|
||
"""
|
||
:param function: 缓动曲线函数,X轴为进度(0至1),Y轴为单位速率
|
||
:param density: 步数密度
|
||
"""
|
||
self.function = function
|
||
self.density = density
|
||
self.speed = self._calculate_speed()
|
||
|
||
def _calculate_speed(self, intervals=100):
|
||
"""
|
||
基于复核中点矩形法计算平均速率
|
||
:param intervals: 积分间隔数
|
||
"""
|
||
return sum(self.function((2 * i + 1) / (2 * intervals))[1] for i in range(intervals)) / intervals
|
||
|
||
|
||
# 缓动曲线
|
||
_EASINGS = {
|
||
"linear": Easing(lambda x: (x, 1), 1.0),
|
||
"ease_in": Easing(lambda x: (x ** 2, 2 * x), 1.5),
|
||
"ease_out": Easing(lambda x: (1 - (1 - x) ** 2, 2 * (1 - x)), 1.5),
|
||
}
|
||
|
||
def __init__(self, pin, frequency=50, min_angle=0, max_angle=180, min_pulsewidth=500, max_pulsewidth=2500, dead_zone=8, duration_per_angle=0.13):
|
||
"""
|
||
:param pin: 引脚编号
|
||
:param frequency: 频率,单位为赫兹
|
||
:param min_angle: 最小转动角度,单位为度
|
||
:param max_angle: 最大转动角度,单位为度
|
||
:param min_pulsewidth: 最小转动角度对应的脉宽,单位为微秒
|
||
:param max_pulsewidth: 最大转动角度对应的脉宽,单位为微秒
|
||
:param dead_zone: 死区,单位为微秒
|
||
:param duration_per_angle: 转动单位角度对应的时长,单位为秒/六十度
|
||
"""
|
||
|
||
import utime
|
||
from machine import Pin, PWM
|
||
|
||
# 尝试初始化PWM
|
||
try:
|
||
self.pwm = PWM(Pin(pin, Pin.OUT))
|
||
# 设置频率
|
||
self.pwm.freq(frequency)
|
||
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.dead_zone = dead_zone * 1000
|
||
# 转动单位角度对应的脉宽,单位为纳秒/度
|
||
self.pulsewidth_per_angle = (self.max_pulsewidth - self.min_pulsewidth) / (self.max_angle - self.min_angle)
|
||
# 转动单位角度对应的时长单位由秒/六十度转为纳秒/度
|
||
self.duration_per_angle = duration_per_angle * 1000 * 1000 * 1000 / 60
|
||
# 是否旋转中
|
||
self.rotating = False
|
||
|
||
# 当前角度
|
||
self.current_angle = self.min_angle
|
||
|
||
def set_angle(self, target_angle, easing="line"):
|
||
"""
|
||
设置旋转角度
|
||
: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数据类型应为字符")
|
||
|
||
# 缓动函数
|
||
self.easing = self._EASINGS.get(easing.low(), self._EASINGS["linear"])
|
||
|
||
# 若为旋转中则返回设置旋转角度失败
|
||
if self.rotating:
|
||
return False
|
||
|
||
# 若目标转动角度对应的脉宽和当前转动角度对应的脉宽差小于死区则返回设置旋转角度成功
|
||
if abs(self._angle_to_pulsewidth(target_angle) - self._angle_to_pulsewidth(self.current_angle)) < self.dead_zone:
|
||
return True
|
||
|
||
steps, duration = self._calculate_steps(abs(target_angle - self.current_angle))
|
||
|
||
def _angle_to_pulsewidth(self, angle):
|
||
"""将转动角度转为脉宽"""
|
||
pulsewidth = self.min_pulsewidth + (angle - self.min_angle) * self.pulsewidth_per_angle
|
||
|
||
return int(round(pulsewidth))
|
||
|
||
def _calculate_steps(self, angle_difference):
|
||
"""计算转动步数""" |