[作品提交] 【Follow me第二季第1期】任务汇总_By CJ

andy11112   2024-8-29 01:42 楼主

大家好,以下是此次Follow me第二季第一期的任务汇总,欢迎交流。

【视频汇总】:

入门任务,0:12

基础任务一,1:10

基础任务二,1:42

基础任务三,2:25

进阶任务,3:32

创意任务二,4:16

【源码链接】https://download.eeworld.com.cn/detail/andy11112/634234

【总体心得】:

关于此次活动,从中学到了Adafruit Circuit Playground Express(CPX)的使用,具备多种传感器是这个板子的特点。

此次使用CircuitPython,大部分设计的思路,可以通过官网以及lib中的example中找到。


  • 入门任务(必做):开发环境搭建,板载LED点亮

1、【任务介绍】首先搭建开发环境,然后尝试点亮板载的红色LED指示灯。

1_CirtuiPython.png

  

  • 然后将UF2文件拖放到CPX盘符中,盘符会短暂消失,然后以“CIRCUITPY”名称出现。
2_CirtuiPy.png
3_lib.png
  • 此次选用Mu Editor,下载并安装(https://codewith.mu/)。Mu Editor编辑器的特点,在于可以同时查看CPX的串口输出,方便确认打印信息和做调试。
  • 用到的硬件和配件,包括CPX板子,舵机(用于创意任务),鳄鱼夹连接线,电池盒,扭蛋等。
image.png  
  • 入门任务需要将红灯点亮,使用基础库,对cp.red_led赋值,即可实现。

3、【代码片段】

from adafruit_circuitplayground import cp

while True:
    cp.red_led = True #赋值,True为点亮,False为熄灭

4、【功能展示】

  • 运行代码后,红色指示灯常亮。

image.png  


  • 基础任务一(必做):控制板载炫彩LED,跑马灯点亮和颜色变换

1、【任务介绍】

2、【软件流程图】

image.png  

3、【代码片段】

import time
from rainbowio import colorwheel
from adafruit_circuitplayground import cp

cp.pixels.brightness = 0.1    #设置彩灯的亮度,0为最暗(熄灭),1为最亮
cp.pixels.auto_write = False   #设置False不会立即生效,需要调用pixels.show()

pixels_on = 1    #点亮彩灯,1为开启,0为关闭
pixels_color = 0    #彩灯颜色变换,1为开启,0为关闭
pixels_cycle = 0    #彩灯跑马灯,1为开启,0为关闭

#定义不同颜色
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
OFF = (0, 0, 0)

def turn_on(wait):    #定义点亮彩灯函数
    cp.pixels[0] = (RED)    #第1颗彩灯点亮,颜色为红色
    cp.pixels.show()
    time.sleep(wait)    #间隔特定时间
    cp.pixels.fill((YELLOW))    #全部彩灯点亮,颜色为黄色
    cp.pixels.show()
    time.sleep(wait)    #间隔特定时间
    cp.pixels.fill((OFF))
    cp.pixels.show()

def color_change(wait):     #定义颜色变换函数
    for i in range(0,255,51):   #颜色变化步进,一轮循环5个颜色
        cp.pixels.fill((colorwheel(i & 255)))    #colorwheel函数可以将一个HSV颜色值转换为RGB颜色值。idx & 255表示将idx除以256取余数,可得一个0-255之间的整数
        cp.pixels.show()
        time.sleep(wait)


def rainbow_cycle(wait):    #定义跑马灯函数
    for j in range(255):
        for i in range(cp.pixels.n):
            idx = int((i * 256 / cp.pixels.n) + j)    #10个彩灯,彩色轮盘上的颜色值等间隔分配给各灯,同步变换颜色
            cp.pixels[i] = colorwheel(idx & 255)
        cp.pixels.show()
        time.sleep(wait)


while True:
    if pixels_on:
        turn_on(1)

    if pixels_color:
        color_change(1)

    if pixels_cycle:
        rainbow_cycle(0.001)

4、【功能展示】

  • 彩灯可以变换不同的颜色,以及实现不同颜色跑马灯效果(如下图)。

image.png  


  • 基础任务二(必做):监测环境温度和光线,通过板载LED展示舒适程度

1、【任务介绍】

  • 该任务的关键,是要获得温度和光强数据,这需要用到板上的温度传感器光传感器
  • 光传感器,从官网中能找到相关信息和代码示例(https://learn.adafruit.com/adafruit-circuit-playground-express/playground-light-sensor)。也可以在下载的库文件夹中的example路径下,找到光传感器的示例(路径:adafruit-circuitpython-bundle-9.x-mpy-20240820\examples\circuitplayground\circuitplayground_light.py),即可以简便的通过cp.light读取光传感器输入值。
  • 温度传感器,官网中有介绍(https://learn.adafruit.com/adafruit-circuit-playground-express/playground-temperature)。同时库文件夹中,也有相关示例(上述同路径下的circuitplayground_temperature.py),通过cp.temperature读取温度值。
  • 读取光强和温度值后,通过判断语句,在温度和光强超过舒适范围时,彩灯显示红灯以作警示;否则彩灯显示绿灯,表示环境条件在舒适范围。

2、【软件流程图】

image.png  

3、【代码片段】

import time
from adafruit_circuitplayground import cp

def scale_range(value):    #定义函数,将光线传感器读数范围(0~320)转换为0~9
    return round(value / 320 * 9)

def light_monitor():
    l = scale_range(cp.light)
    print("Light level:", l)
    if l >= 1 and l < 6:
        return True
    else:
        return False

def temperature_monitor():
    t = cp.temperature
    print("Temperature:", t)
    if t > 17 and t < 30:
        return True
    else:
        return False

cp.pixel_brightness = 0.1
cp.pixels.auto_write = False

RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
OFF = (0, 0, 0)

while True:
    if light_monitor():
        print("Light is normal!")
    if temperature_monitor():
        print("Temperature is normal!")
    if light_monitor() and temperature_monitor():
        cp.pixels.fill(GREEN)
        cp.pixels.show()
    else:
        cp.pixels.fill(RED)
        cp.pixels.show()
    time.sleep(1)

4、【功能展示】

  • 光线充足时,显示绿灯;用手遮挡,光线不足时,显示红灯。
  • 温度不便于调整,暂不做展示。

image.png  


  • 基础任务三(必做):接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警

1、【任务介绍】

  • 该任务,要求检测距离,通过LED展示距离情况,当物体和板子距离非常近时,发出声音报警。
  • 重点在于距离探测。通常的探测手段有红外和微波,CPX集成了红外收发模块,所以考虑基于红外做距离探测。
  • 找到irremote库,其中有transmit功能,不过需要做脉冲和编码配置,对于此次测距来说,似乎有些冗余。
  • 在网络上做搜索,找到了一个视频,介绍adafruit板子做IR Proximity,其中展示了Arduino代码实现。

image.png  

2、【软件流程图】

任务四_1.png

3、【代码片段】

import time
import board
import digitalio
import analogio
from adafruit_circuitplayground import cp

# 定义红外接近传感器引脚
IR_TX_PIN = board.IR_TX    # 指定红外发射引脚
PROX_PIN = board.IR_PROXIMITY    # 指定红外接近传感器引脚

# 初始化引脚
ir_tx = digitalio.DigitalInOut(IR_TX_PIN)    # 配置红外发射引脚
ir_tx.direction = digitalio.Direction.OUTPUT    # 设置红外发射引脚为输出模式
ir_tx.value = False    # 设置初始值为低电平,关闭红外发射

prox_in = analogio.AnalogIn(PROX_PIN)    # 配置红外接近传感器引脚

cp.pixels.auto_write = False    # 关闭自动写入,以便手动控制 NeoPixel
cp.pixels.brightness = 0.1    # 设置 NeoPixel 亮度

def distance_convert(value):    # 将红外接近传感器的值转换为距离
    return round((value-30000) / (42000-30000) * 10)    # 将红外接近传感器的值转换为距离

def pixels_indicator(distance):    # 设置 NeoPixel 指示灯
    if distance <5:    # 如果距离小于5
        color = (0,255,255)    # 设置颜色为蓝绿色
    else:
        color = (255,0,0)    # 距离大于5,设置颜色为红色,用于警示
    for i in range(10):    #用于粗略量化指示距离情况
        if i <= distance:
            cp.pixels[i] = (color)
        else:
            cp.pixels[i] = (0, 0, 0)
    cp.pixels.show()

def alarm(distance):    # 定义报警函数
    if distance > 5:    # 如果距离小于5
        cp.play_tone(262, 0.0001)    # 播放音调

# 主循环
while True:
    total = 0    # 重置总距离
    for i in range(10):
        ir_tx.value = True    # 打开红外发射
        time.sleep(0.01)    # 等待一段时间,使红外发射器发射红外光
        ir_tx.value = False    # 关闭红外发射
        #time.sleep(0.01)    # 等待一段时间,使红外发射器停止发射红外光
        prox = prox_in.value    # 读取红外接近传感器的值
        distance = distance_convert(prox)    # 将红外接近传感器的值转换为距离
        print("proximity:",prox,"distance:",distance,"temperature:",cp.temperature)    # 打印红外接近传感器的值
        total =total + distance    # 计算总距离
        time.sleep(0.01)    #等待一段时间
    
    avg = total / 10    # 计算平均距离
    print("average distance:",avg)    # 打印平均距离
    pixels_indicator(avg)    # 设置 NeoPixel 指示灯
    alarm(avg)    # 调用报警函数

4、【功能展示】

  • 手指逐渐靠近板子,起初亮绿灯,随着距离越来越近,亮灯数量越多,超过5个灯亮起时,变成红灯,并且发出警报声。
12_IR sens.png

  • 进阶任务(必做):制作不倒翁——展示不倒翁运动过程中的不同灯光效果

1、【任务介绍】

  • 通过运动传感器,探测CPX的倾斜活动,用彩灯实现指示效果。
  • 重点在于读取运动传感器的数据,换算成角度信息,并和彩灯位置对应。
  • 库文件夹中example路径(\examples\circuitplayground\circuitplayground_advanced_examples\circuitplayground_gravity_pulls_pixel.py),有类似的实现代码,可作为参考,在此基础上稍作修改,加上不同倾斜位置播放不同频率的音调。

2、【软件流程图】

任务五.png

3、【代码片段】

  • 涉及数学运算,导入math库
import time
import math
from adafruit_circuitplayground import cp

PIXEL_SPACING_ANGLE = 30   # 像素之间,间隔30° 
STANDARD_GRAVITY = 9.81   # 标准重力加速度

BACKGROUND_COLOR = 0, 0, 64    # 背景颜色,蓝色
MIN_BRIGHTNESS = 15    # 最小亮度,15
LIGHTING_ARC_LENGTH = 45    # 照明弧长,45°


def compute_pixel_angles():    # 计算像素角度,排除第5和第11个像素(像素从0开始计数)
    return [
        (300 + PIXEL_SPACING_ANGLE * n) % 360 for n in range(12) if n not in (5, 11)
    ]    # % 360 是为了确保角度在0到360之间,因为角度是循环的


def degrees_between(a1, a2):    # 计算两个角度之间的最小度数差
    smaller = min(a1, a2)    # 取两个角度中的较小值
    larger = max(a1, a2)    # 取两个角度中的较大值
    return min(larger - smaller, 360 + smaller - larger)    # 返回两个角度之间的最小度数差


def pixel_brightness(distance_from_down, accel_magnitude):    
    '''
    计算像素亮度,
    distance_from_down表示像素距离地面的距离,
    accel_magnitude表示加速度计测量的重力加速度的大小。
    '''
    half_lighting_arc_length = LIGHTING_ARC_LENGTH / 2    # 照明弧长的一半

    if accel_magnitude < 0.1 or distance_from_down > half_lighting_arc_length:    # 如果加速度小于0.1或者距离超过一半的照明弧长,则返回None
        return None

    normalized_nearness = 1 - distance_from_down / half_lighting_arc_length    # 计算归一化的接近度
    scale_factor = (255 - MIN_BRIGHTNESS) * accel_magnitude    # 计算缩放因子
    color_part = MIN_BRIGHTNESS + round(normalized_nearness * scale_factor)    # 计算颜色部分
    return color_part


def angle_in_degrees(x, y):    # 计算点(x, y)的角度,返回-180到180之间的角度
    return math.atan2(y, x) / math.pi * 180    # math.atan2(y, x)返回的是弧度,需要转换为角度


def positive_degrees(angle):    # 将-180到180的角度转换为0到360的角度
    return (angle + 360) % 360    # (angle + 360) % 360 是为了确保角度在0到360之间,因为角度是循环的


cp.pixels.brightness = 0.1  # 设置像素亮度为0.1
pixel_positions = compute_pixel_angles()    # 计算像素角度

while True:
    debug = cp.switch    #滑动开关,启用调试模式
    accel_x, accel_y = cp.acceleration[:2]  # Ignore z,cp.acceleration[:2]获取加速度计的加速度值,并转换为列表
    down_angle = positive_degrees(angle_in_degrees(accel_x, accel_y))    # 计算地面的角度
    magnitude_limit = STANDARD_GRAVITY    # 设置加速度计测量的重力加速度的大小为标准重力加速度
    normalized_magnitude = (
        min(math.sqrt(accel_x * accel_x + accel_y * accel_y), magnitude_limit)
        / magnitude_limit
    )    # 计算归一化的重力加速度的大小

    pixels_lit = []    #创建一个空列表,用于存储被照亮的像素
    for i, pixel_position in enumerate(pixel_positions):    # 遍历每个像素
        pe = pixel_brightness(
            degrees_between(pixel_position, down_angle), normalized_magnitude
        )    # 计算像素亮度
        cp.pixels[i] = (pe, 0, 0) if pe else BACKGROUND_COLOR    # 设置像素颜色
        if pe:
            pixels_lit.append((i, pe))    # 如果像素被照亮,则将其添加到pixels_lit列表中
            cp.play_tone(262+i*150, 0.1)    # 播放音调,音调频率为262+i*150,持续时间为0.1秒

    if debug:
        lit_formatted = ", ".join(("{}: {:>3d}".format(p, i) for p, i in pixels_lit))
        print(
            "x: {:>6.2f}, y: {:>6.2f}, angle: {:>6.2f}, mag: {:>3.2f}, pixels: [{}]".format(
                accel_x, accel_y, down_angle, normalized_magnitude, lit_formatted
            )
        )
        time.sleep(0.5)

4、【功能展示】

  • 将CPX、电池盒放在扭蛋中,制成简易【不倒翁】。
  • CPX水平状态,全部彩灯为蓝色
  • CPX倾斜后,倾斜一侧的彩灯变为红色,并播放对应频率的声音。
13_tumbler.png

  • 创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩

1、【任务介绍】

  • 根据任务要求,需要探测环境声音的强弱,然后控制电机转动,实现章鱼触角的伸缩。
  • 探测声音,可用板上的麦克风来实现。但CPX上麦克风无法直接读取声音的模拟输入大小,所以找到了lib的example中的circuitplayground_sound_meter.py,用于实现读取环境音量大小,量化到0~10范围后,并用彩灯来指示。
  • 控制电机,参考官网介绍(https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-servo),实现电机控制。此次使用的是【FS90R】,属于连续舵机,参考Continuous Servo Code
  • 添加条件判断:小音量,不做任何操作;中音量,触角缓慢小幅收缩;大音量,触角快速大幅收缩;按下按键,触角缓慢小幅伸展
  • 连续舵机的前转、后转的速度和行程,不太一致,短时间内没有添加详细的触角伸展逻辑,后续有待补充完善。

2、【软件流程图】

任务六.png

3、【代码片段】

  • 涉及声音采集,导入audiobusio库
  • 涉及舵机控制,导入pwmio库和adafruit_motor中的servo库(将lib中的adafruit_motor文件夹添加到CPX的lib路径中)
import array    #导入array模块
import math    #导入math模块
import board    #导入board模块
import audiobusio    #导入audiobusio模块
import pwmio
import time
from adafruit_motor import servo    #导入servo模块
from adafruit_circuitplayground import cp


def constrain(value, floor, ceiling):
    return max(floor, min(value, ceiling))    #将value限制在floor和ceiling之间,如果value小于floor,则返回floor,如果value大于ceiling,则返回ceiling,否则返回value


def log_scale(input_value, input_min, input_max, output_min, output_max):    #定义一个log_scale函数,用于将输入值映射到输出值
    normalized_input_value = (input_value - input_min) / (input_max - input_min)    #将输入值归一化到0-1之间,即输入值减去最小值除以最大值减去最小值
    return output_min + math.pow(normalized_input_value, 0.630957) * (
        output_max - output_min
    )    #将归一化后的输入值映射到输出值,即输出最小值加上归一化后的输入值乘以输出最大值减去输出最小值,再乘以0.630957


def normalized_rms(values):
    minbuf = int(sum(values) / len(values))    #计算输入信号的均值,即输入信号的总和除以输入信号的长度,并将结果转换为整数,作为输入信号的底值
    return math.sqrt(
        sum(float(sample - minbuf) * (sample - minbuf) for sample in values)
        / len(values)
    )    # 计算输入信号的均方根值,即输入信号的总和除以输入信号的长度,并将结果转换为浮点数,再乘以输入信号的总和除以输入信号的长度,最后取平方根


mic = audiobusio.PDMIn(
    board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16
)   #创建一个PDMIn对象,用于从麦克风读取音频数据,采样率为16000,位深度为16,时钟引脚为MICROPHONE_CLOCK,数据引脚为MICROPHONE_DATA,并将结果赋值给mic

samples = array.array("H", [0] * 160)    #创建一个array对象,用于存储采样数据,长度为160,类型为H,即16位无符号整数,并将结果赋值给samples
mic.record(samples, len(samples))    #开始录音,将录音数据存储到samples中,长度为160
input_floor = normalized_rms(samples) + 10    #计算输入信号的均方根值,并加上10作为输入信号的底值

sensitivity = 500    #  设置灵敏度,即输入信号的底值与输入信号的顶值之间的差值
input_ceiling = input_floor + sensitivity    #计算输入信号的顶值,即输入信号的底值加上灵敏度

pwm = pwmio.PWMOut(board.A1, frequency=50)    #创建一个PWMOut对象,用于控制舵机,频率为50Hz,引脚为A1,并将结果赋值给pwm
my_servo = servo.ContinuousServo(pwm)    #创建一个ContinuousServo对象,用于控制舵机,并将结果赋值给my_servo

peak = 0    #初始化峰值,即输入信号的顶值,用于在NeoPixels上显示峰值
while True:
    mic.record(samples, len(samples))    #开始录音,将录音数据存储到samples中,长度为160
    magnitude = normalized_rms(samples)    #计算输入信号的均方根值,并将结果赋值给magnitude
    print((magnitude,))    #打印输入信号的均方根值

    c = log_scale(
        constrain(magnitude, input_floor, input_ceiling),
        input_floor,
        input_ceiling,
        0,
        10,
    )    #将输入信号的均方根值映射到0-10之间,并将结果赋值给c

    print(("c =", c))

    cp.pixels.fill((0, 0, 0))    #将NeoPixels填充为黑色
    for i in range(10):    #遍历NeoPixels,用于指示音量的大小
        if i < c:
            cp.pixels[i] = (i * (255 // 10), 50, 0)
        if c >= peak:
            peak = min(c, 10 - 1)
        elif peak > 0:
            peak = peak - 1
        if peak > 0:
            cp.pixels[int(peak)] = (80, 0, 255)
    cp.pixels.show()

    if c >= 5 and c < 10:    #中等音量,舵机向前缓慢转动
        print("forward_slow")
        my_servo.throttle = 0.1
        time.sleep(0.05)
        print("stop")
        my_servo.throttle = 0.0
        time.sleep(1)

    if c >= 10:    #高音量,舵机向前快速转动
        print("forward_fast")
        my_servo.throttle = 1
        time.sleep(0.7)
        print("stop")
        my_servo.throttle = 0.0
        time.sleep(1)

    if cp.button_a or cp.button_b:    #按下A或B按钮,舵机向后缓慢转动,用于手动回位
        print("backward_slow")
        my_servo.throttle = -0.1
        time.sleep(0.1)
        print("stop")
        my_servo.throttle = 0.0
        time.sleep(1)

4、【功能展示】

  • 用一次性纸杯,纸条和棉线,配合舵机制成简易的章鱼哥。

image.png  

  • 探测环境声音,用彩灯指示音量大小。根据响应音量,控制章鱼哥触角收缩速度和幅度。
  • 音量大于5(换算后的量化值),触角缓慢小幅收缩;音量大于9,触角快速大幅收缩。
14_octopus.png

 

 

本帖最后由 andy11112 于 2024-9-1 17:43 编辑

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复