基于step-pico实现的时间反应测试器
本项目通过random模块产生一个随机数,每间隔3-5秒点亮led灯。看到灯亮后按下扩展板上的k1按键,采用上升沿中断的方式调用一个回调函数,回调函数里把从灯亮到按下k1的时间差(人的反应时间)显示在oled屏幕和电脑屏幕上。
标签
嵌入式系统
测试
MicroPython
2023寒假一起练
step-pico
hzw_zafu
更新2023-03-29
浙江农林大学
332

基于step-pico实现的时间反应测试器

一、任务要求

随机点亮板上的一个LED,按下板上的一个按键,在显示屏上显示出从灯亮到按键之间的时间,这是心理学上的一个重要实验。

二、实现方式

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

三、开发平台介绍及配置开发环境

1、step-pico平台

step-pico是硬禾学堂基于树莓派RP2040芯片设计的一款完全兼容树莓派pico的开发板。

1)step-pico引脚图

Fj9wo861CSz1rVxw4Yw09uvC8pIO

2)板卡的扩展资源有:

2个按键输入,4个单色LED,12个WS2812B RGB三色灯,1个姿态传感器,1个128*64 OLED显示屏,1个蜂鸣器,1个可调电位计(用于电压表),1路音频信号输入(用于示波器),8位R-2R电阻网络构成的DAC(用于DDS信号发生器)

本项目用到的资源包括:step-pico,按键k1,ws2812b三色灯和ssd1306OLED显示屏。

3)扩展板资源及管脚映射表

FgfdzMPEnjS9VQbmJ1q20ihiLUGrFvVlBde-Dm9ACxLKm2GXsLrPuldp

 

2、软件工具

树莓派官方推荐的开发软件是thonny,软件的下载地址:https://thonny.org/。注意:安装新版本,我windows电脑安装的是thonny4.0.1。因为我是首次接触pico和micropython,在ubuntu下用apt安装的版本是thonny2.9.2,怎么也无法识别pico,然后到thonny官网下载最新版本安装后就能识别并配置了。

打开thonny,进入工具—选项,打开解释器选项卡(或者点击右下角点击配置解释器),参照下图配置解释器,端口不一定是COM9,视情况选择端口。

FkVgRQ-RWEbBhxr9NwjDhge2YznPFsIg7oLuN6P56dt92Ja1ubyaNTrx

 

四、程序流程图

程序流程图如下:

Fr8keF46g1HwnhidiKh3QJgH5Ggd

五、程序实现

1、pico核心板GPIO口的封装board.py

程序如下:

class pin_cfg:
    yellow_led = 20
    blue_led = 21
    green_led = 22
    red_led = 26
    
    buzzer = 19
    mic = 27
    
    i2c0_scl = 17
    i2c0_sda = 16
    i2c1_scl = 15
    i2c1_sda = 14

    spi1_mosi = 11
    spi1_sck = 10
    spi1_dc = 9
    spi1_rstn = 8
    spi1_cs = 29

    adc0 = 26
    adc1 = 27

    k1 = 12
    k2 = 13

    pot = 28

 

2、配置ssd1306OLED显示屏SPI通信oled.py

显示屏驱动用micropython的驱动ssd1306.py模块。

程序如下:

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

 

3、按钮k1采用中断方式工作button.py

代码如下:

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、ws2812b灯的驱动ws2812b.py

程序如下:

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)

 

5、反应测试程序的实现react_game.py

程序如下:

from oled import oled   #用屏幕显示反应时间
import ws2812b   #用ws2812b灯带点灯,看到灯带亮,按下按键,测试反应时间
from button import button  #从button模块导入class button
from board import pin_cfg  #从board模块导入class pin_cfg,此类对pico的引脚进行了封装

from machine import Pin  #从micropython的machine模块导入class pin
import time        #需要用到时间间隔,从micropython导入time模块
import random   #亮灯时间间隔随机,需要导入random,产生一个随机时间
    

def print_result(msg):
    oled.init_display()     #初始化oled屏幕
    print("your reaction time:" + msg)  #pc端屏幕显示内容:your reaction time:100ms
    oled.text("reaction time:",0,32)   #oled显示内容分两行显示:reaction time:
    oled.text(msg,0,42)    #oled显示内容分两行显示:100ms,
    oled.show()            #oled屏幕显示出来
    
timer_start = 0

def k1_callback(Pin):
    global timer_start
    timer_reaction = time.ticks_ms()-timer_start    #反应时间计算
    print_result(str(timer_reaction) + "ms")      #反应时间转换成字符串,作为实参
k1 = button(pin_cfg.k1, k1_callback, trigger=Pin.IRQ_RISING)  #k1按下触发中断,回调k1_callback

while True:
    ws2812b.on_all()                     #ws2812b灯带全亮
    timer_start = time.ticks_ms()   #开始计时
    time.sleep(2)              #ws2812b灯亮2秒
    ws2812b.off_all()        #关闭ws2812b
    time.sleep(random.uniform(3,5))    #随机等待3至5秒后,下一轮循环

六、总结

第一次用micropython做嵌入式开发,对它提供的库还不是很了解,开发过程也出现了不少问题。比如最开始oled显示的文字显示不全,只显示一行文字,我在print_result()函数里改成了分两行显示。还有就是最开始设计的print_result()函数,刚进入函数时我没有对屏幕进行初始化,导致每次显示的内容都与前面的重叠在一起,后面的反应时间无法在屏幕上清晰的显示,增加了oled.init_display()后解决了此问题。最后是while循环里语句的顺序也需要注意。一开始我把timer_start开始计时这段语句放在了ws2812b.off_all()语句后面,导致反应时间一直在10多秒,跟实际不是一个数量级,后来整理思路,调整了语句的顺序,于是解决了问题。

项目的完成,离不开硬禾学堂提供的硬件资源和软件资源,让我从完全没接触过micropython,通过独立完成本次项目,基本熟悉了micropython的嵌入式开发。当然本项目比较简单,想要更好的掌握micropython进行开发,还需要花更多的时间,做更多更复杂的项目。只有在不断的实践中,才能有所提高。

在这里非常感谢电子森林和硬禾学堂,给我们提供这样一个学习平台,不仅免费提供硬件和软件资源,还用完成项目的方式督促我们动手实践,让我们实实在在受益。真的非常感谢!

附件下载
反应测试器源代码.rar
项目运行react_game.py
raspberry-pi-pico-python-sdk.pdf
pico-sdk文档
团队介绍
我是一名电子爱好者,喜欢嵌入式开发和机器人设计与制作。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号