FPGA实战:用Verilog手搓一个出租车计费器(附Quartus II全流程)
FPGA实战从零构建出租车计费器的Verilog实现在嵌入式系统开发领域FPGA因其并行处理能力和硬件可重构特性成为实现复杂逻辑控制的理想选择。本文将带领读者从零开始使用Verilog语言构建一个功能完整的出租车计费系统涵盖从基础模块设计到Quartus II工程部署的全过程。1. 系统架构设计出租车计费器的核心功能模块包括时钟分频模块将50MHz系统时钟转换为不同频率供各子系统使用按键消抖模块消除机械按键的接触抖动问题计程计价模块实现起步价和里程计费逻辑等候计时模块处理停车等待时的计时计费数码管驱动模块控制8位数码管显示各项数据模式切换模块在不同功能界面间切换系统采用层次化设计方法顶层模块通过实例化各功能子模块并定义互连信号构建完整的计费系统。这种模块化设计不仅便于调试也增强了代码的可维护性。module taxi( input clk_50M, reset, start, pluse, key, set, output [7:0] sel, output a,b,c,d,e,f,g,p, output led ); // 内部信号定义和模块实例化 distancemokuai u4(.clk(hz), .flag(flag), .reset(reset), .distance(distance)); timemokuai u5(.clk(hz), .reset(reset), .flag(flag), .s(s), .m(m)); // 其他模块实例化... endmodule2. 关键模块实现细节2.1 智能分频系统现代FPGA设计需要为不同外设提供多种时钟频率。我们的分频模块采用参数化设计可根据需要生成任意频率module div_clk(clk, fs, cko); input clk; input [31:0] fs; output cko; reg cko; parameter N 50_000_000; // 基准频率50MHz reg [31:0] ACC; always (posedge clk) begin if(ACC N/2-1) ACC ACC fs; else begin ACC 0; cko !cko; end end endmodule该模块通过配置fs参数可产生1Hz信号用于里程和计时计数500Hz信号驱动数码管扫描1000Hz信号用于按键消抖处理2.2 高级按键处理系统机械按键的抖动问题会导致多次误触发。我们实现的消抖模块在检测到按键按下后等待10ms再次确认状态module key_shake(clk, key_in, key_out); input clk; // 1000Hz时钟 input key_in; output key_out; reg [6:0] c; reg key_out; always (posedge clk) begin if(!key_in) c c 1; else c 0; if(c 100) key_out 0; else key_out 1; end endmodule消抖后的按键信号再送入模式控制模块实现状态机的稳定切换module key_control(key, clk, module_Flag, SPEED_Flag, price_Flag, fee_Flag); input clk, key; output module_Flag, SPEED_Flag, price_Flag, fee_Flag; reg [2:0] state; always (posedge clk) begin case(state) 0: begin /* 初始化状态 */ end 1: begin fee_Flag 1b1; /* 设置起步价模式 */ end 2: begin price_Flag 1b1; /* 设置单价模式 */ end 3: begin module_Flag 1b1; /* 显示主界面 */ end 4: begin SPEED_Flag 1b1; /* 显示车速界面 */ end endcase end endmodule3. 计费逻辑实现3.1 里程计费模块里程计费采用分段计价方式3公里内收取起步价超过3公里后每公里加收单价module distancemokuai(clk, flag, reset, distance, distance_enable, module_Flag); input clk, flag, reset, module_Flag; output [7:0] distance; output distance_enable; reg [7:0] distance; reg [7:0] cnt; always (posedge clk or negedge reset) begin if(!reset) begin distance 8H00; end else if((flag 1b0) (module_Flag 1b1)) begin if(cnt 8d50) begin cnt 0; // 里程计数逻辑... end else begin cnt cnt 1; end end end assign distance_enable ((distance[7:0] 8H02) (cnt 8d50)) ? 1b1 : 1b0; endmodule3.2 等候时间计费模块等候计费规则为2分钟内不收费超过2分钟后每分钟加收单价module timemokuai(clk, reset, flag, s, m, time_enable); input clk, reset, flag; output [7:0] s, m; output time_enable; reg [7:0] s, m; always (posedge clk or negedge reset) begin if(!reset) begin s 8H00; m 8H00; end else if(flag 1b1) begin // 时分秒计数逻辑... end end assign time_enable ((m[7:0] 8H01) (s[7:0] 8H00)) ? 1b1 : 1b0; endmodule4. 显示系统设计4.1 数码管动态扫描8位数码管采用动态扫描方式驱动通过500Hz的扫描频率实现无闪烁显示module scan_led(clk, DA, DB, DC, DD, DE, DF, DG, DH, a, b, c, d, e, f, g, p, sel); input clk; input [3:0] DA, DB, DC, DD, DE, DF, DG, DH; output a, b, c, d, e, f, g, p; output [7:0] sel; reg [2:0] scan; reg [7:0] sel; reg [3:0] num; always (posedge clk) begin scan scan 1; case(scan) 0: begin sel 8b1111_1110; num DA; end // 其他位选逻辑... 7: begin sel 8b0111_1111; num DH; end endcase end // 七段译码器 assign {a,b,c,d,e,f,g,p} (num 0) ? 8B0000_0011 : // 0 (num 1) ? 8B1001_1111 : // 1 // 其他数字译码... 8B1111_1111; // 熄灭 endmodule4.2 多模式显示控制系统支持四种显示模式主界面显示里程、时间、费用车速显示界面起步价设置界面单价设置界面module switch( input clk, module_Flag, SPEED_Flag, fee_Flag, price_Flag, input [7:0] distance, fee, s, m, input [3:0] q0,q1,q2,q3,q4,q5,q6,q7, output [3:0] DH,DL,MH,ML,SH,SL,FH,FL ); always (posedge clk) begin if(module_Flag) begin // 主界面显示逻辑 DH distance[7:4]; DL distance[3:0]; MH m[7:4]; ML m[3:0]; SH s[7:4]; SL s[3:0]; FH fee[7:4]; FL fee[3:0]; end else if(SPEED_Flag) begin // 车速显示逻辑 DH q7; DL q6; MH q5; ML q4; SH q3; SL q2; FH q1; FL q0; end // 其他模式... end endmodule5. Quartus II工程部署5.1 工程配置要点器件选择根据FPGA开发板型号选择正确器件引脚分配按照开发板原理图分配时钟、按键、数码管等引脚编译选项优化设置平衡资源利用和时序性能仿真测试使用ModelSim进行功能仿真验证5.2 常见问题解决时序违例添加适当的时序约束资源不足优化代码结构减少寄存器使用下载失败检查JTAG连接和供电稳定性显示异常确认扫描频率和位选/段选极性在完成功能仿真后将生成的.sof文件下载到FPGA开发板即可实现完整的出租车计费功能。系统上电后数码管显示全零通过按键可依次进入起步价设置、单价设置、主显示界面和车速显示界面。