1. 从课设到实战自动售货机系统设计全解析第一次接触Verilog时我对着课本上的代码示例一头雾水直到在实验室里亲手实现了一个自动售货机系统才真正理解硬件描述语言的精妙。这个看似简单的课设项目其实包含了数字电路设计的核心思想——用逻辑门和寄存器构建出能完成复杂商业逻辑的智能系统。饮料自动贩卖机的设计需求非常贴近生活每瓶饮料定价1.5元接受1元和0.5元硬币投币需要实现金额计算、找零逻辑和状态显示三大核心功能。在FPGA开发板上我们需要用按键模拟投币和购买操作用LED灯指示交易状态用数码管显示金额信息。这就像用电子积木搭建一个微型商业系统每个模块都需要精心设计。与传统软件开发不同Verilog设计需要同时考虑时序逻辑和组合逻辑。比如投币按键需要防抖处理金额计算需要BCD码转换数码管显示需要动态扫描——这些在软件中可能几行代码就能实现的功能在硬件层面却需要设计专门的电路结构。我在第一次实现时就踩过坑没有处理好按键消抖导致一次按键被误识别为多次触发最终金额计算完全错乱。2. 核心模块设计与实现要点2.1 按键消抖模块的实战技巧机械按键的物理特性决定了它按下时会产生5-10ms的抖动这在数字电路中会造成多次误触发。我最初尝试用简单延时来消抖结果发现这种方法会阻塞整个系统运行。后来改用状态机方案通过20ms计时器来确认稳定按键状态才彻底解决问题。消抖状态机的设计非常精妙它包含UP松开稳定、FILTER0按下抖动、DOWN按下稳定、FILTER1松开抖动四个状态。当检测到边沿信号时会启动20ms计时器对应50MHz时钟的1000000个周期只有稳定达到这个时长才会确认状态转换。这就好比在嘈杂的环境中只有听到持续清晰的指令才会采取行动。module get_num(clk, key_in, num, rst_n); input clk; // 50MHz时钟 input key_in; // 按键输入 input rst_n; output reg [2:0] num0; parameter UP2b00, FILTER02b01, DOWN2b10, FILTER12b11; reg [1:0] state; reg [19:0] cnt; always(posedge clk or negedge rst_n) begin if(!rst_n) state UP; else case(state) UP: if(pedge) state FILTER0; FILTER0: if(cnt_full) state DOWN; DOWN: if(nedge) state FILTER1; FILTER1: if(cnt_full) state UP; endcase end endmodule2.2 金额计算的BCD码魔法硬件设计中直接使用二进制计算会面临一个难题如何用数码管显示十进制金额。我的解决方案是采用8421BCD码——用4位二进制数表示1位十进制数0000-1001。这样12位寄存器就能存储3位十进制金额十位、个位、小数位。金额计算模块需要处理两个关键转换一是将投币数量1元、0.5元个数转换为总金额BCD码二是计算应找零金额。这里有个巧妙的设计由于0.5元硬币的特性找零金额的小数位只能是0或5。我在to_change模块中用change_temp[0]判断是否需要找0.5元大大简化了计算逻辑。module to_change(cola_num, yuan_num, jiao_num, change, change_yuan); input [2:0] cola_num, yuan_num, jiao_num; output reg [3:0] change; output [7:0] change_yuan; always(*) begin if(2*yuan_num jiao_num 3*cola_num) change_temp 2*yuan_num jiao_num - 3*cola_num; else change_temp 2*yuan_num jiao_num; change[3:0] (change_temp[0]1)?4b0101:4b0000; end endmodule3. 数码管显示的艺术与科学3.1 动态扫描的精妙设计六位数码管如果同时点亮需要42个IO口这对FPGA资源是巨大浪费。我采用动态扫描技术——通过快速轮流点亮各位数码管1kHz刷新率利用人眼视觉暂留效应形成稳定显示。这需要精确的时序控制模6计数器产生位选信号SEL配合数据选择器将对应位BCD码送入译码器。module counter6(clk_1kHz, SEL); input clk_1kHz; output reg [2:0] SEL; reg [2:0] counter; always(posedge clk_1kHz) begin if(counter 3b101) counter counter 1b1; else counter 3b000; SEL counter; end endmodule3.2 小数点处理的细节把控金额显示需要精确控制小数点位。我发现个位数码管的小数点需要常亮显示xx.x格式而其他位的小数点应该熄灭。在段选信号生成时特别增加了条件判断当SEL为001找零个位或100投币个位时强制将SEG[0]置1点亮小数点。这种细节处理让显示效果更加专业。always(data) begin case(data) 4b0000: SEG 8b1111_1100; // 0 //...其他数字编码 endcase if(SEL 3b001 || SEL 3b100) SEG[0] 1b1; end4. 状态控制与错误处理机制4.1 购买流程的状态管理整个交易过程需要严格的状态控制复位初始化→投币计数→购买确认→结果展示。buy信号就像交通指挥灯只有它为高电平时才会触发金额校验和找零计算。我特意设计了错误处理机制当投币不足时error灯亮起并全额退款交易成功后take_cola和take_change灯同时点亮。always(posedge clk or negedge rst) begin if(!rst) begin error 1b0; take_cola 1b0; take_change 1b0; end else if(buy) begin if((2*yuan_num jiao_num) (cola_num*3)) begin error 1b1; // 金额不足 end else begin take_cola 1b1; // 交易成功 take_change 1b1; end end end4.2 复位信号的正确使用很多同学容易忽视复位信号的设计。在这个系统中rst低电平有效它会清零所有计数器和状态寄存器。但关键点在于正常操作时必须保持rst为高电平我在实验室就见过有同学因为rst接错导致整个系统无法工作。建议在硬件连接时用拨码开关作为rst信号操作时先复位再拨到高电平开始使用。5. 硬件部署与调试经验分享5.1 引脚分配的黄金法则将设计烧录到FPGA开发板时合理的引脚分配至关重要。我的经验法则是时钟信号接专用时钟引脚如PIN_90按键信号接带有去抖动电路的IO如Key0-Key8数码管位选信号接高驱动能力引脚LED指示灯接普通IO即可特别提醒不同开发板的引脚定义可能不同务必查阅官方文档。我曾经因为错把段选信号接到位选引脚导致数码管显示全乱。5.2 调试技巧与常见问题当系统不工作时建议按以下步骤排查用示波器检查时钟信号是否正常单独测试按键消抖模块观察计数器是否递增仿真计算模块验证BCD码转换是否正确检查数码管各段是否都能正常点亮常见问题包括按键响应不灵敏消抖时间常数可能需要调整金额计算错误检查二进制转BCD码的逻辑显示闪烁动态扫描频率可能不合适1kHz最佳购买逻辑失效确认buy信号的边沿检测是否正确6. 项目优化与扩展方向6.1 现有系统的优化空间虽然基础功能已经实现但还有多处可以改进增加取消交易功能按钮用PWM调节数码管亮度添加声音提示功能扩展支持多种饮料选择增加库存管理功能6.2 进阶学习建议掌握基础版本后可以尝试这些挑战改用I2C接口的数码管驱动器减少IO占用添加NFC支付模块实现远程库存监控功能用PS/2键盘替代物理按键加入温度传感器实现冷饮加热饮切换这个课设项目虽然简单但涵盖了数字系统设计的核心要素。我建议初学者在完全理解每个模块后尝试自己重新实现一遍。你会惊讶地发现看似简单的自动售货机系统背后蕴含着如此丰富的数字电路设计智慧。