基于STEP Pico设计的反应测试器
使用STEP Pico开发一个反应测试器,在按下k2开始测试后等待随机时间后随机点亮板上的一个LED,用户见到灯亮之后按下板上的一个按键k1,在显示屏上显示出从灯亮到按键之间的时间
标签
嵌入式系统
测试
显示
2023寒假在家练
Zensenw
更新2023-03-28
北京理工大学
292

1项目要求

具体要求:随机点亮板上的一个LED,按下板上的一个按键,在显示屏上显示出从灯亮到按键之间的时间。

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

2完成的功能及达到的性能

2.1随机显示LED

在1~12个LED灯中随机打开一个LED,颜色默认为白色,该颜色可以在程序中进行自定义。

2.2预备信号灯

在用户准备完成后(即按下k2键),黄灯亮,提示用户进行准备;在经过随机时间后显示随机LED灯,这段时间红灯亮,提示用户按下k1进行反应;在用户按下k1键后,绿灯亮,提示用户已经按下k1键并且完成检测。

2.3按键检测

k1键设定为反应键,用于在用户看到亮灯后按下按键计算反应时间;

k2键设定为准备键,按下后开始新的一轮测试;

k1,k2键均有消抖功能,在测试中表现良好;

2.4屏幕对应显示

在开始时屏幕显示Reaction Test,在用户准备完成后(即按下k2键),屏幕显示Ready!,提示用户进行准备;在经过随机时间后显示随机LED灯,这段时间屏幕显示Press!,提示用户按下k1进行反应;在用户按下k1键后,屏幕显示一行Reaction Time,下一行显示保留三位小数的反应时间(e.g. 0.123s)提示用户已经按下k1键并且完成检测,将反应时间显示出来。

2.5随机亮灯时间

使用MicroPython的random库随机一个延迟时间,用于延迟后显示LED供用户进行反应测试。

2.6计算反应时间

使用MicroPython的time库的time.ticks_ms()函数计算亮灯后到用户按下的时间。

3实现思路

  • 检测按键,完成按键消抖
  • 延迟随机时间
  • 显示随机LED灯
  • 检测用户的反应时间
  • 相应图形显示

4实现过程

4.1程序流程图

Fpu7a3Vyl2P6gaNRb3NzfEPZOJSf

4.2 LED相关

首先需要在代码中定义开发板上的LED引脚,以便控制LED的亮灭,设定对应的显示颜色,以点亮LED,需要将LED颜色重新设定为#000000,以关闭LED。这一部分在ws2812b.py中已经定义完成,只需要调用ws2812b.on(n)即可打开第n个LED,ws2812b.off(n) 即可关闭第n个LED;

首先需要在代码中定义开发板上的LED引脚,以便控制LED的亮灭,选择要点亮的LED之后,需要将相应的LED引脚设为输出,设定高电平,以点亮LED,设定为低电平,以关闭LED。这一部分在led.py中已经定义完成,只需要调用r.on()即可打开红色LED,r.off()即可关闭红色LED,同理黄色LED、绿色LED、蓝色LED;

from board import pin_cfg
import time
from machine import Pin

r = Pin(pin_cfg.red_led, Pin.OUT)
g = Pin(pin_cfg.green_led, Pin.OUT)
b = Pin(pin_cfg.blue_led, Pin.OUT)
y = Pin(pin_cfg.yellow_led, Pin.OUT)

4.3 随机数相关

之后需要使用随机数生成器来随机选择要点亮的LED,我使用的是MicroPython中的random库,其中有int(random.uniform(0, 12))函数用于生成一个1~12的整数用于选择LED;

在测试中,使用random.uniform(2, 5)生成一个2~5的浮点数,在等待该数时间后开始亮起一个随机LED进行测试。

import random

4.4 按键相关

       项目中k1、k2按键均由button.py进行了声明,在该文件中定义了引脚,并且进行了按键消抖,在主程序循环中只需要检测k1.value()即可完成对于k1按键的检测。

import time
from board import pin_cfg
from machine import Pin

class button:
    def __init__(self, pin, callback=None, trigger=Pin.IRQ_RISING, min_ago=200):
        #print("button init")
        self.callback = callback
            
        self.min_ago = min_ago
        self._next_call = time.ticks_add(time.ticks_ms(), self.min_ago)

        self.pin = Pin(pin, Pin.IN, Pin.PULL_UP)

        self.pin.irq(trigger=trigger, handler=self.debounce_handler)

        self._is_pressed = False

    def call_callback(self, pin):
        #print("call_callback")
        self._is_pressed = True
        if self.callback is not None:
            self.callback(pin)

    def debounce_handler(self, pin):
        #print("debounce")
        if time.ticks_diff(time.ticks_ms(), self._next_call) > 0:
            self._next_call = time.ticks_add(time.ticks_ms(), self.min_ago)
            self.call_callback(pin)

    def value(self):
        p = self._is_pressed
        self._is_pressed = False
        return p

k1 = button(pin_cfg.k1)
k2 = button(pin_cfg.k2)

4.5 屏幕相关

       在项目中从Pico输出到屏幕的驱动由oled.py、ssd1306.py进行了声明,在该文件中定义了屏幕引脚对应,屏幕的初始化方式,数据写入屏幕的方式和基本的图形函数、文字函数;在进行屏幕编辑时,使用oled.text()即可编辑文字,将编辑好的屏幕展示出来需要oled.show(),屏幕重新写需要oled.fill(0)首先进行屏幕清除,然后再将写好的屏幕展示出来。

from machine import Pin, SPI
from ssd1306 import SSD1306_SPI
import framebuf
from board import pin_cfg
 
spi = SPI(1, 100000, mosi=Pin(pin_cfg.spi1_mosi), sck=Pin(pin_cfg.spi1_sck))
oled = SSD1306_SPI(128, 64, spi, Pin(pin_cfg.spi1_dc),Pin(pin_cfg.spi1_rstn), Pin(pin_cfg.spi1_cs))
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces

from micropython import const
import framebuf


# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_IREF_SELECT = const(0xAD)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)

# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        self.buffer = bytearray(self.pages * self.width)
        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
        self.init_display()

    def init_display(self):
        for cmd in (
            SET_DISP,  # display off
            # address setting
            SET_MEM_ADDR,
            0x00,  # horizontal
            # resolution and layout
            SET_DISP_START_LINE,  # start at line 0
            SET_SEG_REMAP | 0x01,  # column addr 127 mapped to SEG0
            SET_MUX_RATIO,
            self.height - 1,
            SET_COM_OUT_DIR | 0x08,  # scan from COM[N] to COM0
            SET_DISP_OFFSET,
            0x00,
            SET_COM_PIN_CFG,
            0x02 if self.width > 2 * self.height else 0x12,
            # timing and driving scheme
            SET_DISP_CLK_DIV,
            0x80,
            SET_PRECHARGE,
            0x22 if self.external_vcc else 0xF1,
            SET_VCOM_DESEL,
            0x30,  # 0.83*Vcc
            # display
            SET_CONTRAST,
            0xFF,  # maximum
            SET_ENTIRE_ON,  # output follows RAM contents
            SET_NORM_INV,  # not inverted
            SET_IREF_SELECT,
            0x30,  # enable internal IREF during display on
            # charge pump
            SET_CHARGE_PUMP,
            0x10 if self.external_vcc else 0x14,
            SET_DISP | 0x01,  # display on
        ):  # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP)

    def poweron(self):
        self.write_cmd(SET_DISP | 0x01)

    def contrast(self, contrast):
        self.write_cmd(SET_CONTRAST)
        self.write_cmd(contrast)

    def invert(self, invert):
        self.write_cmd(SET_NORM_INV | (invert & 1))

    def rotate(self, rotate):
        self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
        self.write_cmd(SET_SEG_REMAP | (rotate & 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width != 128:
            # narrow displays use centred columns
            col_offset = (128 - self.width) // 2
            x0 += col_offset
            x1 += col_offset
        self.write_cmd(SET_COL_ADDR)
        self.write_cmd(x0)
        self.write_cmd(x1)
        self.write_cmd(SET_PAGE_ADDR)
        self.write_cmd(0)
        self.write_cmd(self.pages - 1)
        self.write_data(self.buffer)


class SSD1306_I2C(SSD1306):
    def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        self.write_list = [b"\x40", None]  # Co=0, D/C#=1
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.temp[0] = 0x80  # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)

    def write_data(self, buf):
        self.write_list[1] = buf
        self.i2c.writevto(self.addr, self.write_list)


class SSD1306_SPI(SSD1306):
    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
        self.rate = 10 * 1024 * 1024
        dc.init(dc.OUT, value=0)
        res.init(res.OUT, value=0)
        cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.res = res
        self.cs = cs
        import time

        self.res(1)
        time.sleep_ms(1)
        self.res(0)
        time.sleep_ms(10)
        self.res(1)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(buf)
        self.cs(1)

4.6 时间相关

引用了MicroPython中的time库,在测试LED灯亮起的同时开始计时,将该时间记录在Start_time变量中,在用户反应按下按键k1后将时间记录在End_time中,计算react_time=End_time-Start_time即为用户的反应时间

import time

4.6 程序代码

from button import k1,k2
from board import pin_cfg
from led import r,g,b,y
from oled import oled

import time
import ws2812b
import random

rand_time=0
react_time=0
Start_time=0
End_time=0
cs_led=0

oled.text("Reaction Test",12,20)
oled.show()

def YON():
    y.on()
    r.off()
    g.off()
def RON():
    y.off()
    r.on()
    g.off()
def GON():
    y.off()
    r.off()
    g.on()
def YRGoff():
    y.off()
    r.off()
    g.off()

YRGoff()
ws2812b.off_all()

while True:
    if k2.value():
        oled.fill(0)
        oled.show()
        rand_time=random.uniform(2,5)
        cs_led=int(random.uniform(0,12))
        oled.text("Ready!",12,20)
        oled.show()
        YON()
        time.sleep(rand_time)
        oled.fill(0)
        oled.show()
        oled.text("Press!",12,20)
        oled.show()
        ws2812b.on(cs_led)
        RON()
        Start_time=time.ticks_ms()
        while True:
            if k1.value():
                End_time=time.ticks_ms()
                oled.fill(0)
                oled.show()
                GON()
                ws2812b.off(cs_led)
                break
        react_time=End_time-Start_time
        oled.text("Reaction Time",12,20)
        oled.text('{:3.3f}s'.format(react_time/1000),12,40)
        oled.show()
        print('Reaction Time:'+str(react_time/1000)+'s')
        time.sleep(0.3)

print('Ending...')

5遇到的主要难题

使用STEP Pico时需要进行初始化,将上一次测试中的LED灯关闭,在每一次屏幕刷新时也需要将屏幕重新刷新;在程序的开头完成了屏幕LED等器件的初始化。

测试器的时间精度不够高,在测试过程中,可能会出现延迟问题,例如按键响应速度慢或显示屏显示延迟等。应当在程序中进行适当的延迟和缓冲来确保测试的准确性和可靠性。已经通过程序中将两次计时之间运行的代码削减到最少。

测试器需要具备良好的用户友好性,如合理的界面设计和交互方式等。需要考虑用户的使用体验,并在程序中提供必要的提示和帮助,以确保用户能够正确地操作测试器。已经通过屏幕文字提示和RGYLED灯进行了很大程序上的提示。

6未来展望

该项目后续可以引入更多的交互方式,比如蜂鸣器buzzer的提示功能,对用户的听觉进行刺激,让用户达到更快的反应速度。

该项目可以将测试器设计成类似于游戏的形式,引入奖励机制、排行榜等元素,以吸引更多的用户使用并提高其趣味性和互动性。

该项目可以使用更高精度的计时器和传感器,以提高测试器的精度和稳定性,从而更好地满足用户的需求和期望。

附件下载
MyProgram.zip
烧录MyProgram-main.py即可运行
团队介绍
来自北京理工大学的一名学生万子森
团队成员
Zensenw
北京理工大学学生万子森
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号