赞
踩
设计一个简易的出租车计费系统,实现计价功能,计费标准为按里程收费,起步价为6.00元,当里程小于3公里时,按起步价收费,超过3公里后按1.2元/公里收费。
实现车辆行驶的模拟:能模拟汽车的启动,暂停,停止等状态。
计费显示部分设计:用LED数码管实时显示车费和汽车行驶里程,用一个按键切换车费和里程的显示,里程单位km,记程范围为0-99km。
输入:系统时钟、系统复位、行驶模拟按键(启动、暂停、停止)、显示切换按键
输出:①数码管段选、位选(没有驱动芯片)
②ds数据、oe使能、shcp移位时钟、stcp寄存时钟(74hc595串并转换数码管驱动芯片)
状态机的核心是一个case语块,是一种编程思想。case的条件是不同的状态(state),每个状态下分别进行各种操作,状态的切换由输入信号条件控制。在写法上可以只定义一个状态(state),或者定义两个状态(now_state、next_state)现态和次态。再写一个状态转移,让次态不断传递给现态,完成状态的切换。
在本项目中,使用状态机思想,描述汽车的启动、暂停、停止几种状态。
停止状态:此状态下对车费和里程清零,并判断启动键是否按下,进入启动状态
启动状态:让里程自增(模拟汽车行驶)让车费为6元,当里程大于等于3公里,车费开始每公里1.2元增加。
暂停状态:不进行操作,只判断启动和停止键是否按下进行跳转。
数码管一般是由7个led灯组成,称为7段数码管,加上dp小数点称为8段。
如图,要显示数字“1”,bc亮,其他不亮,段码则为1001_1111,转为16进制从低位到高位数,为F9。开发板上有6位数码管,即6个这样的数码管连接在一起,通过位选来控制让哪一个数码管有效,再通过段选控制显示的内容。
所以,动态显示=位选信号从1-6扫描为周期,例如要显示数字“2023”,在选通1号数码管亮的时候,段选为“3”;接着选通2号数码管亮,段选输出“2”;接着让3号数码管亮,段选“0”;4号数码管亮,段选“2”,这样通过高速4次扫描,由于人眼暂留效应,看到的就是“2023”同时亮起的效果。
明白了数据的获取(计费逻辑)与显示(动态数码管),但实际上要想把一个数字的不同数位分别显示出来,还需要一个拆分功能,把一个数拆成小数位、个位、十位、百位等,然后把不同位分别送入不同数码管动态显示。
起初我是自己用除法写了拆分逻辑,进行电路综合后发现,在FPGA芯片中,除法非常占用资源,导致我的资源块超出(下图报错信息),无法进行布局布线设计。FPGA有硬件乘法器,但没有除法器。如果非要用除法,建议使用ip核除法软核,不过ip核也是使用“+,-,移位”实现的。所以不如自己写一个拆分模块。
- assign fare_g[7:4] = (fare/10) % 10;
- assign fare_g[11:8] = (fare/100) % 10;
- assign fare_g[15:12] = (fare/1000) % 10;
拆分思想是,先补零,移位判断是否大于4,大于就加3再判断。对于十进制数的位数进行判断,例如“234”,3位,补3x4=12个0,进行如下操作后,“2”“3”“4”就被拆开分别放入3组4位二进制里了。(下图取自野火FPGA教程)
对费用数据、里程数据进行拆分后,分别放入段选寄存器中,按位把数据组一个一个送入595芯片,595芯片将串行传入的数据,进行移位存入寄存器,当一组8位数据存好,再复制到输出寄存器并行输出,编写控制移位时钟和寄存器时钟的时序,送入595芯片,即可实现串并转换。
如果没有595芯片,就需要编写动态扫描周期进程,按照位选段选时序要求,依次让数码管不同位显示不同数据。
- module data_gen //数据生成模块
- #(
- parameter CNT_MAX = 26'd49_999_999,
- )
- (
- input wire sys_clk , // 系统时钟
- input wire sys_rst_n , // 系统复位
-
- input wire start , // 启动
- input wire stop , // 停止
- input wire pause , // 暂停
- input wire key_switch , // 切换显示,
-
- output reg [19:0] data, //数据输出
- output reg [5:0] point, //小数点
- output wire sign, //正负号
- output reg seg_en //显示使能
- );
-
- parameter STOP = 4'b0000 , //停止
- START = 4'b0001 , //启动
- PAUSE = 4'b0010 ; //暂停,三个状态值
-
- reg [25:0] cnt_100ms;
- reg cnt_flag;
-
- reg [10:0] fare; // 车费
- reg [6:0] total_distance; // 总行驶里程
- reg [3:0] state; // 车辆状态
-
- //100ms的计费自增周期产生
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_100ms <= 26'd0;
- else if(cnt_100ms == CNT_MAX)
- cnt_100ms <= 26'd0;
- else
- cnt_100ms <= cnt_100ms + 1'b1;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_flag <= 1'b0;
- else if(cnt_100ms == CNT_MAX - 1'b1)
- cnt_flag <= 1'b1;
- else
- cnt_flag <= 1'b0;
-
- //计费逻辑状态机进程
- always@(posedge sys_clk or negedge sys_rst_n)
- if(!sys_rst_n)
- begin
- fare <= 11'b0; //初始化车费、路程等清零
- total_distance <= 7'b0;
- state <= 4'b0;
- end
- else
- begin //状态机各个状态转换
- case (state)
- STOP: // 停止状态
- if(start == 1'b0)
- begin
- state <= START; // 切换到启动状态
- end
- else
- begin
- total_distance <= 7'b0;
- fare <= 11'b0;
- end
- START: // 启动状态
- if(pause == 1'b0)
- begin
- state <= PAUSE; // 切换到暂停状态
- end
- else if(cnt_flag == 1'b1)
- begin
- total_distance <= total_distance + 1'b1; // 模拟行驶
- if(total_distance <= 7'd2)
- begin
- fare <= 11'd60; // 起步价6.00元
- end // 这里用60代表6元,12代表1.2元
- else
- begin
- fare <= fare + 11'd12; // 超过3公里,每公里1.2元
- end
- end
- else if(stop == 1'b0) begin
- state <= STOP; // 切换到停止状态
- end
- PAUSE: // 暂停状态
- if(start == 1'b0)
- begin
- state <= START; // 切换到启动状态
- end
- else if(stop == 1'b0)
- begin
- state <= STOP; // 切换到停止状态
- end
- endcase
- end
-
- //显示切换进程
- always@(posedge sys_clk or negedge sys_rst_n)
- if(!sys_rst_n)
- begin
- data <= 20'd0;
- point <= 6'b000_000;
- end
- else if(key_switch == 1'b0) //显示费用模式
- begin
- data <= fare;
- point <= 6'b000_010;
- end
- else //显示里程模式
- begin
- data <= total_distance;
- point <= 6'b000_000;
- end
-
- assign sign = 1'b0;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- seg_en <= 1'b0;
- else
- seg_en <= 1'b1;
-
- endmodule
- module hc595_ctrl
- (
- input wire sys_clk , //系统时钟
- input wire sys_rst_n, //系统复位
- input wire [7:0] seg , //数码管段选
- input wire [5:0] sel , //数码管位选
-
- output reg shcp , //移位时钟
- output reg stcp , //寄存器时钟
- output reg ds , //数据
- output wire oe //使能
- );
-
- wire [13:0] data ;
- reg [1:0] cnt ;
- reg [3:0] cnt_bit ;
-
- assign data = {seg[0],seg[1],seg[2],seg[3],
- seg[4],seg[5],seg[6],seg[7],sel}; //把段位数据放入data
-
- //产生周期扫描信号
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt <= 2'd0;
- else if(cnt == 2'd3)
- cnt <= 2'd0;
- else
- cnt <= cnt + 2'd1;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_bit <= 4'd0;
- else if((cnt_bit == 4'd13) && (cnt == 2'd3))
- cnt_bit <= 4'd0;
- else if(cnt == 2'd3)
- cnt_bit <= cnt_bit + 1'b1;
- else
- cnt_bit <= cnt_bit;
-
- //按周期扫描信号,依次把数据传入595芯片ds
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- ds <= 1'b0;
- else if(cnt == 2'd0)
- ds <= data[cnt_bit];
- else
- ds <= ds;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- shcp <= 1'b0;
- else if(cnt == 2'd2)
- shcp <= 1'b1;
- else if(cnt == 1'b0)
- shcp <= 1'b0;
- else
- shcp <= shcp;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- stcp <= 1'b0;
- else if((cnt == 1'b0) && (cnt_bit == 4'd0))
- stcp <= 1'b1;
- else if((cnt ==2'd2) && (cnt_bit ==4'd0))
- stcp <= 1'b0;
- else
- stcp <= stcp;
-
- assign oe = 1'b0;
-
- endmodule
- module bcd_8421
- (
- input wire sys_clk ,
- input wire sys_rst_n ,
- input wire [11:0] data , //要拆分的数据
- output reg [3:0] unit , //拆分后的个位
- output reg [3:0] ten , //十位
- output reg [3:0] hun , //百位
- output reg [3:0] tho //千位
-
- );
-
- reg [3:0] cnt_shift;
- reg [27:0] data_shift;
- reg shift_flag;
-
- //循环移位次数
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_shift <= 4'd0;
- else if((cnt_shift == 4'd13)&& (shift_flag == 1'b1))
- cnt_shift <= 4'd0;
- else if(shift_flag == 1'b1)
- cnt_shift <= cnt_shift + 1'b1;
- else
- cnt_shift <= cnt_shift;
-
- //4位一组进行判断>4否? 大于就加3
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- data_shift <= 27'd0;
- else if(cnt_shift == 4'd0)
- data_shift <= {16'b0,data};
- else if((cnt_shift <= 12) && (shift_flag == 1'b0))
- begin
- data_shift[15:12] <= (data_shift[15:12] > 4) ? (data_shift[15:12] +2'd3) : (data_shift[15:12]);
- data_shift[19:16] <= (data_shift[19:16] > 4) ? (data_shift[19:16] +2'd3) : (data_shift[19:16]);
- data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift[23:20] +2'd3) : (data_shift[23:20]);
- data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift[27:24] +2'd3) : (data_shift[27:24]);
- end
- else if((cnt_shift <= 12) && (shift_flag == 1'b1))
- data_shift <= data_shift << 1;
- else
- data_shift <= data_shift;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- shift_flag <= 1'b0;
- else
- shift_flag <= ~shift_flag;
-
- //移位判断一个周期后,进行数据输出
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- begin
- unit <= 4'd0;
- ten <= 4'd0;
- hun <= 4'd0;
- tho <= 4'd0;
- end
- else if(cnt_shift == 5'd13)
- begin
- unit <= data_shift[15:12];
- ten <= data_shift[19:16];
- hun <= data_shift[23:20];
- tho <= data_shift[27:24];
- end
- endmodule
- `timescale 1ns/1ns
- module seg_dynamic
- (
- input wire sys_clk ,
- input wire sys_rst_n,
- input wire [19:0] data ,
- input wire [5:0] point ,
- input wire sign ,
- input wire seg_en ,
- output reg [7:0] seg ,
- output reg [5:0] sel
- );
-
- parameter CNT_MAX = 16'd49_999;
-
- wire [3:0] unit ;
- wire [3:0] ten ;
- wire [3:0] hun ;
- wire [3:0] tho ;
- wire [3:0] t_tho ;
- wire [3:0] h_hun ;
- reg [23:0] data_reg ;
- reg [15:0] cnt_1ms ;
- reg flag_1ms ;
- reg [2:0] cnt_sel ;
- reg [5:0] sel_reg ;
- reg [3:0] data_disp ;
- reg dot_disp ;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- data_reg <= 24'd0;
- else if((h_hun) || (point[5])) //最高位非零,则全显示
- data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
- //次高位非零,少显示一位最高位
- else if(((t_tho) || (point[4])) && (sign == 1'b1))
- data_reg <= {4'd10,t_tho,tho,hun,ten,unit};
- else if(((t_tho) || (point[4])) && (sign == 1'b0))
- data_reg <= {4'd11,t_tho,tho,hun,ten,unit};
-
- else if(((tho) || (point[3])) && (sign == 1'b1))
- data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
- else if(((tho) || (point[3])) && (sign == 1'b0))
- data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
-
- else if(((hun) || (point[2])) && (sign == 1'b1))
- data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
- else if(((hun) || (point[2])) && (sign == 1'b0))
- data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
-
- else if(((ten) || (point[1])) && (sign == 1'b1))
- data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
- else if(((ten) || (point[1])) && (sign == 1'b0))
- data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
- //只显示最低位一位
- else if(((unit) || (point[0])) && (sign == 1'b1))
- data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
- else
- data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
-
- //1ms循环计数
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_1ms <= 16'd0;
- else if(cnt_1ms == CNT_MAX)
- cnt_1ms <= 16'd0;
- else
- cnt_1ms <= cnt_1ms +1'b1;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- flag_1ms <= 1'b0;
- else if(cnt_1ms == CNT_MAX -1'b1)
- flag_1ms <= 1'b1;
- else
- flag_1ms <= 1'b0;
-
- //从0到5循环数,选择当前显示的数码管
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_sel <= 3'd0;
- else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))
- cnt_sel <= 3'd0;
- else if(flag_1ms == 1'b1)
- cnt_sel <= cnt_sel + 3'd1;
- else
- cnt_sel <= cnt_sel;
-
- //根据上面进程,让位选信号移位
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- sel_reg <= 6'b000_000;
- else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
- sel_reg <= 6'b000_001;
- else if(flag_1ms == 1'b1)
- sel_reg <= sel_reg << 1;
- else
- sel_reg <= sel_reg;
-
- //六个数码管轮流显示
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- data_disp <= 4'd0;
- else if((seg_en == 1'b1) &&(flag_1ms == 1'b1))
- case(cnt_sel)
- 3'd0: data_disp <= data_reg[3:0];
- 3'd1: data_disp <= data_reg[7:4];
- 3'd2: data_disp <= data_reg[11:8];
- 3'd3: data_disp <= data_reg[15:12];
- 3'd4: data_disp <= data_reg[19:16];
- 3'd5: data_disp <= data_reg[23:20];
- default:data_disp <= 4'd0;
- endcase
- else
- data_disp <= data_disp;
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- dot_disp <= 1'b1;
- else if(flag_1ms == 1'b1)
- dot_disp <= ~point[cnt_sel];
- else
- dot_disp <= dot_disp;
-
- //数码管显示数字与段码映射
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- seg <= 8'b1111_1111;
- else
- case(data_disp)
- 4'd0: seg <= {dot_disp,7'b100_0000};
- 4'd1: seg <= {dot_disp,7'b111_1001};
- 4'd2: seg <= {dot_disp,7'b010_0100};
- 4'd3: seg <= {dot_disp,7'b011_0000};
- 4'd4: seg <= {dot_disp,7'b001_1001};
- 4'd5: seg <= {dot_disp,7'b001_0010};
- 4'd6: seg <= {dot_disp,7'b000_0010};
- 4'd7: seg <= {dot_disp,7'b111_1000};
- 4'd8: seg <= {dot_disp,7'b000_0000};
- 4'd9: seg <= {dot_disp,7'b001_0000};
- 4'd10: seg <= 8'b1011_1111;
- 4'd11: seg <= 8'b1111_1111;
- default: seg <= 8'b0000_0000;
- endcase
-
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- sel <= 6'b000_000;
- else
- sel <= sel_reg;
-
- bcd_8421 bcd_8421_inst
- (
- .sys_clk (sys_clk ),
- .sys_rst_n (sys_rst_n),
- .data (data ),
- .unit (unit ),
- .ten (ten ),
- .hun (hun ),
- .tho (tho ),
- .t_tho (t_tho ),
- .h_hun (h_hun )
- );
-
- endmodule
5.不使用595芯片版本,产生数码管扫描信号
- //数据与数码管段码映射关系
- always@(posedge sys_clk or negedge sys_rst_n)
- if(!sys_rst_n)
- begin
- seg_data <= 8'd0;
- end
- else
- begin
- case (seg_cache)
- 4'd0: seg_data <= {point,SEG_0};
- 4'd1: seg_data <= {point,SEG_1};
- 4'd2: seg_data <= {point,SEG_2};
- 4'd3: seg_data <= {point,SEG_3};
- 4'd4: seg_data <= {point,SEG_4};
- 4'd5: seg_data <= {point,SEG_5};
- 4'd6: seg_data <= {point,SEG_6};
- 4'd7: seg_data <= {point,SEG_7};
- 4'd8: seg_data <= {point,SEG_8};
- 4'd9: seg_data <= {point,SEG_9};
- default: seg_data <= 8'b1111_1111;
- endcase
- end
-
- //产生数码管位选扫描更新1ms刷新
- always@(posedge clk_1ms or negedge sys_rst_n)
- if(!sys_rst_n)
- cnt_dig <= 1'b0;
- else if(cnt_dig == 2'd3)
- cnt_dig <= 1'b0;
- else
- cnt_dig <= cnt_dig + 1'b1;
-
- //数码管段选位选数据扫描1ms刷新
- always@(posedge clk_1ms or negedge sys_rst_n)
- if(!sys_rst_n)
- begin
- seg_cache <= 8'b1111_1111;
- seg_dig <= 4'b0000;
- end
- else if(key_switch == 1'b0) //显示费用模式
- begin
- case (cnt_dig)
- 2'd0: begin
- seg_dig <= 4'b1110; //点亮第1位数码管
- seg_cache <= fare_g[3:0]; //费用的第1位
- point <= 1'b0; //小数点不亮
- end
- 2'd1: begin
- seg_dig <= 4'b1101; //点亮第2位数码管
- seg_cache <= fare_g[7:4]; //费用的第2位
- point <= 1'b1; //小数点亮
- end
- 2'd2: begin
- seg_dig <= 4'b1011; //点亮第3位数码管
- seg_cache <= fare_g[11:8]; //费用的第3位
- point <= 1'b0; //小数点不亮
- end
- 2'd3: begin
- seg_dig <= 4'b0111; //点亮第4位数码管
- seg_cache <= fare_g[15:12]; //费用的第4位
- point <= 1'b0; //小数点不亮
- end
- default:;
- endcase
- end
- else //显示里程模式
- begin
- case (cnt_dig)
- 2'd0: begin
- seg_dig <= 4'b1110; //同上
- seg_cache <= distance_g[3:0];
- end
- 2'd1: begin
- seg_dig <= 4'b1101;
- seg_cache <= distance_g[7:4];
- end
- 2'd2: begin
- seg_dig <= 4'b1011;
- seg_cache <= 4'd0;
- end
- 2'd3: begin
- seg_dig <= 4'b0111;
- seg_cache <= 4'd0;
- end
- default:;
- endcase
- end
60代表6元,12代表1.2元。由图可见:在里程从1km到3km递增时,费用固定60也即起步价6元;当里程4km,5km递增时,费用递增1.2元。符合设计要求。
Seg_data为数码管段选,seg_dig为数码管位选。由图可见:在费用fare为168,16.8元
位选1110,第1位数码管点亮,段码为00000000,显示数字8;
位选1101,第2位数码管点亮,段码为10000010,显示数字6;
位选1011,第3位数码管点亮,段码为01111001,显示数字1;
位选0111,第4位数码管点亮,段码为01000000,显示数字0。
其中第二位的小数点dp点亮,即代表第一位为小数,实际显示“016.8”,符合设计。
同上分析,在显示里程的时候,total_distance为35km,四位数码管依次显示“0035”。
出租车计费系统演示
通过本次项目实战,巩固了数码管动态显示知识,提高了代码编写思路,在编写逻辑算法时,没有年初刚开始学习FPGA时的那种无从下手。也遇到很多bug,仿真时没有关掉modelsim,仿真报错,原因要关掉才能重新仿真。数据更新与数码管显示的频率要有合理的区分度,不然数据已经变化了,数码管还没动态刷新一个周期。在例化模块的时候,该用wire线型还是reg寄存器型,需要考虑清楚对这个信号进行了时序逻辑操作还是组合逻辑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。