随着全球气候的不断变暖和环境的不断恶化,为了保护我们的地球家园,为了减少碳排放,绿色出行受到越来越多人的选择,共享单车的出现无疑一定层度减少了人们对汽车的依赖,对减少污染气体的排放到了一定的积极作用,可是随之而来共享单车也产生了一系列问题,比如传统共享单车的计费问题,传统共享单车计费方式主要是单一的软件计费,这种计费方式在一些突发状况中并不适用,比如忘带手机或手机没电等状况。本设计提供了一种新型的共享单车硬件计费方式的设想,是一种利用Verilog硬件描述语言基于FPGA的共享单车计费设计。本设计采用层次化的设计方式,模块分明,易于实现和移植。该设计采用硬件方式设计,稳定性更强,能适用于多种共享单车运行环境,更符合当下人们的出行付费习惯,能够更好地满足于市场的需要。
关键词:FPGA,Verilog,计费器,共享单车
共享单车的出现给人们带来了便捷的生活,缩短了人们的最后一公里,人们对共享单车依赖也越来越深,移动支付的普及也为共享单车的推广做出了极大的贡献,但是有时我们会出现手机电量不足等突发状况或者是其他不便使用手机的场景,本设计就是针对这些状况提出了一种新的共享单车计费器计费设想。
Verilog HDL是现在世界上绝大多数数字IC工程师都在使用的一种硬件描述语言,它已经成为业界内数字电路设计的一种标准硬件描述语言。不同于原理图直接描述电路的结构和功能,它是以文本形式来描述数字电路的结构和功能的[1]。Verilog HDL不但可以采用层次化的逻辑设计,而且还可以用于数字系统的仿真验证(用计算机仿真软件对数字逻辑电路的结构和行为进行分析预测,对HDL行为进行解释,一般的输出形式为波形图),逻辑综合(把硬件描述语言的数字电路逻辑模型转变成电路基本原件以及把这些基本元件进行连接生成电路模型)和时序分析等。Verilog HDL的应用场景有算法级别,寄存器传输级,逻辑级、门级和版图级等各个层级的设计和描述[2]。
FPGA(Field Programmable Gate Array,简称FPGA),翻译成中文就是现场可编程逻辑门阵列, FPGA器件属于专用集成电路中的一种半定制电路,它不像CPLD那样采用两级可编程的“与或”阵列来实现逻辑功能,而是基于查找表的原理来实现组合逻辑函数,能够有效的解决原有的器件门电路数较少的问题。FPGA 的基本结构包括可编程的I/O模块,可编程的逻辑块,数字时钟管理模块,嵌入式只读存储器,可编程连线资源,内部包含专用硬核,底层内嵌多种功能单元[3]。FPGA逻辑功能编程简单,就好像向RAM中写数据一样。由于FPGA具有大量的布线资源,编程更加灵活,集成度更高,适合大规模,高性能的数字系统设计以及投资较低的特点,在数字电路设计领域得到了广泛的应用。
(1)芯片:共享单车计费器设计使用的 FPGA 芯片型号为 EP4CE6F17C8,是属于ALTERA公司CYCLONE IV系列的产品。这款芯片主要的参数为,六千二百七十二个逻辑单元LE,三百九十二个乘法器,RAM存储空间为276480bit,有一百七十九个I/O口,推荐的工作电压是一点一二伏特,工作温度最好在零到八十五度之间。这种型号芯片的封装为(球状引脚栅格阵列) BGA 封装,共有二百五十六个引脚。根据官方数据,CYCLONE IV芯片拥有更高的性能以及更低的功耗,相较于上一代功耗减少了百分之二十五。
(2)JTAG接口:JIAG(联合测试工作组)接口可以把Quartus软件编译好的Verilog代码(.JIC文件)烧录进FPGA芯片中,因为FPGA是基于随机存储器的一种结构,它的内部没有固化的flash模块,无法存储数据,因此,通过JTAG下载的程序,关掉电源以后就会丢失,所以每次打开电源是都要重新下载才可以。目前,大多数高级部件都支持JTAG协议。
(3)50MHz有源晶振:晶振在数字电路设计中的基本作用是提供一个时序控制的时钟信号。
(4)六位一体的八段共阳极LED数码管:如图1所示,与普通的LED数码管不同,本设计所使用的码管的段信号都是连在一起的,共有八段,相比于七段的LED数码管,在数字显示右下角多了一个点。他们是通过六个位选信号来选择不同的数码管,这六个位选信号与段选信号一样都是低电平有效,只有供电的引脚为低电平时,对应的数码管才能被点亮。这样在设计的时候就应该添加一个扫描模块,用来选择不同的数码管。由于人眼的视觉暂留以及数码管的辉光现象,当这六个LED数码管以一定的速度循环发光,就可以实现我们的设计需要[4]。
(5)按键,也是低电平有效。
(6)JTAG下载线,USB电源连接线,如图2。
(1)Quartus II 13.0:本设计所使用的FPGA开发工具是Altera公司所推出的CPLD/FPGA开发软件Quartus II 13.0。这款软件的功能非常强大,支持原理图、VHDL、Verilog HDL等多种设计输入形式[5]。Quartus软件还具备综合和仿真的功能,可以完成完整的可编程逻辑器件的设计流程。由于其强大的功能和简洁的界面,Quartus II软件现在已经成为FPGA工程师开发项目工程的主流选择之一。
(2)Modelsim:本设计所使用的仿真软件属于Mentor公司开发的Modelsim仿真软件,它是目前世界上功能最强的硬件描述语言仿真软件之一。它支持PC、Linux、Unix混合平台,它能提供适合数字IC工程师开发工程的仿真环境,其具有强大的仿真能力,它的编译速度非常快,是业界内为数不多的单内核支持VHDL和Verilog HDL混合仿真的仿真器。它具有简洁的图形界面便于与用户交互,便于使用者快速进行开发调试,是FPGA、ASIC等数字系统开发设计的首选软件。
(3)Notepad++:Notepad++是一款简洁、高效的文本编辑器,支持多种常见的语言,具有很多强大的功能,比如自动保存,编辑只读文件,列编辑,格式编码,查找替换等等,可以为工程师开发工程节省大量的时间和精力。
本设计的功能是实现共享单车的计费功能,计费功能的实现是基于设计内的一个计时器[6]。当接入电源且外部信号(i_en)为高时,开启计时,费用的计算按行驶的时间计费,起步价为两元,每半小时加一元,支持最高99元的计费。外置六位LED数码管显示模块。可以显示运行状态,运行时间以及计价金额。当停止运行或计价金额达到最高99元时停止计价。本设计支持0~99小时显示,0~59分钟显示,0~99计价显示。
本设计采用自顶向下的层次化设计方法[7],有计时计费模块、LED控制模块、LED扫描模块、时钟分频模块四个模块,这四个模块在顶层模块连接,系统设计框图如图3所示,其中最重要的模块是计时计费模块。
(1)时钟分频模块:系统时钟是50MHz,对应的时钟周期为20ns,这显然是不适用于本设计输入的,为了便于本设计的硬件实现及观察,本设计加入了时钟分频模块,系统时钟每25000周期,时钟分频模块翻转一次,此时,时钟分频模块输入下一级的时钟周期为1ms。
(2)计时计费&控制模块:本模块主要计时、计费、控制,三大主体构成。下面分别介绍各部分组成:
计时器由五个计数器组成,分别是六十进制秒计数器,十进制分个位计数器,六进制分十位计数器,十进制小时个位计数器,十进制小时十位计数器。当i_en信号为高时,计时开启,计时功能支持最大九十九时五十九分五十九秒计时。计时器是本设计最重要的部分。
计费器,每当计时器满三十分钟时,计费次数加一。计费器的起步价格为两元,每当计时满三十分钟时加一元,最高支持计费金额九十九元,当金额达到九十九元时,停止计费,计时器也由此停止。
控制:i_en信号模拟解锁信号,i_rst_n复位信号。
(3)LED段选模块:本模块的输入信号为计时计费&控制模块输出的八位十六进制小时信号(高四位表示小时十位,低四位表示小时个位),八位十六进制分钟信号(高四位表示分钟十位,低四位表示分钟个位),八位十六进制计价信号(高四位表示十位,低四位表示个位),以及i_dot信号,i_dot信号控制八段LED数码管的点。本模块的主要功能就是由输入的信号向八位LED数码管输出不同的段选信号,由于本设计才用的是共阳极数码管,所以向LED数码管输出的信号是低电平有效。
(4)LED位选模块:本模块的输入信号为时钟信号,由于一共有六个LED数码管,模块内置了一个六进制计数器,在时钟的作用下,一直循环0~5计数,作为译码电路的输入以此来选择0~5位LED数码管。
(5)顶层模块:本模块的作用是集成其他子模块,由图四所示,顶层模块的输入信号有时钟信号,复位信号,启动信号(i_en),输出信号有段选信号o_seg_led以及位选信号o_sel。
本设计采用层次化的设计,是当下非常流行的设计思路。首先在系统级对设计进行功能模块划分,然后在功能级对各个模块进行描述[8],并进行功能仿真,若仿真之后功能达到预期设想,就进行综合,把功能描述转化成门级描述,生成相应的网表文件,在把网表文件下载入FPGA或其他可可编程逻辑器件中进行布局布线。层次化的设计要经过反复验证修改,直到符合设计的所需要的逻辑要求和功耗面积要求等。本设计除了顶层,一共含有四个模块,利用元件例化,可以将已设计好的模块当作一个固化的元件来使用,把设计好的模块和顶层连接在一起,这样就可以映射出一个完整的电路。代码实现如下:
module time_money_ctl( i_clk,i_rst_n,i_en,o_sum); input i_clk; input i_rst_n; input i_en; output o_sum; reg [7:0] sec_reg; reg [7:0] min_reg; reg [3:0] minl_reg; reg [3:0] minh_reg; reg [3:0] hourl_reg; reg [3:0] hourh_reg; reg [3:0] moneyl_reg; reg [3:0] moneyh_reg; reg counter_times; wire [7:0] o_money; wire [7:0] o_minute; wire [7:0] o_hour; reg terminus; //计费到99元 assign o_minute = {minh_reg,minl_reg}; assign o_hour = {hourh_reg,hourl_reg}; assign o_second = sec_reg; assign o_money = {moneyh_reg,moneyl_reg}; assign o_sum = {o_hour,o_minute,o_money}; always@( posedge i_clk or negedge i_rst_n) begin if(!i_rst_n) terminus <= 1'b0; else if (o_money == 8'h99) terminus <= 1'b0; else terminus <= 1'b1; end always@( posedge i_clk or negedge i_rst_n) //second counter begin if(!i_rst_n) sec_reg <= 8'b0; else if (sec_reg >= 8'd59) sec_reg <= 8'b00000000; else if (i_en && terminus) sec_reg <= sec_reg+1'b1; else sec_reg <= sec_reg; end //-----------------minute counter----------------------------------// always@( posedge i_clk or negedge i_rst_n) //60进制分钟计数器 if(!i_rst_n) minl_reg <= 0; else if((minl_reg >= 4'd9) && (sec_reg == 8'd59)) minl_reg <= 4'b0000; else if(sec_reg == 8'd59) minl_reg <= minl_reg+1'b1; else minl_reg <= minl_reg; always@( posedge i_clk or negedge i_rst_n) if(!i_rst_n) minh_reg <= 0; else if((minh_reg == 4'h5) && (minl_reg == 4'h9) && (sec_reg == 8'd59) ) minh_reg = 4'b0000; else if((minl_reg == 4'h9) && (sec_reg == 8'd59)) minh_reg <= minh_reg+1'b1; else minh_reg <= minh_reg; //---------------------------- 计价次数计数器-----------------------------------------// always@( posedge i_clk or negedge i_rst_n) begin if(!i_rst_n) counter_times <= 1'b1; else if((o_minute == 8'h29 || o_minute == 8'h59) && (sec_reg == 8'd59)) counter_times <= 1'b1; else counter_times <= 1'd0; end //--------------------hour counter-------------------------------------// always@( posedge i_clk or negedge i_rst_n) //小时个位计数器 begin if(!i_rst_n) hourl_reg <= 4'b0000; else if((sec_reg == 8'd59) && (o_minute == 8'h59) && (hourl_reg >= 4'h09)) hourl_reg <= 4'b0000; else if((sec_reg == 8'd59) && (o_minute == 8'h59) ) hourl_reg <= hourl_reg+1'b1; else hourl_reg <= hourl_reg; end always@( posedge i_clk or negedge i_rst_n) //小时十位计数器 if(!i_rst_n) hourh_reg <= 4'b00000; else if((hourh_reg == 4'h9) && (o_minute == 8'h59) && (sec_reg == 8'd59)) hourh_reg <= 4'b0000; else if((hourl_reg == 4'h9) && (o_minute == 8'h59) && sec_reg == 8'd59) hourh_reg <= hourh_reg+1'b1; else hourh_reg <= hourh_reg; //--------------------------计费计数器-------------------------------------------------// always@( posedge i_clk or negedge i_rst_n) //计费个位计数器 begin if(!i_rst_n) moneyl_reg <= 4'h0; else if ((moneyl_reg == 4'h9) && counter_times) moneyl_reg <= 4'h0; else if ((o_minute <= 8'h29) && (o_second <= 8'd59) && (o_hour < 8'h01)) moneyl_reg <= 2'd2; else if (counter_times) moneyl_reg <= moneyl_reg+counter_times; else moneyl_reg <= moneyl_reg; end always@( posedge i_clk or negedge i_rst_n) //计费十位计数器 begin if(!i_rst_n) moneyh_reg <= 4'h0; else if ((moneyl_reg == 4'h9) && counter_times) moneyh_reg <= moneyh_reg+1'b1; else moneyh_reg <= moneyh_reg; end endmodule ------------------------------------------------------------------------------------------------------------------------ module ledctl(i_clk,i_rst_n, o_seg_led,i_ctl_seg,i_dot); input i_clk; input i_rst_n; input [3:0] i_ctl_seg; input i_dot; output o_seg_led;reg [7:0] o_seg_led ; //---------------- 八位共阴极LED数码管------------------------// always@(i_ctl_seg) begin case (i_ctl_seg) 4'h0: o_seg_led = {i_dot,7'b1000000}; // LED数码管显示数字0 4'h1: o_seg_led = {i_dot,7'b1111001}; // LED数码管显示数字1 4'h2: o_seg_led = {i_dot,7'b0100100}; // LED数码管显示数字2 4'h3: o_seg_led = {i_dot,7'b0110000}; // LED数码管显示数字3 4'h4: o_seg_led = {i_dot,7'b0011001}; // LED数码管显示数字4 4'h5: o_seg_led = {i_dot,7'b0010010}; // LED数码管显示数字5 4'h6: o_seg_led = {i_dot,7'b0000010}; // LED数码管显示数字6 4'h7: o_seg_led = {i_dot,7'b1111000}; // LED数码管显示数字7 4'h8: o_seg_led = {i_dot,7'b0000000}; // LED数码管显示数字8 4'h9: o_seg_led = {i_dot,7'b0010000}; // LED数码管显示数字9 default: o_seg_led = 8'b1111_1111; endcase end endmodule
module clock ( i_clk,i_rst_n,o_clk); input i_clk; input i_rst_n; output o_clk; reg o_clk; reg [15:0]cnt; always@ (posedge i_clk or negedge i_rst_n) //时钟分频模块25000计数器 begin if(!i_rst_n) cnt <= 16'b0000000000000000; else if(cnt == 16'd24999) cnt <= 16'b0000000000000000; else cnt <= cnt+1'b1; end always@(posedge i_clk or negedge i_rst_n) //每25000个时钟信号翻转一次 begin if(!i_rst_n) o_clk <= 0; else if(cnt == 16'd24999) o_clk <= ~o_clk; else o_clk <= o_clk; end endmodule
module scan(i_clk, i_rst_n, i_sum,o_sel,o_ctl_seg, o_dot); //端口名 input i_clk; //经过分频后的时钟 input i_rst_n; //复位信号 input [23:0] i_sum; output [5:0] o_sel; //LED数码管位选 output [3:0] o_ctl_seg; output o_dot; reg [3:0] o_ctl_seg; reg [5:0] o_sel ; reg o_dot; reg [2:0] cnt ; always@(posedge i_clk or negedge i_rst_n) //计数器,使LED数码管可以循环位选 begin if(!i_rst_n) cnt <= 0; else if(cnt < 3'd5) cnt <= cnt + 1; else cnt <= 0; end always@(posedge i_clk or negedge i_rst_n) //实现LED数码管位选功能 if(!i_rst_n) begin o_sel = 6'b111_111; o_ctl_seg = 24'b0; o_dot = 1'b1; end else begin case (cnt) 3'd0: begin o_sel = 6'b111_110; o_ctl_seg = i_sum[3:0]; o_dot = 1; end 3'd1: begin o_sel = 6'b111_101; o_ctl_seg = i_sum[7:4]; o_dot = 1; end 3'd2: begin o_sel = 6'b111_011; o_ctl_seg = i_sum[11:8]; o_dot = 0; end 3'd3: begin o_sel = 6'b110_111; o_ctl_seg = i_sum[15:12]; o_dot = 1; end 3'd4: begin o_sel = 6'b101_111; o_ctl_seg = i_sum[19:16]; o_dot = 0; end 3'd5: begin o_sel = 6'b011_111; o_ctl_seg = i_sum[23:20]; o_dot = 1; end default: begin o_sel = 6'b111_111; o_ctl_seg = 24'b0; o_dot = 1'b1; end endcase end endmodule
module ledctl( i_clk,i_rst_n,o_seg_led,i_ctl_seg,i_dot); input i_clk; input i_rst_n; input [3:0] i_ctl_seg; input i_dot; output o_seg_led; reg [7:0] o_seg_led ; always@(i_ctl_seg) //段选译码模块 begin case (i_ctl_seg) 4'h0: o_seg_led = {i_dot,7'b1000000}; //0 4'h1: o_seg_led = {i_dot,7'b1111001}; //1 4'h2: o_seg_led = {i_dot,7'b0100100}; //2 4'h3: o_seg_led = {i_dot,7'b0110000}; //3 4'h4: o_seg_led = {i_dot,7'b0011001}; //4 4'h5: o_seg_led = {i_dot,7'b0010010}; //5 4'h6: o_seg_led = {i_dot,7'b0000010}; //6 4'h7: o_seg_led = {i_dot,7'b1111000}; //7 4'h8: o_seg_led = {i_dot,7'b0000000}; //8 4'h9: o_seg_led = {i_dot,7'b0010000}; //9 default: o_seg_led = 8'b1111_1111; endcase end endmodule
实现各个模块的连接和控制信号的输出输入。
module jifeiqi(i_clk,i_rst_n, o_sel, o_seg_led,i_en); input i_clk; //输入时钟 input i_rst_n; //输入复位信号 input i_en; //输入使能,模拟单车解锁信号 output o_sel; //输出位选选信号 output o_seg_led; //输出段选信号 wire [23:0] sum; wire [4:0] ctl_seg; wire dot; wire [7:0] o_seg_led; wire [5:0] o_sel; wire i_en; wire o_clk; ledctl ledctl( .i_clk (o_clk), .i_rst_n (i_rst_n), .o_seg_led (o_seg_led), .i_ctl_seg (ctl_seg), .i_dot (dot) ); scan scan1 ( .i_clk (o_clk), .i_rst_n (i_rst_n), .o_sel (o_sel), .i_sum (sum), .o_ctl_seg (ctl_seg), .o_dot (dot) ); time_money_ctl time_money_ctl ( .i_clk (o_clk), .i_rst_n (i_rst_n), .i_en (i_en), .o_sum (sum) ); clock clock( .i_clk (i_clk), .i_rst_n (i_rst_n), .o_clk (o_clk) ); Endmodule
利用Modelsim软件进行功能仿真。
时钟分频模块把系统输入时钟i_clk,转化为o_clk输出,如图5所示。
如图6所示,计时计费与控制模块的输入是时钟信号i_clk和i_en以及复位信号i_rst_n(低电平有效),输出信号为o_sum,o_sum是一个位宽为24bit的信号,[23:20]为小时信号高位,[19:16]为小时信号低位,[15:12]为分钟信号高位,[11:8]为分钟信号低位,[7:4]为计费信号高位,[3:0]为计费信号低位。输出信号o_sum是屏幕扫描模块的输入。
如图7所示,LED位选模块的输入是时钟信号i_clk,复位信号i_rst_n以及i_sum信号,输出信号是位宽为6bit的o_sel和位宽为4bit的o_ctl_seg及o_dot,o_sel的作用是循环选择不同的LED数码管,o_ctl_seg的作用是为LED段选模块做译码输入,o_dot为LED数码管的点信号输入。
如图8所示,LED段选模块的输入有时钟信号,复位信号,i_dot以及i_clt_sel,输出信号是8bit的o_seg_led,o_seg_led的作用是控制LED数码管的显示数字。
如图9所示,顶层模块通过对各个模块的调用,以及时钟信号的作用对芯片输入o_sel信号和以及o_seg_led信号。
Quartus软件建立的项目工程在经过编译之后会在工程文件目录下面自动生成.sof文件,这个文件可用于仿真验证所设计电路功能与预期电路功能是否符合[9]。第一步我们需要把.sof文件转化成.jic文件,第二步配置好芯片型号及下载方式,然后根据FPGA开发板的原理图配置好引脚,之后安装完成驱动,就可以下载程序了。下面展示几个真实效果,检查该设计是否正确计费[10]。
如图10所示,当开发板上的LED灯显示的时间是一小时二十六分钟,显示的价格是四元。根据设计起步价两元,每半小时加一元,二加一加一等于四,设计完全正确。
如图11所示,当开发板上的LED数码管显示的时间是两小时五十二分钟,显示的价格是七元。根据设计起步价两元,每半小时加一元,二加五等于七,设计完全正确。
如图12所示,当计价达到99元时,停止计时计费,符合设计需要。
共享单车计费器设计采用 Verilog 硬件描述语言,按自顶向下的层次化设计方法,将设计的数字系统分成四个相对独立的模块分别设计 , 分别包括,计时计费&控制模块,时钟分频模块,LED段选模块,LED位选模块。用Modelsim软件对设计的各个模块进行仿真得到波形,直到各个模块的功能仿真都通过后,再将其合成整体,最后进行整体综合仿真,待到仿真通过后,将程序通过JTAG下载器下载到EP4CE6F17C8芯片中,并在FPGA开发板上验证其功能。经过FPGA开发板的验证实现,本设计完全符合所预期的设计需求。最后,本设计功能比较单一,后续可考虑增加里程数计费和时间分段计费等多模式计费功能。
[1] 张辉辉,基于ARM的电脑鼠控制系统研究,西安石油大学硕士论文,2012。
[2] 何谷慧,基于SOPC的运动目标跟踪技术研究,河南科技大学硕士论文,2010。
[3] 张雅兰,基于FPGA的微波炉控制器的设计,大学生论文联合对比库,2016。
[4] 芯驿电子科技有限公司,AX301开发手册,2014。
[5] 黄建新,基于 FPGA 芯片设计出租车计费器的研究,吉林化工学院学报,2003。
[6] 王金明,数字系统设计与Verilog HDL,电子工业出版社,2014。
[7] Michael D. Ciletti,Advanced Digital Design with the Verilog HDL(Second Edition),2014。
[8] 孙瑜,基于FPGA的数据加解密系统设计,大连海事大学硕士论文,2010。
[9] 杨祖芳,曾鹤琼,王瑞瑛, 基于 VHDL 的出租车计费器的设计与研究, 企业技术开发,2013.9。
[10] 张慧,基于FPGA的出租车计费系统设计,山西电子技术,2011. 3。
Abstract: With the development of the global climate warming and environment worsening, in order to protect our home planet, in order to reduce carbon emissions, more and more people choose green travel, came up with the Shared cycling to certain degree layer reduces the dependence on cars people, to reduce pollution emissions to the certain positive role, but then share the bike also produced a series of problems, For example, the traditional bike-sharing billing method is mainly a single software billing method, which is not applicable in some emergencies, such as forgetting to bring your phone or running out of battery. This design provides an idea of a new hardware billing method, which is a shared bike billing method based on Verilog hardware description language based on FPGA. The design is more stable, can be applied to a variety of shared bike operating environment, more in line with the current travel habits of people, can better meet the needs of the market.
Key words: FPGA, Verilog HDL, taximeter, bicycle sharing.