【安徽省机器人大赛单片机与嵌入式赛道竞赛】C平台作品
![](https://img-home.csdnimg.cn/images/20240711042549.png)
目录
二、赛题说明(详细题目已经收走,凭记忆复现)
1、设计目的:
(1)每公里价格(RATE):
(2)停车等待计时(TIME):
(3)行走里程(KM):
(4)行走耗费(FUEL):
(5)停车等待耗费(COST):
(6)到达目的地总耗费(Total):
2、设计要求:
三、作品实现详解
1、实现思路:
2、实现效果:
(1)IDLE状态
(2)START状态
(3)PARK状态
(4)STOP状态
3、实现过程
四、演示视频
五、经验与教训
六、源码自取
一、比赛简介
安徽省机器人大赛单片机与嵌入式赛道竞赛分为多个赛道,笔者参加C赛道使用A7系列FPGA进行比赛。比赛分为两个部分,上午8:00-12:00,参赛队员需要根据现场收到的赛题进行Coding,下午1:00左右评委老师验收打分。
由于是现场根据题目比赛(有点像考试),所以要拿到好成绩的话需要做好充足的准备,且因为行程紧张(大学生特种兵式比赛),保持良好的精神状态是充分发挥的重要前提。
二、赛题说明(详细题目已经收走,凭记忆复现)
1、设计目的:
实现一个出租车计价系统,显示行走里程(KM)、行走耗费(FUEL)、停车等待耗费(COST)、停车等待计时(TIME)、到达目的地总耗费(Total)、每公里价格(RATE)。
(1)每公里价格(RATE):
前两公里每公里2元,两公里以后每公里1元,不足的公里向上取整按照整数公里计算,在RATE中计算。
(2)停车等待计时(TIME):
没有详细说明,本题我们仅使用了分钟和秒两组数值,没有加入小时计时(考虑到一个红绿灯不可能等待一个多小时)。
(3)行走里程(KM):
小轮转动一圈表示一米。
(4)行走耗费(FUEL):
按照行驶里程的总费用。根据要求分析,公里数在[0,1)km为2,在[1,2)为4,大于等于2为(公里数+2).
(5)停车等待耗费(COST):
按照等待时间的总费用。根据要求,每等待20s COST加一。
(6)到达目的地总耗费(Total):
为行走耗费(FUEL)与停车等待耗费(COST)的和。
2、设计要求:
整个系统需要有行驶、临时停车(模拟红灯)、结束(到达目的地)三种状态,使用两个按键S1和S2控制整个系统。按下S1,系统进入行驶状态,直流电机驱动小轮旋转;按下S2,系统进入暂时停车状态,LED灯亮起表示红灯,直流电机停止驱动,计时器开始计时;按下S2,系统恢复行驶状态;按下S1,系统进入结束状态,表示到达目的地,直流电机停止驱动。
赛题要求使用LCD12864进行显示。在行驶状态显示里程数(KM)、行走耗费(FUEL)、每公里价格(RATE);在临时停车状态显示停车等待耗费(COST)、计时器计时时间;在结束状态显示总费用(Total)和总里程数(KM)。
三、作品实现详解
1、实现思路:
采用两段式状态机实现控制器,完成对直流电机、矩阵键盘、光电测速、LED、LCD12864外设的驱动,以及计时器的使能和复位。状态机状态转移图如下所示:
自顶向下,逐步求精。先完成直流电机、矩阵键盘、光电测速、LED、LCD12864、计时器各个子模块,以及控制器模块,再在顶层模块中实例化连接。
2、实现效果:
(1)IDLE状态
(2)START状态
(3)PARK状态
(4)STOP状态
3、实现过程
整个系统由以下六部分组成,共7个module,分别完成后在top中连接。
由于赛方提供实验平台例程,因此直流电机、矩阵键盘、光电测速、LED、LCD12864的驱动仅需要自行修改,不需要一行行输入(几千行代码不是闹着玩的,四小时肯定写不完)。其他的控制器和计数器则需要自己编写。
(1)clk_wiz_0:
将外部晶振20MHz时钟倍频为100MHz时钟。实例化IP即可。
(2)Speed_detect_module:
检测小轮的转数,小轮一圈为20个间隙,通过在固定时间间隔检测接收到的红外光上升沿数就可计算得出转速,同样可以修改为仅计转数。修改例程即可实现。
内部例化一个二进制计数器IP进行计数。
此外需要计数器控制采样时间,起始和结束标志位如下:
根据100MHz时钟测算,每3秒采样一次。
二进制计数器由start_flag使能,输出为Q。
在这里仅讲述我们对例程修改的部分。Pulse_cnt为3秒检测的小轮间隙红外光上升沿次数,由于一圈20个间隙,因此要得到累计的圈数,需要累加:
module speed_detect_module(
input clock,
input reset,
input pulse_from_motor,
output [19:0] speed_value,
output reg [15:0] turns_num
);
reg [32:0] cnt;
always @(posedge clock or posedge reset)
begin
if (reset)
cnt <= 0;
// else if(cnt == 33'd6000000000)
else if(cnt == 33'd300000000)
cnt <= 0;
else
cnt <= cnt + 1'b1;
end
wire start_flag;
wire end_flag;
assign start_flag = (cnt <= 33'd5);
//assign end_flag = (cnt == 33'd6000000000);
assign end_flag = (cnt == 33'd300000000);
reg [1:0] pulse_from_motor_sync;
always @(posedge clock or posedge reset) begin
if (reset)
pulse_from_motor_sync <= 2'b00;
else
pulse_from_motor_sync <= {pulse_from_motor_sync[0],pulse_from_motor};
end
wire pulse_from_motor_pos;
assign pulse_from_motor_pos = (pulse_from_motor_sync == 2'b01);//������
wire [23 : 0] Q;
c_counter_binary_0 c_counter_binary_0_0(
.CLK(clock), // input wire CLK
.CE(pulse_from_motor_pos), // input wire CE
.SCLR(start_flag), // input wire SCLR
.Q(Q) // output wire [23 : 0] Q
);
reg [23:0] pulse_cnt;
always @(posedge clock or posedge reset)
begin
if (reset)begin
pulse_cnt <= 0;
turns_num <= 0;
end
else if(end_flag)begin
pulse_cnt <= Q;
turns_num <= turns_num + pulse_cnt / 20;
end
else begin
turns_num <= turns_num;
pulse_cnt <= pulse_cnt;
end
end
//assign turns_num = turns_num + pulse_cnt / 20;
//assign turns_num = pulse_cnt / 20;
//assign turns_num = pulse_cnt[15:0];
//assign turns_num = turns_num + pulse_cnt[15:0];
assign speed_value [3:0] = turns_num % 10;
assign speed_value [7:4] = turns_num % 100 / 10;
assign speed_value [11:8] = turns_num % 1000 / 100;
assign speed_value [15:12] = turns_num % 10000 / 1000;
assign speed_value [19:16] = turns_num % 100000 / 10000;
endmodule
(3)Button4x4_drive:
矩阵键盘动态扫描与按键消抖。直接把例程拿过来用,由于clk_20ms_flag的使能,矩阵键盘模块自带了按键消抖功能,非常nice!
module button4x4_drive(
input clock,
input reset,
input [3:0] row, //��
output [3:0] col, //��
output [3:0] key_value,
output key_out_flag
);
reg [3:0] col;
reg [3:0] key_value;
reg [31:0] count;
wire clk_20ms_flag;
reg [2:0] state; //״̬��־
reg key_flag; //������־λ
reg key_out_flag;
reg [3:0] col_reg; //�Ĵ�ɨ����ֵ
reg [3:0] row_reg; //�Ĵ�ɨ����ֵ
always @(posedge clock or posedge reset)
begin
if(reset)
count <= 0;
else
count <= count + 1;
end
assign clk_20ms_flag = (count[20:0] == 21'd2000000);
always @(posedge clock or posedge reset)
begin
if(reset)
begin
col <= 4'b0000;
state <= 0;
end
else if(clk_20ms_flag)
case (state)
0:
begin
col[3:0] <= 4'b0000;
key_flag <= 1'b0;
if(row[3:0] != 4'b1111)
begin
state <= 1;
col[3:0] <= 4'b1110;
end //�м����£�ɨ���һ��
else
state <= 0;
end
1:
begin
if(row[3:0] != 4'b1111)
state <= 5;//�ж��Ƿ��ǵ�һ��
else
begin
state <= 2;
col[3:0] <= 4'b1101;
end //ɨ��ڶ���
end
2:
begin
if(row[3:0] != 4'b1111)
state <= 5;//�ж��Ƿ��ǵڶ���
else
begin
state <= 3;
col[3:0] <= 4'b1011;
end //ɨ�������
end
3:
begin
if(row[3:0] != 4'b1111)
state <= 5; //�ж��Ƿ��ǵ���һ��
else
begin
state <= 4;
col[3:0] <= 4'b0111;
end //ɨ�������
end
4:
begin
if(row[3:0] != 4'b1111)
state <= 5;//�ж��Ƿ��ǵ�һ��
else
state <= 0;
end
5:
begin
if(row[3:0] != 4'b1111)
begin
col_reg <= col; //����ɨ����ֵ
row_reg <= row; //����ɨ����ֵ
state <= 5;
key_flag <= 1'b1; //�����
end
else
state <= 0;
end
endcase
end
always @(clock or col_reg or row_reg)
begin
if(key_flag == 1'b1)
begin
key_out_flag <= 1;
case ({col_reg,row_reg})
8'b1110_1110:key_value <= 0;
8'b1110_1101:key_value <= 4;
8'b1110_1011:key_value <= 8;
8'b1110_0111:key_value <= 12;
8'b1101_1110:key_value <= 1;
8'b1101_1101:key_value <= 5;
8'b1101_1011:key_value <= 9;
8'b1101_0111:key_value <= 13;
8'b1011_1110:key_value <= 2;
8'b1011_1101:key_value <= 6;
8'b1011_1011:key_value <= 10;
8'b1011_0111:key_value <= 14;
8'b0111_1110:key_value <= 3;
8'b0111_1101:key_value <= 7;
8'b0111_1011:key_value <= 11;
8'b0111_0111:key_value <= 15;
endcase
end
else
key_out_flag <= 0;
end
endmodule
(4)Timer:
实现秒、分钟的计时,这个例程没有,需要自行编写,比较基础容易实现。
先设计一个分频模块产生1Hz时钟。
module clk_1Hz(
input clk,
output reg clk_1Hz
);
reg [26:0]cnt;
initial begin
clk_1Hz = 0;
cnt = 0;
end
always@(posedge clk)begin
if(cnt == 27'd99999999)begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
always@(posedge clk)begin
if(cnt < 27'd49999999)begin
clk_1Hz <= 1'b0;
end
else begin
clk_1Hz <= 1'b1;
end
end
endmodule
然后使用1Hz时钟进行计数器的控制完成秒表:
module Timer(
input clk,
input en,
input rst_n,
output clk_1Hz,
output reg[5:0]minute_cnt,
output reg[5:0]second_cnt
);
wire second_60_flag,minute_60_flag;
initial begin
minute_cnt = 0;
second_cnt = 0;
end
clk_1Hz clk_1Hz_0(
.clk(clk),
.clk_1Hz(clk_1Hz)
);
assign minute_60_flag = (minute_cnt == 6'd59)?1'b1:1'b0;
assign second_60_flag = (second_cnt == 6'd59)?1'b1:1'b0;
always@(posedge clk_1Hz)begin
if(!rst_n)begin
second_cnt <= 6'd0;
end
else if(en)begin
if(second_60_flag)begin
second_cnt <= 6'd0;
end
else begin
second_cnt <= second_cnt + 6'd1;
end
end
end
always@(posedge clk_1Hz)begin
if(!rst_n)begin
minute_cnt <= 6'd0;
end
else if(en)begin
if(minute_60_flag && second_60_flag)begin
minute_cnt <= 6'd0;
end
else if(second_60_flag)begin
minute_cnt <= minute_cnt + 6'd1;
end
else begin
minute_cnt <= minute_cnt;
end
end
end
endmodule
(5)LCD12864_drive:
实现LCD12864的SSD1306芯片的驱动。修改例程即可实现。由于例程中为静态显示,我们要实现动态显示的话需要接收外部数据。
module lcd12864_drive(
input clock,
input reset,
input [511:0] data,
output lcd12864_rs,
output lcd12864_rw,
output lcd12864_en,
output [7:0] lcd12864_data
);
wire [511:0] data_buf;
/**************************����lcd12864ʱ���ź�*************************/
reg clk_lcd12864;
reg [19:0]cnt;
always @(posedge clock or posedge reset)
begin
if (reset)
begin
cnt <= 20'b0;
clk_lcd12864 <= 0;
end
else if(cnt == 20'd20000) //ʱ��Ƶ�ʷdz���Ҫ��������3k����ʵ��5k���ڵ�0λ������
begin
cnt <= 20'd0;
clk_lcd12864 <= ~clk_lcd12864;
end
else
cnt <= cnt +1'b1;
end
reg [1:0] clk_lcd12864_sync;
always @(posedge clock or posedge reset)
begin
if (reset)
clk_lcd12864_sync <= 2'b00;
else
clk_lcd12864_sync <= {clk_lcd12864_sync[0],clk_lcd12864};
end
assign clk_lcd12864_pos = (clk_lcd12864_sync == 2'b01);
//****************************lcd12864�����ź�*****************************************/
reg [8:0] state; //State Machine code
parameter IDLE = 4'd0;
parameter CMD_WIDTH = 4'd1; //�������ݽӿ�����
parameter CMD_SET = 4'd2; //ѡ��ָ�
parameter CMD_CURSOR = 4'd3; //���ù��
parameter CMD_CLEAR = 4'd4; //����
parameter CMD_ACCESS = 4'd5; //���뷽ʽ���ã����ݶ�д�����
parameter CMD_DDRAM = 4'd6; //DDRAM�е�ַ
parameter DATA_WRITE = 4'd7; //�����
parameter STOP = 4'd8; //
reg lcd12864_rs_r;
reg [7:0] lcd12864_data_r;
reg [7:0] data_buff;
reg [5:0] cnt_time;
//����ܽ�����
assign lcd12864_rs = lcd12864_rs_r;
assign lcd12864_rw = 1'b0;
assign lcd12864_en = clk_lcd12864_sync[1]; //��lcd12864ʱ����ͬ
assign lcd12864_data = lcd12864_data_r;
always @(posedge clock or posedge reset)
begin
if(reset)
begin
lcd12864_rs_r <= 1'b0;
state <= IDLE;
// lcd12864_data_r <= 8'bzzzzzzzz; //����̬
lcd12864_data_r <= 8'b11111111; //����̬
cnt_time <= 6'd0;
end
else if(clk_lcd12864_pos)
begin
case(state)
IDLE:
begin
lcd12864_rs_r <= 1'b0;
cnt_time <= 6'd0;
state <= CMD_WIDTH;
// lcd12864_data_r <= 8'bzzzzzzzz;
lcd12864_data_r <= 8'b11111111;
end
CMD_WIDTH:
begin
lcd12864_rs_r <= 1'b0;
state <= CMD_SET;
lcd12864_data_r <= 8'h30; //8λ���ݿ�
end
CMD_SET:
begin
lcd12864_rs_r <= 1'b0;
state <= CMD_CURSOR;
lcd12864_data_r <= 8'h30; //����ָ�
end
CMD_CURSOR:
begin
lcd12864_rs_r <= 1'b0;
state <= CMD_CLEAR;
lcd12864_data_r <= 8'h0c; // �ع��
end
CMD_CLEAR:
begin
lcd12864_rs_r <= 1'b0;
state <= CMD_ACCESS;
lcd12864_data_r <= 8'h01; //����
end
CMD_ACCESS:
begin
lcd12864_rs_r <= 1'b0;
state <= CMD_DDRAM;
lcd12864_data_r <= 8'h06; //������趨
end
CMD_DDRAM: //��������
begin
lcd12864_rs_r <= 1'b0;
state <= DATA_WRITE;
case (cnt_time)
6'd0: lcd12864_data_r <= 8'h80;
6'd16: lcd12864_data_r <= 8'h90;
6'd32: lcd12864_data_r <= 8'h88;
6'd48: lcd12864_data_r <= 8'h98;
endcase
end
DATA_WRITE: //���
begin
lcd12864_rs_r <= 1'b1;
cnt_time <= cnt_time + 1'b1;
lcd12864_data_r <= data_buff;
case (cnt_time)
6'd15: state <= CMD_DDRAM;
6'd31: state <= CMD_DDRAM;
6'd47: state <= CMD_DDRAM;
6'd63: state <= STOP;
default: state <= DATA_WRITE;
endcase
end
STOP:
begin
lcd12864_rs_r <= 1'b0;
state <= CMD_DDRAM;
lcd12864_data_r <= 8'h80; //�ӵڼ���ѭ��
cnt_time <= 6'd0;
end
default:
state <= IDLE;
endcase
end
end
always @(cnt_time)
begin
case (cnt_time)
6'd0:data_buff <= data[7:0];
6'd1:data_buff <= data[15:8];
6'd2:data_buff <= data[23:16];
6'd3:data_buff <= data[31:24];
6'd4:data_buff <= data[39:32];
6'd5:data_buff <= data[47:40];
6'd6:data_buff <= data[55:48];
6'd7:data_buff <= data[63:56];
6'd8:data_buff <= data[71:64];
6'd9:data_buff <= data[79:72];
6'd10:data_buff <= data[87:80];
6'd11:data_buff <= data[95:88];
6'd12:data_buff <= data[103:96];
6'd13:data_buff <= data[111:104];
6'd14:data_buff <= data[119:112];
6'd15:data_buff <= data[127:120];
6'd16:data_buff <= data[135:128];
6'd17:data_buff <= data[143:136];
6'd18:data_buff <= data[151:144];
6'd19:data_buff <= data[159:152];
6'd20:data_buff <= data[167:160];
6'd21:data_buff <= data[175:168];
6'd22:data_buff <= data[183:176];
6'd23:data_buff <= data[191:184];
6'd24:data_buff <= data[199:192];
6'd25:data_buff <= data[207:200];
6'd26:data_buff <= data[215:208];
6'd27:data_buff <= data[223:216];
6'd28:data_buff <= data[231:224];
6'd29:data_buff <= data[239:232];
6'd30:data_buff <= data[247:240];
6'd31:data_buff <= data[255:248];
6'd32:data_buff <= data[263:256];
6'd33:data_buff <= data[271:264];
6'd34:data_buff <= data[279:272];
6'd35:data_buff <= data[287:280];
6'd36:data_buff <= data[295:288];
6'd37:data_buff <= data[303:296];
6'd38:data_buff <= data[311:304];
6'd39:data_buff <= data[319:312];
6'd40:data_buff <= data[327:320];
6'd41:data_buff <= data[335:328];
6'd42:data_buff <= data[343:336];
6'd43:data_buff <= data[351:344];
6'd44:data_buff <= data[359:352];
6'd45:data_buff <= data[367:360];
6'd46:data_buff <= data[375:368];
6'd47:data_buff <= data[383:376];
6'd48:data_buff <= data[391:384];
6'd49:data_buff <= data[399:392];
6'd50:data_buff <= data[407:400];
6'd51:data_buff <= data[415:408];
6'd52:data_buff <= data[423:416];
6'd53:data_buff <= data[431:424];
6'd54:data_buff <= data[439:432];
6'd55:data_buff <= data[447:440];
6'd56:data_buff <= data[455:448];
6'd57:data_buff <= data[463:456];
6'd58:data_buff <= data[471:464];
6'd59:data_buff <= data[479:472];
6'd60:data_buff <= data[487:480];
6'd61:data_buff <= data[495:488];
6'd62:data_buff <= data[503:496];
6'd63:data_buff <= data[511:504];
default : data_buff <= 8'h20;
endcase
end
endmodule
(6)LCD_Decoder:
对来自时钟的秒、分钟计时、来自光电测速的圈数、以及要显示的FUEL、COST等数值进行数据的转换以用于LCD显示。由于例程中为静态显示,我们要实现动态显示的话需要实时向LCD12864_drive发送需要刷新的数据。
使用function来实现二进制数到LCD字库编码的转换,精简代码。
此外,我要实现界面切换的效果,需要根据控制器的state进行选择输出结果。
module LCD_Decoder(
input clk,
input reset,
input [1:0]state,
input [19:0]speed_value,
input [15:0]turns_num,
input [5:0]minute_cnt,
input [5:0]second_cnt,
output reg [511:0]data
);
localparam n0 = 8'h30;
localparam n1 = 8'h31;
localparam n2 = 8'h32;
localparam n3 = 8'h33;
localparam n4 = 8'h34;
localparam n5 = 8'h35;
localparam n6 = 8'h36;
localparam n7 = 8'h37;
localparam n8 = 8'h38;
localparam n9 = 8'h39;
localparam nn = 8'h0f;
//״̬��������
parameter IDLE = 2'b00;
parameter START= 2'b01;
parameter PARK = 2'b11;
parameter STOP = 2'b10;
reg [7:0]speed_value_0,speed_value_1,speed_value_2,speed_value_3,speed_value_4;
reg [7:0]fuel_value_0,fuel_value_1,fuel_value_2,fuel_value_3;
reg [7:0]Total_cost_BCD_0,Total_cost_BCD_1,Total_cost_BCD_2,Total_cost_BCD_3;
reg [7:0]cost_1,cost_0;
reg [7:0]minute_1,minute_0;
reg [7:0]second_1,second_0;
reg [15:0]fuel;//,KM
reg [7:0]rate;
reg [3:0]cost;
wire [15:0]fuel_BCD;
wire [15:0]Total_cost_BCD;
wire s_20_flag;
reg [4:0]s_20_cnt;
reg [15:0]Total_cost;
reg [26:0]clk_div_cnt;
reg clk_1Hz;
initial begin
speed_value_0 = 0;
speed_value_1 = 0;
speed_value_2 = 0;
speed_value_3 = 0;
speed_value_4 = 0;
fuel_value_0 = 0;
fuel_value_1 = 0;
fuel_value_2 = 0;
fuel_value_3 = 0;
cost_1 = 0;
cost_0 = 0;
// KM = 0;
fuel = 0;
rate = n1;
cost = 1;
s_20_cnt = 0;
Total_cost = 0;
clk_1Hz = 0;
clk_div_cnt = 0;
end
//ʼ�շ�Ƶ
wire div_1Hz_flag;
assign div_1Hz_flag = clk_div_cnt == 27'd99999999;
always@(posedge clk)begin
if(div_1Hz_flag)begin
clk_div_cnt <= 0;
end
else begin
clk_div_cnt <= clk_div_cnt + 1;
end
end
always@(posedge clk)begin
if(clk_div_cnt < 27'd49999999)begin
clk_1Hz <= 1'b0;
end
else begin
clk_1Hz <= 1'b1;
end
end
//ʼ�շ�Ƶ
always@(posedge clk)begin
if(reset)begin
Total_cost <= 16'd0;
end
else begin
Total_cost <= fuel+{12'd0,cost};
end
end
assign s_20_flag = (s_20_cnt == 5'd19)?1'b1:1'b0;
always@(posedge clk)begin
if(reset)begin
s_20_cnt <= 5'd0;
end
else begin
if(state != PARK)begin
s_20_cnt <= 5'd0;
end
else if(s_20_flag && div_1Hz_flag)begin
s_20_cnt <= 5'd0;
end
else if(div_1Hz_flag)begin
s_20_cnt <= s_20_cnt + 5'd1;
end
else begin
s_20_cnt <= s_20_cnt;
end
end
end
always@(posedge clk)begin
if(reset)begin
cost <= 4'd1;
end
else begin
if(state == IDLE)begin
cost <= 4'd1;
end
else if(div_1Hz_flag && s_20_flag)begin
cost <= cost + 4'd1;
end
else begin
cost <= cost;
end
end
end
assign fuel_BCD [3:0] = fuel % 10;
assign fuel_BCD [7:4] = fuel % 100 / 10;
assign fuel_BCD [11:8] = fuel % 1000 / 100;
assign fuel_BCD [15:12] = fuel % 10000 / 1000;
assign Total_cost_BCD [3:0] = Total_cost % 10;
assign Total_cost_BCD [7:4] = Total_cost % 100 / 10;
assign Total_cost_BCD [11:8] = Total_cost % 1000 / 100;
assign Total_cost_BCD [15:12] = Total_cost % 10000 / 1000;
//����ת�� ����
function [7:0]binary2code;
input [3:0]binary;
begin
case(binary)
4'd0:binary2code = n0;
4'd1:binary2code = n1;
4'd2:binary2code = n2;
4'd3:binary2code = n3;
4'd4:binary2code = n4;
4'd5:binary2code = n5;
4'd6:binary2code = n6;
4'd7:binary2code = n7;
4'd8:binary2code = n8;
4'd9:binary2code = n9;
default:binary2code = nn;
endcase
end
endfunction
//����ת�� ����
function [15:0]time2code;
input [5:0]binary;
begin
case(binary)
6'd0:time2code = {n0,n0};
6'd1:time2code ={ n0,n1};
6'd2:time2code = {n0,n2};
6'd3:time2code ={ n0,n3};
6'd4:time2code ={ n0,n4};
6'd5:time2code ={ n0,n5};
6'd6:time2code = {n0,n6};
6'd7:time2code = {n0,n7};
6'd8:time2code = {n0,n8};
6'd9:time2code = {n0,n9};
6'd10:time2code ={ n1,n0};
6'd11:time2code ={ n1,n1};
6'd12:time2code ={ n1,n2};
6'd13:time2code ={ n1,n3};
6'd14:time2code = {n1,n4};
6'd15:time2code = {n1,n5};
6'd16:time2code = {n1,n6};
6'd17:time2code = {n1,n7};
6'd18:time2code = {n1,n8};
6'd19:time2code = {n1,n9};
6'd20:time2code = {n2,n0};
6'd21:time2code = {n2,n1};
6'd22:time2code = {n2,n2};
6'd23:time2code = {n2,n3};
6'd24:time2code = {n2,n4};
6'd25:time2code = {n2,n5};
6'd26:time2code = {n2,n6};
6'd27:time2code = {n2,n7};
6'd28:time2code = {n2,n8};
6'd29:time2code = {n2,n9};
6'd30:time2code = {n3,n0};
6'd31:time2code = {n3,n1};
6'd32:time2code = {n3,n2};
6'd33:time2code = {n3,n3};
6'd34:time2code = {n3,n4};
6'd35:time2code = {n3,n5};
6'd36:time2code = {n3,n6};
6'd37:time2code = {n3,n7};
6'd38:time2code = {n3,n8};
6'd39:time2code = {n3,n9};
6'd40:time2code = {n4,n0};
6'd41:time2code = {n4,n1};
6'd42:time2code = {n4,n2};
6'd43:time2code = {n4,n3};
6'd44:time2code = {n4,n4};
6'd45:time2code = {n4,n5};
6'd46:time2code = {n4,n6};
6'd47:time2code = {n4,n7};
6'd48:time2code = {n4,n8};
6'd49:time2code = {n4,n9};
6'd50:time2code = {n5,n0};
6'd51:time2code = {n5,n1};
6'd52:time2code = {n5,n2};
6'd53:time2code = {n5,n3};
6'd54:time2code = {n5,n4};
6'd55:time2code = {n5,n5};
6'd56:time2code = {n5,n6};
6'd57:time2code = {n5,n7};
6'd58:time2code = {n5,n8};
6'd59:time2code = {n5,n9};
default:time2code = {nn,nn};
endcase
end
endfunction
always @(posedge clk) begin
speed_value_4 <= binary2code(speed_value[19:16]);
speed_value_3 <= binary2code(speed_value[15:12]);
speed_value_2 <= binary2code(speed_value[11:8]);
speed_value_1 <= binary2code(speed_value[7:4]);
speed_value_0 <= binary2code(speed_value[3:0]);
fuel_value_3 <= binary2code(fuel_BCD[15:12]);
fuel_value_2 <= binary2code(fuel_BCD[11:8]);
fuel_value_1 <= binary2code(fuel_BCD[7:4]);
fuel_value_0 <= binary2code(fuel_BCD[3:0]);
Total_cost_BCD_3 <= binary2code(Total_cost_BCD[15:12]);
Total_cost_BCD_2 <= binary2code(Total_cost_BCD[11:8]);
Total_cost_BCD_1 <= binary2code(Total_cost_BCD[7:4]);
Total_cost_BCD_0 <= binary2code(Total_cost_BCD[3:0]);
{minute_1,minute_0} <= time2code(minute_cnt);
{second_1,second_0} <= time2code(second_cnt);
end
always@(posedge clk)begin
case(cost)
4'd0:{cost_1,cost_0} = {n0,n0};
4'd1:{cost_1,cost_0} = {n0,n1};
4'd2:{cost_1,cost_0} = {n0,n2};
4'd3:{cost_1,cost_0} = {n0,n3};
4'd4:{cost_1,cost_0} = {n0,n4};
4'd5:{cost_1,cost_0} = {n0,n5};
4'd6:{cost_1,cost_0} = {n0,n6};
4'd7:{cost_1,cost_0} = {n0,n7};
4'd8:{cost_1,cost_0} = {n0,n8};
4'd9:{cost_1,cost_0} = {n0,n9};
4'd10:{cost_1,cost_0} = {n1,n0};
4'd11:{cost_1,cost_0} = {n1,n1};
4'd12:{cost_1,cost_0} = {n1,n2};
4'd13:{cost_1,cost_0} = {n1,n3};
4'd14:{cost_1,cost_0} = {n1,n4};
4'd15:{cost_1,cost_0} = {n1,n5};
default:{cost_1,cost_0} ={nn, nn};
endcase
end
always@(posedge clk)begin
// KM <= turns_num/1000;
if(reset)begin
fuel <= 32'd2;
rate <= n2;
end
else begin
if(!(|speed_value [15:12]))begin
fuel <= 32'd2;
rate <= n2;
end
else if((speed_value [15:12]==1))begin//||(speed_value [15:12] ==2)
fuel <= 32'd4;
rate <= n2;
end
else begin
fuel <= speed_value [15:12] + 2;
rate <= n1;
end
end
end
always @(*) begin
if(state == START)begin
data = {
"K","S","Z",8'h26,"Y","W","Z",8'h20,"y","b",8'h20,"e","d","a","M",8'h20,
8'h20,8'h20,8'h20,8'h20,8'h20,"m","k",speed_value_0,speed_value_1,speed_value_2,".",speed_value_3,speed_value_4,8'h3a,"M","K",
rate,8'h3a,"E","T","A","R",8'h20,fuel_value_0,fuel_value_1,fuel_value_2,fuel_value_3,8'h3a,"L","E","U","F",
8'h20,8'h20,8'h20,8'h20
,8'h20,"T","R","A","T","S",8'h3a,"E","T","A","T","S"
};
end
else if(state == PARK)begin
data = {
"K","S","Z",8'h26,"Y","W","Z",8'h20,"y","b",8'h20,"e","d","a","M",8'h20,
8'h20,8'h20,8'h20,8'h20,8'h20,8'h20,second_0,second_1,":",minute_0,minute_1,8'h3a,"E","M","I","T",
rate,8'h3a,"E","T","A","R",8'h20,8'h20,8'h20,cost_0,cost_1,8'h3a,"T","S","O","C",
8'h20,8'h20,8'h20,8'h20,8'h20,8'h20,"K","R","A","P",8'h3a,"E","T","A","T","S"
};
end
else if(state == STOP)begin
data = {
"K","S","Z",8'h26,"Y","W","Z",8'h20,"y","b",8'h20,"e","d","a","M",8'h20,
8'h20,8'h20,8'h20,8'h20,8'h20,"m","k",speed_value_0,speed_value_1,speed_value_2,".",speed_value_3,speed_value_4,8'h3a,"M","K",
8'h20,8'h20,8'h20,8'h20,8'h20,8'h20,Total_cost_BCD_0,Total_cost_BCD_1,Total_cost_BCD_2,Total_cost_BCD_3,8'h3a,"l","a","t","o","T",
8'h20,8'h20,8'h20,8'h20,8'h20,8'h20,"P","O","T","S",8'h3a,"E","T","A","T","S"
};
end
else begin//IDLE
data = {
"K","S","Z",8'h26,"Y","W","Z",8'h20,"y","b",8'h20,"e","d","a","M",8'h20,
8'h20,8'h20,8'h20,8'h20,8'h20,"m","k",speed_value_0,speed_value_1,speed_value_2,".",speed_value_3,speed_value_4,8'h3a,"M","K",
rate,8'h3a,"T","T","A","R",8'h20,fuel_value_0,fuel_value_1,fuel_value_2,fuel_value_3,8'h3a,"L","E","U","F",
8'h20,8'h20,8'h20,8'h20,8'h20,8'h20,"E","L","D","I",8'h3a,"E","T","A","T","S"
};
end
end
endmodule
特别声明,由于比赛时间有限,我这里使用了%和/等算数级描述来进行运算,会综合出及其复杂且性能低下的运算逻辑,这可能造成时序违例和面积的浪费,因此大家千万不要在流片的项目中使用!!!仅仅依赖综合器来对电路进行优化可不是一个合格的数字电路设计者。
(7)CTRLOR:
由于使用到了按键复用,因此需要进行上升沿检测,否则仅使用键值进行条件转移的话会在两个状态反复跳转。
控制器是整个电路的灵魂所在,有了它就能控制各个模块的运转。根据我们之前的状态转移图进行编写。
module CTRLOR(
input clk,
input reset,
input [3:0]key_value,
input key_out_flag,
output reg [1:0]cstate,
output reg Timer_en,
output reg Timer_rst_n,
output reg LED_en,
output reg motor_en
);
//״̬��������
parameter IDLE = 2'b00;
parameter START= 2'b01;
parameter PARK = 2'b11;
parameter STOP = 2'b10;
//�����ڲ��ź�
wire s1_flag,s2_flag,s3_flag;
wire key_out_flag_pos;
reg key_out_flag_reg;
always@(posedge clk)begin
if(reset)begin
key_out_flag_reg <= 1'b0;
end
else begin
key_out_flag_reg <= key_out_flag;
end
end
assign key_out_flag_pos = (!key_out_flag_reg && key_out_flag);
assign s1_flag = (key_out_flag_pos && (key_value == 4'd1))?1'b1:1'b0;
assign s2_flag = (key_out_flag_pos && (key_value == 4'd2))?1'b1:1'b0;
assign s3_flag = (key_out_flag_pos && (key_value == 4'd3))?1'b1:1'b0;
//״̬�Ĵ���
reg [1:0]nstate;
initial begin
cstate = IDLE;
nstate = IDLE;
Timer_en = 0;
Timer_rst_n = 0;
LED_en = 1;
motor_en = 0;
end
always@(posedge clk)begin
if(reset)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
always@(*)begin
case(cstate)
IDLE:begin
if(s1_flag)nstate = START;
else nstate = IDLE;
end
START:begin
if(s2_flag)nstate = PARK;
else if(s1_flag)nstate = STOP;
else nstate = START;
end
PARK:begin
if(s2_flag)nstate = START;
else nstate = PARK;
end
STOP:begin
if(s3_flag)nstate = IDLE;
else nstate = STOP;
end
default:nstate = IDLE;
endcase
end
always@(*)begin
case(cstate)
IDLE:begin
Timer_en = 0;
Timer_rst_n = 0;
LED_en = 1;
motor_en = 0;
end
START:begin
Timer_en = 0;
Timer_rst_n = 0;
LED_en = 1;
motor_en = 1;//
end
PARK:begin
Timer_en = 1;
Timer_rst_n = 1;
LED_en = 0;//
motor_en = 0;//
end
STOP:begin
Timer_en = 0;
Timer_rst_n = 1;
LED_en = 1;
motor_en = 0;
end
default:begin
Timer_en = 0;
Timer_rst_n = 0;
LED_en = 1;
motor_en = 0;
end
endcase
end
endmodule
(8)top:
完成模块的实例连接。
module top(
//ϵͳ�ź�
input sysclkin,
input button,
//��������ź�
input [3:0] row, //��
output [3:0] col, //��
//LCD�ź�
output lcd12864_rs,
output lcd12864_rw,
output lcd12864_en,
output [7:0] lcd12864_data,
//�������ź�
input pulse_from_motor,
//ֱ����������ź�
output motor_en,
//LED�ź�
output LED_en
);
//�����ź���
wire clk_100M,clk_100M_180;
wire [3:0] key_value;
wire key_out_flag;
wire [511:0] data;
wire [5:0]minute_cnt;
wire [5:0]second_cnt;
wire clk_1Hz,Timer_en,Timer_rst_n;
wire [19:0] speed_value;
wire [15:0] turns_num;
wire [1:0]cstate;
clk_wiz_0 clk_wiz_0_0(
// Clock out ports
.clk_out1(clk_100M), // output clk_out1
.clk_out2(clk_100M_180), // output clk_out2
// Clock in ports
.clk_in1(sysclkin)
);
speed_detect_module speed_detect_module_0(
.clock(clk_100M),
.reset(!button),
.pulse_from_motor(pulse_from_motor),
.speed_value(speed_value),
.turns_num(turns_num)
);
button4x4_drive button4x4_drive_0(
.clock(clk_100M),
.reset(!button),
.row(row), //��
.col(col), //��
.key_value(key_value),
.key_out_flag(key_out_flag)
);
CTRLOR CTRLOR_0(
.clk(clk_100M),
.reset(!button),
.key_value(key_value),
.key_out_flag(key_out_flag),
.cstate(cstate),
.Timer_en(Timer_en),
.Timer_rst_n(Timer_rst_n),
.LED_en(LED_en),
.motor_en(motor_en)
);
Timer Timer_0(
.clk(clk_100M),
.en(Timer_en),
.rst_n(Timer_rst_n),
.clk_1Hz(clk_1Hz),
.minute_cnt(minute_cnt),
.second_cnt(second_cnt)
);
LCD_Decoder LCD_Decoder_0(
.clk(clk_100M),
.reset(!button),
.state(cstate),
.speed_value(speed_value),
.turns_num(turns_num),
.minute_cnt(minute_cnt),
.second_cnt(second_cnt),
.data(data)
);
lcd12864_drive lcd12864_drive_0(
.clock(clk_100M),
.reset(!button),
.data(data),
.lcd12864_rs(lcd12864_rs),
.lcd12864_rw(lcd12864_rw),
.lcd12864_en(lcd12864_en),
.lcd12864_data(lcd12864_data)
);
endmodule
四、演示视频
【2023安徽省机器人大赛】单片机与嵌入式竞赛C赛道作品_哔哩哔哩_bilibili评审95分,冲击省一等奖,正在等待评审结果源码:https://github.com/lionelZhaowy/Taxi_pricing, 视频播放量 2、弹幕量 0、点赞数 0、投硬币枚数 0、收藏人数 0、转发人数 0, 视频作者 lionel赵, 作者简介 今天也要快乐呀,相关视频:【 竞 赛 党 喜 欢 的 男 孩 子 / 女 孩 子 】,如果我在歌唱大赛掏出《雪distance》你该怎么应对,买单片机吗?,进大疆要什么学历?,【M J】多有趣送我上首页好吗?官方要求15秒视频舞蹈大赛!!!!,学妹拙作作为“粉丝作品”入选STM32峰会展出,铺胶赛道有多黏?为何国内一条没有……,这机器不太对劲,纽北赛道为什么考验车?,【选美比赛】清华大学理工科女生获世界小姐选美大赛中国区冠军https://www.bilibili.com/video/BV15c411A7et/?spm_id_from=333.999.0.0
五、经验与教训
- 平台例程源码要吃透。实验平台会给出相应的资料方便大家备赛,每个外设的驱动原理与模块的编写要做到心中有数,不能仅仅会使用,否则就会出现我们今年光电测速模块原理没有搞清楚,在赛场上现分析造成时间的浪费,导致最终技术文档没有写完而扣掉了5分。
- 技术文档要写好。前面说到我们所有功能都实现了,但是时间不够没有写完技术文档没能满分。可见技术文档的重要性,编写技术文档需要标明各个子模块的功能、数据的流向等,不能只有图片,文字描述非常重要!!!因为评委老师就是因为我们只有图片而没有文字而扣分的。
- 打字速度要快,推荐自带自己熟悉的鼠标和键盘。比赛节奏很快,要高效coding,但是要记得不要忘记带回来,笔者因为着急去打篮球,下午评审完成后匆匆收拾了一下把无线键鼠套装的USB接头落在了比赛的机房,回学校才发现,目前正在焦急的等待比赛场地那边老师的回应(QAQ)。
六、源码自取
https://github.com/lionelZhaowy/Taxi_pricinghttps://github.com/lionelZhaowy/Taxi_pricing
2401_83602130: 大佬,这个比赛结果在哪公示呀,我好像没找到官网
2401_84891530: 大佬有下载链接吗