FPGA - RS232串口实现串口接收

FPGA集训第6天,尝试去完成用FPGA实现和串口助手完成接收上位机信息的指令过程。

 

串口接收的过程是 -串行数据转并行数据的过程

 

1=1bit开始+8bit数据+1bit结束)

故输入除了时钟和复位之外,只需要一个接收数据线rx_data即可,输出则为一个8位并行输出uart_data和一个rx_done表示接收完成

本次实验是通过上位机去实现串口数据发送0000-1111来控制开发板的四个LED亮灭,故输出端口为4位LED端口。  

module UART_RXD(
    input                sysclk          ,
    input                rst_n           ,
    input                rx_data         ,      /// 0 0010_1100 1 
    
    output  reg [3:0]   rx_led           
//    output  reg         rx_done
    );

  实验的难点在于根据时序图去设计代码来实现整个接收过程

 

跟随代码,我们来了解整个串口模块的设计流程

  开发板的系统时钟为125MHZ,配置的波特率为115200,计算结果DELAY为该波特率每一位数据的发送时间

////////////参数
parameter        SYSCLK    = 125_000_000     ;
parameter        BAUD      = 115_200         ;
localparam        DELAY = SYSCLK/BAUD         ;     //该波特率下最大不能超过的CNT值,超过则结果不对  125 000 000 ns / 115200 (bit/ns) =  1085ns,即每一位的最大发送时间为1085ns
localparam        MID   = DELAY/2         ;

  一些后面的代码需要的参数

复制代码
reg         [11:0]     cnt       ;    //用来记录发送每一位数据的当前时间(ns为单位),最大值为 DELAY-1
reg         [3:0]      cnt_bit     ;  //bit数计数   0-9 记录接收了几位数据,总计10位
reg                    rx_flag      ;  ///使能信号   接收使能
////////////////////////产生开始信号(二级寄存)   ------二级寄存是用来判断上升沿和下降沿所使用的
reg     rx_reg1         ;  //记录当前rx_data的电平
reg     rx_reg2         ;  //记录上一个时刻rx_reg1的电平
wire    start_flag      ;  //模块工作使能位
reg [7:0]   uart_data        ;     ///串口的接收是从低位到高位的 如发送来的数据是 8h17 0001 0111  对应接收时接收顺序为 1110 1000 即从低位开始 对应uart_data从 uart_Data[0] -> uart_data[1] 
reg         rx_done;     //接收完成标志
复制代码

 

  只需要记住一点,Verilog中上升沿和下降沿的判断实现通过寄存器即可判断出来

复制代码
//模块工作标志位设置(判断是否有数据进来,即有没有下降沿的到来)
always@(posedge sysclk)
    if(!rst_n)begin
        rx_reg1 <= 1;    
        rx_reg2 <= 1;
    end
    else begin
        rx_reg1 <= rx_data;    
        rx_reg2 <= rx_reg1;
    end
assign     start_flag =  ~rx_reg1 & rx_reg2;    //识别到下降沿开始接收,识别到下降沿后模块开始工作,工作标志位拉高
复制代码

//接收开始标志位,当模块开始工作时,开启接收(至于为什么要区分出来模块工作使能和接收使能,大概是因为后面还有发送使能,能够区分开吧  

rx_flag拉高条件:start_flag为高

rx_flag拉低条件:接收满10bit且cnt计数达到中值

 

复制代码
//接收开始标志位设置
always@(posedge sysclk)
    if(!rst_n)
        rx_flag <= 0;
    else if(start_flag)  //模块工作,开始接收
        rx_flag <= 1;
    else if(cnt_bit == 9 && cnt == MID - 1)  //接收到10位数据时停止接收工作 , cnt计数达到MID处是因为此处数据比较稳定
        rx_flag <= 0;
    else
        rx_flag <= rx_flag  ;  //其它时刻保持稳定
复制代码

clk_cnt计数条件:当开始接收时开始计数停止计数
clk_cnt停止条件:当没在接收时停止计数清0,或者计数满DELAY值时也需要清0
复制代码
/////cnt不断自增判断当前执行时间
always@(posedge sysclk)
    if(!rst_n)
        cnt <= 0;
    else if(rx_flag)begin
        if(cnt == DELAY - 1)
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end
    else   ///rx_flag == 0
        cnt <= 0;
复制代码

 图里的rx_cnt对应的就是cnt_bit,即当前接收了几位数据

cnt_bit计数条件:当达到DELAY - 即计一个数的最大值
cnt_bit停止条件:当计满10个数据
复制代码
///////cnt_bit:记录当前已经接收到几个数据
always@(posedge sysclk)
    if(!rst_n)
        cnt_bit <= 0;
    else if(rx_flag)begin
        if(cnt == DELAY - 1)begin       //每达到最大时间认定为接收1位数据
            if(cnt_bit == 9)        //计满清0
                cnt_bit <= 0;
            else
                cnt_bit <= cnt_bit + 1;
        end
        else
           cnt_bit <= cnt_bit;          //如果还没到DELAY,保持不变    
    end  
    else                    //如果没有在接收,清0
        cnt_bit <= 0;   ///0:起始位   1-8:数据位   9:停止位
复制代码

在有数据到时接收数据到uart_data中,其它时刻都是0

复制代码
//接收端串行转并行数据
always@(posedge sysclk)
    if(!rst_n)
        uart_data <= 0;
    else if(rx_flag)begin  
        if(cnt_bit > 0 && cnt_bit < 9 && cnt == MID - 1) ///1-8   因为第0,9位分别是开始位和停止位,故不需要接收,我们只要1-8的数据位
            uart_data[cnt_bit - 1]  <=  rx_data ; /////先传最低位 【0】 再到【1】,2,3.....【7】
        else
            uart_data <= uart_data;          //在开始位和停止位保持不变即可
    end
    else
        uart_data <= 0;       //没有接收时清0
复制代码

 uart_done对应rx_done,在接收到第9位即最后一位数据后拉高表示接收结束

复制代码
//接收完成标志位设置
always@(posedge sysclk)
    if(!rst_n)
        rx_done <= 0;
    else if(rx_flag)begin
        if(cnt_bit == 9 && cnt == MID/2 - 1)    //MID/2处置高还是因为该时刻数据稳定,且避免和前面rx_flag处冲突,这样会更稳定?我不太清楚.......
           rx_done <= 1;
        else
           rx_done <= 0; 
    end
    else
        rx_done <= 0;
复制代码

在接收完成之后,将接收到数据的前4位发给LED,控制LED的亮灭。主要用来判断接收是否实现。

复制代码
always@(posedge sysclk)begin
    if(!rst_n)
        rx_led <= 4'd0;
    else if(rx_done)
        begin
            rx_led[0] <= uart_data[0];
            rx_led[1] <= uart_data[1];
            rx_led[2] <= uart_data[2];
            rx_led[3] <= uart_data[3];
        end
    else
        rx_led <= rx_led;

end
复制代码

 

完整代码如下:

复制代码
`timescale 1ns / 1ps
///////串口接收端
module UART_RXD(
    input                sysclk          ,
    input                rst_n           ,
    input                rx_data         ,      /// 0 0010_1100 1 
    
    output  reg [3:0]   rx_led           
//    output  reg         rx_done
    );
////////////参数
parameter        SYSCLK    = 125_000_000     ;
parameter        BAUD      = 115_200         ;
localparam        DELAY = SYSCLK/BAUD         ;     //该波特率下最大不能超过的CNT值,超过则结果不对
localparam        MID   = DELAY/2         ;
reg         [11:0]     cnt       ;  
reg         [3:0]      cnt_bit     ;  ////bit数计数   0-9
reg                    rx_flag      ;       ///使能信号
////////////////////////产生开始信号(二级寄存)
reg     rx_reg1         ;
reg     rx_reg2         ;
wire    start_flag      ;
reg [7:0]   uart_data        ;     ///8'h34 = 8'b0011_0100
reg         rx_done;
//模块工作标志位设置
always@(posedge sysclk)
    if(!rst_n)begin
        rx_reg1 <= 1;    
        rx_reg2 <= 1;
    end
    else begin
        rx_reg1 <= rx_data;    
        rx_reg2 <= rx_reg1;
    end
assign     start_flag =  ~rx_reg1 & rx_reg2;    //识别到上升沿开始接收
//接收开始标志位设置
always@(posedge sysclk)
    if(!rst_n)
        rx_flag <= 0;
    else if(start_flag)
        rx_flag <= 1;
    else if(cnt_bit == 9 && cnt == MID - 1)
        rx_flag <= 0;
    else
        rx_flag <= rx_flag  ;
/////cnt不断自增判断当前执行时间
always@(posedge sysclk)
    if(!rst_n)
        cnt <= 0;
    else if(rx_flag)begin
        if(cnt == DELAY - 1)
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end
    else   ///rx_flag == 0
        cnt <= 0;
///////cnt_bit:记录当前已经接收到几个数据
always@(posedge sysclk)
    if(!rst_n)
        cnt_bit <= 0;
    else if(rx_flag)begin
        if(cnt == DELAY - 1)begin       //每达到最大时间认定为接收1位数据
            if(cnt_bit == 9)
                cnt_bit <= 0;
            else
                cnt_bit <= cnt_bit + 1;
        end
        else
           cnt_bit <= cnt_bit;            
    end  
    else
        cnt_bit <= 0;   ///0:起始位   1-8:数据位   9:停止位
//////// 8       rx_data 1    数据的采集   uart_data[cnt_bit]  <=  rx_data ;
//接收端串行转并行数据
always@(posedge sysclk)
    if(!rst_n)
        uart_data <= 0;
    else if(rx_flag)begin
        if(cnt_bit > 0 && cnt_bit < 9 && cnt == MID - 1) ///1-8
            uart_data[cnt_bit - 1]  <=  rx_data ; /////先传最低位
        else
            uart_data <= uart_data;
    end
    else
        uart_data <= 0;     
//接收完成标志位设置
always@(posedge sysclk)
    if(!rst_n)
        rx_done <= 0;
    else if(rx_flag)begin
        if(cnt_bit == 9 && cnt == MID/2 - 1)
           rx_done <= 1;
        else
           rx_done <= 0; 
    end
    else
        rx_done <= 0;
        
always@(posedge sysclk)begin
    if(!rst_n)
        rx_led <= 4'd0;
    else if(rx_done)
        begin
            rx_led[0] <= uart_data[0];
            rx_led[1] <= uart_data[1];
            rx_led[2] <= uart_data[2];
            rx_led[3] <= uart_data[3];
        end
    else
        rx_led <= rx_led;

end
         
endmodule
复制代码

引脚约束文件

复制代码
set_property PACKAGE_PIN M14 [get_ports {rx_led[3]}]
set_property PACKAGE_PIN N16 [get_ports {rx_led[2]}]
set_property PACKAGE_PIN P14 [get_ports {rx_led[1]}]
set_property PACKAGE_PIN R14 [get_ports {rx_led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[0]}]
set_property PACKAGE_PIN U5 [get_ports rst_n]
set_property PACKAGE_PIN Y9 [get_ports rx_data]
set_property PACKAGE_PIN H16 [get_ports sysclk]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rx_data]
set_property IOSTANDARD LVCMOS33 [get_ports sysclk]
复制代码

 

实验结果

串口调试助手输入数字’7‘,成功点亮3个LED  (0111)

 

posted @   (喜欢黑夜的孩子)  阅读(284)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示