基于FPGA的手柄遥控排爆机器人

本文介绍了一款基于FPGA的遥控排爆机器人设计方案,利用PS2游戏手柄进行远程控制,通过Verilog代码实现了SPI协议解析及PWM信号输出,控制履带底盘和6自由度机械臂的动作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FPGA开发之 游戏手柄遥控排爆机器人

  • 使用对象:PS2游戏手柄,L298N电机驱动模块,履带底盘,6自由度机械臂
  • 使用环境:ISE14.7和BASYS2开发板

1.排爆机器人展示

这里写图片描述

这里写图片描述
整体原理简介
该排爆机器人硬件部分为一个履带底盘和一个6自由度的机械臂,通过PS2游戏手柄遥控,fpga开发板接收遥控信号并控制机器人的运动,输出6路PWM波控制6自由度机械臂,还有2路pwm波控制底盘电机。

2.Verilog代码实现

所用PS2游戏手柄的SPI协议解析和输出PWM波的原理可以参考代码注释和我的前两篇博客

`timescale 1ns / 1ps
module spi_control_pwm(
    input clk,              //板载50Mhz时钟信号
    input reset,的
    output [7:0] ledout,       //板上八个LED灯亮,指示正常工作状态以及第四个接收到byte
    output spi_clk,         //SPI通讯时钟线
    output spi_cs,          //SPI通讯片选信号线
    output spi_mosi,        //SPI通讯写端口
    input spi_miso,         //SPI通讯读端口
    inout [7:0] pwm_out,    //八路PWM输出
    output [3:0] motor_io     //控制电机正反转
    );
reg [1:0] moto1=0;    //控制电机正反转,原理参考L298N电机驱动模块原理图
reg [1:0] moto2=0;
reg [7:0] data_out; 
reg [7:0] data_in1;    //接收到的数据,八位的字节
reg [7:0] data_in2;
reg [7:0] data_in3;
reg [7:0] data_in4;
reg [7:0] data_in5;
reg [7:0] data_in6;
reg sclk=1;          //SPI通讯时钟线  
reg smosi=1;           //SPI通讯写
reg smiso=0;           //SPI通讯读
reg scs;                //SPI通讯片选信号
reg [9:0] cnt_clk_6us=0;
reg clk_6us=1;
reg [15:0] cnt_1020us=0;
reg clk_1020us=0;
reg [7:0] led_set=0;
reg trig=1;
reg [3:0] count_for_trig=0;
reg [7:0] count_trig=0;
reg [3:0] motor_set=0;
reg [7:0] pwm_set=0;
reg [19:0] count_for_pwmclk=0;
reg pwm_clk=0;
always @(posedge clk)
begin
        if(cnt_clk_6us == 10'b00_1001_0110-1) begin     //产生6us信号
            cnt_clk_6us <= 0;
            clk_6us <= ~clk_6us;   //按位取反
        end
        else
            cnt_clk_6us <= cnt_clk_6us + 1;


            if(cnt_1020us == 16'b0110_0011_1001_1100-1) begin    //周期1020us
            cnt_1020us <= 0;
            clk_1020us <= ~clk_1020us;  
        end
        else
            cnt_1020us <= cnt_1020us + 1;

            if(count_for_pwmclk == 20'b0000_0000_0000_1111_1010-1) begin    // 0.01ms触发一次,故pwm波形精度为0.01ms
            count_for_pwmclk <= 0;
            pwm_clk <= ~pwm_clk;   //按位取反
        end
        else
            count_for_pwmclk <= count_for_pwmclk + 1;
end


reg [11:0] count_pwm=0;
reg [11:0] pwm_compare1=12'b0000_1001_0110;  //初值定在150,即1.5ms,是舵机的中位
reg [11:0] pwm_compare2=12'b0000_1001_0110;
reg [11:0] pwm_compare3=12'b0000_1001_0100;   //初值148,控制360度舵机  
reg [11:0] pwm_compare4=12'b0000_1001_0110;
reg [11:0] pwm_compare5=12'b0000_1001_0110;
reg [11:0] pwm_compare6=12'b0000_1001_0110;
reg [11:0] pwm_compare7=12'b0000_1001_0110;
reg [11:0] pwm_compare8=12'b0000_1001_0110;
reg [11:0] speed_temp;

reg pwm_flag1=0;
reg pwm_flag2=0;
reg pwm_flag3=0;
reg pwm_flag4=0;
reg pwm_flag5=0;
reg pwm_flag6=0;
reg pwm_flag7=0;
reg pwm_flag8=0;

always @( posedge clk_1020us)
begin
led_set<=data_in1;
count_for_trig<=count_for_trig+1;
    if (count_for_trig==4'b0001)
    trig<=0;
    else if (count_for_trig==4'b0010)
    trig<=1;
    else    if (count_for_trig==4'b1010)
    count_for_trig<=4'b0000;
end
    ////////////控制pwm信号/////////
always @(posedge scs)    //每10ms跟新一次PWM输出和电机驱动状态
begin
    if ((data_in1[4]==0)&(pwm_compare1<250))    //control pwm1
    pwm_compare1<=pwm_compare1+1;
    else if((data_in1[6]==0)&(pwm_compare1>50))
    pwm_compare1<=pwm_compare1-1;

    if ((data_in1[7]==0)&(pwm_compare2<250))    //control pwm2
    pwm_compare2<=pwm_compare2+1; 
    else if((data_in1[5]==0)&(pwm_compare2>50))
    pwm_compare2<=pwm_compare2-1;

    if ((data_in2[2]==0)&(pwm_compare3<250))    //control pwm3  1.48ms控制360度舵机
    pwm_compare3<=pwm_compare3+1;
    else if((data_in2[3]==0)&(pwm_compare3>50))
    pwm_compare3<=pwm_compare3-1;

    if ((data_in2[6]==0)&(pwm_compare4<250))    //control pwm4
    pwm_compare4<=pwm_compare4+1;
    else if((data_in2[4]==0)&(pwm_compare4>50))
    pwm_compare4<=pwm_compare4-1;

    if ((data_in2[0]==0)&(pwm_compare5<250))        //control pwm5
    pwm_compare5<=pwm_compare5+1;
    else if((data_in2[1]==0)&(pwm_compare5>50))
    pwm_compare5<=pwm_compare5-1;

    if ((data_in2[7]==0)&(pwm_compare6<250))        //control pwm6
    pwm_compare6<=pwm_compare6+1;
    else if((data_in2[5]==0)&(pwm_compare6>50))
    pwm_compare6<=pwm_compare6-1;





    if ((data_in4<127)|(data_in4>132))      //检测左摇杆Y方向是否拨动
    begin
        if (data_in4<123)  //前进
            begin
            speed_temp<=((~data_in4)&7'b01111111)<<4;
            if ((data_in5>126)&(data_in5<135)) //偏向拨杆没有拨动  
            begin
            pwm_compare7<=speed_temp;
            pwm_compare8<=speed_temp;
            end
            else if (data_in5<127)
                begin
                pwm_compare8<=speed_temp-(((~data_in5)&7'b01111111)<<4);   //左右偏航
                pwm_compare7<=speed_temp;
                end
            else if (data_in5>132)
                    begin
                    pwm_compare7<=speed_temp-((data_in5-128)<<4);
                    pwm_compare8<=speed_temp;
                    end

            moto1<=2'b10;
            moto2<=2'b10;
            end
           else if(data_in4>132) //后退
                    begin
                    moto1<=2'b01;
                    moto2<=2'b01;
                    pwm_compare7<=(data_in4-128)<<3;
                    pwm_compare8<=pwm_compare7;
                    end
    end
    else if((data_in4>126)&(data_in4<132))
            begin
                moto1<=2'b00;
                moto2<=2'b00;
                if((data_in3<123)|(data_in3>132))    //原地左右转
                begin
                    if (data_in3<123)
                    begin
                    pwm_compare7<=((~data_in3)&7'b01111111)<<3;
                    pwm_compare8<=((~data_in3)&7'b01111111)<<3;
                    moto1<=2'b10;
                    moto2<=2'b01;
                    end

                   else if (data_in3>132)
                            begin
                            pwm_compare7<=(data_in3-128)<<3;
                            pwm_compare8<=(data_in3-128)<<3;
                            moto1<=2'b01;
                            moto2<=2'b10;
                            end
                end
         end



end


always @(posedge pwm_clk)    //控制八路pwm信号输出
begin
    count_pwm<=count_pwm+1;

    if (count_pwm < pwm_compare1)   //pwm1
        pwm_flag1<=1;
        else
        pwm_flag1<=0;

    if (count_pwm < pwm_compare2)   //pwm2
        pwm_flag2<=1;
        else
        pwm_flag2<=0;

    if (count_pwm < pwm_compare3)   //pwm3
        pwm_flag3<=1;
        else
        pwm_flag3<=0;

    if (count_pwm < pwm_compare4)   //pwm4
        pwm_flag4<=1;
        else
        pwm_flag4<=0;   

    if (count_pwm < pwm_compare5)   //pwm5
        pwm_flag5<=1;
        else
        pwm_flag5<=0;

    if (count_pwm < pwm_compare6)   //pwm6
        pwm_flag6<=1;
        else
        pwm_flag6<=0;

    if (count_pwm < pwm_compare7)   //pwm7
        pwm_flag7<=1;
        else
        pwm_flag7<=0;

    if (count_pwm < pwm_compare8)   //pwm8
        pwm_flag8<=1;
        else
        pwm_flag8<=0;   


    if (count_pwm ==12'b0111_1101_0000-1)
    count_pwm<=0;

end

always @(negedge clk_6us)
begin
    if (trig==0)
    begin
    scs<=0;
    count_trig<=count_trig+1;
    //////////spi_clk波形产生////////////
            if ((0<count_trig)&(count_trig<17))   //byte1
            sclk<=~sclk;
            else if ((19<count_trig)&(count_trig<36))  //byte2
            sclk<=~sclk;
            else if ((38<count_trig)&(count_trig<55))  //byte3
            sclk<=~sclk;
            else if ((57<count_trig)&(count_trig<74))  //byte4
            sclk<=~sclk;
            else if ((76<count_trig)&(count_trig<93))  //byte5
            sclk<=~sclk;
            else if ((95<count_trig)&(count_trig<112))  //byte6
            sclk<=~sclk;
            else if ((114<count_trig)&(count_trig<131))  //byte7
            sclk<=~sclk;
            else if ((133<count_trig)&(count_trig<150))  //byte8
            sclk<=~sclk;
            else if ((152<count_trig)&(count_trig<169))  //byte9
            sclk<=~sclk;

    ///////////////////mosi波形产生///////////////////
    if (count_trig<2)
        smosi<=1;
    else if ((20<count_trig)&(count_trig<23))
        smosi<=1;
    else if ((30<count_trig)&(count_trig<33))
        smosi<=1;
    else 
        smosi<=0;


    //////////////读取miso////////////////////



        if (count_trig==58)    //读byte4
        data_in1[0]<=spi_miso;
    else if (count_trig==60)
        data_in1[1]<=spi_miso;
    else if (count_trig==62)
        data_in1[2]<=spi_miso;
    else if (count_trig==64)
        data_in1[3]<=spi_miso;
    else if (count_trig==66)
        data_in1[4]<=spi_miso;
    else if (count_trig==68)
        data_in1[5]<=spi_miso;
    else if (count_trig==70)
        data_in1[6]<=spi_miso;
    else if (count_trig==72)
        data_in1[7]<=spi_miso;  


    if (count_trig==77)    //读byte5
        data_in2[0]<=spi_miso;
    else if (count_trig==79)
        data_in2[1]<=spi_miso;
    else if (count_trig==81)
        data_in2[2]<=spi_miso;
    else if (count_trig==83)
        data_in2[3]<=spi_miso;
    else if (count_trig==85)
        data_in2[4]<=spi_miso;
    else if (count_trig==87)
        data_in2[5]<=spi_miso;
    else if (count_trig==89)
        data_in2[6]<=spi_miso;
    else if (count_trig==91)
        data_in2[7]<=spi_miso;  

    if (count_trig==96)    //读byte6
        data_in3[0]<=spi_miso;
    else if (count_trig==98)
        data_in3[1]<=spi_miso;
    else if (count_trig==100)
        data_in3[2]<=spi_miso;
    else if (count_trig==102)
        data_in3[3]<=spi_miso;
    else if (count_trig==104)
        data_in3[4]<=spi_miso;
    else if (count_trig==106)
        data_in3[5]<=spi_miso;
    else if (count_trig==108)
        data_in3[6]<=spi_miso;
    else if (count_trig==110)
        data_in3[7]<=spi_miso;  

        if (count_trig==115)    //读byte7
        data_in4[0]<=spi_miso;
    else if (count_trig==117)
        data_in4[1]<=spi_miso;
    else if (count_trig==119)
        data_in4[2]<=spi_miso;
    else if (count_trig==121)
        data_in4[3]<=spi_miso;
    else if (count_trig==123)
        data_in4[4]<=spi_miso;
    else if (count_trig==125)
        data_in4[5]<=spi_miso;
    else if (count_trig==127)
        data_in4[6]<=spi_miso;
    else if (count_trig==129)
        data_in4[7]<=spi_miso;

                if (count_trig==134)    //读byte8
        data_in5[0]<=spi_miso;
    else if (count_trig==136)
        data_in5[1]<=spi_miso;
    else if (count_trig==138)
        data_in5[2]<=spi_miso;
    else if (count_trig==140)
        data_in5[3]<=spi_miso;
    else if (count_trig==142)
        data_in5[4]<=spi_miso;
    else if (count_trig==144)
        data_in5[5]<=spi_miso;
    else if (count_trig==146)
        data_in5[6]<=spi_miso;
    else if (count_trig==148)
        data_in5[7]<=spi_miso;


    if (count_trig==153)    //读byte8
        data_in6[0]<=spi_miso;
    else if (count_trig==155)
        data_in6[1]<=spi_miso;
    else if (count_trig==157)
        data_in6[2]<=spi_miso;
    else if (count_trig==159)
        data_in6[3]<=spi_miso;
    else if (count_trig==161)
        data_in6[4]<=spi_miso;
    else if (count_trig==163)
        data_in6[5]<=spi_miso;
    else if (count_trig==165)
        data_in6[6]<=spi_miso;
    else if (count_trig==167)
        data_in6[7]<=spi_miso;  
    end

    else if(trig==1)
        begin
        scs<=1;
        sclk<=1;
        count_trig<=0;
        end


end


assign pwm_out[0]=pwm_flag1;
assign pwm_out[1]=pwm_flag2;
assign pwm_out[2]=pwm_flag3;
assign pwm_out[3]=pwm_flag4;
assign pwm_out[4]=pwm_flag5;
assign pwm_out[5]=pwm_flag6;
assign pwm_out[6]=pwm_flag7;
assign pwm_out[7]=pwm_flag8;
assign motor_io[0]=moto1[1];
assign motor_io[1]=moto1[0];
assign motor_io[2]=moto2[1];
assign motor_io[3]=moto2[0];
assign ledout = led_set;
assign spi_clk=sclk;
assign spi_cs=scs;
assign spi_cs=scs;
assign spi_mosi=smosi;

endmodule

3.供电系统接法以及负载注意事项

本机器人上一共有五个电压档次
- 电池是22.4V,30C,6s的锂电池
- 电机是9V的直流电机
- 舵机驱动电压是6~7.4V
- FPGA开发板是5V的直流电压输出,通过板载电源芯片3.3V电压输出

下面分块验证负载情况

因为电池是2600mah,30C的航模锂电池,输出电流可以达到22V、7.8A,故采用两个稳压模块将22.4V电池电压分别降到和7V。每个稳压源最大输出电流5A。
这里写图片描述
这里写图片描述

对于电机驱动而言,我们采用的是L298N舵机驱动模块,电机的最大电流200mA,L298N的最大输出电流为2A,电机电流400mA小于L298N输出电流2A小于降压模块输出电流5A,故此路电流没有问题。
这里写图片描述

对于舵机而言,由于店家给不出工作电流
这里写图片描述

所以我实测了最大负载臂的驱动舵机峰值电流为1.1A
这里写图片描述
这样的六个舵机并联在一起,当舵机联动时负载电流可能超过稳压模块输出的5A,导致供电不足,系统重启。

以上供电系统任然有缺陷存在,写这些是为了提醒大家在做实物的时候需要考虑到供电问题以及一些模电知识,才能做出稳定可靠的系统。

<think>我们正在处理一个关于使用Verilog进行电机控制程序设计的问题。用户请求示例代码和教程。根据提供的引用,我们有关于Verilog在电机控制中应用的信息,特别是引用[3]和[4]提供了相关的背景和代码片段。引用[3]提到:“基于嵌入式FPGA的电机控制设计。电机控制是嵌入式系统中常见的任务之一,而基于可编程逻辑器件(FPGA)的电机控制设计提供了一种高度可定制化和灵活性强的解决方案。本文将详细介绍如何利用嵌入式FPGA实现电机控制,并提供相应的源代码示例。”[^3]引用[4]展示了一个简单的PWM控制器模块的Verilog代码示例[^4]。引用[5]则提供了一个电机保护模块的代码示例[^5]。因此,我们可以为用户提供一个简单的Verilog电机控制程序示例,特别是PWM(脉宽调制)控制,因为PWM是电机控制中常用的技术。同时,我们可以提供一些学习资源或教程建议。由于用户要求示例代码和教程,我们将:1.提供一个简单的PWM电机控制器的Verilog代码示例,并解释其工作原理。2.提供一些关于如何学习Verilog电机控制程序设计的建议(教程)。注意:代码示例需要符合Verilog语法,并且要注释清晰。此外,根据系统级指令,我们需要生成相关问题。步骤:1.介绍PWM在电机控制中的作用。2.展示一个PWM控制器的Verilog模块代码,并逐行解释。3.提供一些学习资源和教程建议。4.生成相关问题。注意:我们引用的内容来自提供的引用,但用户真正的需求是示例代码和教程,因此我们将重点放在提供代码和指导上。</think>### Verilog电机控制程序设计指南 #### PWM电机控制基础 脉宽调制(PWM)是电机控制的核心技术,通过调节占空比控制电机转速。Verilog实现的关键模块如下: ```verilog module motor_pwm ( input clk, // 50MHz时钟 input reset, // 复位信号 input [7:0] duty, // 占空比控制(0-255) output reg pwm_out // PWM输出到电机驱动器 ); reg [7:0] counter; // 8位计数器 always @(posedge clk or posedge reset) begin if (reset) begin counter <= 0; pwm_out <= 0; end else begin counter <= counter + 1; // 比较器实现占空比控制 pwm_out <= (counter < duty) ? 1 : 0; end end endmodule ``` **代码说明**: 1. `counter`每时钟周期自增,范围$0 \sim 255$ 2. 当`counter < duty`时输出高电平 3. 占空比计算公式:$D = \frac{duty}{256} \times 100\%$ 4. 时钟频率50MHz时,PWM频率≈195kHz #### H桥电机驱动控制 ```verilog module h_bridge_driver ( input clk, input [1:0] direction, // 00:停止 01:正转 10:反转 output reg A1, A2, // H桥控制信号 output reg B1, B2 ); // PWM生成模块实例化 motor_pwm pwm_inst (.clk(clk), .reset(0), .duty(200), .pwm_out(pwm)); always @(*) begin case(direction) 2'b01: {A1,A2,B1,B2} = {pwm, 0, 0, pwm}; // 正转 2'b10: {A1,A2,B1,B2} = {0, pwm, pwm, 0}; // 反转 default: {A1,A2,B1,B2} = 4'b0000; // 停止 endcase end endmodule ``` **保护机制实现**(引用[5]安全功能)[^5]: ```verilog module MotorProtection( input clk, input overheat, // 温度传感器 input overcurrent, // 电流检测 output reg fault // 故障信号 ); always @(posedge clk) begin fault <= overheat | overcurrent; // 任一异常触发保护 end endmodule ``` #### 学习资源推荐 1. **FPGA开发平台**: - 使用Quartus II/Primary开发环境(引用[4])[^4] - 实验板推荐:DE10-Lite(含电机接口) 2. **核心学习路径**: ```mermaid graph LR A[Verilog基础语法] --> B[状态机设计] B --> C[PWM发生器] C --> D[H桥控制] D --> E[闭环PID控制] ``` 3. **开源参考项目**: - ODrive开源电机控制方案(引用[5]高级特性)[^5] - FPGA电机控制套件(引用[2]完整工具链)[^2] 4. **关键调试技巧**: - 使用SignalTap II实时捕获PWM波形 - 逐步增加占空比验证电机响应 - 过流保护阈值设定:$I_{max} = \frac{V_{supply}}{R_{motor}} \times 150\%$ #### 应用场景(引用[3]嵌入式系统)[^3] 1. 工业机械臂关节控制 2. 电动汽车驱动系统 3. 无人机电调(ESC)模块 4. 精密医疗设备电机控制
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值