UART串口协议详解及FPGA实现

UART协议详解

UART(Universal Asynchronous Receiver/Transmitter)即通用异步收发器,是一种串行、异步、全双工的通信协议。

一帧由起始位(1位)、数据位(5-9位)、校验位(0或1位)、停止位(1-2位)等四部分构成。

数据传输线空闲的时候保持高电平,当开始传输时,拉低一个时钟周期,这就是起始位。之后传输数据位,一般从小端开始传输。然后紧随一个可选的奇偶校验位。然后电平拉高,指示一帧发送的终止,即停止位。

采样时钟一般为波特率的数倍(常见的如8倍或16倍),并在中间时刻进行采样,以避免滑码或误码。

在这里插入图片描述

空闲位:

UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平

起始位:

开始进行数据传输时发送方要先发出一个低电平’0’来表示传输字符的开始。因为空闲位一直是高电平所以开始第一次通讯时先发送一个明显区别于空闲状态的信号即为低电平。

数据位:

起始位之后就是要传输的数据,数据可以是5,6,7,8,9位,构成一个字符,一般都是8位。先发送最低位最后发送最高位。

奇偶校验位:

数据位传送完成后,要进行奇偶校验,串口校验分几种方式:

1.无校验(no parity)

2.奇校验(odd parity):如果数据位中’1’的数目是偶数,则校验位为’1’,如果’1’的数目是奇数,校验位为’0’。

3.偶校验(even parity):如果数据为中’1’的数目是偶数,则校验位为’0’,如果为奇数,校验位为’1’。

4.mark parity:校验位始终为1

5.space parity:校验位始终为0

停止位:

数据结束标志,可以是1位,1.5位,2位的高电平。

波特率:

数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率9600bps,115200bps等等,其他标准的波特率是1200,2400,4800,19200,38400,57600。举个例子,如果串口波特率设置为9600bps,那么传输一个比特需要的时间是1/9600≈104.2us。

流程图

UART实际上可以分为UART_tx、UART_rx两个独立的部分,其流程图分别如下:

  • UART_TX

在这里插入图片描述

  • UART_RX

在这里插入图片描述

FPGA代码实现

/******************************FILE HEAD**********************************
 * file_name         : uart.v
 * function          : uart顶层模块
 * author            : 今朝无言
 * version & date    : 2021/9/16 & v1.0
 *************************************************************************/
module uart(
	input               clk_50M,
	input               rst_n,

	output              TXD,
	input               RXD,

	input       [7:0]   wrdat,
	input               tx_en,
	output              tx_done,

	output  reg [7:0]   rddat,
	output              rx_done,

	output              dataerror,			//数据出错标志
	output              frameerror,		    //帧出错标志

    input       [19:0]  Baudrate,	        //波特率
	input       [3:0]   DataLen,		    //数据帧长(小于等于8)     小端
	input               isParity,			//是否有校验位
	input               ParityMode			//奇偶校验标志 0为偶校验 1为奇校验
);								        	//起始位和停止位都为1bit

reg     [19:0]  Baudrate_reg    = 9600;
wire    [23:0]  cnt_ceil        = 3_125_000/Baudrate_reg;
reg     [23:0]  cnt             = 0;
reg             clk             = 0;

always @(negedge rst_n) begin
	Baudrate_reg    <= Baudrate;
end

    always @(posedge clk_50M) begin //驱动时钟,为波特率的16倍
	if(cnt>=cnt_ceil)
		cnt <= 1;
	else
		cnt <= cnt+1;
	
	if(cnt>cnt_ceil>>1)
		clk <= 0;
	else
		clk <= 1;
end

//发送模块
uart_tx uart_tx_inst(
	.clk			(clk),
	.rst_n			(rst_n),
	.tx				(TXD),
	.data			(wrdat),
	.tx_en			(tx_en),
	.tx_done		(tx_done),
	.DataLen_wire	(DataLen),
	.isParity_wire	(isParity),
	.ParityMode_wire(ParityMode)
);

//接收模块
wire    [7:0]   rddat_wire;
uart_rx uart_rx_inst(
	.clk			(clk),
	.rst_n			(rst_n),
	.rx				(RXD),
	.dataout		(rddat_wire),
	.rx_done		(rx_done),
	.dataerror		(dataerror),
	.frameerror		(frameerror),
	.DataLen_wire	(DataLen),
	.isParity_wire	(isParity),
	.ParityMode_wire(ParityMode)
);

always @(posedge rx_done) begin
	rddat   <= rddat_wire;
end

endmodule
//END OF uart.v FILE***************************************************
/******************************FILE HEAD**********************************
 * file_name         : uart_tx.v
 * function          : uart发送子模块
 * author            : 今朝无言
 * version & date    : 2021/9/16 & v1.0
 *************************************************************************/
module uart_tx(
	input               clk,
	input               rst_n,

	output  reg         tx,
	input       [7:0]   data,
	input               tx_en,
	output              tx_done,

	input       [3:0]   DataLen_wire,
	input               isParity_wire,
	input               ParityMode_wire
);

reg         busy;   //线路状态指示,高为线路忙,低为线路空闲
reg         send;
reg         wrsigbuf;
reg         wrsigrise;
reg         presult;
reg [7:0]   cnt;

reg [3:0]   DataLen		= 4'd8;
reg         isParity	= 1'b0;
reg         paritymode	= 1'b0;

reg [3:0]   dataN_send	= 4'd0;    //记录当前将要发送的数据(亦即已发送的数据位个数)

always @(negedge rst_n) begin   //在rst拉低时配置数据位长度、是否使用校验位、奇偶校验
	DataLen		<= DataLen_wire;
	isParity    <= isParity_wire;
	paritymode	<= ParityMode_wire;
end

//检测上升沿
always @(posedge clk) begin
	wrsigbuf    <= tx_en;
	wrsigrise   <= (~wrsigbuf) & tx_en;
end

//发送结束信号
assign tx_done  = ~busy;

//启动串口发送程序
always @(posedge clk) begin
    if(wrsigrise && (~busy)) begin //当发送命令有效且线路为空闲时,启动新的数据发送
		send    <= 1'b1;
	end
   else if(cnt==((DataLen+2+isParity)<<4)-4) begin
		send    <= 1'b0;
	end
end

//串口发送程序,16个时钟发送一个bit
always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		tx          <= 1'b1;
		busy        <= 1'b0;
		cnt         <= 8'd0;
		presult     <= 1'b0;
		dataN_send  <= 4'd0;
	end
	else if(send==1'b1) begin
		if(cnt==8'd0) begin
			tx          <= 1'b0;     //产生起始位
			dataN_send  <= 4'd0;
			presult     <= paritymode;
			busy        <= 1'b1;
			cnt         <= cnt + 8'd1;
		end
		else if(cnt==(dataN_send+1)<<4 && dataN_send<DataLen) begin
            tx          <= data[dataN_send];    //发送数据位  小端
			presult     <= data[dataN_send]^presult;
			busy        <= 1'b1;
			cnt         <= cnt + 8'd1;
			dataN_send  <= dataN_send+1;
		end
		else if(cnt==(DataLen+1)<<4) begin
			if(isParity)begin
				tx      <= presult; //发送奇偶校验位
				busy    <= 1'b1;
				cnt     <= cnt + 8'd1;
			end
			else begin
				tx      <= 1'b1; //发送停止位
				busy    <= 1'b1;
				cnt     <= cnt + 8'd1;
			end
		end
		else if(cnt==((DataLen+2)<<4)-4) begin
			if(isParity) begin
				tx      <= 1'b1; //发送停止位
				busy    <= 1'b1;
				cnt     <= cnt + 8'd1;
			end
			else begin
				tx      <= 1'b1;
				busy    <= 1'b0;
				cnt     <= cnt + 8'd1;
			end
		end
		else if(cnt==((DataLen+3)<<4)-4) begin
			tx      <= 1'b1;
			busy    <= 1'b0;
			cnt     <= cnt + 8'd1;
		end
		else begin
			cnt <= cnt + 8'd1;
		end
	end
	else begin
		tx      <= 1'b1;
		cnt     <= 8'd0;
		busy    <= 1'b0;
	end
end

endmodule
//END OF uart_tx.v FILE***************************************************
/******************************FILE HEAD**********************************
 * file_name         : uart_rx.v
 * function          : uart接收子模块
 * author            : 今朝无言
 * version & date    : 2021/9/16 & v1.0
 *************************************************************************/
module uart_rx(
	input               clk,            //采样时钟
	input               rst_n,

	input               rx,
	output  reg [7:0]   dataout,
	output              rx_done,        //数据接收完成

	output  reg         dataerror,	    //数据出错
	output  reg         frameerror,	    //帧出错

	input       [3:0]   DataLen_wire,
	input               isParity_wire,
	input               ParityMode_wire
);

reg         rdsig;
reg [7:0]   cnt;
reg         rxbuf, rxfall, receive;
reg         presult, busy;

assign rx_done	= ~busy; //上升沿对应一帧接收完成的时刻

reg [3:0]   DataLen     = 4'd8;
reg         isParity    = 1'b0;
reg         paritymode  = 1'b0;

    reg [3:0]   dataN_rec   = 4'd0; //记录当前将要接收的数据位(亦即已接收的数据位个数)

always @(negedge rst_n) begin //在rst拉低时配置数据位长度、是否使用校验位、奇偶校验
	DataLen     <= DataLen_wire;
	isParity    <= isParity_wire;
	paritymode  <= ParityMode_wire;
end

    always @(posedge clk) begin //检测线路的下降沿
	rxbuf   <= rx;
	rxfall  <= rxbuf & (~rx);
end

/**********************启动串口接收程序********************************/
always @(posedge clk) begin
    if (rxfall && (~busy)) begin //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
		receive <= 1'b1;
	end
	else if(cnt == ((DataLen+2+isParity)<<4)-4) begin //接收数据完成
		receive <= 1'b0;
	end
end

/********串口接收程序, 16个时钟接收一个bit*********/
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		busy        <= 1'b0;
		cnt         <= 8'd0;
		rdsig       <= 1'b0;
		frameerror  <= 1'b0;
		dataerror   <= 1'b0;
		presult     <= 1'b0;
		dataout		<= 8'd0;
	end
	else if(receive == 1'b1) begin
        if(cnt == 8'd0) begin //接收起始位
			busy 		<= 1'b1;
			presult 	<= paritymode;
			dataN_rec	<= 4'd0;
			cnt 		<= cnt + 8'd1;
			rdsig 		<= 1'b0;
			dataout		<= 8'd0;
		end
        else if((cnt == ((dataN_rec+1)<<4) + 8) && (dataN_rec < DataLen)) begin //接收数据位   在每个bit的中间采样,避免滑码或误码
			busy                <= 1'b1;
			dataout[dataN_rec]  <= rx;
			presult             <= presult^rx;
			cnt                 <= cnt + 8'd1;
			rdsig               <= 1'b0;
			dataN_rec           <= dataN_rec + 1;
		end
		else if(cnt == ((DataLen+1)<<4) + 8) begin
            if(isParity) begin //如有校验位,接收奇偶校验位
				busy <= 1'b1;
				if(presult == rx)
					dataerror <= 1'b0;
				else
					dataerror <= 1'b1; //如果奇偶校验位不对,表示数据出错
				cnt     <= cnt + 8'd1;
				rdsig   <= 1'b1;
			end
			else begin //否则接收停止位
				busy <= 1'b1;
				if(1'b1 == rx) begin
					frameerror <= 1'b0;
				end
				else begin
					frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错
				end
				cnt     <= cnt + 8'd1;
				rdsig   <= 1'b1;
			end
		end
        else if(cnt == ((DataLen+2)<<4) + 8) begin //有奇偶校验位,则在该时刻接收停止位
			busy <= 1'b1;
			if(rx == 1'b1) begin
				frameerror <= 1'b0;
			end
			else begin
				frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错
			end
			cnt     <= cnt + 8'd1;
			rdsig   <= 1'b1;
		end
		else begin
			cnt <= cnt + 8'd1;
		end
	end
	else begin
		cnt     <= 8'd0;
		busy    <= 1'b0;
		rdsig   <= 1'b0;
	end
end

endmodule
//END OF uart_rx.v FILE***************************************************

仿真

/******************************FILE HEAD**********************************
 * file_name         : uart_tb.v
 * function          : uart模块的TestBench
 * author            : 今朝无言
 * version & date    : 2021/9/17 & v1.0
 *************************************************************************/
`default_nettype none
`timescale 1ns/1ps

module uart_tb();

parameter Baudrate		= 115200;
parameter dataLen		= 8;
parameter isParity		= 1;
parameter ParityMode	= 0;

parameter timeDelay		= 1e9/Baudrate;
parameter cntBaud		= 5e7/Baudrate;

reg				clk_50M;
reg				rst;

reg				RXD;
wire			TXD;

reg		[7:0]	wrdat;
wire	[7:0]	rddat;

reg				tx_en;
wire			tx_done;
wire			rx_done;
wire			dataerror;
wire			frameerror;

//initial vars
reg start_tb;
initial begin
	clk_50M		<= 0;
	RXD 		<= 1;
	tx_en		<= 0;
	start_tb	<= 0;
	rst 		<= 1;	//初始化UART模块
	#20 rst 	<= 0;
	#20 rst 	<= 1;

	start_tb 	<= 1;
end

//main test_bench
initial begin
	wait(start_tb);
	#timeDelay;

	//TXD_test
	write_txd(8'h12);
	write_txd(8'h34);
	write_txd(8'h56);

	//RXD_test
	read_rxd(8'h78);
	read_rxd(8'h9A);

	#(timeDelay*10);
	$stop;
end

//----------------------------------------------------------------------------
always begin					//50MHz时钟
	#10 clk_50M <= 1;
	#10 clk_50M <= 0;
end

reg         clk_Baudrate    = 0;
reg [12:0]  cnt_Baudrate    = 0;
always @(posedge clk_50M) begin
	if(cnt_Baudrate < cntBaud)
        cnt_Baudrate <= cnt_Baudrate + 1;
	else
        cnt_Baudrate <= 0;
	
	if(cnt_Baudrate < cntBaud/2)
        clk_Baudrate <= 1;
	else
        clk_Baudrate <= 0;
end

//测试TX模块
task write_txd;
	input [dataLen-1:0] data;
	
	begin
		wrdat				<= data;
		tx_en				<= 1;
		#timeDelay	tx_en	<= 0;
		#(timeDelay*(dataLen+(isParity?1:0)+1));
	end
endtask

//测试RX模块
task read_rxd;
	input [dataLen-1:0] data;

	begin
		RXD	<= 0;
		#timeDelay;

		begin : SendData
			integer i;
			for(i=0; i<dataLen; i=i+1) begin
			  RXD <= data[i];
			  #timeDelay;
			end
		end
		
		if(isParity) begin
			RXD <= ^data;
			#timeDelay;
		end

		RXD <= 1;
		#timeDelay;
	end
endtask

uart uart_inst(
	.clk_50M	(clk_50M),
	.rst_n		(rst),
	.TXD		(TXD),
	.RXD		(RXD),
	.wrdat		(wrdat),
	.tx_en		(tx_en),
	.tx_done	(tx_done),
	.rddat		(rddat),
	.rx_done	(rx_done),
	.dataerror	(dataerror),
	.frameerror	(frameerror),
	.Baudrate	(Baudrate),
	.DataLen	(dataLen),
	.isParity	(isParity),
	.ParityMode	(ParityMode)
);

endmodule
//END OF uart_tb.v FILE***************************************************

仿真结果:
在这里插入图片描述

上板测试

测试思路:PC端通过串口调试助手向FPGA发送数据,FPGA端接收到数据后,转发这个数据回PC端,作为应答信号。即进行回环测试。

测试结果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今朝无言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值