diff --git a/RP2350ZERO/main.py b/RP2350ZERO/main.py index 57f2b5d..893ee9f 100644 --- a/RP2350ZERO/main.py +++ b/RP2350ZERO/main.py @@ -7,8 +7,8 @@ led = Pin(0, Pin.OUT) toggle_switch = MTS102(1) while True: - if toggle_switch.switched: - led.on() - else: - led.off() - utime.sleep_ms(200) \ No newline at end of file + if toggle_switch.switched: + led.on() + else: + led.off() + utime.sleep_ms(200) diff --git a/RP2350ZERO/utils.py b/RP2350ZERO/utils.py index 88adbe9..9475b50 100644 --- a/RP2350ZERO/utils.py +++ b/RP2350ZERO/utils.py @@ -1,7 +1,3 @@ -import utime -from machine import Pin - - class MTS102: """MTS102(三脚二档钮子开关)""" @@ -15,6 +11,9 @@ class MTS102: self.pull_down = pull_down + import utime + from machine import Pin + # 尝试初始化引脚 try: self.pin = Pin( @@ -63,10 +62,12 @@ class WS2812: raise TypeError("led_beads数据类型应为整数") if not led_beads >= 1: - raise ValueError("led_beads数值应大于等于1") + raise ValueError("led_beads应大于等于1") self.led_beads = led_beads + import utime + from machine import Pin from neopixel import NeoPixel # 尝试初始化LED @@ -98,7 +99,7 @@ class WS2812: raise TypeError("通道数据数据类型应为整数") if not 0 <= channel_value <= 255: - raise ValueError("通道数据数值应在0~255范围内") + raise ValueError("通道数据应在0~255范围内") for bead_idx, bead_color in enumerate(colors): # 将RGB转为GRB并设置灯珠颜色 @@ -115,51 +116,153 @@ class WS2812: raise TypeError("hue_offset数据类型应为整数") if not 0 <= hue_offset <= 60: - raise ValueError("hue_offset数值应在0~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 - region * 60) * 255 // 60 + remainder = (hue % 60) * 255 // 60 + # RGB颜色通道中的最小值分量 + primary = (value * (255 - saturation)) // 255 + # RGB颜色通道中的过渡分量 + tertiary = (value * remainder) // 255 - # 最小分量 - P = (value * (255 - saturation)) // 255 - # 过渡值 - transition = (value * remainder) // 255 - - # 根据色域将HSV转为RGB并新增至颜色列表 - colors.append( - { - 0: (value, transition, P), # 由红色渐变为黄色 - 1: (value - transition, value, P), # 由黄色渐变为绿色 - 2: (P, value, transition), # 由绿色渐变为青色 - 3: (P, value - transition, value), # 由青色渐变为蓝色 - 4: (transition, P, value), # 由蓝色渐变为洋红色 - 5: (value, P, value - transition), # 由洋红色渐变为红色 - }[region] - ) + # 根据色域将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, 1, 1, 1, 1, 1][region] + hue = hue + [1, 3, 3, 3, 3, 1][region] - utime.sleep_ms(100) + utime.sleep_ms(20) class Servo: """舵机(基类)""" - # 运动轨迹 - _TRAJECTORIES = { - "linear": lambda x: (x, 1), # 表示当前进度和速度 + + 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): + """计算转动步数""" \ No newline at end of file