1. 项目概述
这个基于FPGA的自动售货机控制系统是我在Quartus II环境下用Verilog HDL开发的一个实践项目。它模拟了真实售货机的基本功能:6种商品的库存管理、选择购买、价格显示、60秒付款倒计时以及缺货提示。作为一名FPGA开发者,我经常需要设计这样的状态机控制系统来锻炼自己的逻辑设计能力。
系统最核心的特点是将业务逻辑(状态控制)和显示驱动完全解耦。这种架构设计使得后期维护和功能扩展变得非常方便 - 比如要增加商品种类或者更换显示方式,只需要修改对应的模块即可,不会影响整体系统稳定性。在实际测试中,这套系统在Cyclone IV开发板上运行稳定,完全达到了设计要求。
2. 系统架构设计
2.1 整体框架
系统采用典型的"控制层+显示层"双模块架构:
- 控制模块(state_ctrl.v):处理所有业务逻辑
- 显示模块(display_num.v):负责数码管和LED驱动
这种分离设计有几个明显优势:
- 代码可维护性强 - 显示逻辑变更不会影响核心业务
- 资源利用率高 - 可以针对不同功能优化实现方式
- 调试更方便 - 可以单独测试每个模块
2.2 状态机设计
核心状态机包含5个主要状态:
- 待机状态(s_idle):等待用户选择商品
- 付款状态(s_coin):60秒倒计时
- 取消状态(s_coin_return):超时处理
- 出货状态(s_change):成功交易处理
- 结束状态(s_end):返回待机
状态转换逻辑完全遵循售货机的实际业务流程,每个状态都有明确的进入和退出条件。这种设计确保了系统行为的可预测性。
3. 核心模块实现
3.1 状态控制模块
状态控制模块(state_ctrl)是整个系统的大脑,主要功能包括:
verilog复制module state_ctrl(
input clk_in, //24MHz主时钟
input reset_n, //复位信号
input [6:1] select, //6个商品选择按键
input confirm_key, //付款确认
output less_led, //缺货指示灯
output [3:0] price, //商品价格显示
output succeed_led, //交易成功灯
output [7:0] timer, //60秒倒计时BCD
output [3:0] stock [6:1] //6种商品库存
);
3.1.1 按键处理
所有按键输入都经过消抖处理:
verilog复制//按键消抖计数器
reg [19:0] debounce_cnt [6:0];
always @(posedge clk_in) begin
for(i=0; i<7; i=i+1) begin
if(debounce_cnt[i] > 0)
debounce_cnt[i] <= debounce_cnt[i] - 1;
else if(key_raw[i])
debounce_cnt[i] <= 20'd500000; //约21ms消抖
end
end
3.1.2 库存管理
6种商品库存用4位寄存器存储(0-9):
verilog复制reg [3:0] stock [6:1];
initial begin
stock[1] = 4'd5; //初始化库存
stock[2] = 4'd3;
//...其他商品初始化
end
库存扣减逻辑:
verilog复制always @(posedge clk_in) begin
if(state == s_change) begin
case(selected_item)
1: stock[1] <= stock[1] - 1;
//...其他商品处理
endcase
end
end
3.2 显示驱动模块
显示模块负责将库存和倒计时信息输出到数码管和LED:
verilog复制module display_num(
input clk,
input [3:0] stock [6:1],
input [7:0] timer,
input [3:0] price,
output reg [7:0] seg,
output reg [5:0] dig
);
3.2.1 数码管动态扫描
采用常见的动态扫描方式驱动6位数码管:
verilog复制//扫描计数器
reg [19:0] scan_cnt;
always @(posedge clk) begin
scan_cnt <= scan_cnt + 1;
if(scan_cnt == 20'd100000) begin
scan_cnt <= 0;
dig <= {dig[4:0], dig[5]}; //循环移位
end
end
//段选数据生成
always @(*) begin
case(dig)
6'b111110: seg = num_to_seg(stock[1]);
//...其他位数处理
6'b101111: seg = num_to_seg(timer[3:0]);
6'b011111: seg = num_to_seg(timer[7:4]);
endcase
end
4. 关键功能实现细节
4.1 60秒倒计时实现
倒计时使用BCD码格式,便于数码管直接显示:
verilog复制reg [7:0] timer; //高4位十秒,低4位秒
always @(posedge clk_1Hz) begin
if(state == s_coin) begin
if(timer[3:0] == 0) begin
timer[3:0] <= 9;
timer[7:4] <= timer[7:4] - 1;
end else begin
timer[3:0] <= timer[3:0] - 1;
end
end else begin
timer <= 8'h60; //重置为60秒
end
end
1Hz时钟分频:
verilog复制reg [24:0] div_cnt;
wire clk_1Hz = (div_cnt == 25'd24_000_000);
always @(posedge clk_in) begin
if(div_cnt >= 25'd24_000_000)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1;
end
4.2 价格显示逻辑
价格通过4位LED显示,直接输出锁存的价格值:
verilog复制assign price_led = (state == s_coin || state == s_wait) ? locked_price : 4'b0;
5. 系统调试与优化
5.1 常见问题排查
在实际调试中遇到几个典型问题:
-
数码管显示闪烁
- 原因:扫描频率过低
- 解决:将扫描频率提高到200Hz以上
-
按键响应不灵敏
- 原因:消抖时间设置不当
- 解决:调整消抖时间为20ms左右
-
状态机卡死
- 原因:未覆盖所有状态转换条件
- 解决:添加default状态和超时复位
5.2 资源优化技巧
-
共享计数器
- 多个定时器可以共用同一个分频器
- 节省LE(Logic Element)资源
-
状态编码优化
- 使用独热码(one-hot)编码状态
- 比二进制编码更节省组合逻辑
-
显示数据复用
- 数码管段选数据动态生成
- 减少寄存器使用量
6. 扩展与改进建议
这个基础版本还可以进一步扩展:
-
增加支付方式
- 添加硬币识别模块
- 支持移动支付信号输入
-
库存持久化
- 添加EEPROM接口
- 断电保存库存信息
-
远程监控
- 添加UART通信
- 实现库存远程查询
在实际部署中,建议添加这些防护措施:
- 所有输入信号添加施密特触发器
- 关键状态机添加看门狗定时器
- 重要寄存器添加软复位功能
这个项目完整展示了如何使用Verilog在FPGA上实现一个典型的有限状态机系统。通过模块化设计和层次化实现,既保证了功能的完整性,又确保了代码的可维护性。对于FPGA初学者来说,这类项目是掌握硬件描述语言和数字系统设计理念的绝佳实践。