2024年寒假练 - 基于小脚丫FPGA核心板实现反应速度测试系统
该项目使用了基于Lattice MXO2的小脚丫FPGA核心板,实现了反应速度测试系统的设计,它的主要功能为:测试反应速度、求反应速度平均值、比较两位用户的反应速度平均值大小。
标签
FPGA
小李电子实验室
更新2024-03-28
华北电力大学
341

项目需求

设计一款反应时间测试系统,测试两个队友(队友A和队友B)看到LED亮起后按键的时间,将响应时间显示在数码管上,每个人测量8次做平均,并将两个人的响应时间作对比,显示出哪一方赢得比赛。

游戏规则:

按下“启动”按钮后,两个7 段显示屏立即设置为显示全0,然后随机一段时间后(大约1 到10秒),相应测试轮次的“立即反应”LED 亮起,并启动毫秒计时器,数码管开始显示计时器值(以毫秒为单位递增)。

“立即反应”LED 亮起后,队友必须尽快按下“响应”按钮来停止计时器。 停止的计时器将包含“立即反应”LED亮起和按钮按下之间的毫秒数,并且该时间将显示在数码管上。

再次按下“启动”按钮将清除计时器并开始新的测试。

重复单个反应时间测量八次,每一个测试,相应的8个LED中的一个亮起,并将八个测量的反应时间值存储在临时保持寄存器中。

按下“平均”按钮可平均8次的测量值,并将平均后的数值显示在数码管上,8个LED全部亮起。

切换队友 - 将SW1的状态改变,重复测试8次,并平均八次的测试结果。

按下“比较”按钮,通过RGB三色灯指示哪个队友获胜(平均用时最短),并在数码管上显示相应的响应时间。

需求分析

本项目要求使用FPGA实现一个反应测试系统,主要功能有测量反应时间,求取反应时间平均值和数据比较功能,此外还需要数码管、LED灯驱动功能。游戏规则已在项目需求中清晰表述,此处不再赘述。由大量统计数据得,人的反应时间不会小于100ms,使用1ms为单位计时将无法在数码管上完整显示,使用5ms为单位并不能直观地看出反应时间的具体数值,因此本项目采用10ms为单位记录与显示反应时间。

系统设计

经过分析,游戏的主要功能,即反应速度的测试和评比,可采用状态机进行设计,同时自动根据当前状态更新LED、数码管的显示状态,如图1所示。

图1 游戏状态机设计

对于数据存储模块,可采用类似移位寄存器的形式循环存储最新的8次反应时间,当数据改变时自动求取最近8次测试的平均值,如图2所示。

 

图2 数据存储模块设计

将12MHz的输入时钟频率进行分频得到100Hz信号用于记录反应时间与进行特定时间的延时。通过时钟信号驱动一个加法计数器,当用户按下“启动”按钮后取出计数器的值,达到“随机延时”的效果。同时分频信号也用于毫秒计时器的输入时钟。

因为RGB LED需要有常亮和低亮度两种效果,要求精度不高,因此可以采用如下方法设计LED驱动模块。流程图如图3所示

图3 PWM模块设计

此模块由系统时钟驱动,当寄存器的值为3时,打开LED并清零寄存器,否则关闭LED并使寄存器自增。易得寄存器值为3的循环次数占总次数的四分之一。用这种方法实现的PWM不仅可以较为高效地利用FPGA资源,而且可以使LED亮度差异更明显。

在主模块中例化两个RGB LED模块,分别代表队友1和队友2。模块接受一个当前状态输入,查表后(PWM通过带条件表达式的assign语句实现)打开对应颜色的LED输出。

因为项目需求中对各个按钮的分工明确,逻辑上没有出现一个按钮同时作用于两个状态的情况,因此不需要对按钮进行额外的消抖操作,直接在状态机中判断按钮电平,切换状态即可。

数码管驱动

因为毫秒计时器的输出是二进制数,而数码管需要2位10进制数作为输入。在单片机平台一般通过对10进行整除来实现。但FPGA没有硬件除法器,直接进行除法和取余数操作将会消耗较多的FPGA资源。因此采用“加3移位法”将二进制转化为BCD码来实现此功能。因为本人水平有限,此处采用最直接的方法实现此功能。

关键代码实现

随机数生成代码

随机延时1.png

用FPGA产生随机数是本项目的一个难点。为了用较为简单的方式实现不可预测的随机数,我采用一个计数器(counter)在每个时钟自增。当用户按下按钮1开始测试时,顶层模块向RandDelay模块传递开始随机延时信号(reset),此时随机延时模块从counter中取出当前的计数值,然后利用10毫秒时钟延时一段时间。因为用户按下按钮的时间相对FPGA启动时间是随机的,同时因为输入时钟频率非常高,计数器更新的速度也非常快,不可能人为控制产生的随机数大小,因此可以实现随机延时的效果。

特别地,因为项目要求随机延时不小于1秒,需要判断定时器的值是否小于100。第一次我的方案如流程图所示,每次循环判断计数值。为了节约fpga资源,改为每次装载定时器数据时自动将counter的值加100,实现最少延时1秒的效果。

module RandDelay (
    input wire reset,
    input wire clk,
    input wire clk_10ms,
    output reg timeout
);
    reg [8:0] rnd = 9'd100;
    always @(posedge clk) begin
        rnd = rnd + 9'b1;
    end
    reg [9:0] counter;
    reg [9:0] delay;

    always @(posedge clk_10ms or posedge reset) begin
        if (reset) begin
            counter <= 0;
            timeout <= 0;
            delay <= rnd + 10'd100;
        end else begin
            if (counter < delay) begin
                counter <= counter + 10'd1;
                timeout <= 0;
            end else begin
                timeout <= 1;
            end
        end
    end

endmodule

二进制转BCD代码

module bin_to_bcd(
    input wire [6:0] bin,  // 8位二进制输入
    output [7:0] bcd   // BCD输出,4位用于十位,4位用于个位
);
    integer i;
    reg [14:0] shift_reg; // 扩展的移位寄存器,包括8位输入和额外的4位用于处理过程

    always @(bin) begin
        shift_reg = {8'b00000000, bin}; // 初始化移位寄存器,前4位为0,后8位为输入的二进制数
        // 对每一位二进制数进行处理,共12次迭代
        for (i = 0; i < 7; i = i + 1) begin
            // 从左至右检查每个BCD位
            if (shift_reg[10:7] > 4)
                shift_reg[10:7] = shift_reg[10:7] + 4'd3;
            if (shift_reg[14:11] > 4)
                shift_reg[14:11] = shift_reg[14:11] + 4'd3;
           
            // 左移整个寄存器
            shift_reg = shift_reg << 1;
        end
    end
    assign bcd = shift_reg[14:7]; // 将最终的BCD结果赋值给输出
endmodule

数据存储代码

module data_storage(
    input [6:0] datain,
    input save,
    input clk,
    output [6:0] avg
);
    reg [6:0] data [7:0];
    reg [9:0] sum;
    reg [2:0] cnt = 3'b0;
    reg [3:0] sum_idx;
    initial sum_idx = 4'b1000;
    // 提取信号
    reg last_save;
    reg save_reg;
    always @(posedge clk) begin
        last_save <= save_reg;
        save_reg <= save;
    end
    always @(posedge clk) begin
        if(save_reg != last_save) begin
            data[cnt] = datain;
            cnt = cnt + 3'b1;
            sum_idx = 4'b0000;
            sum = 10'd0;
        end
        casez (sum_idx)
        4'b0zzz: begin
            sum = sum + data[sum_idx];
            sum_idx = sum_idx + 4'b0001;
        end
        4'b1000:;
        default: begin
            sum_idx = 4'b1000;
        end
        endcase
    end
    assign avg = sum[9:3];

endmodule

因篇幅有限,其它代码请看附件。

各个模块的仿真

因为项目代码量较大,故采用Modelsim仿真软件进行仿真。各个模块的仿真图如下。注:仿真中将10ms分频器的分频系数由60000改成了5,方便观察分频效果。

二进制转BCD模块仿真测试

仿真代码:

    reg [6:0] bin;
    wire [7:0] bcd;

    bin_to_bcd bintobcd(
        .bin(bin),
        .bcd(bcd)
    );
    initial begin
        for (bin = 0; bin < 100; bin = bin + 1) begin
            #10;
        end
    end

image.png

数据存储与求平均值模块仿真测试

仿真代码:

    reg [6:0] datain;
    reg save;
    reg clk;
    wire [6:0] avg;
    data_storage ds(
        .datain(datain),
        .save(save),
        .clk(clk),
        .avg(avg)
    );
    initial begin
        save = 1;
        clk = 0;
        #100;
        for (datain = 10; datain < 18; datain = datain + 1) begin
            save = ~save;
            #100;
        end
    end
    always #2 clk = ~clk;

image.png

分频器仿真测试(分频系数由60000改为6)

仿真代码:

    reg clk;
    wire freq_out;
    frequency_divider fd(
        .clk(clk),
        .freq_out(freq_out)
    );
    initial begin
        clk = 0;
    end
    always #5 clk = ~clk;

image.png

数码管模块仿真测试

仿真代码:

    reg onoff;
    reg [3:0] seg_data_1;
    reg [3:0] seg_data_2;
    wire [8:0] seg_led_1;
    wire [8:0] seg_led_2;
    LEDSegment ls(
        .onoff(onoff),
        .seg_data_1(seg_data_1),
        .seg_data_2(seg_data_2),
        .seg_led_1(seg_led_1),
        .seg_led_2(seg_led_2)
    );
    initial begin
        onoff = 1;
        seg_data_1 = 0;
        seg_data_2 = 0;
        #10;
        seg_data_1 = 1;
        #10;
        seg_data_2 = 2;
        #10;
        seg_data_1 = 3;
        #10;
        seg_data_2 = 4;
        #10;
        onoff = 0;
        #10;
        onoff = 1;
        #10;
        $stop;
    end

image.png

 RGB LED模块仿真测试

仿真代码:

    reg [2:0] stat;
    reg clk;
    wire r;
    wire g;
    wire b;
    RGBLED rgb(
        .state(stat),
        .clk(clk),
        .out_r(r),
        .out_g(g),
        .out_b(b)
    );
    initial begin
        stat = 3'b000;
        clk = 0;
        #100;
        stat = 3'b001;
        #100;
        stat = 3'b010;
        #100;
        stat = 3'b011;
        #100;
        stat = 3'b100;
        #100;
        stat = 3'b101;
        #100;
        #100;
        #100;
    end
    always #5 clk = ~clk;

image.png

 随机延时模块仿真测试

仿真代码:

`timescale 1ns/1ns
module testbench;
    reg clk = 0;
    reg clk10 = 0;
    reg reset = 0;
    wire timeout;
    RandDelay rndDelay(
        .reset(reset),
        .clk(clk),
        .clk_10ms(clk10),
        .timeout(timeout)
    );
    initial begin
        #100;
        reset = 1;
        #100;
        reset = 0;
    end
    always @ (posedge timeout) begin
        $display("Current_time: %t", $time);
        #50;
        reset = 1;
        #50;
        reset = 0;
    end
    always #5 clk = ~clk;
    always #50 clk10 = ~clk10;
endmodule

image.png

image.png

可以看到,延时的时间差是随机的。

FPGA资源占用

Design Summary:
Number of registers: 156 out of 4635 (3%)
PFU registers: 156 out of 4320 (4%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 225 out of 2160 (10%)
SLICEs as Logic/ROM: 213 out of 2160 (10%)
SLICEs as RAM: 12 out of 1620 (1%)
SLICEs as Carry: 42 out of 2160 (2%)
Number of LUT4s: 430 out of 4320 (10%)
附件下载
archive.zip
源代码
团队介绍
个人作品
团队成员
小李电子实验室
B站同名
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号