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

文章目录三、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
【1FPGA_Verilog学习之旅---浅谈UART】CTS
清除发送
Pin9
RI

1  FPGA_Verilog学习之旅---浅谈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(inputsys_clk_50,// 全局时钟信号inputsys_rst_n,// 复位信号(低有效)inputuart_rxd,// UART接收端口outputuart_txd// UART发送端口);parameterCLK_FREQ = 50_000_000;// 定义系统时钟频率parameterUART_BPS = 115200;// 定义串口波特率wireuart_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(inputclk,// 全局时钟信号inputrst_n,// 复位信号(低有效)inputuart_rxd,// UART接收端口(RXD) outputreguart_done,// 接收完一帧数据的标志信号outputreg[7:0]uart_data// 接收的数据);parameter CLK_FREQ = 50_000_000;// 系统时钟频率50MHzparameter UART_BPS = 115200;// 串口的波特率localparam BPS_CNT = CLK_FREQ/UART_BPS; // 计算多少个时钟周期为1个波特率周期,在此系统时钟50MHz,波特率115200时,BPS_CNT=434,即计数434个时钟周期为1个波特率周期reguart_rxd_d0;// 边沿检测-接收延时中间变量d0reguart_rxd_d1;// 边沿检测-接收延时中间变量d1regrxd_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;// 接收数据寄存器,串行接收完一帧数据后,先临时寄存到这个寄存器,接收完一帧数据后,统一并行送到输出口 , 实现串转并wirestart_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;endend// 当start_flag脉冲信号(起始位信号)来临时,FPGA进入一帧数据的接收状态,即将rxd_flag置1// 一帧数据传输最后 , 在停止位中间时关闭接收状态 , 即将rxd_flag置0always @(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;endend// 根据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;endelse beginclk_cnt <= clk_cnt + 1'b1;rxd_cnt <= rxd_cnt;endendelse beginrxd_cnt <=4'd0;clk_cnt <= 16'd0;endend// 根据接收数据计数器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;endelse 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;endendendmodule
module uart_send(inputclk,// 全局时钟信号inputrst_n,// 复位信号(低有效)inputuart_en,// 发送使能信号input[7:0]uart_data,// 待发送数据outputreguart_txd// UART发送端口);parameter CLK_FREQ = 50_000_000;// 系统时钟频率50MHzparameter UART_BPS = 115200;// 串口的波特率parameter BPS_CNT= CLK_FREQ/UART_BPS;reguart_en_d0; reguart_en_d1;reg [15:0] clk_cnt;// 系统时钟计数器reg [ 3:0] txd_cnt;// 发送数据计数器regtxd_flag;// 发送过程标志信号reg [ 7:0] txd_data;// 寄存要发送数据wireen_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;endelse beginuart_en_d0 <= uart_en;uart_en_d1 <= uart_en_d0;endend// 当脉冲信号en_flag到达时,寄存待发送的数据 , 并进入发送过程always @(posedge clk or negedge rst_n) beginif(!rst_n) begintxd_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) beginif(!rst_n) beginclk_cnt <= 16'd0;txd_cnt <= 4'd0;endelse 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;endend// 根据发送数据计数器来给uart发送端口赋值,实现并转串always @(posedge clk or negedge rst_n) beginif(!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;endend
将输入的信号,进行延时两个周期,同时取反延时信号0,将取反的延时信号0 “与上” 延时信号1 , 在此期间会产生一个时钟周期的脉冲信号,时序图如下:
注:若为检测上升沿,则只需把程序中= (~) & ;
替换为=& (~);即可完成检测上升沿,并给出一个时钟周期脉冲信号 。
总结
此篇文章总结了本人前段时间使用DE2-115开发板进行的FPGA—UART串口实验,在此以RS-232串口通信为例进行笔记记录,另有RS-485串口通信代码未在此文给出,不过大致类似,可仿照RS-232写出RS-485通信代码 。
(文中多有参考正点原子—FPGA串口开发资料)