FPGA_Verilog学习之旅(1)---浅谈UART


前言

记录本人第一次CSDN发帖,开启FPGA学习记录之旅,后期会不定时更新
---------此篇文章主要内容:FPGA-UART学习笔记(以RS-232通信为例)


一、UART是什么

UART(Universal Asynchronous Receiver and Transmitter),一种采用异步串行通信方式的通用异步收发传输器。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据(实现数据的串并转换)。
(注:发送过程为 “先发送低位,后发送高位”)
1.协议层:通信协议,包括数据格式、传输速率等。
2.物理层:接口类型、电平标准等。
:常见的串行通信接口:常见的串行通信接口

二、UART–协议层

1、数据格式

UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。
异步串行通信数据格式
异步串行通信数据格式

1.空闲状态:在没有数据传输时,数据线处于空闲状态,保持为高电平。
2.起始位:在空闲状态下,一旦检测到数据线由1->0,则认为0开始的地方为起始位,标志着一帧数据的开始,即接收方检测到起始位时,就开始准备接收数据。
3.数据位:在上图中有7个数据位,实际上数据位可以为5/6/7/8位,而8位才是最常用的。
4.校验位:校验位用来检测数据传输过程中是否出错,分为奇校验和偶校验。

  • 奇校验:让数据位加上校验位中的“1”的个数保持为奇数。
  • 偶校验:让数据位加上校验位中的“1”的个数保持为偶数。

5.停止位:在上图中为1个停止位,实际上可以为1/1.5/2个停止位,停止位标志着一帧数据的结束,在停止位之后,数据线回到空闲状态。若在空闲状态下再次检测到起始位,则标志着下一帧数据的开始。

2、传输速率

串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位bps(位/秒)。常用的波特率有9600/19200/38400/57600以及115200等。

三、UART–物理层

1、接口标准

针对异步通信的接口标准有RS232、RS422、RS485等。

接口标准逻辑1逻辑0说明优缺点
RS232-15V+15V负逻辑电平;3线全双工(TXD、RXD、GND);点对点双向通信传输速度相对较低;传输距离短(15m)
RS422差值电压+(2-6)V差值电压-(2-6)V差分传输;4线全双工;点对多主从通信抗干扰能力强;传输速度高;传输距离远
RS485差值电压+(2-6)V差值电压-(2-6)V差分传输;2线半双工;多点双向通信能够实现多个发送、接收设备双向通信
  • 单端传输(RS232):使用一根信号线加一根地线去传输一路信号,使用信号线与地线的电平差值去表示要传输的信号。
  • 差分传输(RS422/RS485):使用两根信号线去传输一路信号,这两根信号线的幅值相等、极性相反,相比于单端传输,抗干扰能力很强。
  • 点对点双向通信:只能有两个设备进行双向通信。
  • 点对多主从通信:一个主设备加多个从设备,主设备和各个从设备之间可以实现双向通信,但是从设备之间不能通信。
  • 多点双向通信:一个主设备加多个从设备,主设备和各个从设备之间可以实现双向通信,且各个从设备之间也可以实现双向通信。

RS-232标准的串口常见接口类型:DB9

DB9接口示意图

DB9接口定义如下:

引脚定义引脚名称功能说明
Pin1DCD数据载波检测
Pin2RXD接受数据
Pin3TXD发送数据
Pin4DTR数据终端准备
Pin5GND地线
Pin6DSR数据准备就绪
Pin7RTS请求发送
Pin8CTS清除发送
Pin9RI振铃显示

注:常用引脚Pin2(RXD)、Pin3(TXD)、Pin5(GND)
另,附:
在这里插入图片描述

四、FPGA–UART硬件设计

由于RS232采用负逻辑电平,而FPGA采用TTL电平,故使用FPGA与外部RS232通信时需要一个电平转换过程,由于本人此实验用的是DE2-115开发板,由开发板自带ZT3232芯片完成电平转换过程。

逻辑0逻辑1
RS232<—>FPGA+15V<—>0V-15V<—>+3.3V

在这里插入图片描述

五、FPGA–UART程序设计

本次串口实验测试例程:开发板与上位机通过RS-232通信,完成数据环回实验。

1、系统框图

在这里插入图片描述

2、协议层

起始位1位,数据位8位,停止位1位,无校验位,波特率115200bps。
在这里插入图片描述

3、顶层模块RTL Viewer

在这里插入图片描述

4、Verilog HDL代码实现

本人在代码中进行较为详细的注释,方便理解与学习,注释有误的地方望批评指正。

  • 顶层模块:
module Top_UART_RS232(
    input           sys_clk_50,       // 全局时钟信号
    input           sys_rst_n,        // 复位信号(低有效)
    input           uart_rxd,         // UART接收端口
    
    output          uart_txd          // UART发送端口
);
    
parameter  CLK_FREQ = 50_000_000;     // 定义系统时钟频率
parameter  UART_BPS = 115200;         // 定义串口波特率
     
wire       uart_en_w;                 // UART发送使能
wire [7:0] uart_data_w;               // UART发送数据

     
uart_recv #(                          // 串口接收模块
    .CLK_FREQ       (CLK_FREQ   ),    // 设置系统时钟频率
    .UART_BPS       (UART_BPS   )     // 设置串口接收波特率
) u_uart_recv(                
    .clk            (sys_clk_50 ), 
    .rst_n          (sys_rst_n  ),
    .uart_rxd       (uart_rxd   ),
    
    .uart_done      (uart_en_w  ),    // 用接收端接收完一帧的标志信号作为发送端的使能信号
    .uart_data      (uart_data_w)
);
    
uart_send #(                          // 串口发送模块
    .CLK_FREQ       (CLK_FREQ   ),    // 设置系统时钟频率
    .UART_BPS       (UART_BPS   )     // 设置串口发送波特率
) u_uart_send(                 
    .clk            (sys_clk_50 ),
    .rst_n          (sys_rst_n  ),  
    .uart_en        (uart_en_w  ),
    .uart_data      (uart_data_w),
    
    .uart_txd       (uart_txd   )
 );

endmodule 
  • 接收模块:
module uart_recv(
   input             clk,               // 全局时钟信号
   input             rst_n,             // 复位信号(低有效)
   input             uart_rxd,          // UART接收端口(RXD) 

   output  reg       uart_done,         // 接收完一帧数据的标志信号
   output  reg[7:0]  uart_data          // 接收的数据
);

parameter CLK_FREQ = 50_000_000;        // 系统时钟频率50MHz
parameter UART_BPS = 115200;            // 串口的波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; // 计算多少个时钟周期为1个波特率周期,在此系统时钟50MHz,波特率115200时,BPS_CNT=434,即计数434个时钟周期为1个波特率周期

reg        uart_rxd_d0;    // 边沿检测-接收延时中间变量d0
reg        uart_rxd_d1;    // 边沿检测-接收延时中间变量d1
reg        rxd_flag;       // 处于接收状态标志信号
reg [ 3:0] rxd_cnt;        // 接收数据计数器,计算每一帧中FPGA接收了多少个波特率周期,比如:当开始传送数据时,即过了起始位,此时rxd_cnt为1,已经过了一个波特率周期
reg [15:0] clk_cnt;        // 系统时钟计数器,计数BPS_CNT个时钟周期为一个波特率周期,若波特率为115200,则计数‘434个时钟周期=1个波特率周期’
reg [ 7:0] rxd_data;       // 接收数据寄存器,串行接收完一帧数据后,先临时寄存到这个寄存器,接收完一帧数据后,统一并行送到输出口,实现串转并

wire       start_flag;     // 检测起始位下降沿来临,给出一个时钟周期标志位(不过由于每次检测到RXD端口出现下降沿,就会有高脉冲,故在接收有效数据过程中也会出现高脉冲,不过不影响正常工作)


// 常用的边沿检测手段,在这里捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_flag = (~uart_rxd_d0) & uart_rxd_d1;

always @(posedge clk or negedge rst_n) // 对UART接收端口的数据延迟两个时钟周期
begin
   if(!rst_n) begin
      uart_rxd_d0 <= 1'b0;
      uart_rxd_d1 <= 1'b0;
   end
   else begin 
      uart_rxd_d0 <= uart_rxd;
      uart_rxd_d1 <= uart_rxd_d0;
   end
end

// 当start_flag脉冲信号(起始位信号)来临时,FPGA进入一帧数据的接收状态,即将rxd_flag置1
// 一帧数据传输最后,在停止位中间时关闭接收状态,即将rxd_flag置0
always @(posedge clk or negedge rst_n)
begin
   if(!rst_n) 
      rxd_flag <= 1'b0;
   else begin 
      if(start_flag == 1'b1)                                // 起始位来临后,进入接收状态,rxd_flag标志位置1
         rxd_flag <= 1'b1;
      else if((rxd_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))  // 当计数完9个波特率周期(1起始位+8数据位),再计数至停止位中间,关闭接收状态信号
         rxd_flag <= 1'b0;
      else
         rxd_flag <= rxd_flag;
   end    
end

// 根据clk_cnt的循环计数,每一帧传输中,累计rxd_cnt个波特率周期。即当处于接收状态时,计算:从起始位开始,累计过了多少个波特率周期
always @(posedge clk or negedge rst_n)
begin
   if(!rst_n) begin
      rxd_cnt <=  4'd0;
      clk_cnt <= 16'd0;
   end
   else if(rxd_flag == 1'b1) begin          // 当系统处于接收状态(rxd_flag为1),根据计多少个时钟周期,算出一个波特率周期,并进行波特率周期累加计算
      if(clk_cnt == BPS_CNT - 1'b1) begin   // 计数BPS_CNT个时钟周期为1个波特率周期                         
         clk_cnt <= 16'd0;
         rxd_cnt <= rxd_cnt + 1'b1;
      end   
      else begin
         clk_cnt <= clk_cnt + 1'b1;
         rxd_cnt <= rxd_cnt;
      end
   end
   else begin
      rxd_cnt <=  4'd0;
      clk_cnt <= 16'd0;
   end
end

// 根据接收数据计数器rxd_cnt来寄存uart接收端口数据,将串口接收到的串行数据转换为并行数据寄存,实现串转并
always @(posedge clk or negedge rst_n)
begin
   if(!rst_n) 
      rxd_data <= 8'd0;
   else if(rxd_flag == 1'b1) begin              // 当系统处于接收状态(rxd_flag为1),进行数据接收
      if(clk_cnt == BPS_CNT/2) begin            // 判断系统时钟计数器计数到数据位中间时(即每次计到波特率周期的中间位置),此时采集的数据是最准确的
         case(rxd_cnt)                          // UART协议先发送数据的低位,再发送数据的高位,故先接收低位再接收高位         
             4'd1: rxd_data[0] <= uart_rxd_d1;  // 寄存数据位最低位
             4'd2: rxd_data[1] <= uart_rxd_d1;
             4'd3: rxd_data[2] <= uart_rxd_d1;
             4'd4: rxd_data[3] <= uart_rxd_d1;
             4'd5: rxd_data[4] <= uart_rxd_d1;
             4'd6: rxd_data[5] <= uart_rxd_d1;
             4'd7: rxd_data[6] <= uart_rxd_d1;
             4'd8: rxd_data[7] <= uart_rxd_d1;  // 寄存数据位最高位
             default:; 
         endcase
      end
      else 
         rxd_data <= rxd_data;
   end   
   else 
      rxd_data <= 8'd0;
end

// 每一帧数据接收完毕之后,给出一个标志信号uart_done,通过uart_data输出接收到的数据 
always @(posedge clk or negedge rst_n)
begin
   if(!rst_n) begin
      uart_data <= 8'd0;
      uart_done <= 1'b0;
   end
   else if(rxd_cnt == 4'd9) begin
      uart_data <= rxd_data;
      uart_done <= 1'b1;
   end
   else begin
      uart_data <= 8'd0;
      uart_done <= 1'b0;
   end
end

endmodule
  • 发送模块:
module uart_send(
    input	      clk,                   // 全局时钟信号
    input         rst_n,                 // 复位信号(低有效)
    input         uart_en,               // 发送使能信号
    input  [7:0]  uart_data,             // 待发送数据
    
    output  reg   uart_txd               // UART发送端口
    );
    
parameter CLK_FREQ = 50_000_000;         // 系统时钟频率50MHz
parameter UART_BPS = 115200;             // 串口的波特率
parameter BPS_CNT  = CLK_FREQ/UART_BPS;  

reg        uart_en_d0; 
reg        uart_en_d1;  
reg [15:0] clk_cnt;                      // 系统时钟计数器
reg [ 3:0] txd_cnt;                      // 发送数据计数器
reg        txd_flag;                     // 发送过程标志信号
reg [ 7:0] txd_data;                     // 寄存要发送数据

wire       en_flag;

// 常用的边沿检测手段,在这里捕获发送使能信号uart_en的上升沿,得到一个时钟周期的脉冲信号
assign en_flag = uart_en_d0 & (~uart_en_d1) ;
                                                
always @(posedge clk or negedge rst_n) begin // 对发送使能信号uart_en延迟两个时钟周期        
    if(!rst_n) begin
        uart_en_d0 <= 1'b0;                                  
        uart_en_d1 <= 1'b0;
    end                                                      
    else begin                                               
        uart_en_d0 <= uart_en;                               
        uart_en_d1 <= uart_en_d0;                            
    end
end

// 当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程          
always @(posedge clk or negedge rst_n) begin         
    if(!rst_n) begin                                  
        txd_flag <= 1'b0;
        txd_data <= 8'd0;
    end 
    else if(en_flag) begin               // 检测到发送使能上升沿                      
        txd_flag <= 1'b1;                // 进入发送过程,标志位txd_flag拉高
        txd_data <= uart_data;           // 寄存待发送的数据
    end
    else if((txd_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2)) begin  // 计数到停止位中间时,停止发送过程                      
        txd_flag <= 1'b0;                // 发送过程结束,标志位txd_flag拉低
        txd_data <= 8'd0;
    end
    else begin
        txd_flag <= txd_flag;
        txd_data <= txd_data;
    end 
end

// 进入发送过程后,启动系统时钟计数器与发送数据计数器
always @(posedge clk or negedge rst_n) begin         
    if(!rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        txd_cnt <= 4'd0;
    end                                                      
    else if(txd_flag) begin                  // 处于发送过程
        if (clk_cnt == BPS_CNT - 1) begin
            clk_cnt <= 16'd0;                // 对系统时钟计数达一个波特率周期后清零
            txd_cnt <= txd_cnt + 1'b1;       // 此时发送数据计数器加1
        end
        else begin
            clk_cnt <= clk_cnt + 1'b1;
            txd_cnt <= txd_cnt;    
        end
    end
    else begin                              //发送过程结束
        clk_cnt <= 16'd0;
        txd_cnt  <= 4'd0;
    end
end

// 根据发送数据计数器来给uart发送端口赋值,实现并转串
always @(posedge clk or negedge rst_n) begin        
    if(!rst_n)  
        uart_txd <= 1'b1;        
    else if(txd_flag)
        case(txd_cnt)
            4'd0: uart_txd <= 1'b0;          //起始位,为一个波特率周期的低电平 
            4'd1: uart_txd <= txd_data[0];   //数据位最低位
            4'd2: uart_txd <= txd_data[1];
            4'd3: uart_txd <= txd_data[2];
            4'd4: uart_txd <= txd_data[3];
            4'd5: uart_txd <= txd_data[4];
            4'd6: uart_txd <= txd_data[5];
            4'd7: uart_txd <= txd_data[6];
            4'd8: uart_txd <= txd_data[7];   //数据位最高位
            4'd9: uart_txd <= 1'b1;          //停止位
            default: ;
        endcase
    else 
        uart_txd <= 1'b1;                    //空闲时发送端口为高电平
end

endmodule

------程序中有用到一种经典的边沿检测手段:
即捕获接收端的下降沿(起始位)或上升沿(发送标志位),从而得到一个时钟周期的脉冲信号,下述以检测下降沿为例说明:

assign start_flag = (~uart_rxd_d0) & uart_rxd_d1;

always @(posedge clk or negedge rst_n) 
begin
   if(!rst_n) begin
      uart_rxd_d0 <= 1'b0;
      uart_rxd_d1 <= 1'b0;
   end
   else begin 
      uart_rxd_d0 <= uart_rxd;
      uart_rxd_d1 <= uart_rxd_d0;
   end
end

将输入的信号,进行延时两个周期,同时取反延时信号0,将取反的延时信号0 “与上” 延时信号1,在此期间会产生一个时钟周期的脉冲信号,时序图如下:
边沿检测时序图
注:若为检测上升沿,则只需把程序中assign start_flag = (~uart_rxd_d0) & uart_rxd_d1;

替换为assign start_flag = uart_rxd_d0 & (~uart_rxd_d1);即可完成检测上升沿,并给出一个时钟周期脉冲信号。

总结

此篇文章总结了本人前段时间使用DE2-115开发板进行的FPGA—UART串口实验,在此以RS-232串口通信为例进行笔记记录,另有RS-485串口通信代码未在此文给出,不过大致类似,可仿照RS-232写出RS-485通信代码。
(文中多有参考正点原子—FPGA串口开发资料)

  • 11
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值