1 项目需求和硬件介绍
1.1 项目需求——制作一个反应测试器
具体要求:随机点亮板上的一个LED,按下板上的一个按键,在显示屏上显示出从灯亮到按键之间的时间,这是心理学上的一个重要实验
实现方式:通过软件产生随机数,程序启动以后在随机数控制的时间下点亮板上的LED,被测试者按下按键以后,处理器计算从点亮灯到接收到按键之间的时间差,并将时间差通过USB显示在PC上,也可以将OLED用起来,在OLED上显示时间信息。
1.2 硬件介绍
Step Pico是一款低成本,高性能的微控制器开发板,具有灵活数字接口。基于树莓派Pico的嵌入式系统学习平台,专为嵌入式系统学习而设计,其可以通过C/C++以及MicroPython编程来学习嵌入式系统的工作原理和应用。
板卡硬件:
2个按键输入
4个单色LED
12个WS2812B RGB三色灯
1个姿态传感器
1个128*64 OLED显示屏
1个蜂鸣器
1个可调电位计(用于电压表)
1路音频信号输入(用于示波器)
8位R-2R电阻网络构成的DAC(用于DDS信号发生器)
2 完成的功能以及达到的性能
2.1 游戏开始的预告
每局反应游戏开始前,灯会亮三秒,然后熄灭三秒,之后游戏开始,给玩家反应时间。
2.2 游戏开始,随机的led灯亮起
四个led灯随机亮起3到5秒,然后熄灭。玩家需要按下k1键,然后oled屏上就会出现从灯灭到玩家按下k1键中间的时间,即为反应时间。每局游戏相隔10秒,十秒之后会开始新的一局。
3 实现思路
使用Micropython的machine模块配置了板上的LED和按键引脚。然后使用GPIO输出高电平来点亮LED,并使用utime模块等待一段随机的时间。
随后,使用一个while循环来等待按键被按下。在while循环中,我们不断地读取按键的状态,直到按键被按下为止。
一旦按键被按下,就计算出灯亮到按键按下的时间差,然后在显示屏上显示这个时间。
4 实现过程
4.1 配置led和按键引脚
配置按键引脚,定义位置button,调用位置React_Game
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)
配置灯,定义位置ws2812b,调用位置React_Game
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()
配置old屏幕,定义位置oled,调用位置React_Game
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))
4.2 定义中断函数
定义中断函数,定义位置React_Game,调用位置React_Game
def k1_callback(pin):
global timer_start
timer_reaction=time.ticks_ms() - timer_start
print_result(str(timer_reaction)+" ms")
当按下k1按键后,启动中断程序,将反应时间打印在oled屏幕上,若重复按下k1按键,之前打印的会被清除掉,然后打印新的时间上去。每局游戏内重复按下k1按键只会显示该局内的时间。
下面是打印在oled上的函数print_result
def print_result(msg):
oled.fill(0)
oled.text("Reaction Time: ",0,20)
print(msg)
oled.text(msg,0,40)
oled.show()
4.3 主程序
While大循环,每局游戏开始前所有灯亮三秒,然后熄灭三秒,以提示玩家做好准备。然后在四个led灯中随机选取一个亮起,亮起时间为随机的3到5秒。每局游戏间隔10秒
while True:
ws2812b.on_all()
time.sleep(3)
ws2812b.off_all()
time.sleep(3)
led_On=random.choice(ledToChoose)
led_On.on()
time.sleep(random.uniform(3,5))
led_On.off()
timer_start=time.ticks_ms()
time.sleep(10)
5 遇到的主要难题
最开始写的代码中,使用了一个while循环来等待按键被按下,这种方式被称为轮询方式。轮询方式的缺点是需要不断地查询按键的状态,因此会占用处理器的时间,可能导致其他任务的延迟。另外,如果按键被按下的时间非常短,可能会导致轮询方式无法及时检测到按键的状态。
后来使用中断可以避免这些问题,因为中断可以在按键被按下时立即执行相应的代码,而不需要等待查询按键状态。这样可以节省处理器的时间,并且可以及时响应按键事件。在Micropython中,可以使用machine模块的Pin类来配置引脚中断,实现按键的中断处理。
中断优先级问题:如果同时存在多个中断,可能会出现中断优先级的问题。在Micropython中,中断处理函数会在中断触发时立即执行,并且会屏蔽其他中断,直到中断处理函数执行完毕。如果存在多个中断,需要根据不同的中断优先级,适当地配置中断触发方式和中断处理函数,以避免中断冲突和竞争。
OLED屏幕驱动问题:如果OLED屏幕的驱动没有正确配置,可能会出现OLED屏幕无法正常显示的问题。在使用OLED屏幕时,需要确保正确配置OLED屏幕的驱动和初始化参数。
GPIO引脚使用问题:如果GPIO引脚的使用没有正确配置,可能会出现引脚无法输出或读取的问题。在使用GPIO引脚时,需要确保正确配置引脚的输入输出模式、上下拉电阻和引脚的电压和电流等参数。
6 未来的计划建议
增加更多的通信接口:除了GPIO引脚外,树莓派Pico扩展版还可以增加更多的通信接口,比如Ethernet、WiFi、Bluetooth、LoRa等。这样可以使树莓派Pico扩展版更加通用,可以用于更多的应用场景。
增加更多的存储空间:树莓派Pico扩展版可以增加更多的存储空间,比如Flash存储、SD卡接口等。这样可以存储更多的程序和数据,使树莓派Pico扩展版更加强大和实用。
增加更多的显示和输入接口:树莓派Pico扩展版可以增加更多的显示和输入接口,比如LCD显示屏、触摸屏、键盘等。这样可以使树莓派Pico扩展版更加易于使用和交互。
增加更多的扩展板和模块:树莓派Pico扩展版可以增加更多的扩展板和模块,比如机器人控制板、音频处理板、视频处理板等。这样可以使树莓派Pico扩展版更加多样化和实用。