1 项目介绍
1.1 项目要求
制作一个反应测试器。具体要求为:随机点亮板上的一个LED,按下板上的一个按键后,显示出从灯亮到按键之间的时间,这是心理学上的一个重要实验。实现方式:通过软件产生随机数,程序启动以后在随机数控制的时间下点亮板上的LED,被测试者按下按键以后,处理器计算从点亮灯到接收到按键之间的时间差,并将时间差通过USB显示在PC上,也可以将OLED用起来,在OLED上显示时间信息。
1.2 软件介绍
Thonny —— 一个面向初学者的 Python IDE。Thonny 由爱沙尼亚的 Tartu 大学开发,针对初学者学习Python语言优化了很多工具上的方式和方法。它有很多优点:
- 界面简单,视图开关非常方便,容易上手。
- 相同名称变量突出显示,方便查找写错的变量名。
- 错误代码高亮提示,方便纠错。
1.3 硬件介绍
硬件使用基于树莓派Pico的嵌入式系统学习平台。该平台专为嵌入式系统学习而设计,其可以通过C/C++以及MicroPython编程来学习嵌入式系统的工作原理和应用。该平台主要包含了:
- 树莓派Pico扩展板 x1
- 硬禾版本树莓派Pico核心模块 - STEP Pico x1
板上集成了2个按键输入、4个单色LED、12个RGB三色灯、1个蜂鸣器等等多个模块,非常适合嵌入式系统初学者学习使用。
2 设计思路
因为要测试反应速度,所以LED灯的亮起应该是随机时间的,我们需要通过软件产生随机数,在一个比较合理的时间内灯随机亮起。之后被测试者按下按键,这时需要计算出从灯亮到按键之间的时间差并且将时间输出在pc上。比较简单的思路就是,记录灯亮起的时间和按键上升沿或下降沿触发的时间,两者相减即可得到反应时间。程序路程图如下:
3 代码实现
根据要求我们要先导入需要的几个库。首先是machine库里的Pin,用它来控制几个我们需要的引脚。还要导入time库用于记录时间、导入random库用于产生随机数。
from machine import Pin
import time
import random
接下来设置我们需要使用的led和按键。这里我选择了黄色的led灯和k1按键。
y_led = Pin(20, Pin.OUT)
k1 = Pin(12, Pin.IN, Pin.PULL_UP)
按照思路我们应该在一段随机时间之后点亮led灯并记录开始时间了。这里我先用第一行代码 y_led.off() 确保灯是关着的,然后在我设置的2到5秒内随机打开黄色的led灯,并且记录下这时候的时间保存在变量timer_start。
y_led.off()
time.sleep(random.uniform(2,5))
y_led.on()
timer_start = time.ticks_ms()
等待测试者按下k1按键,即检测到k1的下降沿或者上升沿触发后,我们要执行一段新的指令。这时候我们可以使用中断。中断的基本实现方法是:先确定“中断输入”,定义怎样的状态变化被认为是一个中断,同时还要创建一个“中断处理程序”函数,我们希望在检测到中断时执行该函数的内容。
中断输入:按键下降沿信号
中断处理程序:将中断触发时的时间减去led亮的开始时间得到最终需要的反应时间timer_reaction,并将这个时间以字符串的形式打印。
is_pressed = False
def irq_handler(pin):
global is_pressed
if not is_pressed:
is_pressed = True
y_led.off()
timer_reaction = time.ticks_ms() - timer_start
print("Your reaction time was " + str(timer_reaction) + "ms")
可以看到这里我们定义了一个全局变量 is_pressed,它的作用是:当第一次进行中断处理程序时,满足条件语句就重新赋值为 True 。这使得再一次按下k1按键触发中断后,不再满足条件语句,从而结束游戏,避免了多次输出其他无效的数据。
我们将“中断处理程序 “与 “中断输入 “结合即可得到所需触发中断的程序。
k1.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler)
到此为止,我们完成了整体代码:
from machine import Pin
import time
import random
y_led = Pin(20, Pin.OUT)
k1 = Pin(12, Pin.IN, Pin.PULL_UP)
is_pressed = False
def irq_handler(pin):
global is_pressed
if not is_pressed:
is_pressed = True
y_led.off()
timer_reaction = time.ticks_ms() - timer_start
print("Your reaction time was " + str(timer_reaction) + "ms")
y_led.off()
time.sleep(random.uniform(2,5))
y_led.on()
timer_start = time.ticks_ms()
k1.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler)
4 实验结果
运行脚本后2到5s内黄色led灯亮起
按下k1按键后灯熄灭并且在pc上打印反应时间
5 遇到的难题及解决方法
因为python接触的比较少,所以使用MicroPython编程时候遇到了不少问题,但是问题基本不大,都能快速解决。其中困扰我时间比较久的是time库里面的一些函数。在进行反应时间计算时,如果用time.localtime()函数,将会返回一个日期时间元组,而且最低刻度是秒,用秒来作为精度表示反应时间显然是不合适的。time.ticks_ms()能够返回毫秒为单位的计数器,很适合来做这个工作,但是其中的原理我并没有读明白。没有进行中断,我使用time.sleep()作为反应间隔来测试计时器时,发现输出的值总为0。后来阅读中文版MicroPython库里面time相关的内容后发现了一段解释:直接对这些值执行标准数学运算(+、-)或关系运算符(<、<=、>、>=)将导致无效结果。然而在我后来将这段代码写进中断处理程序后是可以正确进行减法的,因此并没有再做修改,后续还需探索具体原因。
6 未来的计划或建议
树莓派Pico嵌入式系统学习板上有很多有意思的模块,都可以供我学习研究。老师展示过的使用12个RGB灯制作时钟激发了我的兴趣,还有很多有创造力的想法等我去实现。短期内计划学习使用全彩oled显示屏,优化现已完成的代码,并尝试在显示屏上添加测试开始界面和测试结果输出。