2024年寒假练 - 基于小脚丫FPGA核心板的秒表
该项目使用了小脚丫FPGA核心板,实现了具有启动、停止、递增和清除功能的秒表的设计,它的主要功能为:通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。。
标签
FPGA
秒表
2024寒假在家一起练
Peking
更新2024-04-02
67

1 项目需求

  • 通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。秒
  • 表使用四个按钮输入:开始、停止、增量和清除(重置)。开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。

小脚丫STEP-MXO2-LPC介绍

硬件规范

  • 核心器件:Lattice LCMXO2-4000HC-4MG132
    • 132脚BGA封装,引脚间距0.5mm,芯片尺寸8mm x 8mm;
    • 上电瞬时启动,启动时间<1ms;
    • 4320个LUT资源, 96Kbit 用户闪存,92Kbit RAM;
    • 2+2路PLL+DLL;
    • 嵌入式功能块(硬核):一路SPI、一路定时器、2路I2C
    • 支持DDR/DDR2/LPDDR存储器;
    • 104个可热插拔I/O;
    • 内核电压2.5-3.3V;
  • 板载资源:
    • 两位7段数码管;
    • 两个RGB三色LED;
    • 8路用户LED;
    • 4路拨码开关;
    • 4路按键;
  • 36个用户可扩展I/O(其中包括一路SPI硬核接口和一路I2C硬核接口)
  • 支持的开发工具思德普开发的Web IDE以及Lattice官方提供的Diamond
  • 支持MICO32/8软核处理器以及RISC-V软核
  • 板上集成FPGA编程器,采用U盘的模式
  • 一路USB Type C接口,可用于给核心板供电、给FPGA下载JED文件以及同上位机通过UART通信
  • 板卡尺寸52mm x 18mm


引脚定义

2 完成的功能

2.1.秒表显示

通过数码管实时显示计数值,从0.0秒递增到9.9秒后翻转。

2.2 按键控制

通过四个轻触按键分别实现开始、停止、增量和清除(重置)功能。

2.3.定时器

使用10Hz定时器源,每0.1秒更新一次计数值。

2.4.中断处理

利用中断处理函数精确控制计数值的更新频率。

 

3 实现思路

  • 首先,我们需要一个时钟转换模块 Clock,将输入的12MHz时钟信号转换为10Hz的时钟信号,用于秒表的计时。
  • 接下来是设计一个计数器模块 Counter,根据10Hz的时钟信号进行计数。该模块应该包含一个计数器,能够实现递增功能,并在达到99时归零。实现这个模块后,根据题目要求还需要根据添加启动、停止功能的逻辑。
  • 然后需要将计数值显示出来,所以设计了将计数器的值转换为七段显示器的输出的模块 SevenSegmentDisplay 和 SevenSegmentDisplay1,将计数器的值转换为七段显示器的编码输出,用于显示秒表的时间。
  • 最后是秒表模块 Stopwatch,将以上各个模块整合在一起。在这个模块中,将时钟模块、计数器模块和七段显示模块连接在一起,并根据输入的信号实现启动、停止、递增和清除功能的逻辑。

利用webide实现设计。

webide无需下载即可在网页设计,并且可以非常方便的分配管脚,一键下载jed文件,非常值得推荐

image.png

利用电子森林AI助手,可以生成简单Verilog代码,大大减少设计时间,提高效率。

image.png

4 实现过程

4.1 程序流程图 

fpga秒表.png

4.2 时钟转换模块 Clock

功能:

  • 将输入的12MHz时钟信号转换为10Hz的时钟信号,用于秒表的计时。

输入:

  • clk_12MHz:12MHz的输入时钟信号

输出:

  • clk_10Hz:10Hz的输出时钟信号

工作原理:

  • 接收来自外部的12MHz时钟信号 clk_12MHz。利用适当的逻辑或计数器,将12MHz时钟信号转换为10Hz的时钟信号 clk_10Hz。输出10Hz的时钟信号,用于驱动秒表的计时功能。

时钟转换模块:

module Clock
(
input wire clk_12MHz,
output reg clk_10Hz
);
parameter WIDTH = 24;
parameter N = 1200000;
reg [WIDTH-1:0] cnt_p;
always @ (posedge clk_12MHz)
begin
if(cnt_p==N-1)
cnt_p<=0;
else
cnt_p<=cnt_p+1;

if(cnt_p<(N>>1))
clk_10Hz<=1;
else
clk_10Hz<=0;
end
endmodule

4.3 计数器模块 Counter

功能

  • 根据10Hz的时钟信号进行计数。该模块包含一个计数器,能够实现递增功能,并在达到99时归零。

输入

  • clk_10Hz:10Hz的时钟信号
  • reset:重置信号,当接收到重置信号时,计数器归零

输出

  • count:当前计数器的值,范围从0到99

工作原理

  1. 每次接收到10Hz的时钟信号 clk_10Hz,计数器递增一个单位。
  2. 当计数器的值达到99时,自动归零。
  3. 如果接收到重置信号 reset,计数器立即归零。
  4. 输出当前计数器的值 count,范围在0到99之间。

启动、停止功能逻辑

  • 在秒表模块中,根据启动、停止信号的控制,可以通过控制时钟信号的传递来实现启动和停止功能。当接收到启动信号时,允许时钟信号传递到计数器模块,使计数器开始计数;当接收到停止信号时,停止时钟信号的传递,计数器停止计数,保持当前值不变。
module Counter(
input wire clk,
input wire reset,
input wire increment,
input wire start,
input wire stop,
output wire [3:0] ones_digit,
output wire [3:0] tenths_digit
);
reg [3:0] ones_counter;
reg [3:0] tenths_counter;
reg [1:0] state;
reg [7:0] counter;
wire [11:0] counter1;
reg increment_triggered;
always @(posedge clk or posedge reset )
begin
if(!start)
state<=1;
if(!stop)
state<=0;
if (reset)
begin
counter <= 0;
state<=0;
end
else if (state)
begin
if(counter==99) // cnt 等于 9 则归零
counter <=8'd0;
else
counter <= counter + 1'b1; // cnt 小于 9 则累加
end
if (increment)
increment_triggered = 1;
if ((!increment)&increment_triggered )
begin
increment_triggered = 0;
if(counter==99) // cnt 等于 9 则归零
counter <=8'd0;
else
counter <= counter + 1'b1; // cnt 小于 9 则累加
end
end
Bin2bcd bin2bcd(.bitcode(counter),.bcdcode(counter1));
assign tenths_digit = counter1[7:4];
assign ones_digit = counter1[3:0];
endmodule

4.4 数码管显示模块

功能:

  • 将计数器的值转换为七段显示器的输出,带小数点。

输入:

  • count:计数器的值,范围从0到99

输出:

  • segment_output:七段显示器的编码输出,包括小数点,用于显示计数器的值

工作原理:

  • 根据计数器的值 count,将其转换为对应的带小数点的七段显示器的编码输出。
  • 输出转换后的七段显示器编码 segment_output,包括小数点,用于显示计数器的值。
module SevenSegmentDisplay(
input wire [3:0] value,
output wire [8:0] segment_output
);
// 七段显示器的编码表
reg [8:0] segment_encoding [0:9];
initial begin
segment_encoding[0] = 9'h3f; //对存储器中数赋值9'b00_0011_1111,共阴极接地,DP点变低不亮,7段显示数字 0
segment_encoding[1] = 9'h06; //7段显示数字 1
segment_encoding[2] = 9'h5b; //7段显示数字 2
segment_encoding[3] = 9'h4f; //7段显示数字 3
segment_encoding[4] = 9'h66; //7段显示数字 4
segment_encoding[5] = 9'h6d; //7段显示数字 5
segment_encoding[6] = 9'h7d; //7段显示数字 6
segment_encoding[7] = 9'h07; //7段显示数字 7
segment_encoding[8] = 9'h7f; //7段显示数字 8
segment_encoding[9] = 9'h6f; //7段显示数字 9
end

assign segment_output = segment_encoding[value];

endmodule
module SevenSegmentDisplay1(
input wire [3:0] value,
output wire [8:0] segment_output
);
// 七段显示器的编码表
reg [8:0] segment_encoding [0:9];
initial begin
segment_encoding[0] = 9'hbf; //7段显示数字 0.
segment_encoding[1] = 9'h86; //7段显示数字 1.
segment_encoding[2] = 9'hdb; //7段显示数字 2.
segment_encoding[3] = 9'hcf; //7段显示数字 3.
segment_encoding[4] = 9'he6; //7段显示数字 4.
segment_encoding[5] = 9'hed; //7段显示数字 5.
segment_encoding[6] = 9'hfd; //7段显示数字 6.
segment_encoding[7] = 9'h87; //7段显示数字 7.
segment_encoding[8] = 9'hff; //7段显示数字 8.
segment_encoding[9] = 9'hef; //7段显示数字 9.
end

assign segment_output = segment_encoding[value];

endmodule

4.5 秒表模块 Stopwatch

功能

  • 将时钟模块、计数器模块和七段显示器输出模块整合在一起,实现启动、停止、递增和清除功能的逻辑。

输入

  • clk_10Hz:10Hz的时钟信号
  • start:启动信号,用于启动秒表
  • stop:停止信号,用于停止秒表
  • clear:清除信号,用于清除秒表
  • 其他可能的输入信号,如重置信号等(根据需要添加)

输出

  • 七段显示器的编码输出,用于显示秒表的时间

工作原理

  1. 接收来自时钟模块的时钟信号 clk_10Hz
  2. 根据输入的信号 startstopclear 等,控制计数器模块的启动、停止、清除操作。
  3. 将计数器模块的计数值传递给七段显示器输出模块,实现时间的显示。
  4. 根据输入信号的控制,实现秒表的启动、停止、递增和清除功能的逻辑。

启动、停止功能逻辑

  • 当接收到启动信号 start 时,允许时钟信号传递到计数器模块,启动计数。
  • 当接收到停止信号 stop 时,停止时钟信号的传递,停止计数,保持当前值不变。

清除功能逻辑

  • 当接收到清除信号 clear 时,将计数器模块归零,并清空七段显示器的显示。
module Stopwatch(
input wire clk_12MHz,
input wire increment,
input wire clear,
input wire start,
input wire stop,
output wire [8:0] ones_digit,
output wire [8:0] tenths_digit
);
wire [3:0] ones_counter_value;
wire [3:0] tenths_counter_value;
wire [8:0] ones_segment_output;
wire [8:0] tenths_segment_output;

Clock clock(.clk_12MHz(clk_12MHz), .clk_10Hz(clk_10Hz));
Counter counter(.clk(clk_10Hz), .reset(!clear),.start(start),.stop(stop), .increment(increment ), .ones_digit(ones_counter_value), .tenths_digit(tenths_counter_value));
SevenSegmentDisplay ones_display(.value(ones_counter_value), .segment_output(ones_segment_output));
SevenSegmentDisplay1 tenths_display(.value(tenths_counter_value), .segment_output(tenths_segment_output));

assign ones_digit = ones_segment_output;
assign tenths_digit = tenths_segment_output;

endmodule

4.6资源利用率

image.png

  • 寄存器数量:总共4635个寄存器中使用了54个(1%),这些寄存器可以用于存储数据或状态信息。
  • PFU(Programmable Function Unit)寄存器:在4320个PFU寄存器中使用了54个(1%),PFU通常用于实现逻辑功能。
  • SLICE数量:总共2160个SLICE中使用了85个(4%),SLICE是FPGA中的基本逻辑单元。
  • LUT4数量:在4320个LUT4中使用了160个(4%),LUT4是FPGA中的Look-Up Table单元,用于实现逻辑功能。
  • PIO(Parallel Input/Output)寄存器:在315个PIO寄存器中没有被使用,PIO通常用于处理输入输出。
  • 块RAM数量:在10个块RAM中没有被使用

资源占用较少。

5 遇到的主要难题

5.1 always语句赋值问题

在Verilog中,确保敏感信号具有相同的触发方式,并且在always块中只使用reg类型的变量进行赋值操作,并且这些赋值操作需要保持一致,即要么都是阻塞赋值,要么都是非阻塞赋值。这些规则有助于确保时序逻辑的正确性和避免潜在的问题,最重要的是可以减少大量调试时间


建议:

  1. 相同触发方式
    • always块中,确保所有敏感信号具有相同的触发方式,例如都是在时钟的上升沿或下降沿触发。混合使用不同的触发方式可能会导致意外的行为和时序问题。
  2. 只使用reg类型变量
    • always块中,应该只使用reg类型的变量进行赋值操作。reg类型的变量用于存储时序逻辑的状态,并且在时钟信号触发时更新。
  3. 保持一致的赋值方式
    • always块中,确保所有的赋值操作要么都是阻塞赋值(=),要么都是非阻塞赋值(<=)。混合使用这两种赋值方式可能会导致意外的逻辑错误和时序问题。


5.2 时序逻辑和时钟域交叉问题

在数字电路设计中,时钟信号的稳定性和时序逻辑的正确性至关重要。确保各模块在正确的时钟域中操作,避免时钟域交叉可能是一个巨大的工程。


5.3 注意webide的run.log文件报错

在进行fpga设计时,难免会出现因代码问题导致的映射错误。但是在使用webide设计时,如果有映射错误,只会在run.log文件的最后一行报错,并且报错后可以正常下载上一次jed文件,这是难以发现的,这会使得

6 未来的计划建议

该项目已经成功实现了秒表的功能,并达到了预期指标。然而,还有许多可以提升与扩展的地方:

功能扩展与优化

  • 考虑增加更多功能,如计次功能、倒计时功能、闹钟功能等,以提升秒表的实用性和功能性,当然这需要增加数码管数量。

性能优化:

  • 对代码进行优化,提高秒表的响应速度和稳定性。
附件下载
秒表.zip
团队介绍
电子信息工程专业学生
团队成员
Peking
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号