基于STEP Pico的嵌入式系统制作一个反应测试器
在随机数控制的时间下随机点亮板上的LED,被测试者按下按键以后,处理器计算从点亮灯到接收到按键之间的时间差,显示在PC端和OLED上。
标签
嵌入式系统
显示
lijiaxin1
更新2023-03-29
北京理工大学
322

内容介绍

1 项目需求

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

2 完成的功能

WS2812B灯串在随机时间,随机一个灯的点亮。

在按下K1键后执行中断程序,计算并输出灯亮和按下按键的时间差,并在PC端和OLED屏上显示。

其中,对OLED显示时不能换行、不能刷新的问题,进行了优化。

Frw7cEtR_T88v690OrcE9AFBXFGQ

3 环境配置

  1 thonny:

         作为官方推荐的开发软件,thonny页面简洁,基础功能齐全,简单易上手,非常适合初学者(比如我)。安装的教程网上比较多,这里推荐一个硬禾的教学视频https://class.eetree.cn/live_pc/l_60fe7f4fe4b0a27d0e360f74

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

         硬禾学堂为“暑期一起练”制作了一个平台,这平台正是我视频中演示用到的板子他的原理图如下,具体可以参考https://www.eetree.cn/project/detail/103

4 程序实现:

程序均使用micropython编写。

4.1 模块介绍

首先需要下载picospuch的库,可以从github下载最新版的,也可以用电子森林上,开源项目中使用的旧版本,因为我初学时跟着开源项目学习的,这里就分享下我使用的旧版本的使用。

1 显示屏的使用

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

2 machine库

下面介绍的是项目中使用到的外设,由于比较基础,这里就只把他们的使用代码示例贴出来

      按键(PIN+外部中断)

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)

     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)

 

5 实现思路

利用module——oled、 button、 random、 time、 ws2812b

分别实现OLED的显示功能、K1按键的信号输入、随机数的产生、LED的亮/灭时间控制和WS2812B LED灯串的随机点亮和关灭。

程序循环执行WS2812B灯串在随机时间,随机一个灯的点亮,利用中断,在按下K1键后执行中断程序,计算并输出灯亮和按下按键的时间差,并在PC端和OLED屏上显示。

6 实现过程

6.1 程序流程图

FmJruOXN95Y47eNXR70nWBFH0bXl

注:每个框图右下角名称为执行该功能的主要函数

6.2 ws2812b点亮/熄灭

while True:
    time.sleep(random.uniform(3,5)) 
#利用随机函数产生随机时间,使LED灯在随机时间后点亮
    ws2812b.on(random.randint(1, 12))
#随机一个LED灯点亮
    timer_start = time.ticks_ms()
#记录点亮时间
    time.sleep(3)
    ws2812b.off_all() #灯灭

6.3 中断程序

timer_start = 0 #全局变量
def k1_callback(pin): #定义中断函数
    global timer_start
    timer_reaction = time.ticks_ms() - timer_start  #计算反应时间差
    print_result(“reaction time was :”+ str(timer_reaction) + “ms”,0,0) 
#PC端和OLED显示结果
k1 = button(pin_cfg.k1, k1_callback, trigger=Pin.IRQ_FALLING) 
#K1键信息输入

6.4 结果显示(msg未处理版)

def print_result(msg):
    #SD1306 OLED显示函数
    print(msg)
    oled.text(msg,0,0) 
    oled.show() #显示文字
    

7 遇到的主要难题

OLED显示时不能自动换行,内容溢出屏幕不能显示

为此需要对传递的msg字符进行处理。

具体实现代码如下:

p=0 #设置全局变量用于计数
def print_result(msg,c=0,r=0):#c这个参数可以平移显示的位置
    #SD1306 OLED显示函数
    print(msg)
    global p #调用全局变量
    if p>40:  #40为4行文字 1行为10像素点
        p=0
        oled.fill(0) #清空屏幕
    colum=int(len(msg)/15)+2  #每行可以显示15个字符 包含数字字母数字
    i=0
    for a in range(0,colum):
        oled.text(msg[i:i+15],c,a*10+p) #循环写入每行需要显示的文字
        i=i+15 #用于输入下一行文字 
        p=a*10+p #换行
    oled.show() #显示文字

8 整体实现

import ws2812b
from oled import oled
from button import button
from board import pin_cfg

import time
from machine import Pin
import random
p=0 #设置全局变量用于计数
def print_result(msg,c=0,r=0):#c这个参数可以平移显示的位置
    #SD1306 OLED显示函数
    print(msg)
    global p #调用全局变量
    if p>40:  #40为4行文字 1行为10像素点
        p=0
        oled.fill(0) #清空屏幕
    colum=int(len(msg)/15)+2  #每行可以显示15个字符 包含数字字母数字
    i=0
    for a in range(0,colum):
        oled.text(msg[i:i+15],c,a*10+p) #循环写入每行需要显示的文字
        i=i+15 #用于输入下一行文字 
        p=a*10+p #换行
    oled.show() #显示文字
    
    
    

timer_start = 0

def k1_callback(pin):
    global timer_start
    timer_reaction = time.ticks_ms() - timer_start
    print_result("reaction time was :"+ str(timer_reaction) + "ms",0,0)
k1 = button(pin_cfg.k1, k1_callback, trigger=Pin.IRQ_FALLING)

while True:
    time.sleep(random.uniform(3,5))
    ws2812b.on(random.randint(1, 12))
    timer_start = time.ticks_ms()
    time.sleep(3)
    ws2812b.off_all()
   

9 未来的计划建议

该项目已经成功地实现了反应测试器的全部功能,但是针对显示内容可以进行更多的优化。比如,尝试缩放OLED显示的字体大小,输入中文到屏幕里。

python的开发难度比C/C++低不少,易于上手,但是速度上不及C/C++。通过这次项目的开发,我基本掌握了micropython的开发。我学会了micropython开发ssc 1306 OLED显示器,ws2812b。不可否认,在开发时间敏感,速度不敏感的场合,micropython有独当一面的优势,操作简单。

未来期望,学习其他的可编程组件,拓展实现更多的功能。

附件下载
reaction-game.py
实现项目1反应测试器的micropython代码
oled.py
用到的module
ws2812b.py
用到的module
团队介绍
团队成员1人,李佳昕,北京理工大学。本次参加2023寒假一起练- 基于STEP Pico的嵌入式系统的学习活动,并由本人完成了项目一,以及细节优化。
团队成员
李佳昕
来自北京理工大学,电子信息工程(实验班)专业
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号