去年夏天我在电子设计实验室带学生时,发现很多初学者对FPGA既向往又畏惧。他们最常问的问题是:"老师,有没有一个项目能让我完整走通FPGA开发全流程?"这就是我设计这个自动售货机项目的初衷——用最贴近生活的场景,带大家体验从需求分析到硬件实现的完整过程。
这个系列教程会分为7天完成,今天的第一天我们将聚焦在项目框架搭建和基础功能模块设计。选择自动售货机作为教学项目有三个优势:首先,它的业务流程清晰可控(投币-选择-出货-找零);其次,所有功能模块都可以拆解为标准的数字电路单元;最重要的是,完成后的作品可以直接用开发板上的LED和按键进行交互演示,不需要额外购买配件。
我推荐使用Xilinx Artix-7系列开发板(如Basys3或Nexys4 DDR),原因有三:一是该系列芯片在学术和工业界应用广泛;二是其逻辑资源足够支撑本项目需求(约需占用2000个LUT);三是配套的Vivado工具链对学生有免费授权。如果手头只有Altera(现Intel)开发板也没关系,我会在关键步骤说明Quartus下的差异操作。
重要提示:购买开发板时务必确认配套下载器的型号,老款的Platform Cable USB可能不支持最新版开发工具。
安装Vivado时最容易踩的坑是版本兼容性问题。建议:
我整理了一份常见安装问题速查表:
| 错误现象 | 解决方案 |
|---|---|
| 安装卡在"Generating installed device list" | 关闭杀毒软件后重试 |
| 启动时提示license错误 | 检查环境变量XILINXD_LICENSE_FILE是否指向有效license文件 |
| 工程无法识别开发板 | 重新安装USB驱动(通常在安装目录的\Vivado\2023.2\data\xicom\cable_drivers下) |
我们把自动售货机拆解为五个核心模块:
这是整个系统的控制核心,采用经典的三段式状态机写法(后续会给出完整代码):
verilog复制parameter IDLE = 2'b00; // 待机状态
parameter PAY = 2'b01; // 投币中
parameter VEND = 2'b10; // 出货状态
parameter CHANGE = 2'b11; // 找零状态
always @(posedge clk) begin
case(current_state)
IDLE: if(coin_in) next_state <= PAY;
PAY: if(select_goods && money_ok) next_state <= VEND;
VEND: if(vend_done) next_state <= CHANGE;
CHANGE: if(change_done) next_state <= IDLE;
endcase
end
用按键消抖电路处理硬币投入信号是最容易出错的地方。这里分享一个经过实测可靠的消抖方案:
verilog复制module debounce (
input clk, // 10MHz时钟
input button, // 原始按键信号
output reg out // 消抖后信号
);
reg [19:0] count;
always @(posedge clk) begin
if(button) begin
if(count < 20'd999_999) count <= count + 1;
else out <= 1'b1;
end
else begin
count <= 0;
out <= 1'b0;
end
end
endmodule
这个设计的特点:
开发板通常配备4位7段数码管,我们需要实现:
金额显示的核心算法:
verilog复制// 二进制转BCD(适用于0-99元)
always @(*) begin
bcd_tens = total_money / 10;
bcd_units = total_money % 10;
end
// 数码管动态扫描
reg [1:0] scan_cnt;
always @(posedge scan_clk) begin
case(scan_cnt)
2'b00: begin seg_en <= 4'b1110; seg_data <= digit_units; end
2'b01: begin seg_en <= 4'b1101; seg_data <= digit_tens; end
// 其他位同理
endcase
scan_cnt <= scan_cnt + 1;
end
现象:综合报告显示LUT使用量远超预期
排查步骤:
硬件调试三板斧:
调整三个参数:
今天我们先完成这些基础模块,明天将实现商品选择逻辑和金额计算模块。建议大家在搭建好环境后,先重点调试投币识别和数码管显示这两个最直观的功能。实际教学中发现,很多同学卡在数码管显示不正常的问题上,这时候可以用一个简单的测试程序单独验证显示模块:
verilog复制// 数码管测试程序
reg [3:0] test_num = 0;
always @(posedge clk_1hz)
test_num <= test_num + 1;
assign seg_data = test_num; // 观察数码管是否显示0-F循环
遇到具体问题时,建议先用这类简化测试定位故障模块,再逐步添加功能。这就是FPGA开发中非常重要的"分治法"调试思路。