安徽省机器人大赛C平台作品竞赛——单片机与嵌入式赛道

目录

一、比赛简介

二、赛题说明(详细题目已经收走,凭记忆复现)

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

五、经验与教训

  1. 平台例程源码要吃透。实验平台会给出相应的资料方便大家备赛,每个外设的驱动原理与模块的编写要做到心中有数,不能仅仅会使用,否则就会出现我们今年光电测速模块原理没有搞清楚,在赛场上现分析造成时间的浪费,导致最终技术文档没有写完而扣掉了5分。
  2. 技术文档要写好。前面说到我们所有功能都实现了,但是时间不够没有写完技术文档没能满分。可见技术文档的重要性,编写技术文档需要标明各个子模块的功能、数据的流向等,不能只有图片,文字描述非常重要!!!因为评委老师就是因为我们只有图片而没有文字而扣分的。
  3. 打字速度要快,推荐自带自己熟悉的鼠标和键盘。比赛节奏很快,要高效coding,但是要记得不要忘记带回来,笔者因为着急去打篮球,下午评审完成后匆匆收拾了一下把无线键鼠套装的USB接头落在了比赛的机房,回学校才发现,目前正在焦急的等待比赛场地那边老师的回应(QAQ)。

六、源码自取

https://github.com/lionelZhaowy/Taxi_pricinghttps://github.com/lionelZhaowy/Taxi_pricing

作者:LionelZhao

物联沃分享整理
物联沃-IOTWORD物联网 » 安徽省机器人大赛C平台作品竞赛——单片机与嵌入式赛道

发表评论