FPGA基础之UART通信

串口协议简介
串口通讯(Universal Asynchronous Receiver/Transmitter,UART)是一种异步传输的通用数据总线,包括了RS232、RS485等接口标准和总线标准,传输简单,将数据在串行通信与并行通信之间转换,可以实现全双工发送和接收。 UART的工作原理是将数据的每一个bit从低到高,依次输出到总线上,其通信协议时序如下:
在这里插入图片描述

图1 串口时序

如上图所示,为串口协议进行数据传输的时序状态。一个完整的字节传输包括了1个起始位,8个数据位和1个停止位,总共10位数据来计算。其中,数据位可设置为5、6、7或者8(默认)。波特率(Baud)表示串口设备数据传输的速率,在图中,垂直虚线表示进行数据的切换,中间的黑点表示数据采样点。典型的波特率设置一般为9600、19200、38400、57600和115200,波特率越高,数据的传输越快。

串口发送

根据串口通信的时序,在FPGA上实现串口的发送设计。
波特率时钟计算
在进行串口发送时,需要进行波特率的时钟计算,来确定当前发送的数据时钟。首先计算出对应波特率的时钟周期,假设波特率为115200,其时钟周期为1000000000/115200=8680.6ns,根据当前的系统时钟(sys_clk)频率,算出时钟计数值。波特率时钟计数值的代码如下:

	localparam  BUAT_RATE9600 = 3'd0,
				BUAT_RATE19200 = 3'd1,
				BUAT_RATE38400 = 3'd2,
				BUAT_RATE57600 = 3'd3,
				BUAT_RATE115200 = 3'd4;

	localparam  BUAT_SENDRATE9600 = 17'd104167/SYS_CLOCK_PERIOD - 1,
				BUAT_SENDRATE19200 = 16'd52083/SYS_CLOCK_PERIOD - 1,
				BUAT_SENDRATE38400 = 15'd26041/SYS_CLOCK_PERIOD - 1,
				BUAT_SENDRATE57600 = 15'd17361/SYS_CLOCK_PERIOD - 1,
				BUAT_SENDRATE115200 = 14'd8680/SYS_CLOCK_PERIOD - 1;

	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			bsp_DR <= BUAT_SENDRATE9600;
		else begin
			case(buad_set_i)
			BUAT_RATE9600: bsp_DR <= BUAT_SENDRATE9600;
			BUAT_RATE19200: bsp_DR <= BUAT_SENDRATE19200;
			BUAT_RATE38400: bsp_DR <= BUAT_SENDRATE38400;
			BUAT_RATE57600: bsp_DR <= BUAT_SENDRATE57600;
			BUAT_RATE115200: bsp_DR <= BUAT_SENDRATE115200;
			default:bsp_DR <= BUAT_SENDRATE9600;
			endcase
		end
	end

分频计数
在计算出波特率的计数值后,利用计数器来生成波特率的时钟,实现代码如下:

	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			bsp_div_cnt <= 13'd0;
		else if(uart_state == 1'b0)
			bsp_div_cnt <= 13'd0;
		else if(bsp_div_cnt == bsp_DR)
			bsp_div_cnt <= 13'd0;
		else
			bsp_div_cnt <= bsp_div_cnt + 13'd1;
	end
	
	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			bsp_clk <= 1'b0;
		else if(bsp_div_cnt == 13'd1)
			bsp_clk <= 1'b1;
		else
			bsp_clk <= 1'b0;
	end

状态输出
在每一次串口发送完成之后,发送一个时钟周期的完成信号,代码如下:

	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			bsp_cnt <= 4'd0;
		else if(bsp_cnt == 4'd11)
			bsp_cnt <= 4'd0;
		else if(bsp_clk)
			bsp_cnt <= bsp_cnt + 4'd1;
		else
			bsp_cnt <= bsp_cnt;
	end
	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			tx_done <= 1'b0;
		else if(bsp_cnt == 4'd11)
			tx_done <= 1'b1;
		else
			tx_done <= 1'b0;
	end

	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			uart_state <= 1'b0;
		else if(send_en_i)
			uart_state <= 1'b1;
		else if(bsp_cnt == 4'd11)
			uart_state <= 1'b0;
		else
			uart_state <= uart_state;
	end

数据输出
发送数据的输出则是依靠一个十选一的多路选择器,将数据发送到总线上。在进行传输之前,获取到传输的数据,在选择器中选择输出。代码实现如下:

	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			tx_data <= 8'b0;
		else if(send_en_i)
			tx_data <= send_data_i;
		else
			tx_data <= tx_data;
	end
	
	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			uart_tx <= 1'b1;
		else begin
			case(bsp_cnt)
				0: uart_tx <= 1'b1;
				1: uart_tx <= START_BIT;
				2: uart_tx <= tx_data[0];
				3: uart_tx <= tx_data[1];
				4: uart_tx <= tx_data[2];
				5: uart_tx <= tx_data[3];
				6: uart_tx <= tx_data[4];
				7: uart_tx <= tx_data[5];
				8: uart_tx <= tx_data[6];
				9: uart_tx <= tx_data[7];
				10: uart_tx <= STOP_BIT;
				default uart_tx <= 1'b1;
			endcase
		end
	end

串口接收

根据串口协议的时序图,可知在每一位的中点即图中黑点位置进行采样,数据是最准确的。但在实际的电路中,可能因为干扰等其他因素的影响,只采样一次可能采集到错误的数据,因此,在进行串口接收时,对每一位进行多次采样求概率的方式判定状态,如图所示:
在这里插入图片描述

图2 接收判定

如图所示,将每bit数据分成16份,只在中间的6份中进行采样,采样6次,取采样次数多的电平作为采样结果。
边沿获取
输入的数据作为异步信号,需要同步到系统时钟的时钟域,处理方式如下:

	//信号同步
	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)begin
			s0_uart_rx <= 1'b0;
			s1_uart_rx <= 1'b0;
		end
		else begin
			s0_uart_rx <= uart_rx_i;
			s1_uart_rx <= s0_uart_rx;
		end
	end
	
	//边沿获取
	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)begin
			tmp0_uart_rx <= 1'b0;
			tmp1_uart_rx <= 1'b0;
		end
		else begin
			tmp0_uart_rx <= s1_uart_rx;
			tmp1_uart_rx <= tmp0_uart_rx;
		end
	end
	
	assign uart_rx_falling = (!tmp0_uart_rx) & tmp1_uart_rx;

采样时钟计数
按照之前的发送波特率计数的方式,需要在每一个周期再除以16,作为采样时钟的计数值,例如波特率115200,周期为8680ns,除以16后在除以系统时钟周期,作为波特率115200的采样计数值,代码如下:

	localparam  BUAT_RATE9600 = 3'd0,
				BUAT_RATE19200 = 3'd1,
				BUAT_RATE38400 = 3'd2,
				BUAT_RATE57600 = 3'd3,
				BUAT_RATE115200 = 3'd4;
								
	localparam  CNT_NUM_9600 = 13'd6510/SYS_CLOCK_PERIOD - 1,
				CNT_NUM_19200 = 12'd3225/SYS_CLOCK_PERIOD - 1,
				CNT_NUM_38400 = 11'd1627/SYS_CLOCK_PERIOD - 1,
				CNT_NUM_57600 = 11'd1085/SYS_CLOCK_PERIOD - 1,
				CNT_NUM_115200 = 10'd542/SYS_CLOCK_PERIOD - 1;
				
	always@(posedge sys_clk_i or negedge rst_n)begin
		if(!rst_n)
			bsp_DR <= CNT_NUM_9600;
		else begin
			case(buad_set_i)
				BUAT_RATE9600: bsp_DR <= CNT_NUM_9600;
				BUAT_RATE19200: bsp_DR <= CNT_NUM_19200;
				BUAT_RATE38400: bsp_DR <= CNT_NUM_38400;
				BUAT_RATE57600: bsp_DR <= CNT_NUM_57600;
				BUAT_RATE115200: bsp_DR <= CNT_NUM_115200;
				default: bsp_DR <= CNT_NUM_9600;
			endcase
		end
	end

分频计数
串口接收的分频计数与发送的分频计数基本相同,唯一的差别是在获取到下降沿时,进行连续的采样,若采样到两次高电平,则表示当前的下降沿时干扰信号,并非实际的传输信号,则停止计数。代码实现如下:

localparam BSP_CNT_NUM = 8'd159;
	
always@(posedge sys_clk_i or negedge rst_n)begin
	if(!rst_n)
		bsp_cnt <= 8'd0;
	else if((bsp_cnt == BSP_CNT_NUM) | ((bsp_cnt == 8'd12) && (start_bit > 2)))
		bsp_cnt <= 8'd0;
	else if(bsp_clk)
		bsp_cnt <= bsp_cnt + 8'd1;
	else		bsp_cnt <= bsp_cnt;
end
	
always@(posedge sys_clk_i or negedge rst_n)begin
	if(!rst_n)
		rx_done <= 1'b0;
	else if(bsp_cnt == BSP_CNT_NUM)
		rx_done <= 1'b1;
	else
		rx_done <= 1'b0;
end

数据接收
对数据的接收,也是采用多路选择器的方式,对6次采样的数据叠加,判断每一bit的电平状态,实现代码如下:

reg [2:0]start_bit,stop_bit;	//起始位停止位
reg [2:0]r_data_collect[7:0];	//采集到的数据
reg [7:0]tmp_r_data;

always@(posedge sys_clk_i or negedge rst_n)begin
	if(!rst_n)begin
		start_bit <= 3'd0;
		r_data_collect[0] <= 3'd0;
		r_data_collect[1] <= 3'd0;
		r_data_collect[2] <= 3'd0;
		r_data_collect[3] <= 3'd0;
		r_data_collect[4] <= 3'd0;
		r_data_collect[5] <= 3'd0;
		r_data_collect[6] <= 3'd0;
		r_data_collect[7] <= 3'd0;
		stop_bit <= 3'd0;
	end
	else if(bsp_clk)begin
		case(bsp_cnt)
			0:
				begin
					start_bit <= 3'd0;
					r_data_collect[0] <= 3'd0;
					r_data_collect[1] <= 3'd0;
					r_data_collect[2] <= 3'd0;
					r_data_collect[3] <= 3'd0;
					r_data_collect[4] <= 3'd0;
					r_data_collect[5] <= 3'd0;
					r_data_collect[6] <= 3'd0;
					r_data_collect[7] <= 3'd0;
					stop_bit <= 3'd0;
				end
			6,7,8,9,10,11: start_bit <= start_bit + s1_uart_rx;
			22,23,24,25,26,27: r_data_collect[0] <= r_data_collect[0] + s1_uart_rx;
			38,39,40,41,42,43: r_data_collect[1] <= r_data_collect[1] + s1_uart_rx;
			54,55,56,57,58,59: r_data_collect[2] <= r_data_collect[2] + s1_uart_rx;
			70,71,72,73,74,75: r_data_collect[3] <= r_data_collect[3] + s1_uart_rx;
			86,87,88,89,90,91: r_data_collect[4] <= r_data_collect[4] + s1_uart_rx;
			102,103,104,105,106,107: r_data_collect[5] <= r_data_collect[5] + s1_uart_rx;
			118,119,120,121,122,123: r_data_collect[6] <= r_data_collect[6] + s1_uart_rx;
			134,135,136,137,138,139: r_data_collect[7] <= r_data_collect[7] + s1_uart_rx;
			150,151,152,153,154,155: stop_bit <= stop_bit + s1_uart_rx;
			default:
				begin
					start_bit <= start_bit;
					r_data_collect[0] <= r_data_collect[0];
					r_data_collect[1] <= r_data_collect[1];
					r_data_collect[2] <= r_data_collect[2];
					r_data_collect[3] <= r_data_collect[3];
					r_data_collect[4] <= r_data_collect[4];
					r_data_collect[5] <= r_data_collect[5];
					r_data_collect[6] <= r_data_collect[6];
					r_data_collect[7] <= r_data_collect[7];											
					stop_bit <= stop_bit;
			end
		endcase
	end	
end
	
always@(posedge sys_clk_i or negedge rst_n)begin
	if(!rst_n)
		tmp_r_data <= 8'b0;
	else if(bsp_cnt == BSP_CNT_NUM)begin
		tmp_r_data[0] <= r_data_collect[0][2];
		tmp_r_data[1] <= r_data_collect[1][2];
		tmp_r_data[2] <= r_data_collect[2][2];
		tmp_r_data[3] <= r_data_collect[3][2];
		tmp_r_data[4] <= r_data_collect[4][2];
		tmp_r_data[5] <= r_data_collect[5][2];
		tmp_r_data[6] <= r_data_collect[6][2];
		tmp_r_data[7] <= r_data_collect[7][2];
	end
	else
		tmp_r_data <= tmp_r_data;
end

仿真测试

创建激励文件,对串口功能进行仿真测试,仿真结果如下:
在这里插入图片描述
如图所示,TX发送的数据为01,tx的发送时序在uart_tx_o上可看出,bsp_clk作为数据bit位的传输计数。在RX中,rx_data_o表示接收到的数据,rx_done_o表示接收完成信号。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值