353|2

11

帖子

3

TA的资源

一粒金砂(中级)

楼主
 

【Follow me第二季第1期】使用circuitpython开发并完成全部任务 [复制链接]

  本帖最后由 StreakingJerry 于 2024-8-8 11:58 编辑

项目介绍

本项目主控为Adafruit Circuit Playground Express,我另外搭配了一块树莓派zero 2w,用来辅助完成章鱼哥的拓展任务。开箱的器件如下:

 

 

视频讲解:


 

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

circuitpython烧写非常简单,在下面的页面下载最新版的官方uf2格式固件:

https://circuitpython.org/board/circuitplayground_express/

然后插上开发板,点击一下reset按钮,将会出现一个叫做CPLAYBOOT的磁盘,将UF2固件复制进去就可以完成烧写。

 

烧写完成后,circuitpython会变成另外一个磁盘,代码文件code.py就在其中,我们直接修改这个文件就可以改变代码,因此可以直接用vscode,将工作目录设置为这个磁盘就可以。并且可以使用串口插件来读取到串口数据。

   

接下来可以正式开始开发。

 

这块板子由于是adafruit官方出品,因此在cpy固件内已经freeze了所有所需的库,可以从源代码中看到:

 

因此我们直接写代码就可以。这块板子有一个专门的库去对应他上面的所有外设,叫做adafruit_circuitplayground.express。只需要导入他,就可以直接点灯并blink,闪烁的灯是USB接口旁边的红灯。由于项目有多个任务需要完成,因此我们加一个判断来方便任务切换。

from adafruit_circuitplayground.express import cpx

# 任务选择
task1 = 0
task2 = 0
task3 = 0
task4 = 0
task5 = 0
task6 = 0
task7 = 1

# 入门任务(必做):开发环境搭建,板载LED点亮
if task1:
    while True:
        cpx.red_led = True  # Turns the little LED next to USB on
        time.sleep(0.5)
        cpx.red_led = False
        time.sleep(0.5)

 

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

为了实现跑马灯效果,需要先定义一个函数来计算灯的RGB值,然后用该函数分别设置每个灯的RGB,再循环增加index就可以。在程序开头我们还可以定义一下常用的颜色,灯珠数量,以及并设置一下亮度,方便后面使用。

color = {
    "black": 0x000000,
    "white": 0xFFFFFF,
    "red": 0xFF0000,
    "green": 0x00FF00,
    "blue": 0x0000FF,
    "cyan": 0x00FFFF,
    "magenta": 0xFF00FF,
    "yellow": 0xFFFF00,
}


# Not too bright!
cpx.pixels.brightness = 0.3
# Number of Pixel on board
pix_num = 10

def wheel(pos):
    # Input a value 0 to 255 to get a color value.
    # The colours are a transition r - g - b - back to r.
    if (pos < 0) or (pos > 255):
        return color["black"]
    if pos < 85:
        return (int(255 - pos * 3), int(pos * 3), 0)
    elif pos < 170:
        pos -= 85
        return (0, int(255 - (pos * 3)), int(pos * 3))
    else:
        pos -= 170
    return (int(pos * 3), 0, int(255 - pos * 3))

# 基础任务一(必做):控制板载炫彩LED,跑马灯点亮和颜色变换
if task2:
    pixeln = 0
    while True:
        for p in range(pix_num):
            rainbow = wheel(255 // pix_num * ((pixeln + p) % pix_num))
            cpx.pixels[p] = tuple(
                int(c * ((pix_num - (pixeln + p) % pix_num)) / pix_num) for c in rainbow
            )
        # Each time 'round we tick off one pixel at a time
        if cpx.switch:  # depending on the switch we'll go clockwise
            pixeln += 1
            if pixeln > pix_num - 1:
                pixeln = 0
        else:  # or counter clockwise
            pixeln -= 1
            if pixeln < 0:
                pixeln = pix_num - 1

 

 

 

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

通过cpx读取到光线和温度数据后,可以根据当前数值和舒适数值的差异,计算出一个”难受指数“,用这个数字的大小来表示舒适度。数字越大时,代表环境越不好,板子上的RGB灯会亮的越多。在这之前,我们还需要写一个范围映射函数,来把一个范围内的变量映射到另一个范围。

def map_range(input_value, input_min, input_max, output_min, output_max):
    std_value = (input_value - input_min) / (input_max - input_min)
    return std_value * (output_max - output_min) + output_min

# 基础任务二(必做):监测环境温度和光线,通过板载LED展示舒适程度
if task3:
    while True:
        temperature_value = cpx.temperature
        light_value = cpx.light
        discomfort_value = abs(light_value - 20) + abs(temperature_value - 25)
        print(
            "Temperature: %0.1f *C; Light Level: %d; Discomfort Level: %d"
            % (temperature_value, light_value, discomfort_value)
        )
        max_value = 100
        min_value = 20
        discomfort_value = min(discomfort_value, max_value)
        discomfort_value = max(discomfort_value, min_value)
        discomfort_index = int(map_range(discomfort_value,min_value,max_value,0,10))
        for p in range(10):
            if p <= discomfort_index:
                cpx.pixels[p] = tuple(int(c * ((10 - p % 10)) / 10.0) for c in wheel(25 * (p % 10)))
            else:
                cpx.pixels[p] = color["black"]

 

   

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

 

这个任务使用了板子上的红外发射和接收。如果板子面前有障碍物,就会反射更多的红外发射光,接收管会有更大的读数,通过这种方式来检测侵入。当侵入超过设定阈值,我这里设置为5时,则播放一个声音来报警。

 

这里最为有趣的是,需要让红外LED闪一下,关闭LED后再去读取传感器的数值。不能直接让红外LED亮着去读数。具体什么原理我还没想明白,希望大神赐教。

# 基础任务三(必做):接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警
if task4:
    ir_tx = DigitalInOut(board.IR_TX)
    ir_tx.direction = Direction.OUTPUT
    proximity = AnalogIn(board.IR_PROXIMITY)
    while True:
        ir_tx.value = True
        time.sleep(0.001)
        ir_tx.value = False
        proximity_value = proximity.value
        print("proximity Level: %d" % proximity_value)
        max_value = 42500
        min_value = 31500
        interval_value = (max_value - min_value) / 11
        proximity_index = (proximity_value - min_value) // interval_value
        for p in range(10):
            if p <= proximity_index:
                cpx.pixels[p] = tuple(int(c * ((10 - p % 10)) / 10.0) for c in wheel(25 * (p % 10)))
            else:
                cpx.pixels[p] = color["black"]
        if proximity_index > 5:
            cpx.play_file("Fanfare.wav")

 

 

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

这个任务中我们先读出xyz加速度的值,这里我们只使用x和y的数据,也就是板子平面上的。通过笛卡尔坐标转换极坐标的方式,用反三角函数和勾股定理计算出不倒翁倾斜的角度和倾斜的程度,并在对应方向亮起指示灯。

# 进阶任务(必做):制作不倒翁——展示不倒翁运动过程中的不同灯光效果
if task5:
    while True:
        x, y, z = cpx.acceleration
        angle = (2 - (math.atan2(x, y) / math.pi + 1)) * 180
        magnitude = math.sqrt((x * x) + (y * y))
        print(
            "Accelerometer: (%0.1f, %0.1f, %0.1f) m/s^2; Angle: %0.1f, Magnitude: %0.1f"
            % (x, y, z, angle, magnitude)
        )
        if magnitude > 2:
            magnitude = min(magnitude,9.8)
            for p in range(10):
                if p == (angle * 10 // 360):
                    cpx.pixels[p] = tuple(
                        int(c * ((10 - p % 10)) / 10.0) for c in wheel(25 * (p % 10))
                    )
                else:
                    cpx.pixels[p] = color["black"]

 

 

 

创意任务三:水果钢琴——通过触摸水果弹奏音乐,并配合灯光效果

板子上除了DAC A0外,其余所有引脚均支持电容触摸功能。程序检测到某一个引脚被触摸后,会亮起离它最近的LED,并播放对应音高的音。由于水果是导电的,也具备一定的电容,因此我们把引脚引出来连接到水果上也同样有效。只是记得,这个传感器的原理是测量电容值的变化量,因此会在初始化的时候记录当前电容测量值作为参考。因此,接上水果后,需要重新运行代码,改变一下基准参考。

# 创意任务三:水果钢琴——通过触摸水果弹奏音乐,并配合灯光效果
if task7:
    while True:
        if cpx.touch_A4:
            cpx.start_tone(524)
            cpx.pixels[0] = color["magenta"]
        elif cpx.touch_A5:
            cpx.start_tone(588)
            cpx.pixels[1] = color["magenta"]
        elif cpx.touch_A6:
            cpx.start_tone(660)
            cpx.pixels[3] = color["magenta"]
        elif cpx.touch_A7:
            cpx.start_tone(698)
            cpx.pixels[4] = color["magenta"]
        elif cpx.touch_A1:
            cpx.start_tone(784)
            cpx.pixels[6] = color["magenta"]
        elif cpx.touch_A2:
            cpx.start_tone(880)
            cpx.pixels[8] = color["magenta"]
        elif cpx.touch_A3:
            cpx.start_tone(988)
            cpx.pixels[9] = color["magenta"]
        else:
            cpx.stop_tone()
            for p in range(10):
                cpx.pixels[p] = color["black"]

 

 

 

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

这个任务我使用了两种方式实现。

第一种是直接驱动舵机,当检测到声音后,计算声音升压的平均值,然后将这个值转到舵机可接受的pwm脉宽范围,并将PWM信号送出去驱动舵机,同时根据平均声压大小来亮起对应数量的指示灯。PWM输出使用的引脚是A1。

# 创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩
if task6:
    def servo_duty_cycle(pulse_us, frequency=50):
        period_us = 1.0 / frequency * 1000000.0
        duty_cycle = int(pulse_us / (period_us / 65535.0))
        return duty_cycle

    def mean(values):
        return sum(values) / len(values)

    def normalized_rms(values):
        minbuf = int(mean(values))
        sum_of_samples = sum(
            float(sample - minbuf) * (sample - minbuf) for sample in values
        )

        return math.sqrt(sum_of_samples / len(values))

    mic = audiobusio.PDMIn(
        board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16
    )

    samples = array.array("H", [0] * 320)
    mic.record(samples, len(samples))
    pwm = pwmio.PWMOut(board.A1, frequency=50, duty_cycle=0)

    while True:
        mic.record(samples, len(samples))
        sound_value = normalized_rms(samples)
        max_value = 1500
        min_value = 150
        sound_value = min(sound_value, max_value)
        sound_value = max(sound_value, min_value)
        print("Sound Level: %d" % sound_value)
        pwm.duty_cycle = servo_duty_cycle(int(map_range(sound_value, min_value, max_value, 500, 2500)))
        sound_index = int(map_range(sound_value, min_value, max_value, 0, 10))
        for p in range(10):
            if p <= sound_index:
                cpx.pixels[p] = tuple(
                    int(c * ((10 - p % 10)) / 10.0) for c in wheel(25 * (p % 10))
                )
            else:
                cpx.pixels[p] = color["black"]  

 

 

第二种方式是开发板仅作为信号输出,并不直接驱动舵机,舵机用树莓派zero 2w来驱动。这就需要树莓派先读取PWM信号,再输出pwm信号。读取PWM信号需要用到精确的中断计时,对树莓派来说有一定难度,使用pigpio库来实现。

import time
import pigpio
import read_PWM

pi = pigpio.pi()

p = read_PWM.reader(pi, 23)

while True:
    f = p.frequency()
    us = p.pulse_width()
    dc = p.duty_cycle()
    print("f={:.1f} pw={} dc={:.2f}".format(f, int(us + 0.5), dc))

    if 500 <= us <= 2500:
        pi.set_servo_pulsewidth(24, us)

    time.sleep(0.01)

p.cancel()
pi.stop()

 

我们把单片机的PWM街道23号脚,把舵机接到24号脚上。

 

然后运行代码:

#!/bin/bash

sudo pigpiod
python main.py

 

 

心得体会

第一次使用如此方便的开发板,官方甚至为这块板子专门写了一个cpx库,用起来简直就是一键调用,甚至连初始化代码等都可以直接省略。可以说,这块开发板是我迄今为止见到过最适合新手小白入门的板子。

 

完整的项目代码放在下面。大家测试的时候需要注意,由于CPY是脚本语言,因此即使我们没有运行某一部分的代码,这部分代码所需的内存依旧会被计算在内。所有7个任务占用的内存超过了板子的内存限制。由于章鱼哥部分代码占用内存较大,因此在测试其他任务时,需要将章鱼哥的全部代码都注释掉才可以。

https://download.eeworld.com.cn/detail/eew_AG2DvH/633949

 

最新回复

确实是很方便,出来了红外的接近功能,其他都是一步到位    详情 回复 发表于 2024-8-9 10:16
点赞 关注
 
 

回复
举报

6773

帖子

2

TA的资源

版主

沙发
 

完成的真快,感觉这个板子做个产品也很好。

 
 
 

回复

6060

帖子

6

TA的资源

版主

板凳
 

确实是很方便,出来了红外的接近功能,其他都是一步到位 

个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表