2024年寒假练 - 基于Lattice MXO2的小脚丫FPGA核心板实现反应测试器
该项目使用了Lattice MXO2的小脚丫FPGA核心板以及WebIDE平台,实现了反应测试器的设计,它的主要功能为:测试两个队友(队友A和队友B)看到LED亮起后按键的时间,将响应时间显示在数码管上,每个人测量8次做平均,并将两个人的响应时间做对比,显示出哪一方赢得比赛。。
标签
FPGA
反应测试
2024寒假练
Lattice MXO2的小脚丫FPGA核心板
kalworth
更新2024-04-01
哈尔滨工程大学
246

一、项目需求

1.核心功能

  • 测试反应时间:系统能够测量两位队友(A和B)从LED亮起至按键按下的反应时间,并将其显示在数码管上。
  • 多次测试与平均:每位队友需进行8次测试,系统应能存储并计算这8次测试的平均反应时间。
  • 比较与结果展示:系统应比较两位队友的平均反应时间,判断胜负,并通过RGB灯和数码管展示结果。

2.硬件要求

  • 数码管:用于实时显示反应时间。
  • LED灯组:8个LED灯,用于指示测试轮次。
  • RGB三色灯:2个,分别代表队友A和B,显示测试状态和结果。
  • 开关:用于切换测试的队友。
  • 轻触按键:包括启动、响应、平均和比较功能。

3.操作流程

  • 按下启动键,数码管归零,随机时间后LED灯亮起,计时开始。
  • 队友看到LED灯亮起后尽快按下响应键,计时停止,时间显示在数码管上。
  • 重复上述步骤8次,每次不同的LED灯亮起。
  • 按下平均键,计算并显示8次测试的平均时间。
  • 切换开关至另一位队友,重复上述步骤。
  • 两位队友均完成后,按下比较键,显示胜者及其平均反应时间,RGB灯指示结果。

二、需求分析

1.测试反应时间

  • 系统需要实现一个毫秒级精度的计时器,能够准确记录从LED灯亮起至按键按下的时间差。
  • 计时器需要在LED灯亮起时开始,并在按键按下时停止,同时记录下时间并在数码管上显示。

2.多次测试和数据存储

  • 系统需要具备存储功能,以便能够保存每位队友的8次测试数据。
  • 在每次测试完成后,系统需要将当前测试数据加入到存储中,并准备进行下一次测试。

3.平均反应时间计算

  • 在8次测试全部完成后,系统需要计算并显示这8次测试的平均反应时间。
  • 平均反应时间的计算应准确无误,并能够以易于阅读的方式在数码管上显示。

4.比较结果与展示

  • 系统需要比较两位队友的平均反应时间,判断哪位队友的反应更快。
  • 比较结果需要通过RGB三色灯和数码管进行直观的展示,包括胜者的标识和平均反应时间。

三、实现方式

1.硬件实现

  • 数码管使用核心板板载的两位数码管,时间单位为10ms。
  • LED灯组使用核心板板载的8位LED,通过对应端口的高低电平控制LED亮灭。
  • RGB灯使用核心板板载2个RGB灯表示玩家状态。
  • 板载SW1开关状态对应参加测试的两位队友,SW2作为重启开关(rst)。
  • 按下板载按键1~按键4对应启动、响应、平均、比较功能。

2.软件实现

  • 初始化设置:在系统上电后,进行必要的初始化设置,包括数码管清零、LED灯组关闭、RGB LED设置为初始状态。
  • 测试流程控制:编写测试流程控制逻辑,实现按下启动键后的随机延时、LED灯亮起、计时器启动等功能。在按键按下后,停止计时器,并将时间显示在数码管上。
  • 数据存储与处理:定义变量或数组来存储每位队友的8次测试数据;在每次测试完成后,将数据保存到相应的存储位置;在按下平均键后,计算并显示平均反应时间。
  • 比较与结果展示:编写比较逻辑,比较两位队友的平均反应时间,确定胜者。控制RGB LED显示胜者的颜色(白色高亮),并通过数码管显示胜者的平均反应时间。

四、功能框图

五、代码说明

1.数码管显示(部分)

  always @ (posedge clk) begin

// 查表分离个位和十位 00 ~ 99
case (cnt_s)
8'd0:begin dataH <= 0; dataL <= 0; end
8'd1:begin dataH <= 0; dataL <= 1; end
8'd2:begin dataH <= 0; dataL <= 2; end
8'd3:begin dataH <= 0; dataL <= 3; end
8'd4:begin dataH <= 0; dataL <= 4; end
8'd5:begin dataH <= 0; dataL <= 5; end
8'd6:begin dataH <= 0; dataL <= 6; end
8'd7:begin dataH <= 0; dataL <= 7; end
8'd8:begin dataH <= 0; dataL <= 8; end
8'd9:begin dataH <= 0; dataL <= 9; end
8'd10:begin dataH <= 1; dataL <= 0; end
8'd11:begin dataH <= 1; dataL <= 1; end
......
8'd97:begin dataH <= 9; dataL <= 7; end
8'd98:begin dataH <= 9; dataL <= 8; end
8'd99:begin dataH <= 9; dataL <= 9; end
default: begin dataH <= 10; dataL <= 10; end // 对于100及以上的数字不显示
endcase

seg_led_1 <= seg[dataH];
seg_led_2 <= seg[dataL];
end
  • cnt_s:需要显示的数字,通常为两位数。
  • 两位数显示,使用查表法对每个数码管显示的数字进行确定,方便快捷。
  • dataH和dataL分别代表显示数字的高位和低位。
  • seg_led1和seg_led2为八段数码管输出端口。

2.按键脉冲发生器(部分)

    output [3:0] key_pulse; // 按键有效脉冲输出  

reg [3:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
reg [3:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
reg [3:0] key_sec_pre; //延时后检测电平寄存器变量
reg [3:0] key_sec;
wire [3:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲

initial begin
key_rst <= {4{1'b1}}; //初始化时给key_rst赋值全为1
key_rst_pre <= {4{1'b1}};
cnt <= 18'h0;
key_sec <= {4{1'b1}};
key_sec_pre <= {4{1'b1}};
end

assign key_pulse = key_sec_pre & (~key_sec);

//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
always @(posedge clk)
begin
key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
end

assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平

reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器

//产生20ms延时,当检测到key_edge有效是计数器清零开始计数
always @(posedge clk)
begin
if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end

//延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
always @(posedge clk)
begin
if (cnt==18'h3ffff)
key_sec <= key;
end

always @(posedge clk)
begin
key_sec_pre <= key_sec;
end
  • 按键脉冲发生器借鉴WebIDE中示例项目,对按键信号进行消抖处理,并产生对于的脉冲信号。

3.随机数生成器(伪随机数)

    output reg [7:0] random_number; // 8位随机数输出 

reg [7:0] number_temp; // 临时存储的8位数值,用于计算随机数

// 10-99 循环递增
always @ (posedge clk or negedge rst) begin
if(!rst) begin
number_temp <= 8'd0;
random_number <= 8'd10;
end
else begin
if(number_temp >= 89)
number_temp <= 0;
else
number_temp <= number_temp + 8'd1;

random_number <= number_temp + 8'd10;
end
end
  • 利用clk时钟信号,生成10~99的两位数(对应1~10秒),周期性循环递增,配合启动按键按下获取当前数字作为伪随机数。

4.RGB显示

	reg [2:0] rgb_color_lose;    // 输的一方的白色pwm

reg [31:0] rgb_cnt; // 基础计数
reg [31:0] rgb_lose_cnt; // CNT,PWM参考值
localparam ToggleFreq = 1000000; // 反转计数频率
localparam ToggleCntMaxPriod = 1000; // ARR pwm频率为 ToggleFreq / ToggleCntMaxPriod
localparam ToggleDuty = 100; // ToggleDuty/ToggleCntMaxPriod 为白色占空比
localparam ToggleMaxPriod = 12_000_000 / ToggleFreq; // 最大计数值

always @ (posedge clk or negedge rst) begin
if(!rst) begin
rgb_cnt <= 0;
end
else begin
rgb_cnt <= rgb_cnt + 1;
if(rgb_cnt == (ToggleMaxPriod - 1)) begin
rgb_lose_cnt <= rgb_lose_cnt + 1;
if(rgb_lose_cnt == (ToggleCntMaxPriod - 1)) begin
rgb_lose_cnt <= 0;
end
rgb_cnt <= 0;
end
// pwm
if(rgb_lose_cnt < (ToggleDuty - 1)) begin
rgb_color_lose <= 3'b000;
end
else begin
rgb_color_lose <= 3'b111;
end
end
  • 以上为RGB中,PWM信号的生成,题目要求输的一方白色暗淡,对RGB灯三路输出同时设置pwm信号(以一定频率和目标计数值反转三路电平),设置占空比较低,则为白色暗淡。
    initial begin

rgb_color[0] = 3'b000; // 白
rgb_color[1] = 3'b001; // 青
rgb_color[2] = 3'b010; // 紫
rgb_color[3] = 3'b011; // 蓝
rgb_color[4] = 3'b100; // 黄
rgb_color[5] = 3'b101; // 绿
rgb_color[6] = 3'b110; // 红
rgb_color[7] = 3'b111; // 无

rgb_color_lose = 3'b000;

end
// 比赛未结束, 角色判断
if(player1_state != 2'b11) begin
case(player1_state)
2'b00: begin rgb_1 <= rgb_color[5]; end
2'b01: begin rgb_1 <= rgb_color[3]; end
2'b10: begin rgb_1 <= rgb_color[6]; end
endcase
else begin
if(winner != 2'b10) begin
rgb_1 <= rgb_color[0];
end
else begin
rgb_1 <= rgb_color_lose;
end
end
  • 以上以一个玩家的状态灯为例(另一个代码相同)
  • 将非pwm(即高低电平控制)的8种颜色的RGB状态封装在数组当中。
  • play1_state为2位的数据,四种状态代表不同的队友状态,其中2'b11代表已经完成比较。
  • 未完成比较则按情况赋予RGB灯状态,若完成比较则按输赢给予RGB灯普通白光或者PWM暗淡白光。
  • 加入了平局状况下,两个RGB灯皆为白色高亮。

5.反应测试逻辑部分

    /* player1 相关变量 */
reg p1_start_flag; // 玩家1开始标志
reg [2:0] led_idx_p1;
reg [7:0] led_start_p1; // led启动
reg [7:0] led_time_delay_start_p1; // led随机延时启动
reg [7:0] led_time_delay_finished_p1; // led随机延时结束
reg [7:0] led_time_encode_start_p1; // led记录响应时间
reg [7:0] led_finished_p1; // led响应结束
reg [15:0] cnt_s_total_p1; // 记录总时间
reg [7:0] cnt_s_avg_p1; // 记录平均时间
reg cnt_avg_flag_p1; // 平均标志位
reg all_led_finished_flag_p1; // 所有led相应测试结束

/* player2 相关变量 */
reg p2_start_flag; // 玩家2开始标志
reg [2:0] led_idx_p2;
reg [7:0] led_start_p2; // led启动
reg [7:0] led_time_delay_start_p2; // led随机延时启动
reg [7:0] led_time_delay_finished_p2; // led随机延时结束
reg [7:0] led_time_encode_start_p2; // led记录响应时间
reg [7:0] led_finished_p2; // led响应结束
reg [15:0] cnt_s_total_p2; // 记录总时间
reg [7:0] cnt_s_avg_p2; // 记录平均时间
reg cnt_avg_flag_p2; // 平均标志位
reg all_led_finished_flag_p2; // 所有led相应测试结束

/* 共用变量 */
reg [7:0] led_delay_time; // 延时时间
reg [31:0] cnt_temp_led; // 延时事件记录
reg [7:0] cnt_s_led; // 延时时间记录
reg compare_flag; // 比较标志位
reg [1:0] winner; // 获胜者 00-比赛未结束 01-玩家1获胜 10-玩家2获胜 11-平局
reg [7:0] led;
  • 以上反应测试过程中所用到的各种标志位和寄存器,player1和player2分开两套相同的数据储存,避免相互影响。
    // 启动
always @ (posedge key_pulse[0] or negedge rst) begin
if(!rst) begin
led_start_p1 <= 8'd0;
led_start_p2 <= 8'd0;
p1_start_flag <= 0;
p2_start_flag <= 0;
end
else begin
// 玩家1
if(!player) begin
if(!p1_start_flag) begin
p1_start_flag <= 1;
end
if(led_start_p1[led_idx_p1] == 0) begin
led_start_p1[led_idx_p1] <= 1;
end
end
// 玩家2
else begin
if(!p2_start_flag) begin
p2_start_flag <= 1;
end
if(led_start_p2[led_idx_p2] == 0) begin
led_start_p2[led_idx_p2] <= 1;
end
end
end
end

// 响应
always @ (posedge key_pulse[1] or negedge rst) begin
if(!rst) begin
led_finished_p1 <= 8'd0;
led_finished_p2 <= 8'd0;
end
else begin
// 玩家1
if(!player) begin
if(led_start_p1[led_idx_p1] && led_time_delay_start_p1[led_idx_p1] && led_time_delay_finished_p1[led_idx_p1] && led_time_encode_start_p1[led_idx_p1] && !led_finished_p1[led_idx_p1]) begin
led_finished_p1[led_idx_p1] <= 1;
end
end
// 玩家2
else begin
if(led_start_p2[led_idx_p2] && led_time_delay_start_p2[led_idx_p2] && led_time_delay_finished_p2[led_idx_p2] && led_time_encode_start_p2[led_idx_p2] && !led_finished_p2[led_idx_p2]) begin
led_finished_p2[led_idx_p2] <= 1;
end
end
end
end

// 平均
always @ (posedge key_pulse[2] or negedge rst) begin
if(!rst) begin
cnt_avg_flag_p1 <= 0;
cnt_avg_flag_p2 <= 0;
cnt_s_avg_p1 <= 0;
cnt_s_avg_p2 <= 0;
end
else begin
// 玩家1
if(!player) begin
if(all_led_finished_flag_p1 && !cnt_avg_flag_p1) begin
cnt_avg_flag_p1 <= 1;
cnt_s_avg_p1 <= (cnt_s_total_p1 >> 3);
end
end
// 玩家2
else begin
if(all_led_finished_flag_p2 && !cnt_avg_flag_p2) begin
cnt_avg_flag_p2 <= 1;
cnt_s_avg_p2 <= (cnt_s_total_p2 >> 3);
end
end
end
end

// 比较
always @ (posedge key_pulse[3] or negedge rst) begin
if(!rst) begin
compare_flag <= 0;
end
else begin
if(cnt_avg_flag_p1 && cnt_avg_flag_p2) begin
compare_flag <= 1;
end
end
end
  • 以上key_pulse[0]~[4]为四路按键脉冲信号,分别对应启动、响应、平均、比较。
  • 当对应按键按下时,改变对应标志位。
  • 比较按键按下,对应总时间右移3位(除以8)得到平均时间。
    	    if(!player) begin
// 未启动
if(!led_start_p1[led_idx_p1] && led_idx_p1 == 3'b000) begin
led <= 8'hff;
end
// LED打开准备,准备延时
else if(led_start_p1[led_idx_p1] && !led_time_delay_start_p1[led_idx_p1]) begin
led_time_delay_start_p1[led_idx_p1] <= 1;
led_delay_time <= random_number; // 获取随机数
cnt_temp_led <= 0;
cnt_s_led <= 0;
cnt_s <= 0;
cnt_temp <= 0;
end
// 延时中
else if(led_start_p1[led_idx_p1] && led_time_delay_start_p1[led_idx_p1] && !led_time_delay_finished_p1[led_idx_p1]) begin
if(cnt_temp_led == (1_200_000 - 1)) begin
if(cnt_s_led == led_delay_time) begin
cnt_s_led <= 0;
led_time_delay_finished_p1[led_idx_p1] <= 1;
end
else begin
cnt_s_led <= cnt_s_led + 8'b1;
end
cnt_temp_led <= 0;
end
else begin
cnt_temp_led <= cnt_temp_led + 32'b1;
end
end
// 打开LED,开始计时
else if(led_start_p1[led_idx_p1] && led_time_delay_start_p1[led_idx_p1] && led_time_delay_finished_p1[led_idx_p1] && !led_time_encode_start_p1[led_idx_p1]) begin
led[led_idx_p1] <= 0;
led_time_encode_start_p1[led_idx_p1] <= 1;
end
// 记录时间直到按键响应
else if(led_start_p1[led_idx_p1] && led_time_delay_start_p1[led_idx_p1] && led_time_delay_finished_p1[led_idx_p1] && led_time_encode_start_p1[led_idx_p1] && !led_finished_p1[led_idx_p1]) begin
if(cnt_temp == (CNT_PERIOD - 1)) begin
if(cnt_s == CNT_MAX) begin
cnt_s <= CNT_MAX; // 最大值限制
end
else begin
cnt_s <= cnt_s + 8'b1;
end
cnt_temp <= 0;
end
else begin
cnt_temp <= cnt_temp + 32'b1;
end
end
// 检测响应,关闭LED
else if(led_start_p1[led_idx_p1] && led_time_delay_start_p1[led_idx_p1] && led_time_delay_finished_p1[led_idx_p1] && led_time_encode_start_p1[led_idx_p1] && led_finished_p1[led_idx_p1]) begin
led[led_idx_p1] <= 1;
led_idx_p1 <= led_idx_p1 + 3'b1;
//cnt_s_total_p1 <= cnt_s_total_p1 + cnt_s;

if(led_finished_p1 == 8'hff) begin
all_led_finished_flag_p1 <= 1;
end

if(!all_led_finished_flag_p1) begin
cnt_s_total_p1 <= cnt_s_total_p1 + cnt_s;
end
end
end
  • 以上以player1的反应测试过程逻辑代码为例
  • player是通过sw1切换的队友状态0或1
  • 反应测试是一个循序渐进的过程,首先当启动键按下,逻辑开始运行:开始->获取随机时间->延时->打开led并开始计时->等待响应->响应完成并且计时停止->总用时增加->结束。
  • 以上过程经过8次,测试完成,测试完成标志位置1。
     	//玩家1
//测试未完成
if(!all_led_finished_flag_p1) begin
player1_state <= 2'b00;
end
//测试完成,平均未完成
else if (all_led_finished_flag_p1 && !cnt_avg_flag_p1) begin
player1_state <= 2'b01;
end
//测试完成,平均完成,比较未完成
else if (all_led_finished_flag_p1 && cnt_avg_flag_p1 && !compare_flag) begin
player1_state <= 2'b10;
if(!player) begin
cnt_s <= cnt_s_avg_p1;
led <= 8'h00;
end
end
//测试完成,平均完成,比较完成
else if (all_led_finished_flag_p1 && cnt_avg_flag_p1 && compare_flag) begin
player1_state <= 2'b11;
end
  • 以上根据测试过程中以及按键的响应的标志位来改变队友状态。
            // 比较
if(compare_flag) begin
if(cnt_s_avg_p1 < cnt_s_avg_p2) begin
winner = 2'b01;
cnt_s <= cnt_s_avg_p1;
end
else if(cnt_s_avg_p1 > cnt_s_avg_p2) begin
winner = 2'b10;
cnt_s <= cnt_s_avg_p2;
end
else if(cnt_s_avg_p1 == cnt_s_avg_p2) begin
winner = 2'b11;
cnt_s <= cnt_s_avg_p1;
end
led <= 8'h00;
end
end
  • 以上对两个平均时间进行对比,得到比赛结果。

六、仿真波形图

1.按键脉冲发生器

  • Key[0]和Key[1]两个脉冲代表按键按下和释放的过程,对应的key_pulse延时一段时间后(即消抖)产生一次脉冲。

2.数码管显示

  • 显示数字cnt_s(0x31),十进制数字为51,得到数字高位和低位5和1。

3.随机数生成器(伪随机,循环递增)

  • 一个周期,random最大值为0x63,即99(对应10s)。

4.PWM生成器(低电平有效)

七、FPGA资源利用说明

附件下载
Code.zip
工程文件
implement.jed
烧录文件
团队介绍
人工智能 大三在读
团队成员
kalworth
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号