目录

 前言

FPGA任务1:控制舵机转动

Pwm_sevo.v代码

代码讲解

FPGA任务2:实现通信

UART_Top.v代码

UART_Tx.v代码

UART_RX.v代码

FPGA任务3:MCU发送数据控制FPGA,控制舵机转动

TOP_design.v代码

关于接口板的使用与FPGA引脚分配

总结


 前言

在微控讨论贡献度时,做FPGA和MCU的同学贡献度通常会高的离谱,但是事实上这两方面工作难度是有,但是如果认真学习这方面知识,微控的MCU与FPGA难度并不高。

我认为微控作为一个小组作业,有分工合作,每个人都要完成自己分内的东西,秘书,公关,产品经理所作文书工作虽然简单,但是要做好也不容易,也并不应该因为选择了做这个,最终微控得到了很低的得分。FPGA,MCU部分的有一定门槛,但是在我看来绝大多数做该部分的同学,并没有真正去学习这方面的知识,而是使用着从各处找的工程,或者在别人的博文上抄的代码,不知其原理,每天坐在大厅里改来改去,改到凌晨三四点,看似辛苦,做的大部分都是无用功罢了。

所以写本文章的目的把我这次微控所写的工程进行讲解,搭建一个可以直接使用的微控系统设计的代码框架,帮助学弟学妹们在面对微控可以轻松完成MCU与FPGA任务,减少难度的同时不希望出现某些做这两部分工作,就觉得自己比秘书,产品经理,公关做的努力多得多,要求高出20%贡献度的情况。(可以高但是不要高的离谱,例如MCU+FPGA的人拿20%以上,剩下人只有15%甚至更低,这种情况在本人看来十分离谱!!!除非你的队友什么都不干!)

话不多说,先放上我的工程链接zlzj12/Week1_FPGA,直接下载即可。

希望同学们可以认真阅读,轻松应对微控。

FPGA部分微控对其具体要求为

①实现FPGA控制舵机转动。

②实现FPGA与MCU的通信。

③通过MCU发送数据控制FPGA,FPGA进而控制舵机转动。

 我使用的Vivado版本为2023.2,下载时使用了大约300GB存储空间。这里推荐使用2018版本,软件内存占用小。由于Vivadio版本兼容性的问题,可能我的工程不能直接使用。所以接下来讲解具体实现与代码编写。包括使用时该如何修改我给出的代码。

如果有同学对于Vivado软件的环境不熟悉,不知道怎么进行代码仿真,综合,引脚分配,可以去B站看一下 小梅哥FPGA视频,目的是熟练Vivado软件的环境,如何新建工程,编写代码,烧录代码等基础操作。知道这些操作后,FPGA部分可以直接复制下面我提及的5个V语言文件,然后新建工程,自己跑一边综合仿真生成比特率文件烧录至FPGA上。【零基础轻松学习FPGA】小梅哥Xilinx FPGA基础入门到项目应用培训教程(2024全新课程已上线)_哔哩哔哩_bilibili

FPGA代码的模块化非常清晰,我的编写习惯为在顶层模块中调用小模块实现功能。可以理解为C语言在main.c文件中调用其他文件中函数的关系。小模块即为要调用的函数。

FPGA任务1:控制舵机转动

控制舵机的原理为通过FPGA产生周期20ms的PWM波通过改变占空比,即高电平存在时间,控制舵机转动角度。使用的舵机转动角度映射关系为0°-180°(0.5ms-2.5ms)。

下面给出的代码为最终版本代码,实现了外部发送degree角度来控制舵机转动。

如果语法看不懂也没关系,直接使用就可以,下面会详细讲解代码的内容。

Pwm_sevo.v代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/03/24 19:14:18
// Design Name: 
// Module Name: Pwm_sevo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module Pwm_sevo
(
    input       clk     ,
    input       rst_n   ,
    input  wire[7:0]     degree  ,
    output reg  pwm

    );
    //参数
    parameter TIM_20ms      = 20'd 100_0000;          //50M时钟分频50
    parameter TIM_0d5ms     = 20'd 2_5000;            //0.5ms  0度
    parameter TIM_1ms       = 20'd 5_0000;            //1ms   45度
    parameter TIM_1d5ms     = 20'd 7_5000;            //1.5ms 90度
    parameter TIM_2ms       = 20'd 10_0000;           //2ms   135度
    parameter TIM_2d5ms     = 20'd 12_5000;           //2.5ms 180度
    reg [31:0]      cnt_pwm    ;
    reg [31:0]      cnt         ;
    //内部信号I
    //wire            add_cnt;
    //wire            end_cnt;
    //功能
    //20ms计数
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt <= 0;
        end
        else  if(cnt >= TIM_20ms - 1)begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1;
        end
    end
    
    //assign add_cnt = 1;
    //ssign end_cnt = add_cnt && cnt == TM_20ms - 1;
    //修改pwm波形
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            
        end
        else if(degree<=8'd180)begin
            cnt_pwm <= 32'd2_5000 + degree*32'd10_0000/32'd180;
        end
        else begin
            cnt_pwm <= 32'd2_5000 ;//超过180度复位
        end

    end

    //输出pwm
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            pwm <= 0;
        end
        else if (cnt < cnt_pwm) begin
            pwm <= 1;
        end
        else begin
            pwm <= 0;
        end
    end


endmodule

代码讲解

接下来讲解具体代码(目的是实现占空比)

 这段代码为Verilog中的模块,input为输入,output为输出。

只实现控制舵机,即产生固定占空比的PWM波,那我们只需确定占空比即可,通过degree控制占空比。

clk是输入的时钟信号,rst_n为低电平复位信号,该信号拉低将会一直在复位状态。

module Pwm_sevo
(
    input       clk     ,
    input       rst_n   ,
    input  wire[7:0]     degree  ,
    output reg  pwm

    );


endmodule

 这部分是定义的一些参数与变量。parameter定义常量 可以类比为C语言中的define。reg [31:0]定义寄存器变量,定义了一个32位寄存器。cnt_pwm这个寄存器变量控制了pwm波占空比。

    //参数
    parameter TIM_20ms      = 20'd 100_0000;          //50M时钟分频50
    parameter TIM_0d5ms     = 20'd 2_5000;            //0.5ms  0度
    parameter TIM_1ms       = 20'd 5_0000;            //1ms   45度
    parameter TIM_1d5ms     = 20'd 7_5000;            //1.5ms 90度
    parameter TIM_2ms       = 20'd 10_0000;           //2ms   135度
    parameter TIM_2d5ms     = 20'd 12_5000;           //2.5ms 180度
    reg [31:0]      cnt_pwm    ;
    reg [31:0]      cnt         ;

 这部分代码实现20ms计数,即产生周期20ms的pwm波。这段代码不需要修改。

    //20ms计数
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt <= 0;
        end
        else  if(cnt >= TIM_20ms - 1)begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1;
        end
    end

 这部分代码通过接收到的degree,给cnt_pwm赋值,控制PWM波的占空比,控制角度。

    //修改pwm波形
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            
        end
        else if(degree<=8'd180)begin
            cnt_pwm <= 32'd2_5000 + degree*32'd10_0000/32'd180;
        end
        else begin
            cnt_pwm <= 32'd2_5000 ;//超过180度复位
        end

    end

 这段代码实现了PWM的输出。

    //输出pwm
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            pwm <= 0;
        end
        else if (cnt < cnt_pwm) begin
            pwm <= 1;
        end
        else begin
            pwm <= 0;
        end
    end

FPGA任务2:实现通信

FPGA与MCU通信,此处我选择使用UART的方式。

数据传输链只有MCU——>FPGA,传输的数据为舵机转动角度,所以规定MCU只发送角度信息即degree给FPGA。degree限制在0~180°,这样子MCU仅发送八位数据(一字节)就可以控制舵机。

这部分代码直接复制使用,因为我也是抄这篇博文,略做修改的。【FPGA协议篇】UART通信及其verilog实现(代码采用传参实现模块通用性,适用于快速开发)_fpga串口波特率自适应设计-CSDN博客

UART部分代码的结构为 UART_Top.v 顶层文件调用 UART_TX.v UART_RX.v 两个模块,所以有三段代码,这部分代码不做讲解,大家会复制到自己工程中即可。

要修改串口通信的波特率,不用在此处修改,因为这里我们做的都是模块设计,UART模块作为整个工程中一个模块,那么我们可以通过顶层来直接设置传输的通信波特率。这部分代码直接使用。

UART_Top.v代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/06 21:10:44
// Design Name: 
// Module Name: UART_Top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module UART_Top(
    input i_clk_sys,
    input i_rst_n,
    input i_uart_rx,
    output o_uart_tx,
    output o_ld_parity

    );

    localparam DATA_WIDTH = 8;
    localparam BAUD_RATE = 9600;
    localparam PARITY_ON = 0;
    localparam PARITY_TYPE = 0;
    
    wire w_rx_done;
    wire[DATA_WIDTH-1 : 0] w_data;
    
    
    UART_RX 
    #(
            .CLK_FRE(50),         //时钟频率,默认时钟频率为50MHz
            .DATA_WIDTH(DATA_WIDTH),       //有效数据位,缺省为8位
            .PARITY_ON(PARITY_ON),        //校验位,1为有校验位,0为无校验位,缺省为0
            .PARITY_TYPE(PARITY_TYPE),      //校验类型,1为奇校验,0为偶校验,缺省为偶校验
            .BAUD_RATE(BAUD_RATE)      //波特率,缺省为9600
    ) u_uart_rx
    (
        .i_clk_sys(i_clk_sys),      //系统时钟
        .i_rst_n(i_rst_n),        //全局异步复位,低电平有效
        .i_uart_rx(i_uart_rx),      //UART输入
        .o_uart_data(w_data),    //UART接收数据
        .o_ld_parity(o_ld_parity),    //校验位检验LED,高电平位为校验正确
        .o_rx_done(w_rx_done)       //UART数据接收完成标志
    );
    
    UART_TX
    #(
        .CLK_FRE(50),         //时钟频率,默认时钟频率为50MHz
        .DATA_WIDTH(DATA_WIDTH),       //有效数据位,缺省为8位
        .PARITY_ON(PARITY_ON),        //校验位,1为有校验位,0为无校验位,缺省为0
        .PARITY_TYPE(PARITY_TYPE),      //校验类型,1为奇校验,0为偶校验,缺省为偶校验
        .BAUD_RATE(BAUD_RATE)      //波特率,缺省为9600
    ) u_uart_tx
    (   .i_clk_sys(i_clk_sys),      //系统时钟
        .i_rst_n(i_rst_n),        //全局异步复位
        .i_data_tx(w_data),      //传输数据输入
        .i_data_valid(w_rx_done),   //传输数据有效
        .o_uart_tx(o_uart_tx)       //UART输出
        );
    

endmodule

UART_Tx.v代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/06 21:09:50
// Design Name: 
// Module Name: UART_TX
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module UART_TX
#(
   parameter CLK_FRE = 50,         //时钟频率,默认时钟频率为50MHz
   parameter DATA_WIDTH = 8,       //有效数据位,缺省为8位
   parameter PARITY_ON = 0,        //校验位,1为有校验位,0为无校验位,缺省为0
   parameter PARITY_TYPE = 0,      //校验类型,1为奇校验,0为偶校验,缺省为偶校验
   parameter BAUD_RATE = 9600      //波特率,缺省为9600
)
(   input                       i_clk_sys,      //系统时钟
   input                       i_rst_n,        //全局异步复位
   input [DATA_WIDTH-1 : 0]    i_data_tx,      //传输数据输入
   input                       i_data_valid,   //传输数据有效
   output reg                  o_uart_tx       //UART输出
   );

       
   //状态机定义
           reg [2:0] r_current_state;  //当前状态
           reg [2:0] r_next_state;     //次态
               
           localparam STATE_IDLE = 3'b000;         //空闲状态
           localparam STATE_START = 3'b001;        //开始状态
           localparam STATE_DATA = 3'b011;         //数据发送状态
           localparam STATE_PARITY = 3'b100;       //数据校验计算和发送
           localparam STATE_END = 3'b101;          //结束状态
           
           
           localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;   //波特计数周期
           
           reg baud_valid;                         //波特计数有效位
           reg [15:0] baud_cnt;                    //波特率计数器 
           reg baud_pulse;                         //波特率采样脉冲
           
           reg [3:0]   r_tx_cnt;      //接收数据位计数
           
           //波特率计数器
           always@(posedge i_clk_sys or negedge i_rst_n)
               begin
                   if(!i_rst_n)
                       baud_cnt <= 16'h0000;
                   else if(!baud_valid)
                       baud_cnt <= 16'h0000;
                   else if(baud_cnt == CYCLE - 1)
                       baud_cnt <= 16'h0000;
                   else
                       baud_cnt <= baud_cnt + 1'b1;
               end
               
           //波特采样脉冲
           always@(posedge i_clk_sys or negedge i_rst_n)
               begin
                   if(!i_rst_n)
                       baud_pulse <= 1'b0;
                   else if(baud_cnt == CYCLE/2-1)
                       baud_pulse <= 1'b1;
                   else
                       baud_pulse <= 1'b0;
               end
           
           //状态机状态变化定义
           always@(posedge i_clk_sys or negedge i_rst_n)
               begin
                   if(!i_rst_n)
                       r_current_state <= STATE_IDLE;
                   else if(!baud_valid)
                       r_current_state <= STATE_IDLE;
                   else if(baud_valid && baud_cnt == 16'h0000)
                       r_current_state <= r_next_state;
               end
           
           //状态机次态定义
           always@(*)
               begin
                   case(r_current_state)
                       STATE_IDLE:     r_next_state <= STATE_START;
                       STATE_START:    r_next_state <= STATE_DATA;
                       STATE_DATA:
                           if(r_tx_cnt == DATA_WIDTH)
                               begin
                                   if(PARITY_ON == 0)
                                       r_next_state <= STATE_END;
                                   else
                                       r_next_state <= STATE_PARITY;       //校验位开启时进入校验状态
                               end
                           else
                               begin
                                       r_next_state <= STATE_DATA;
                               end
                       STATE_PARITY:   r_next_state <= STATE_END;
                       STATE_END:      r_next_state <= STATE_IDLE;
                       default:;
                   endcase
               end
   
   
   reg [DATA_WIDTH-1 : 0]      r_data_tx;
   reg                         r_parity_check;
   //状态机输出逻辑
   always@(posedge i_clk_sys or negedge i_rst_n)
       begin
           if(!i_rst_n)
               begin
                   baud_valid  <= 1'b0;
                   r_data_tx   <= 'd0;
                   o_uart_tx   <= 1'b1;
                   r_tx_cnt    <= 4'd0;
                   r_parity_check <= 1'b0;
               end
           else
               case(r_current_state)
                   STATE_IDLE:begin
                           o_uart_tx   <= 1'b1;
                           r_tx_cnt    <= 4'd0;
                           r_parity_check <= 4'd0;
                           if(i_data_valid)
                               begin
                                   baud_valid <= 1'b1;
                                   r_data_tx <= i_data_tx;
                               end
                       end
                   STATE_START:begin
                           if(baud_pulse)
                               o_uart_tx   <= 1'b0;
                       end
                   STATE_DATA:begin
                           if(baud_pulse)
                               begin
                                   r_tx_cnt <= r_tx_cnt + 1'b1;
                                   o_uart_tx <= r_data_tx[0];
                                   r_parity_check <= r_parity_check + r_data_tx[0];
                                   r_data_tx <= {1'b0 ,r_data_tx[DATA_WIDTH-1:1]};
                               end
                       end
                   STATE_PARITY:begin
                           if(baud_pulse)
                               begin
                                   if(PARITY_TYPE == 1)
                                       o_uart_tx <= r_parity_check;
                                   else
                                       o_uart_tx <= r_parity_check + 1'b1;
                               end
                       end
                   STATE_END:begin
                           if(baud_pulse)
                               begin
                                   o_uart_tx <= 1'b1;
                                   baud_valid <= 1'b0;
                               end
                       end
                   default:;
               endcase
       end




endmodule

UART_RX.v代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/06 21:09:34
// Design Name: 
// Module Name: UART_RX
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module UART_RX
#(
    parameter CLK_FRE = 50,         //时钟频率,默认时钟频率为50MHz
    parameter DATA_WIDTH = 8,       //有效数据位,缺省为8位
    parameter PARITY_ON = 0,        //校验位,1为有校验位,0为无校验位,缺省为0
    parameter PARITY_TYPE = 0,      //校验类型,1为奇校验,0为偶校验,缺省为偶校验
    parameter BAUD_RATE = 9600      //波特率,缺省为9600
)
(
    input                           i_clk_sys,      //系统时钟
    input                           i_rst_n,        //全局异步复位,低电平有效
    input                           i_uart_rx,      //UART输入
    output reg[DATA_WIDTH-1 :0]     o_uart_data,    //UART接收数据
    output reg                      o_ld_parity,    //校验位检验LED,高电平位为校验正确
    output reg                      o_rx_done       //UART数据接收完成标志
    );

    
    /*
    UART输入是异步输入,最好是同步到FPGA内部的时钟域
    可以省略,但在异步电路中,保持时钟域同步是一个良好习惯
    */
    reg sync_uart_rx;
    always@(posedge i_clk_sys or negedge i_rst_n)
        begin
            if(!i_rst_n)
                sync_uart_rx <= 1'b1;
            else
                sync_uart_rx <= i_uart_rx;
        end
    
    /*
    连续采样五个接收路电平来判断rx是否有信号传来
    用五个采样信号来作为判断标准可以有效排除毛刺噪声带来的误判
    */
    reg [4:0] r_flag_rcv_start;
    wire w_rcv_start;
    always@(posedge i_clk_sys or negedge i_rst_n)
        begin
            if(!i_rst_n)
                r_flag_rcv_start <= 5'b11111;
            else
                r_flag_rcv_start <= {r_flag_rcv_start[3:0], sync_uart_rx};
        end
    
    
    //状态机定义
    reg [2:0] r_current_state;  //当前状态
    reg [2:0] r_next_state;     //次态
        
    localparam STATE_IDLE = 3'b000;         //空闲状态
    localparam STATE_START = 3'b001;        //开始状态
    localparam STATE_DATA = 3'b011;         //数据接收状态
    localparam STATE_PARITY = 3'b100;       //数据校验状态
    localparam STATE_END = 3'b101;          //结束状态
    
    
    localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;   //波特计数周期
    
    reg baud_valid;                         //波特计数有效位
    reg [15:0] baud_cnt;                    //波特率计数器 
    reg baud_pulse;                         //波特率采样脉冲
    
    reg [3:0]   r_rcv_cnt;      //接收数据位计数
    
    //波特率计数器
    always@(posedge i_clk_sys or negedge i_rst_n)
        begin
            if(!i_rst_n)
                baud_cnt <= 16'h0000;
            else if(!baud_valid)
                baud_cnt <= 16'h0000;
            else if(baud_cnt == CYCLE - 1)
                baud_cnt <= 16'h0000;
            else
                baud_cnt <= baud_cnt + 1'b1;
        end
        
    //波特采样脉冲
    always@(posedge i_clk_sys or negedge i_rst_n)
        begin
            if(!i_rst_n)
                baud_pulse <= 1'b0;
            else if(baud_cnt == CYCLE/2-1)
                baud_pulse <= 1'b1;
            else
                baud_pulse <= 1'b0;
        end
    
    //状态机状态变化定义
    always@(posedge i_clk_sys or negedge i_rst_n)
        begin
            if(!i_rst_n)
                r_current_state <= STATE_IDLE;
            else if(!baud_valid)
                r_current_state <= STATE_IDLE;
            else if(baud_valid && baud_cnt == 16'h0000)
                r_current_state <= r_next_state;
        end
    
    //状态机次态定义
    always@(*)
        begin
            case(r_current_state)
                STATE_IDLE:     r_next_state <= STATE_START;
                STATE_START:    r_next_state <= STATE_DATA;
                STATE_DATA:
                    if(r_rcv_cnt == DATA_WIDTH)
                        begin
                            if(PARITY_ON == 0)
                                r_next_state <= STATE_END;
                            else
                                r_next_state <= STATE_PARITY;       //校验位开启时进入校验状态
                        end
                    else
                        begin
                                r_next_state <= STATE_DATA;
                        end
                STATE_PARITY:   r_next_state <= STATE_END;
                STATE_END:      r_next_state <= STATE_IDLE;
                default:;
            endcase
        end
    
    
    reg[DATA_WIDTH - 1 :0] r_data_rcv;
    reg r_parity_check;
    
    //状态机输出逻辑
    always@(posedge i_clk_sys or negedge i_rst_n)
        begin
            if(!i_rst_n)
                begin
                    baud_valid <= 1'b0;
                    r_data_rcv <= 'd0;
                    r_rcv_cnt <= 4'd0;
                    r_parity_check <= 1'b0;
                    o_uart_data <= 'd0;
                    o_ld_parity <= 1'b0;
                    o_rx_done <= 1'b0;
                end
            else
                case(r_current_state)
                    STATE_IDLE:begin
                            //闲置状态下对寄存器进行复位
                            r_rcv_cnt <= 4'd0;
                            r_data_rcv <= 'd0;
                            r_parity_check <= 1'b0;
                            o_rx_done <= 1'b0;
                            //连续检测到低电平时认为UART传来数据,拉高baud_valid
                            if(r_flag_rcv_start == 5'b00000)
                                baud_valid <= 1'b1;
                        end
                    STATE_START:begin
                            if(baud_pulse && sync_uart_rx)     //波特率采样脉冲到来时再次检测是否为低电平,如果不为低电平,认为前期误检测,重新进入IDLE状态
                                baud_valid <= 1'b0;
                        end
                    STATE_DATA:begin
                            if(baud_pulse)
                                begin
                                    r_data_rcv <= {sync_uart_rx, r_data_rcv[DATA_WIDTH-1 : 1]}; //数据移位存储
                                    r_rcv_cnt <= r_rcv_cnt + 1'b1;                          //数据位计数
                                    r_parity_check <= r_parity_check + sync_uart_rx;        //校验位做加法验证高电平的奇偶
                                end
                        end
                    STATE_PARITY:begin
                            if(baud_pulse)
                                begin
                                //校验检测,正确则o_ld_parity拉高,可输出给led检测,如果闪烁则表示有错误数据发生
                                    if(r_parity_check + sync_uart_rx == PARITY_TYPE)
                                        o_ld_parity <= 1'b1;
                                    else
                                        o_ld_parity <= 1'b0;
                                end
                            else
                                        o_ld_parity <= o_ld_parity;
                        end
                    STATE_END:begin
                            if(baud_pulse)
                                begin
                                //没有校验位或者校验位正确时才输出数据,否则直接丢弃数据
                                    if(PARITY_ON == 0 || o_ld_parity)
                                        begin
                                            o_uart_data <= r_data_rcv;
                                            o_rx_done <= 1'b1;
                                        end
                                end
                            else
                                begin
                                    o_rx_done <= 1'b0;
                                end
                            
                            if(baud_cnt == 16'h0000)
                                    baud_valid <= 1'b0;
                        end
                    default:;
                endcase
        end
    
    
endmodule

FPGA任务3:MCU发送数据控制FPGA,控制舵机转动

上面我们已经完成了UART,舵机控制模块的编写,接下来编写顶层模块,由于题目给了三个舵机,此处代码实现了三个舵机联合控制。具体的逻辑为 MCU连续发送三个角度数据给FPGA,FPGA将三个角度数据分别控制三个舵机。只需要修改模块例化下的BAUD_RATE参数就可以修改波特率。

这部分代码也可以直接使用,但是有一个bug是每次上电后,MCU连续发送三个数据控制的舵机顺序不同,需要自己修改发送角度的顺序吗,此处修改MCU代码,FPGA代码不用动。

这个顶层代码也不做讲解,看注释即可。

TOP_design.v代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/06 21:05:22
// Design Name: 
// Module Name: TOP_design
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module TOP_design(
    input i_clk_sys,//时钟
    input i_rst_n,//复位
    input i_uart_rx,
    output o_uart_tx,
    output o_ld_parity,
    output    wire w_rx_done,
    output [2:0] pwm           // 三路PWM输出
    );
//模块例化
    localparam DATA_WIDTH = 8;
    localparam BAUD_RATE = 9600;
    localparam PARITY_ON = 0;
    localparam PARITY_TYPE = 0;
    
// 内部信号声明
    wire [7:0] w_data;
    reg  [7:0] reg1, reg2, reg3;  // 新增三个数据寄存器
    reg  [1:0] cnt;               // 接收计数器    
    
    UART_RX 
    #(
            .CLK_FRE(50),         //时钟频率,默认时钟频率为50MHz
            .DATA_WIDTH(DATA_WIDTH),       //有效数据位,缺省为8位
            .PARITY_ON(PARITY_ON),        //校验位,1为有校验位,0为无校验位,缺省为0
            .PARITY_TYPE(PARITY_TYPE),      //校验类型,1为奇校验,0为偶校验,缺省为偶校验
            .BAUD_RATE(BAUD_RATE)      //波特率,缺省为9600
    ) u_uart_rx
    (
        .i_clk_sys(i_clk_sys),      //系统时钟
        .i_rst_n(i_rst_n),        //全局异步复位,低电平有效
        .i_uart_rx(i_uart_rx),      //UART输入
        .o_uart_data(w_data),    //UART接收数据
        .o_ld_parity(o_ld_parity),    //校验位检验LED,高电平位为校验正确
        .o_rx_done(w_rx_done)       //UART数据接收完成标志
    );
// 数据分配控制逻辑
    always @(posedge i_clk_sys or negedge i_rst_n) begin
        if (!i_rst_n) begin
            cnt  <= 2'b00;
            reg1 <= 8'h00;
            reg2 <= 8'h00;
            reg3 <= 8'h00;
        end 
        else begin
            if (w_rx_done) begin
                case (cnt)
                    2'b00: reg1 <= w_data;  // 第一个数据存reg1
                    2'b01: reg2 <= w_data;  // 第二个数据存reg2
                    2'b10: reg3 <= w_data;  // 第三个数据存reg3
                endcase
                cnt <= (cnt == 2'b10) ? 2'b00 : cnt + 1;  // 循环计数
            end
        end
    end
// 三路PWM模块例化
    Pwm_sevo P_Pwm_sevo1 (
        .clk(i_clk_sys),
        .rst_n(i_rst_n),
        .degree(reg1),  // 使用第一个寄存器
        .pwm(pwm[0])
    );

    Pwm_sevo P_Pwm_sevo2 (
        .clk(i_clk_sys),
        .rst_n(i_rst_n),
        .degree(reg2),  // 使用第二个寄存器
        .pwm(pwm[1])
    );

    Pwm_sevo P_Pwm_sevo3 (
        .clk(i_clk_sys),
        .rst_n(i_rst_n),
        .degree(reg3),  // 使用第三个寄存器
        .pwm(pwm[2])
    );


endmodule

关于接口板的使用与FPGA引脚分配

最后讲一下FPGA引脚分配,发设备时会发一个绿色的大接口板,阅读接口板手册,按照手册连接接口板,FPGA引出了如下接口至接口板,恰好三个5V供电供给舵机。

这是我的引脚配置,PWM即为PWM波输出口,用于控制舵机,i_uart_rx为UART的RX,连接MCU的TX, 用于接收信息,i_rst_n为低电平复位信息线,这里我接到了板子的拨码开关,常置高电平。D4是50M时钟输出口,一定接ckl信号。其他信号线无所谓。

总结

 微控FPGA部分较为公式化,代码可以直接进行移植,完全不用改动我提交的工程,做好引脚分配即可。

最后要注意一点,我们必须将Top_design.v文件置于顶层,这样子才能是一样的结果。

如下图,虽然我有很多的文件,但是只有TOP_desigin是顶层文件,所以仿真,综合时用的该文件。顶层文件一定一定不能设置错误。右边有三个方格。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐