基于STEP Pico的嵌入式系统学习平台制作一个反应测试器
随机点亮板上的一个LED,按下板上的一个按键,在显示屏上显示出从灯亮到按键之间的时间,这是心理学上的一个重要实验。
标签
嵌入式系统
USB
2023寒假在家练
Veritas
更新2023-03-29
北京理工大学
300

项目总结报告

一、项目描述

此次项目是发布在2023寒假在家一起练活动平台上的诸多任务书中的其中一个,本项目做的是基于STEP Pico的嵌入式系统学习平台中的项目一。也即制作一个反应测试器。具体要求如下:随机点亮板上的一个LED,按下板上的一个按键,在显示屏上显示出从灯亮到按键之间的时间,这是心理学上的一个重要实验。实现方式:通过软件产生随机数,程序启动以后在随机数控制的时间下点亮板上的LED,被测试者按下按键以后,处理器计算从点亮灯到接收到按键之间的时间差,并将时间差通过USB显示在PC上,也可以将OLED用起来,在OLED上显示时间信息。

设计思路严格按照任务书中的具体要求来实现,并在原有基础上于OLED屏幕上显示更加充分且人性化的字幕信息。首先需要调用button、board、oled、time、ws2812b和random库(如下代码所示):

from button import k1,k2
from board import pin_cfg
from oled import oled
import time
import ws2812b
import random
import ws2812b
import random

其中button库用于k1、k2键的使能、检查与判定,oled控制Pico板中的小屏幕,ws2812b用以全局控制,time记录时间主要用于beginning_time和ending_time的记录与收集,并通过两者的相减得到所求的reaction_time。

代码具体实现流程如下:接入芯片后,运行代码。首先在液晶屏上显示Reaction Test!字符以表示反应测试器已经准备就绪,使用者按下k2键后程序将使用random函数,在1~5秒的时间范围内均匀随机取数作为系统waiting_time,并在液晶屏上显示It's ready!。waiting_time时间一过,程序则将在1-12号led中任取一个数,分别对应1-12盏led灯,并将其点亮,记录时间为beginning_time。液晶屏上将同时显示Now!Press!(表示这时使用者应该要按k1键,以完成反应测试了)。使用者按下k1键后,记录时间戳为ending_time。将ending_time与beginning_time两者相减得出的值赋值给reaction_time作为输出时间,并在液晶屏幕上显示:Reaction_time:+反应时间的具体值,单位为秒。同时在电脑平台上也将显示测试结果。由于程序是循环程序,在得出一次测试结果之后,再次按下k2,系统将重新准备并在waiting_time段落重复。具体代码如下:

waiting_time=0
reaction_time=0
beginning_time=0
ending_time=0
led_num=0
oled.text("Reaction Test!",12,20)
oled.show()
ws2812b.off_all()
while True:
    if k2.value():
        oled.fill(0)
        oled.show()
        waiting_time=random.uniform(1,5)
        led_num=int(random.uniform(0,12))
        oled.text("It's ready!",12,20)
        oled.show()
        time.sleep(waiting_time)
        oled.fill(0)
        oled.show()
        oled.text("Now! Press!",12,20)
        oled.show()
        ws2812b.on(led_num)
        beginning_time=time.ticks_ms()
        while True:
            if k1.value():
                ending_time=time.ticks_ms()
                oled.fill(0)
                oled.show()
                ws2812b.off(led_num)
                break
        reaction_time=ending_time-beginning_time
        oled.text("Reaction Time",12,20)
        oled.text('{:3.3f}s'.format(reaction_time/1000),12,40)
        oled.show()
        print('Reaction Time'+str(reaction_time/1000)+'s')

二、完成的功能即达到的性能。

2.1准备阶段:

按下k2键,液晶屏幕上显示从“Reaction Test!”变化为“It's Ready!”,系统此时进入等待阶段,等待时长由random()函数在1s-5s中等概率随机生成。

Fl1W2TwNZh-5Jvg5B_0l1xkqkw03

2.2系统等待时长为waiting_time的时间后亮灯,液晶屏幕上显示从“It's Ready!”变化为“Now!Press!”

Fixs5J6niZfVzL1OQGIumwweM_v3

2.3按下k1后,一轮反应测试环节结束液晶屏幕上显示从“Now!Press!”变化为“Reaction Time”以显示最终的反应测试结果。

Fto8TdH1qh86HdiWkq7gUZKyBnR2

三、主要代码片段及说明:

waiting_time=0#定义waiting_time(系统在使用者按下k2后到随机led灯点亮之间的等待时间)
reaction_time=0#定义reaction_time(反应时间,也即需要输出的结果)、beginning_time(从系统接入到led灯点亮之间的时间差)
beginning_time=0#定义beginning_time(从系统接入到led灯点亮之间的时间差)
ending_time=0#定义ending_time(从系统接入到使用者按下k1之间的时间差)
led_num=0#定义led_num(将要点亮的led灯的编号,于1-12中随机生成)
oled.text("Reaction Test!",12,20)
oled.show()#液晶屏上显示“Reaction Test!”        
ws2812b.off_all()#初始化所有led灯,将其灭掉
while True:#开始循环
    if k2.value():#若使用者按下k2则执行下列语句
        oled.fill(0)
        oled.show()#清屏
        waiting_time=random.uniform(1,5)#在1s-5s中等概率取数,并赋值给waiting_time
        led_num=int(random.uniform(0,12))#在1-12中的十二个整数中等概率取书并赋值给led_num
        oled.text("It's ready!",12,20)
        oled.show()#液晶屏上显示“It's Ready!”
        time.sleep(waiting_time)#系统等待,时长为waiting_time,单位为秒
        oled.fill(0)
        oled.show()#清屏
        oled.text("Now! Press!",12,20)
        oled.show()#液晶屏上显示“Now!Press!”
        ws2812b.on(led_num)#点亮编号为led_num所赋值的led灯
        beginning_time=time.ticks_ms()#记录目前时间戳并赋值给beginning_time
        while True:
            if k1.value():#若使用者按下k1,则执行下列语句
                ending_time=time.ticks_ms()#记录目前时间戳并赋值给ending_time
                oled.fill(0)
                oled.show()#清屏
                ws2812b.off(led_num)#灭掉之前亮起的led灯
                break#跳出内循环
        reaction_time=ending_time-beginning_time#将ending_time 与 beginning_time相减,并将得到的数值赋值给reaction_time
        oled.text("Reaction Time",12,20)
        oled.text('{:3.3f}s'.format(reaction_time/1000),12,40)
        oled.show()#液晶屏上显示“Reaction Time”+ reaction_time/1000后的数值+“s”(因为记录的时间数值单位为毫秒)
        print('Reaction time'+str(reaction_time/1000)+'s')#在电脑屏幕上显示测试结果

框图和软件流程图如下:

FtRjWym91W07SEFy-zeHmp4ue7-2

 

四、硬件介绍:

Raspberry Pico 是一款树莓派官方设计的低成本,高性能的微控制器开发板,具有灵活数字接口。硬件上,采用 Raspberry 官方自主研发的 RP2040 微控制器芯片,搭载了ARM Cortex M0 + 双核处理器,高达 133MHz 的运行频率,内置了 264KB 的 SRAM 和 2MB 的内存,还板载有多达 26 个多功能的 GPIO 引脚。软件上,可选择树莓派提供的 C/C++ SDK,或者使用 MicroPython 进行开发,且配套有完善的开发资料教程,可方便快速入门开发,并嵌入应用到产品中。

产品特性:

1、采用了 Raspberry Pi 官方自主设计的 RP2040 微控制器芯片。

2、搭载了双核 ARM Cortex M0 + 处理器,运行频率高达 133MHz 灵活时钟。

3、内置了 264KB 的 SRAM 和 2MB 的片上 Flash。

4、邮票孔设计,可直接焊接集成到用户自主设计的底板上。

5、USB1.1 主机和设备支持。

6、支持低功耗睡眠和休眠模式。

7、可通过 USB 识别为大容量存储器进行拖放式下载程序。

8、多达 26 个多功能的 GPIO 引脚。

9、2 个 SPI,2 个 I2C,2 个 UART,3 个 12 位 ADC,16 个可控 PWM 通道。

10、精确的片上时钟和定时器。

11、温度传感器。

12、片上加速浮点库。

13、8 个可编程 I/O (PIO) 状态机,用于自定义外设支持。

五、遇到的主要难题及解决方法

难题一:Thonny框架的搭建问题,一开始遇到了系统不兼容的问题,后来通过官网下载合适的版本得以解决。

难题二:取随机数的细节问题。由于random()函数是一个左开右闭的均匀取值函数,一开始并没有发现,因此在1-12取值时写的是int(random.uniform(1,12))但后来发现1号灯从没亮过,查了random()函数定义后才恍然大悟。

难题三:循环的嵌套问题。一开始并未尝试用循环嵌套,但程序一直报错,思索之后发现加一层嵌套能更好地保护k1的使能程序段,因此加之。并在之后程序一直保持稳定。

六、未来的计划

首先十分感谢硬禾提供了这样一个学习单片机嵌入式编程的机会和平台,这不仅是对过往知识的总结,也是能力的提高。

未来将更广泛的尝试和榨取Raspberry Pico的性能,因为此板的能力远大于此,可以实现更高级、更有趣、更有用的功能。

  • 调用库代码

board:

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

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)

oled:

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

button:

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)
软硬件
电路图
附件下载
Eetree Project 1.py
主程序
库.rar
调用的库文件
团队介绍
裴云翔
团队成员
Veritas
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号