当前位置:   article > 正文

基于FPGA的蓝牙循迹小车项目_基于fpga的智能小车项目

基于fpga的智能小车项目

超声波模块

超声波测距简介

      超声波是由机械振动产生的, 可在不同介质中以不同的速度传播, 具有定向性好、能量集中、传输过程中衰减较小、反射能力较强等优点。超声波传感器可广泛应用于非接触式检测方法,它不受光线、被测物颜色等影响, 对恶劣的工作环境具有一定的适应能力, 因此在水文液位测量、车辆自动导航、物体识别等领域有着广泛的应用。

      超声波测距是通过不断检测超声波发射后遇到障碍物所反射的回波, 从而测出发射和接收回波的时间差Δt , 然后求出距离S 。在速度v 已知的情况下,距离S 的计算,公式如下:S = vΔt/ 2在空气中,常温下超声波的传播速度是334 /,但其传播速度V 易受空气中温度、湿度、压强等因素的影响,其中受温度的影响较大,如温度每升高1 ℃, 声速增加约0. 6 / 秒。因此在测距精度要求很高的情况下, 应通过温度补偿的方法对传播速度加以校正。已知现场环境温度T , 超声波传播速度V 的计算公式如下:

      V = 331. 5+0.607T

      这样, 只要测得超声波发射和接收回波的时间差Δt 以及现场环境温度T,就可以精确计算出发射点到障碍物之间的距离。

HC-SR04超声波测距模块简介

      HC-SR04超声波测距模块可提供2cm-400cm的非接触式距离感测功能,测距精度可达高到3mm;模块包括超声波发射器、接收器与控制电路。

HC-SR04超声波测距模块特点

      1、典型工作用电压:5V

      2、超小静态工作电流:小于5mA

      3、感应角度(R3 电阻越大,增益越高,探测角度越大)

            R3 电阻为392,不大于15

            R3 电阻为472, 不大于30

      4、探测距离(R3 电阻可调节增益,即调节探测距离)

            R3 电阻为392 2cm-450cm

            R3 电阻为472 2cm-700cm

      5、高精度:可达0.3cm

      6、盲区(2cm)超近

HC-SR04超声波测距模块管脚

      VCC5V)、 Trig(控制端)、 Echo(接收端)、地(GND

      使用方法:控制口发一个10US 以上的高电平,就可以在接收口等待高电平输出。一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离。如此不断的周期测,就可以达到你移动测量的值了。

HC-SR04超声波测距模块工作原理

      1、采用IO 触发测距,给至少10us 的高电平信号;

      2、模块自动发送8 40khz 的方波,自动检测是否有信号返回;

      3、有信号返回,通过IO 输出高电平,高电平持续时间就是超声波从发射到返回时间测试距离=(高电平时间*声速(340M/S))/2

模块时序图

  以上时序图表明只需要提供一个10US以上的脉冲触发信号,该模块内部将发出840kHz周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。距离=高电平时间*声速

代码编写

Verilog
`timescale 1ns / 1ps
module hc_sr(
    input               sys_clk,
    input               sys_rst_n,
    input               echo,
    output              trig,
    output  [15:0]      distance
);
    parameter  delay = 35000750;//15*50 + 70*50_000
一个超声波触发周期
    localparam S0 = 3'b001;
    localparam S1 = 3'b010;
    localparam S2 = 3'b100;
    reg [31:0]  cnt_70ms;   //超声波发射信号的周期计数器
    reg [2:0]   state;
    reg [2:0]   next_state;
    reg [31:0]  cnt_echo;   //回响信号寄存器
    reg [31:0]  cnt_echo_d0;//回响信号打拍
   
    //超声波发射信号的周期计数器赋值
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)begin
            cnt_70ms <= 32'b0;
        end
        else if(cnt_70ms == delay - 32'b1)
            cnt_70ms <= 32'b0;
        else
            cnt_70ms <= cnt_70ms + 32'b1;
    end
   
    //给trig赋值
    assign trig = (cnt_70ms < 15*50) ? 1'b1 : 1'b0;
   
    //状态转换
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            state <= S0;
        else
            state <= next_state;
    end
    //次态逻辑
    always@(*)begin
        if(!sys_rst_n)
            next_state = S0;
        else
            case(state)
                S0: if(echo)
                        next_state = S1;
                    else
                        next_state = S0;
                S1: if(~echo)
                        next_state <= S2;
                    else
                        next_state <= S1;
                S2: next_state <= S0;
                default: next_state <= S0;
            endcase
    end
    //逻辑输出,各个状态的动作
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)  begin
            cnt_echo <= 1'b0;
        end
        else
            case(state)
                S0: begin
                        cnt_echo <= 0;
                        cnt_echo_d0 <= cnt_echo_d0;
                    end
                S1: begin
                        cnt_echo <= cnt_echo + 1;
                        cnt_echo_d0 <= cnt_echo_d0;
                     end
                S2: begin
                        cnt_echo <= 0;
                        cnt_echo_d0 <= cnt_echo;
                    end
                default : begin
                        cnt_echo <= 0;
                        cnt_echo_d0 <= cnt_echo_d0;
                    end
            endcase
        end
    //将回响的值赋值给distance
    assign distance = (cnt_echo_d0*20)/1000/58;
   

endmodule

系统框图

数码管显示距离

Verilog
`timescale 1ns / 1ps
module seg(
    input                   sys_clk,
    input                   sys_rst_n,
    input     [15:0]        number,
    output  reg    [7:0]    seg,        //
段选
    output   reg    [3:0]   sel         //位选
    );
    parameter           CNT_US_MAX = 50;
    //数据处理,将每一位数字提取出来
    wire        [3:0]   ge;
    wire        [3:0]   shi;
    wire        [3:0]   bai;
    wire        [3:0]   qian;
    reg         [3:0]   data;
    assign  ge = number%10;
    assign  shi = number/10%10;
    assign  bai = number/100%10;
    assign  qian = number/1000%10;
    
    //状态机
    localparam  IDLE = 3'd0;
    localparam  GE = 3'd1;
    localparam  SHI = 3'd2;
    localparam  BAI = 3'd3;
    localparam  QIAN = 3'd4;
   
    //定义现态和次态
    reg [2:0]   state;
    reg [2:0]   next_state;
    reg [15:0]  cnt_us;
   
    //状态转换
    always @(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end
    //次态逻辑
    always @(*)begin
        case(state)
            IDLE:   next_state = GE;
            GE: if(cnt_us == CNT_US_MAX - 16'b1)
                    next_state = SHI;
                else
                    next_state = state;
            SHI:if(cnt_us == CNT_US_MAX - 16'b1)
                    next_state =BAI;
                else
                    next_state = state;
            BAI:if(cnt_us == CNT_US_MAX - 16'b1)
                    next_state = QIAN;
                else
                    next_state = state;
            QIAN:if(cnt_us == CNT_US_MAX - 16'b1)
                    next_state = IDLE;
                else
                    next_state = state;
            default:next_state = state;
        endcase
    end
    //给cnt_us赋值
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            cnt_us = 15'b0;
        else if(cnt_us == CNT_US_MAX - 6'b1)
            cnt_us = 15'b0;
        else
            cnt_us = cnt_us + 16'b1;
    end
    //逻辑输出,控制段选的数据索引和位选
    always@(*)begin
        case(state)
            IDLE: begin
                    data = 4'd0;
                    sel = 4'b0000;
                end
            GE : begin
                    data = ge;
                    sel = 4'b0001;
                end
            SHI : begin
                    data = shi;
                    sel = 4'b0010;
                end
            BAI : begin
                    data = bai;
                    sel = 4'b0100;
                end
            QIAN : begin
                    data = qian;
                    sel = 4'b1000;
                end
            default:begin
                    data = 4'd0;
                    sel = 4'b0000;
                end
        endcase
    end
    //给段选赋值
    always@(*)begin
        case (data)
            4'd0:seg =~8'h3f;
            4'd1:seg =~8'h06;
            4'd2:seg =~8'h5b;
            4'd3:seg =~8'h4f;
            4'd4:seg =~8'h66;
            4'd5:seg =~8'h6d;
            4'd6:seg =~8'h7d;
            4'd7:seg =~8'h07;
            4'd8:seg =~8'h7f;
            4'd9:seg =~8'h6f;
            default: seg = ~8'h3f;
        endcase
    end
endmodule

驱动模块

使用L298驱动芯片驱动四个直流电机。

像这样的连接方式,淘宝卖家一般都会有教程。

使能A和使能B默认用跳线帽连接5V,此时是不调速模式。拔掉跳线帽,即可使用PWM控制速度。

蓝牙模块

控制方法

蓝牙交互信号使用UART通信,通过手机连接蓝牙,发送控制信号给板子,来控制直流电机的工作方式。

UART通信

Verilog
module uart_rx(
    input               clk         ,  //
系统时钟
    input               rst_n       ,  //系统复位,低有效

    input               uart_rxd    ,  //UART接收端口
    output  reg         uart_rx_done,  //UART接收完成信号
    output  reg  [7:0]  uart_rx_data   //UART接收到的数据
    );

//parameter define
parameter CLK_FREQ = 50000000;               //系统时钟频率
parameter UART_BPS = 115200  ;               //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次

//reg define
reg          uart_rxd_d0;
reg          uart_rxd_d1;
reg          uart_rxd_d2;
reg          rx_flag    ;  //接收过程标志信号
reg  [3:0 ]  rx_cnt     ;  //接收数据计数器
reg  [15:0]  baud_cnt   ;  //波特率计数器
reg  [7:0 ]  rx_data_t  ;  //接收数据寄存器

//wire define
wire        start_en;

//*****************************************************
//**                    main code
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_en = uart_rxd_d2 & (~uart_rxd_d1) & (~rx_flag);

//针对异步信号的同步处理
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
        uart_rxd_d2 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd;
        uart_rxd_d1 <= uart_rxd_d0;
        uart_rxd_d2 <= uart_rxd_d1;
    end
end

//给接收标志赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rx_flag <= 1'b0;
    else if(start_en)    //检测到起始位
        rx_flag <= 1'b1; //接收过程中,标志信号rx_flag拉高
    //在停止位一半的时候,即接收过程结束,标志信号rx_flag拉低
    else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))
        rx_flag <= 1'b0;
    else
        rx_flag <= rx_flag;
end       

//波特率的计数器赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        baud_cnt <= 16'd0;
    else if(rx_flag) begin     //处于接收过程时,波特率计数器(baud_cnt)进行循环计数
        if(baud_cnt < BAUD_CNT_MAX - 1'b1)
            baud_cnt <= baud_cnt + 16'b1;
        else
            baud_cnt <= 16'd0; //计数达到一个波特率周期后清零
    end   
    else
        baud_cnt <= 16'd0;     //接收过程结束时计数器清零
end

//对接收数据计数器(rx_cnt)进行赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rx_cnt <= 4'd0;
    else if(rx_flag) begin                  //处于接收过程时rx_cnt才进行计数
        if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时
            rx_cnt <= rx_cnt + 1'b1;        //接收数据计数器加1
        else
            rx_cnt <= rx_cnt;
    end
    else
        rx_cnt <= 4'd0;                     //接收过程结束时计数器清零
end       

//根据rx_cnt来寄存rxd端口的数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rx_data_t <= 8'b0;
    else if(rx_flag) begin                           //系统处于接收过程时
        if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin  //判断baud_cnt是否计数到数据位的中间
           case(rx_cnt)
               4'd1 : rx_data_t[0] <= uart_rxd_d2;   //寄存数据的最低位
               4'd2 : rx_data_t[1] <= uart_rxd_d2;
               4'd3 : rx_data_t[2] <= uart_rxd_d2;
               4'd4 : rx_data_t[3] <= uart_rxd_d2;
               4'd5 : rx_data_t[4] <= uart_rxd_d2;
               4'd6 : rx_data_t[5] <= uart_rxd_d2;
               4'd7 : rx_data_t[6] <= uart_rxd_d2;
               4'd8 : rx_data_t[7] <= uart_rxd_d2;   //寄存数据的高低位
               default : ;
            endcase 
        end
        else
            rx_data_t <= rx_data_t;
    end
    else
        rx_data_t <= 8'b0;
end       

//给接收完成信号和接收到的数据赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= 8'b0;
    end
    //当接收数据计数器计数到停止位,且baud_cnt计数到停止位的中间时
    else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin
        uart_rx_done <= 1'b1     ;  //拉高接收完成信号
        uart_rx_data <= rx_data_t;  //并对UART接收到的数据进行赋值
    end   
    else begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= uart_rx_data;
    end
end
endmodule

循迹模块

使用两个红外传感器放在小车最前处的左右端,红外传感器使用DO口,根据两个DO口的信号驱动小车。

Verilog
module xunji(
    input                   sys_clk,
    input                   sys_rst_n,
    input       [1:0]       ddoo,
    output  reg [3:0]       car1        //car1
是循迹模式
);
    //循迹
    always @(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            car1 <= 4'b000;     
        else if(ddoo == 2'b00)
            car1 <= 4'b1010;    //前进
        else if(ddoo == 2'b10)
            car1 <= 4'b0110;    //左转
        else if(ddoo == 2'b01)
            car1 <= 4'b1001;    //右转
        else if(ddoo == 2'b11)
            car1 <= 4'b0000;    //停止
    end
endmodule

避障模块

Verilog
module  avoid(
    input               sys_clk,
    input               sys_rst_n,
    input   [15:0]      number,
    output  reg [3:0]   car_a
);

    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            car_a <= 4'b0000;
        else if(number < 25)
            car_a <= 4'b0110;
        else
            car_a <= 4'b1010;
    end
endmodule

顶层模块

Verilog
module little_car(
    input                   sys_clk,
    input                   sys_rst_n,
    input       [1:0]       ddoo,
    input                   echo,
    input                   uart_rxd,
    output                  trig,
    output      [7:0]       seg,
    output      [3:0]       sel,
    output  reg [3:0]       car
);
    parameter               CLK_FREQ = 50000000;   //
系统时钟频率
    parameter               UART_BPS = 9600  ;   //串口波特率
    parameter               CNT_US_MAX = 50;
    parameter  delay = 35000750;//15*50 + 70*50_000一个超声波触发周期
   
    wire    [15:0]          number;
    wire    [3:0]           car1;
    wire    [3:0]           car_a;
    wire    [7:0]           data;
    //控制小车
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            car <= 4'b0000;
        else if(data == 8'h02 )
            car <= 4'b1010; //前进
        else if(data == 8'h08)
            car <= 4'b0101; //后退
        else if(data == 8'h04)
            car <= 4'b0110; //左转
        else if(data == 8'h06)
            car <= 4'b1001; // 右转
        else if(data == 8'h05)
            car <= 4'b0000;
        else if(data == 8'h0f)
            car <= car1;    //循迹
        else if(data == 8'hff)
            car <= car_a;   //避障
        else
            car <= car;
    end
    //循迹例化
    xunji   u_xunji(
    .sys_clk                (sys_clk),
    .sys_rst_n              (sys_rst_n),
    .ddoo                   (ddoo),
    .car1                   (car1)
    );
   
    //超声波例化
    hc_sr #(
    .delay                  (delay)
    )u_hc_sr(
    .sys_clk                (sys_clk),
    .sys_rst_n              (sys_rst_n),
    .echo                   (echo),
    .trig                   (trig),
    .distance               (number)
    );
   
    //避障例化
    avoid u_avoid(
    .sys_clk                (sys_clk),
    .sys_rst_n              (sys_rst_n),
    .number                 (number),
    .car_a                  (car_a)
    );
   
    //数码管例化
    seg #(
    .CNT_US_MAX             (CNT_US_MAX)
    )u_seg(
    .sys_clk                (sys_clk  ),
    .sys_rst_n              (sys_rst_n),
    .number                 (number   ),
    .seg                    (seg      ),
    .sel                    (sel      )
    );
    //uart通信
    uart_rx #(
    .CLK_FREQ               (CLK_FREQ),
    .UART_BPS               (UART_BPS)
    )u_uart_rx(
    .clk                    (sys_clk         ),
    .rst_n                  (sys_rst_n       ),
    .uart_rxd               (uart_rxd    ),
    .uart_rx_done           (uart_rx_done),
    .uart_rx_data           (data)
    ); 
   
endmodule

仿真文件

模仿蓝UART通信给小车发送控制信号,看连接直流电机的信号是否符合预期。

Verilog
`timescale  1ns/1ns   //仿真的单位/仿真的精度
module tb_little_car();

//parameter define
parameter  CLK_PERIOD = 20;//时钟周期为20ns

//reg define
reg            sys_clk  ;  //时钟信号
reg            sys_rst_n;  //复位信号
reg            uart_rxd ;  //UART接收端口
reg [1:0]      ddoo;
reg             echo;
reg             uart_rxd;
        
//wire define
wire            trig;
wire    [3:0]   sel;
wire    [7:0]   seg;
wire    [3:0]   car;

initial begin
    sys_clk <= 1'b0;
    sys_rst_n <= 1'b0;
    uart_rxd <= 1'b1;
    ddoo <= 2'b00;
    echo <= 1'b0;
    #400
    sys_rst_n <= 1'b1;
    //发送8'h55  8'b000_0010   
    #2000
    uart_rxd <= 1'b0;   //起始位
    #104160
    uart_rxd <= 1'b0;   //D0
    #104160
    uart_rxd <= 1'b1;   //D1
    #104160
    uart_rxd <= 1'b0;   //D2
    #104160
    uart_rxd <= 1'b0;   //D3
    #104160
    uart_rxd <= 1'b0;   //D4
    #104160
    uart_rxd <= 1'b0;   //D5
    #104160
    uart_rxd <= 1'b0;   //D6
    #104160
    uart_rxd <= 1'b0;   //D7
    #104160
    uart_rxd <= 1'b1;   //停止位
    #104160
    uart_rxd <= 1'b1;   //空闲状态
   
    //发送8'h55  8'b000_1000   
    #2000
    uart_rxd <= 1'b0;   //起始位
    #104160
    uart_rxd <= 1'b0;   //D0
    #104160
    uart_rxd <= 1'b0;   //D1
    #104160
    uart_rxd <= 1'b0;   //D2
    #104160
    uart_rxd <= 1'b1;   //D3
    #104160
    uart_rxd <= 1'b0;   //D4
    #104160
    uart_rxd <= 1'b0;   //D5
    #104160
    uart_rxd <= 1'b0;   //D6
    #104160
    uart_rxd <= 1'b0;   //D7
    #104160
    uart_rxd <= 1'b1;   //停止位
    #104160
    uart_rxd <= 1'b1;   //空闲状态
   
    //发送8'h55  8'b000_1000   
    #2000
    uart_rxd <= 1'b0;   //起始位
    #104160
    uart_rxd <= 1'b0;   //D0
    #104160
    uart_rxd <= 1'b0;   //D1
    #104160
    uart_rxd <= 1'b0;   //D2
    #104160
    uart_rxd <= 1'b1;   //D3
    #104160
    uart_rxd <= 1'b0;   //D4
    #104160
    uart_rxd <= 1'b0;   //D5
    #104160
    uart_rxd <= 1'b0;   //D6
    #104160
    uart_rxd <= 1'b0;   //D7
    #104160
    uart_rxd <= 1'b1;   //停止位
    #104160
    uart_rxd <= 1'b1;   //空闲状态
   
    //发送8'h55  8'b000_0100   
    #2000
    uart_rxd <= 1'b0;   //起始位
    #104160
    uart_rxd <= 1'b0;   //D0
    #104160
    uart_rxd <= 1'b0;   //D1
    #104160
    uart_rxd <= 1'b1;   //D2
    #104160
    uart_rxd <= 1'b0;   //D3
    #104160
    uart_rxd <= 1'b0;   //D4
    #104160
    uart_rxd <= 1'b0;   //D5
    #104160
    uart_rxd <= 1'b0;   //D6
    #104160
    uart_rxd <= 1'b0;   //D7
    #104160
    uart_rxd <= 1'b1;   //停止位
    #104160
    uart_rxd <= 1'b1;   //空闲状态
end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;

//例化顶层模块
little_car  u_little_car(
    .sys_clk      (sys_clk  ),
    .sys_rst_n    (sys_rst_n),
    .uart_rxd     (uart_rxd ),
    .ddoo         (ddoo     ),
    .trig         (trig),
    .echo         (echo),
    .sel          (sel),
    .seg          (seg),
    .car          (car)
    );

endmodule

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/893163
推荐阅读
相关标签
  

闽ICP备14008679号