MicroPython/RP2350ZERO/utils.py

268 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class MTS102:
"""MTS102三脚二档钮子开关"""
def __init__(self, pin, pull_down=False):
"""
:param pin: 引脚编号
:param pull_down: 引脚下拉电阻断开为低电平接通为高电平默认为False。若使用下拉电阻因钮子接通后断开可能存留电荷/漏电故需在引脚和GND并联1K10K欧电阻
"""
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("通道数据应在0255范围内")
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应在060范围内")
# 初始化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至1Y轴为单位速率
: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):
"""计算转动步数"""