首页 >> 大全

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

2023-12-30 大全 25 作者:考证青年

文章目录 三、UART--物理层 四、FPGA--UART硬件设计五、FPGA--UART程序设计 总结

前言

记录本人第一次CSDN发帖,开启FPGA学习记录之旅,后期会不定时更新

---------此篇文章主要内容:FPGA-UART学习笔记(以RS-232通信为例)

一、UART是什么

UART( and ),一种采用异步串行通信方式的通用异步收发传输器。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据(实现数据的串并转换)。

(注:发送过程为 “先发送低位,后发送高位”)

1.协议层:通信协议,包括数据格式、传输速率等。

2.物理层:接口类型、电平标准等。

附:常见的串行通信接口:

二、UART–协议层 1、数据格式

UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。

异步串行通信数据格式:

1.空闲状态:在没有数据传输时,数据线处于空闲状态,保持为高电平。

2.起始位:在空闲状态下,一旦检测到数据线由1->0,则认为0开始的地方为起始位,标志着一帧数据的开始,即接收方检测到起始位时,就开始准备接收数据。

3.数据位:在上图中有7个数据位,实际上数据位可以为5/6/7/8位,而8位才是最常用的。

4.校验位:校验位用来检测数据传输过程中是否出错,分为奇校验和偶校验。

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

2、传输速率

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

三、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线半双工;多点双向通信

能够实现多个发送、接收设备双向通信

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

DB9接口定义如下:

引脚定义引脚名称功能说明

Pin1

DCD

数据载波检测

Pin2

RXD

接受数据

Pin3

TXD

发送数据

Pin4

DTR

数据终端准备

Pin5

GND

地线

Pin6

DSR

数据准备就绪

Pin7

RTS

请求发送

Pin8

CTS

清除发送

Pin9

RI

FPGA_Verilog学习之旅(1)---浅谈UART_FPGA_Verilog学习之旅(1)---浅谈UART_

振铃显示

注:常用引脚Pin2(RXD)、Pin3(TXD)、Pin5(GND)

另,附:

四、FPGA–UART硬件设计

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

逻辑0逻辑1

+15V0V

-15V+3.3V

五、FPGA–UART程序设计

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

1、系统框图

2、协议层

起始位1位,数据位8位,停止位1位,无校验位,波特率。

3、顶层模块RTL

4、 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接收端口的数据延迟两个时钟周期
beginif(!rst_n) beginuart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;endelse 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)
beginif(!rst_n) rxd_flag <= 1'b0;else begin if(start_flag == 1'b1)                                // 起始位来临后,进入接收状态,rxd_flag标志位置1rxd_flag <= 1'b1;else if((rxd_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))  // 当计数完9个波特率周期(1起始位+8数据位),再计数至停止位中间,关闭接收状态信号rxd_flag <= 1'b0;elserxd_flag <= rxd_flag;end    
end// 根据clk_cnt的循环计数,每一帧传输中,累计rxd_cnt个波特率周期。即当处于接收状态时,计算:从起始位开始,累计过了多少个波特率周期
always @(posedge clk or negedge rst_n)
beginif(!rst_n) beginrxd_cnt <=  4'd0;clk_cnt <= 16'd0;endelse 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 beginclk_cnt <= clk_cnt + 1'b1;rxd_cnt <= rxd_cnt;endendelse beginrxd_cnt <=  4'd0;clk_cnt <= 16'd0;end
end// 根据接收数据计数器rxd_cnt来寄存uart接收端口数据,将串口接收到的串行数据转换为并行数据寄存,实现串转并
always @(posedge clk or negedge rst_n)
beginif(!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:; endcaseendelse rxd_data <= rxd_data;end   else rxd_data <= 8'd0;
end// 每一帧数据接收完毕之后,给出一个标志信号uart_done,通过uart_data输出接收到的数据 
always @(posedge clk or negedge rst_n)
beginif(!rst_n) beginuart_data <= 8'd0;uart_done <= 1'b0;endelse if(rxd_cnt == 4'd9) beginuart_data <= rxd_data;uart_done <= 1'b1;endelse beginuart_data <= 8'd0;uart_done <= 1'b0;end
endendmodule

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) beginuart_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;           // 寄存待发送的数据endelse if((txd_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2)) begin  // 计数到停止位中间时,停止发送过程                      txd_flag <= 1'b0;                // 发送过程结束,标志位txd_flag拉低txd_data <= 8'd0;endelse begintxd_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) beginclk_cnt <= 16'd0;                // 对系统时钟计数达一个波特率周期后清零txd_cnt <= txd_cnt + 1'b1;       // 此时发送数据计数器加1endelse beginclk_cnt <= clk_cnt + 1'b1;txd_cnt <= txd_cnt;    endendelse 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: ;endcaseelse uart_txd <= 1'b1;                    //空闲时发送端口为高电平
endendmodule

------程序中有用到一种经典的边沿检测手段:

即捕获接收端的下降沿(起始位)或上升沿(发送标志位),从而得到一个时钟周期的脉冲信号,下述以检测下降沿为例说明:

assign start_flag = (~uart_rxd_d0) & uart_rxd_d1;always @(posedge clk or negedge rst_n) 
beginif(!rst_n) beginuart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;endelse begin uart_rxd_d0 <= uart_rxd;uart_rxd_d1 <= uart_rxd_d0;end
end

将输入的信号,进行延时两个周期,同时取反延时信号0,将取反的延时信号0 “与上” 延时信号1,在此期间会产生一个时钟周期的脉冲信号,时序图如下:

注:若为检测上升沿,则只需把程序中 = (~) & ;

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

总结

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

(文中多有参考正点原子—FPGA串口开发资料)

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了