【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
|