2024寒假练-基于小脚丫FPGA核心板设计秒表
该项目使用了Web IDE开发工具、Lattice Diamond软件、Verilog语言,实现了按键控制的秒表的设计,它的主要功能为:在小脚丫FPGA核心板上创建一个2位数秒表,秒表应0.0 秒计数到 9.9秒,然后翻转倒计时,计数值每0.1秒精确更新一次。秒表使用四个按钮输入:开始、停止、增量和清除(重置)用于控制秒表进入这四种状态。。
标签
FPGA
数字逻辑
2024寒假在家一起练
再欲乘风归
更新2024-04-01
北京理工大学
286

1.项目功能介绍

本项目旨在使用小脚丫FPGA核心板上的2个七段显示数码管和4个轻触按键,实现一个功能完备的计时时间范围在10秒内的秒表,具体要求秒表从0.0秒计数到9.9秒,计满后进行翻转,即秒表需要从0.0秒又计回9.9秒,如此往复循环。计数值每0.1秒精确更新一次。

四个轻触:

  • 按下开始按钮后,每0.1秒计数一次。数码管将显示当前计数器值。
  • 按下停止按钮后,计数器停止递增,但数码管将继续显示当前计数器值,以便随时查看。
  • 每次按下增量按钮,数码管上显示的值将增加一次,无论按住增量按钮多长时间。
  • 按下清除按钮将强制计数器值归零。

2.设计思路

2.1确定输入和输出:

根据项目要求,我们需要开始、停止、增量和清除这四个按钮,根据项目要求我们知道这四个按钮一一对应着小脚丫上的轻触开关,另外还有两个输出数码管,用于输出计时的输出。

2.2设计状态机:

根据学习过的数字电路的相关知识,最终决定使用状态机来控制秒表的不同功能。设计状态包括开始(start)、停止(stop)、增量(increment)和清除(clear)状态四个主题逻辑模块,需要根据输入的不同来控制秒表所处不同状态,还有用于按钮的边缘检测的start_prev,stop_prev,increment_prev三个个状态用于检测轻触开关,之后根据秒表要求的逻辑功能来确定状态之间的转换条件和转换关系,构成一个完整的状态机。

2.3时钟分频模块和数码管驱动模块设计:

因为这次板材提供的信号为12MHz,所以需要引入一个时钟分频模块产生10Hz时钟信号,使得计数器每0.1秒更新一次。其次就是七段数码管的显示模块,根据秒表计时数更新数码管的显示。这两个部分都可以编写好之后例化到顶层模块中。

2.4计数器逻辑设计:

这里需要我们设计一个可以自动变化的计数器,因为实际当中是分两位显示的,所以我们可以分别设置seg_data_1和seg_data_2两个量视其为个位和十位来分别进行计数操作,当个位计满10后十位进一。除此之外,当计时达到9.9秒时,还需要翻转使得计数器递减会0.0秒,所以还需要加入一个counting计时变量来判断时钟计时状态,是否继续当前计时操作。

2.6其他附加模块设计:

因为在Web IDE上实践的时候,还看到了其他很多有趣的项目文件,于是我就希望在设计秒表的基础上再加入一些其他的模块,使得小脚丫上的各个部件能被充分利用。在这里我一共加入了两个模块:一个是8个LED的流水灯模块,另一个时两个RGB_LED等的呼吸灯模块,这两个模块基本上都是在Web IDE的示例项目中稍做修改得到的,最后例化到顶层模块即可

2.7流程框图:

3.硬件简单介绍

  • 使用了USB Type C接口提供板上+5V供电、FPGA的配置,并新增了UART通信的功能,因此无需再通过其它端口与PC进行数据通信
  • 支持U盘模式(连接到上位机的USB端口,上位机自动弹出StepFPGA的U盘盘符)的下载,任何操作系统的电脑 - Windows、Mac OS以及Linux(包括树莓派)都可以在不安装任何驱动程序的情况下,直接将生成的jed配置文件发送到StepFPGA盘中即可完成编程
  • 用户不必再下载安装Diamond软件,即可在任何一款电脑上通过浏览器进行FPGA的编程和编译。图形化的界面使得操作非常直观、便捷。

4.实现的功能介绍

4.1秒表逻辑功能介绍

4.1.1开始按键

该按钮按下后计时器的秒表开始工作,每0.1秒更新一次计数,数码管紧跟着改变显示的数值

4.1.2停止按键

按下按键后计时停止,数码管显示当前计数值

4.1.3增量按键

每次按下增量按键后,不论按下的时间多长,都使得秒表计数加一,在执行这一个操作时应当尽量使得秒表停下来,这样能够更加直观的看到具体效果,便于我们的调整

4.1.4清零按键

清零按键就是执行复位操作,将秒表当前计数值强制归为零,使其重新开始计数,该功能需要在停止之后再进行操作,保证秒表比较稳定

4.2RGB和LED效果灯功能介绍

RGB和LED效果灯是附加的项目,一是提高板材管脚的使用,二是确定板材无损坏以及美观,所以让其不受轻触开关影响,一直产生效果。所以选择展示上电之后的初始状态,RGB开始执行呼吸灯的操作,呼吸周期为1s,每个颜色的呼吸灯呼吸两次之后切换下一个,周期往复,同时LED灯也开始执行流水灯操作,保持LED灯中总有四个亮起以实现流水灯效果,秒表代码烧录成功。

本次项目这些效果大都是动态效果,所以图片展示不是很全面,主要还是在视频中展示效果

4.3资源占用情况

5.主要代码片段说明

这是38译码器的核心部分,用于流水灯模块的led信号输出,根据不同输入值的状态,来保证总有四位是低有效的(本次led灯都是低有效的)。

case (sw) // 根据sw的值进行条件判断,保证输入sw的值怎么变化,led总有四个值是低有效的
3'b111: led = 8'b0000_1111;
3'b110: led = 8'b1000_0111;
3'b101: led = 8'b1100_0011;
3'b100: led = 8'b1110_0001;
3'b011: led = 8'b1111_0000;
3'b010: led = 8'b0111_1000;
3'b001: led = 8'b0011_1100;
3'b000: led = 8'b0001_1110;
default: led = 8'b1111_1111;
endcase

该段代码是显示数码管驱动模块中为数码管赋值的部分,七段数码管共有九位信号输入,最高有效位在本次项目中默认都是有效的,因为十位和个位有有无小数点的区别,所以二者赋值不完全相同,这取决于次高有效位,下面的代码是对十位的具体赋值情况,个位只需相应地减去1000_0000即可

		seg2[0] = 9'hbf; //对存储器中第二个数赋值较之第一个只是多了一个DP信号的区别,这个数码管需要显示小数点
seg2[1] = 9'h86; //7段显示数字 1
seg2[2] = 9'hdb; //7段显示数字 2
seg2[3] = 9'hcf; //7段显示数字 3
seg2[4] = 9'he6; //7段显示数字 4
seg2[5] = 9'hed; //7段显示数字 5
seg2[6] = 9'hfd; //7段显示数字 6
seg2[7] = 9'h87; //7段显示数字 7
seg2[8] = 9'hff; //7段显示数字 8
seg2[9] = 9'hef; //7段显示数字 9

这段代码是流水灯模块的顶层代码,它实例化了两个模块:一个是数字解码模块decode38,用于将输入信号cnt解码为输出信号led;另一个是分频器模块divide,用于生成1Hz的时钟信号clk1h。最后通过一个时钟触发的计数器,实现了对cnt的循环计数功能。

//例化module decode38,相当于调用
decode38 u1 (
.sw(cnt), //例化的输入端口连接到cnt,输出端口连接到led
.led(led)
);

//例化分频器模块,产生一个1Hz时钟信号
divide #(.WIDTH(24),.N(1200000)) u2 ( //传递参数
.clk(clk),
.clkout(clk1h)
);

//1Hz时钟上升沿触发计数器,循环计数
always @(posedge clk1h)begin
cnt <= cnt +1;
end

这段代码是一个微妙计数器模块,根据控制信号进行计数和清零,并通过add_cnt_us和end_cnt_us信号指示计数器的状态,这只是一个最低级的计数器模块,后续还会有两个类似的毫秒和秒计数器,三个计数器加起来要计满TIME_US*TIME_MS*TIME_1S =1200000次,也就是1s,用于控制本次呼吸灯的呼吸周期,三个计数模块的逻辑和功能是很相似的,不多赘述。

parameter TIME_US = 4'd12;
parameter TIME_MS = 10'd1000;
parameter TIME_1S = 10'd1000;

//us计数器
wire add_cnt_us;//us计数器开始标志
wire end_cnt_us;//us计数器结束标志

always @(posedge clk) begin
if(add_cnt_us)begin
if(end_cnt_us)begin //end_cnt_us为1,计满清零
cnt_us <= 1'b0;
end
else begin
cnt_us <= cnt_us + 1'b1;
end
end
else begin
cnt_us <= cnt_us;
end
end

过这段代码实现流水灯的状态会按照顺序切换。计数器的值会循环在0到MAX2-1之间变化,控制流水灯的状态cstate的切换,这里MAX2的值的设定为48000000是为了契合前边的计数器模块,可以使得每个灯在完成两次完整的呼吸之后再切换下一个。

parameter MAX2 = 48_000_000;  // 最大计数值
reg [30:0] cnt2; // 计数器
reg [2:0] cstate; // 流水灯状态

always @(posedge clk) begin
if (cnt2 == MAX2 - 1'b1) begin // 如果计数器达到最大值减1
cnt2 <= 1'b0; // 计数器清零
cstate <= cstate + 1'b1; // 流水灯状态加1,实现状态切换
end
else begin
cnt2 <= cnt2 + 1'b1; // 计数器递增
cstate <= cstate; // 保持流水灯状态不变
end
end

该代码段的功能是根据不同的流水灯状态和flag的值,通过对cnt_1s和cnt_ms进行比较,决定将led赋予不同的值。并且根据cstate状态不同来让不同颜色的灯亮起,这里只展示其中的一个情况,其他情况和这种情况类似,知识改变赋值即可。

case(cstate)
3'd0 : begin
if(!flag)begin
led <= cnt_1s > cnt_ms ? 3'b000 : 3'b111;
end
else if(flag)begin
led <= cnt_1s < cnt_ms ? 3'b000 : 3'b111;
end
end
endcase​

这段代码的功能是呼吸灯的顶层模块,只是进行了两个breath模块的实例化,分别对应两个RGB_LED,使得它们两个能够步调一致地产生呼吸灯效果。

	breath u1(
.clk(clk), // 输入端口,连接到时钟信号
.led(led_1) // 输出端口,连接到LED_1引脚
);

breath u2(
.clk(clk), // 输入端口,连接到时钟信号
.led(led_2) // 输出端口,连接到LED_2引脚
);

这段代码是本次项目的核心部分,该代码实现了一个名为 Stopwatch 的计时器模块,前边是对管脚的一些定义,用于控制输入和输出,该模块的具有描述如下:

该模块首先是通过实例化呼吸灯模块 top_breath和闪烁灯模块 flashled来实现呼吸灯和流水灯的效果展示,然后例化的LED模块使用于两个七段数码管的显示输出,divide模块则是产生了我们需要的10Hz的时钟信号clk_div,实例化完所有的模块之后利用initial模块来对一些值进行初始化的操作。

在后面轻触按键的设计模块,主题逻辑是利用if语言检测当前按键状态及其边缘状态,确保轻触按键按下之后秒表才会进行状态的切换,否则一直进行秒表的自动计时。

在模块内部,还有一个最为关键的计时模块,它使用寄存器 seg_data_1 和 seg_data_2 分别存储数码管的个位和十位的显示数据。同时,使用寄存器 counting 标志计时状态,使用寄存器 count_up 标志计时方向,从而根据10Hz的时钟信号的边沿来进行触发,从而实现总体的功能。

module Stopwatch(
input clk, // 时钟信号
input wire start, // 开始输入
input wire stop, // 停止输入
input wire increment, // 增量输入
input wire clear, // 清除输入
output wire [8:0] seg_led_1, // 数码管1输出端口,用于显示数码信号
output wire [8:0] seg_led_2, // 数码管2输出端口,用于显示数码信号
output wire [2:0] RGB_led_1, // RGB LED 1输出端口,用于控制颜色
output wire [2:0] RGB_led_2, // RGB LED 2输出端口,用于控制颜色
output wire [7:0] led_out // LED输出端口,用于控制LED灯
);
// 实例化数码管译码模块
reg [3:0] seg_data_1;
reg [3:0] seg_data_2;

LED led(
.seg_data_1(seg_data_1),
.seg_data_2(seg_data_2),
.seg_led_1(seg_led_1),
.seg_led_2(seg_led_2)
);

// 实例化整数分频器模块产生实验所需的10Hz时钟信号
wire clkout; // 分频系数为12_000_000
divide #(.WIDTH(20),.N(1_200_000)) divider(
.clk(clk),
.clkout(clk_div)
);


top_breath breathing(
.clk(clk), // 输入端口,连接到时钟信号
.led_1(RGB_led_1), // 输出端口,连接到RGB_led_1引脚
.led_2(RGB_led_2) // 输出端口,连接到RGB_led_2引脚
);

flashled flash(
.clk(clk), // 输入端口,连接到时钟信号
.led(led_out) // 输出端口,连接到led_out引脚
);


// 用于按钮的边缘检测
reg start_prev,stop_prev,increment_prev;
// 按键状态标志
reg counting;
initial
begin
seg_data_1 = 4'b0000;
seg_data_2 = 4'b0000;
counting <= 0;
start_prev = 1;
stop_prev = 0;
increment_prev = 0;
end

always @(posedge clk_div) begin
// 检测清零按键是否被按下
if (!clear) begin
counting <= 0;
seg_data_1 <= 0;
seg_data_2 <= 0;
end
else begin
if (!start && start_prev) begin
counting <= 1; // 启动计时
end
// 边缘检测:检测停止按键是否被按下
else if (stop && !stop_prev) begin
counting <= 0; // 停止计时
end
// 边缘检测:如果在计时状态并且检测到增量按键被按下
else if (increment && !increment_prev) begin
if (seg_data_1 == 9) begin
if (seg_data_2 == 9) begin
seg_data_2 <= 0; // 当十位也达到最大值时,重置为0
end else begin
seg_data_2 <= seg_data_2 + 1; // 个位溢出,十位增加
end
seg_data_1 <= 0; // 个位从9重置为0
end else begin
seg_data_1 <= seg_data_1 + 1; // 个位自增
end
end
// 更新前一个状态,为下一次边缘检测做准备
start_prev <= start;
stop_prev <= stop;
increment_prev <= increment;
// 计数逻辑
if(counting) begin
if(seg_data_1 == 9) begin
if(seg_data_2 == 9) begin
seg_data_2 <= 0;
seg_data_1 <= 0; // 当到达9.9秒时,数值归零
end else begin
seg_data_2 <= seg_data_2 + 1; // 十位数加一
seg_data_1 <= 0; // 个位重置为0
end
end else begin
seg_data_1 <= seg_data_1 + 1; // 个位数加一
end
end
end
end

endmodule

6.遇到的困难及解决方法

6.1AI工具的使用不是很理想

本次项目要求我们需要利用一些AI工具来辅助我们进行设计,我本次选择的chatGPT工具来完成这次的设计,虽然选择的任务比较简单,但是我开始直接将任务交给GPT的话,它完成的代码正确率还是很有限的,有很多即是不不进行编译也能发现很多错误,需要自己修改。除此之外,很多时候会有答非所问的情况,它很多时候都不能准确地理解到我的核心思想,回答并不是我想的。

解决方法:针AI工具使用这一点,首先我是根据自己分析出的一些明显的问题再反问GPT,让他在原有的基础上不断改进,能够在软件中编译成功,然后将代码烧录在板子上,根据实际表现再和GPT进行相关讨论,不断改进,在这个过程中还可以查阅一些资料,继续让GPT改进代码。

6.2附加模块的改进

因为在Web IDE看到的一些示例项目,所以我有了将他们加入我原有功能上的想法,但是这些项目和实际的还是有一定的差距,就比如流水灯的示例模块同时只有一个LED亮起,呼吸灯的实力模块只能让一个LED灯产生呼吸效果。这些和我自己的想法都是有一定的差距,需要进行一定的修改。

解决方法:这一部分的解决还是较为容易的,在代码等信息比较充足,只需要进行小部分的修改时,GPT的优势还是很明显的,可以很快地帮助我们优化方案,解决问题。

6.3逻辑功能的实现

在开始进行代码编写的过程当中,没有注意到按键的状态关系,所以最后使得我的项目再复位clear按键上出现了一定的问题,按下这个案件之后不能很好地实现清零操作,分析之后应该就是逻辑功能的编写错误导致最终结果出现了一定的问题。

解决方法:这个问题解决主要还是不断地微调逻辑结构进行观察是否满足要求,最后发现其实再always模块中,我的if else语句是一层一层嵌套的,各个按键之间有着明显的优先级,因而复位时会受到影响,所以最后我调整代码结构,使得clear按键的if语句和其他三个独立起来,就能实现清零了

7.未来计划建议

  • 秒表计时还可以更加丰富,我觉得当计满9.9s时可以利用led来显示更高位的数值,这样能让秒表记录更加长的时间
  • 我觉得还可以用sw开关来改变秒表的递增时间,不同开关可以计不同的时间长度
  • RGB的呼吸灯效果还可以更加丰富,颜色组合还有很多可能
  • 可以再考虑扩展版的一些使用


附件下载
clk.zip
项目文件
团队介绍
北京理工大学
团队成员
惠高攀
北京理工大学电子信息工程专业
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号