基于STEP Pico完成反应测试器的制作
使用python语言在树莓派pico上实现简单的反应测试功能
标签
MicroPython
2023寒假在家练
反应测试
z77
更新2023-03-29
北京理工大学
231

一、项目需求

制作一个反应测试器,随机点亮板上的一个LED,按下板上的一个按键,在显示屏上显示出从灯亮到按键之间的时间。

实现方式:通过软件产生随机数,程序启动以后在随机数控制的时间下点亮板上的LED,被测试者按下按键以后,处理器计算从点亮灯到接收到按键之间的时间差,并将时间差通过USB显示在PC上,也可以将OLED用起来,在OLED上显示时间信息。

二、环境配置

1、thonny:

 作为官方推荐的开发软件,thonny页面简洁,基础功能齐全,简单易上手,非常适合初学者(比如我)。   

2、硬禾学堂树莓派pico平台

硬禾学堂为“暑期一起练”制作了一个平台,正是我视频中演示用到的板子,其原理图如下

AWgqP8fEH9bUy0ee+EAAAAASUVORK5CYII=wEnF75i7eXRuwAAAABJRU5ErkJggg==

三、程序实现

此次全部代码由micropython编写,其中所用到的代码库来自于gitee网站,感谢大佬的分享。

本程序主要代码如下:

from button import k1, k2 #K1按钮用于反应检测,K2按钮用于启动测试
from board import pin_cfg
from oled import oled
from machine import Pin
import random #生成随机数
import time
import ws2812b #控制LED灯的ws2812b模块

time_reaction = 0 #定义反应时间变量
time_start = 0 #定义开始时间变量
rd_time = 0 #定义随机时间变量
end_time = 0 #定义结束时间变量

oled.text("Reaction Test",10,20)
oled.text("press k2 to run",5,30) # 在屏幕上显示开始提示
oled.show()
test_state = False #定义测试状态变量
while True: #开始计时
    if k2.value() and not test_state: #当K2被按下时且当前没有进行测试
        test_state = True #开始进行测试
        oled.fill(0) #清屏
        oled.text("READY!",5,25)
        oled.show() 
        rd_time = random.uniform(2,7) #产生随机时间
        time.sleep(rd_time)
        ws2812b.on(int(random.uniform(0,12)),"#00FF00") #产生随机LED位置
        oled.fill(0)
        oled.text("GO!",50,25)
        oled.show()
        time_start = time.ticks_ms() #将此时的时间赋予time_start
        while True:
            if k1.value():
                end_time = time.ticks_ms()
                oled.fill(0)
                oled.show()
                ws2812b.off_all()
                break  
        time_reaction = end_time - time_start
        time_reaction = round((time_reaction)/1000,2) #取小数点后两位
        print(time_reaction)
        oled.text("Reaction Time:",5,25)
        oled.text(str(time_reaction) + 's',45,35)
        oled.show()
        test_state = False  

这段代码是一个反应测试程序,其中包括以下组件:

  1. 导入了按钮、板子、OLED显示屏、随机数、时间、和LED灯模块的库
  2. 设置了反应时间、开始时间、随机时间、和结束时间等变量
  3. 显示了"Reaction Test"和"press k2 to run"的文字提示,提示用户按下K2开始测试
  4. 当按下K2时,开始进行测试,先在屏幕上显示"READY!",然后随机生成一个时间,等待该时间后再在屏幕上显示"GO!"和亮起一个随机LED灯
  5. 开始计时反应时间,等待用户按下K1,记录结束时间,计算反应时间
  6. 在屏幕上显示反应时间,等待下一轮测试

设计框图如下:

 288YMA3CEwAWBCYALAgMAFgQWACwILABIAFgQkACwITABYEJgAsCEwAWBCYALAgMAFgQWACwILABIAFgQkACwITABYEJgAsCEwAWBCYALAgMAFgQWACwILABIAFgQkACwITABYEJgAsCEwAWBCYALAgMAFgQWACwILABIAFgQkACwITABYEJgAsCEwAWBCYALAgMAHgrcfjB8+Uz+wP1q+DAAAAAElFTkSuQmCC

整个程序的逻辑比较清晰,先进行初始化并提示用户按下K2开始测试,然后在按下K2后开始测试,测试结束后在屏幕上显示结果,并等待下一轮测试。其中,随机数的生成和LED灯的控制,可以增加测试的随机性和趣味性。

所使用的板载代码库中用到了控制LED灯的ws2812b代码如下

import array, time, math
from machine import Pin
import rp2

LED_COUNT = 12 # number of LEDs in ring light
PIN_NUM = 18 # pin connected to ring light
brightness = 1.0 # 0.1 = darker, 1.0 = brightest

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
             autopull=True, pull_thresh=24) # PIO configuration
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()

state_mach = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
state_mach.active(1)

pixel_array = array.array("I", [0 for _ in range(LED_COUNT)])

def update_pix(brightness_input=brightness): # dimming colors and updating state machine (state_mach)
    dimmer_array = array.array("I", [0 for _ in range(LED_COUNT)])
    for ii,cc in enumerate(pixel_array):
        r = int(((cc >> 8) & 0xFF) * brightness_input) # 8-bit red dimmed to brightness
        g = int(((cc >> 16) & 0xFF) * brightness_input) # 8-bit green dimmed to brightness
        b = int((cc & 0xFF) * brightness_input) # 8-bit blue dimmed to brightness
        dimmer_array[ii] = (g<<16) + (r<<8) + b # 24-bit color dimmed to brightness
    state_mach.put(dimmer_array, 8) # update the state machine with new colors
    time.sleep_ms(10)

def set_24bit(ii, color): # set colors to 24-bit format inside pixel_array
    color = hex_to_rgb(color)
    pixel_array[ii] = (color[1]<<16) + (color[0]<<8) + color[2] # set 24-bit color
    
def hex_to_rgb(hex_val):
    return tuple(int(hex_val.lstrip('#')[ii:ii+2],16) for ii in (0,2,4))

def on(n, color = "#ffffff"):
    if not ((n >= 1 and n <= 12) and isinstance(n, int)):
        print("arg error")
        return
    set_24bit((n - 1) % 12, color)
    update_pix()
    
def off(n, color = "#000000"):
    if not ((n >= 1 and n <= 12) and isinstance(n, int)):
        print("arg error")
        return
    set_24bit((n - 1) % 12, color)
    update_pix()

def on_all(color = "#ffffff"):
    for i in range(0,12):
        set_24bit(i, color)
    update_pix()

def off_all(color = "#000000"):
    for i in range(0,12):
        set_24bit(i, color)
    update_pix()

def light_value(l):
    if l > 255: l = 255
    elif l < 0: l = 0
    return "#{0:02x}{1:02x}{2:02x}".format(l, l, l)

class PixelDisplay():
    def __init__(self):
        self.pixel_array = array.array("I", [0 for _ in range(12)])

    def set_color(self, n, color):
        """set the color of pixel 'n
        n - 1...12
        color - color tuple"""
        self.pixel_array[(n - 1) % LED_COUNT] = (color[1]<<16) + (color[0]<<8) + color[2]

    def get_color(self, n):
        v = self.pixel_array[(n - 1) % LED_COUNT]
        return ((v >> 8) & 0xff, (v >> 16) & 0xff, v & 0xff)
    
    def fill(self, c):
        for i in range(1, LED_COUNT + 1):
            self.set_color(i, c)

    def dim(self, brightness_input = 1, n = None):
        if n is not None:
            cc = self.pixel_array[n - 1]
            r = int(((cc >> 8) & 0xFF) * brightness_input) # 8-bit red dimmed to brightness
            g = int(((cc >> 16) & 0xFF) * brightness_input) # 8-bit green dimmed to brightness
            b = int((cc & 0xFF) * brightness_input) # 8-bit blue dimmed to brightness
            self.pixel_array[n - 1] = (g<<16) + (r<<8) + b # 24-bit color dimmed to brightness
        else:
            for ii,cc in enumerate(self.pixel_array):
                r = int(((cc >> 8) & 0xFF) * brightness_input) # 8-bit red dimmed to brightness
                g = int(((cc >> 16) & 0xFF) * brightness_input) # 8-bit green dimmed to brightness
                b = int((cc & 0xFF) * brightness_input) # 8-bit blue dimmed to brightness
                self.pixel_array[ii] = (g<<16) + (r<<8) + b # 24-bit color dimmed to brightness
            
    def rainbow(self, offset = 0):
        for i in range(1, LED_COUNT + 1):
            rc_index = (i * 256 // LED_COUNT) + offset
            self.set_color(i, wheel(rc_index & 255))

    def render(self):
        state_mach.put(self.pixel_array, 8)

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 (0, 0, 0)
  if pos < 85:
    return (255 - pos * 3, pos * 3, 0)
  if pos < 170:
    pos -= 85
    return (0, 255 - pos * 3, pos * 3)
  pos -= 170
  return (pos * 3, 0, 255 - pos * 3)

在这个脚本中,我遇到了以下一些难点

  1. 硬件连接问题:该脚本需要连接多个硬件组件,如按钮、OLED屏幕和LED灯,如果连接不正确或存在故障,则可能导致脚本无法正常工作。
  2. 时间测量问题:脚本使用时间函数来测量反应时间,但这种方法可能会受到硬件和操作系统的影响,导致时间精度和准确性存在一定的误差。
  3. 用户操作问题:该脚本依赖用户按下正确的按钮来记录反应时间,如果用户在测试期间按错按钮或按下按钮的时间不准确,则可能导致测量结果不准确。
  4. 代码优化问题:该脚本只是一个简单的实现,代码可能存在一些潜在的优化问题,例如,如何更好地处理硬件中断、如何提高代码的可读性等等。如果需要更高质量的实现,则需要考虑这些方面。

以下是实物展示图:

9k=ZZ9k=

在学习过程中,我了解了关于Micro Python的一些知识,Micro Python是一种嵌入式Python 3实现,它可以在微控制器上运行。

其优势在于:

  1. 简单易学:Micro Python提供了Python语言的所有基本功能和语法,因此如果你熟悉Python语言,就可以很快地学会Micro Python。
  2. 小巧高效:Micro Python非常小巧,运行效率高。它可以在微控制器等低资源设备上运行,并且可以快速启动和运行。
  3. 可移植性强:Micro Python可以运行在不同的硬件平台上,并且支持大量的标准库和扩展库,使其在不同的应用场景下非常灵活和可扩展。
  4. 方便调试:Micro Python可以通过REPL(Read Evaluate Print Loop)进行实时调试,也可以通过WebREPL在Web浏览器上远程调试。

劣势在于:

  1. 有限的资源:Micro Python运行在微控制器等低资源设备上,因此其资源有限,不能像计算机一样执行较复杂的操作。
  2. 学习曲线较陡峭:虽然Micro Python语言和Python非常相似,但由于硬件平台和应用场景的不同,需要学习一些特定的库和API,因此学习曲线较陡峭。
  3. 有限的库支持:虽然Micro Python支持大量的标准库和扩展库,但是与标准Python相比,其库支持仍然有限,有些第三方库没有支持。
  4. 性能较低:由于MicroPython是一种解释性语言,因此其执行速度相对较慢。这使得它不适合处理需要高性能计算的任务。
  5. 不适合大型项目:由于MicroPython具有较小的内存占用和较低的性能,因此它不适合用于大型项目的开发。如果需要处理大量数据或需要高性能计算,则需要选择其他语言或工具。

在学习micro python的过程中,我有一些心得体会。首先Python 是一种入门级语言,易于学习和使用。它的语法简单明了,适合初学者掌握编程基础。它拥有大量的库和模块,可以实现各种各样的功能,如网络编程、数据分析、机器学习等。这些库不仅可以提高编程效率,还可以降低代码复杂度。其次,Python 是一种开放性语言,有着庞大的社区和开发者支持。开发者可以免费获取并使用 Python,也可以通过开源社区贡献代码和分享经验。因此它可以用于各种用途,如 Web 开发、游戏开发、桌面应用程序等。同时,Python 也是数据科学、机器学习和人工智能领域的主流语言,由于Python独特的语法结构和编码风格,使得代码更具可读性和优美性,同时,Python 的命名规范和注释习惯也为代码维护和协作提供了便利。总的来说,学习 Python 可以让我掌握编程基础和编程思维,也可以让我在未来的职业生涯中获得更多的机会和竞争优势。

感谢电子森林举办的这次活动,也感谢硬禾学堂为我们提供的steppico嵌入式学习平台,使我对基于micropython的嵌入式系统学习有了更浓厚的兴趣,希望之后电子森林能多举办类似的活动给我们带来更多丰富的电信知识。

附件下载
反应测试.py
团队介绍
北京理工大学
团队成员
汪辰宇
北京理工大学
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号