UART串口通信

一、通信分类

(一)串行通信

  • 工作方式: 在串行通信中,数据位按照顺序依次传输,也就是说,每一位数据都依次发送或接收。
  • 优点: 相对简单,不容易出现数据冲突。由于只有一个通信通道,线路的布线较为简单。
  • 缺点: 速度相对较慢,因为每次只能传输一个位。不适合大量数据的快速传输。
  • 典型应用:USB,UART,光纤通信。

(二)并行通信

  • 工作方式: 在并行通信中,多个数据位同时传输。每个位都有自己的通道,可以独立传输,从而在同一时刻传输多个位。
  • 优点: 速度相对较快,尤其适合大量数据的快速传输。适用于需要同时处理多个数据位的情况。
  • 缺点: 复杂度较高,占用引脚资源多。容易出现数据同步问题
  • 典型应用:适用于短距离传输,长距离传输成本高

(三)同步通信

  • 工作方式: 在同步通信中,数据的发送和接收是在预定的时钟信号下进行的。数据的发送方和接收方需要共享相同的时钟源,以确保数据的同步。
  • 优点: 数据传输的时序清晰,易于管理和理解。适用于需要实时性和精确同步的应用。
  • 缺点: 高频时钟衰减较快,通信过程易发生信号畸变,相位偏移,因此传输距离不宜过长。
  • 典型应用:I2C,HDMI,CPU与内存接口等。

(四)异步通信

  • 工作方式: 在异步通信中,数据的发送和接收不依赖于共享时钟信号。每个数据块之间有一定的间隔,而不需要严格的时序同步。通信设备会使用起始位和停止位来标识数据块的开始和结束。
  • 优点: 较为灵活,不需要精确的时钟同步。传输媒介简单,传输距离长
  • 缺点: 在高速传输和长距离通信时,由于没有同步的时钟,可能会出现数据失真。接收端解码复杂性增加,可靠性降低
  • 典型应用:UART,USB1.1,USB2.0,光纤通信

(五)单工通信

  • 定义: 单工通信是一种单向通信模式,数据只能在一个方向上传输。通常,一个设备是发送者,而另一个设备是接收者。
  • 特点: 通信双方只能在一个特定的方向上传输信息,其中一个设备只能发送,而另一个只能接收。
  • 例子: 无线电广播、电视广播等。

(六)半双工通信

  • 定义: 半双工通信是一种双向通信模式,但是通信双方不能同时进行数据传输。在任意时刻,一个设备可以发送,而另一个设备可以接收,但不能同时进行。
  • 特点: 数据传输是双向的,但是不能同时发生,需要在发送和接收之间切换。
  • 例子: 对讲机、传统的无线电通信等。

(七)全双工通信

  • 定义: 全双工通信是一种双向通信模式,其中通信的双方能够同时进行发送和接收操作,允许双方同时传输数据。
  • 特点: 双方可以在同一时间内同时发送和接收数据,实现真正的双向通信。
  • 例子: 电话通话、互联网语音通话、典型的计算机网络通信等。

二、UART简介

        UART ( universal asynchronous receiver-transmitter )是一种采用全双工异步串行通信方式的通用异步收发传输器。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

(一)协议层

1.波特率

        波特率是指每秒钟传输的比特数,是通信双方事先约定好的。通信的双方必须使用相同的波特率才能正常通信。单位是 bps (位 / 秒)。常用的波特率有 9600 、 19200 、 38400 、 57600 以及115200 等。如波特率为9600bps的计数器需要计数(8/9600/时钟周期)次才可以传输1Byte数据。

2.数据帧格式

        数据帧的格式包括起始位、数据位、奇偶校验位和停止位的组合。

        空闲:线路上为高电平,即逻辑值为1。

        起始位:逻辑值为0,表示字符帧的开始。

        数据:先发低位再发高位。

        校验位:奇偶校验。

        停止位:高电平代表一个字符帧结束。

(二)物理层

1.电气连接

        UART至少需要两根数据线,即发送线(TX)和接收线(RX)。TX用于发送数据,RX用于接收数据。

2.接口标准

        常见的有TTL,RS232,RS422,RS485等标准。

(三)存在的问题

1.电气接口不统一

        UART 一般直接使用处理器使用的电平,即TTL电平,但是不同处理器之间使用的电平存在差异,所以不同的处理器使用UART通信一般不能直接相连。

        UART没有规定不同器件连接时的连接标准,所以不同器件之间通过UART连接不太方便

2.抗干扰能力差

        UART一般直接使用TTL电平表示0和1,因此抗干扰能力弱,在传输过程中容易出错。

3.传输距离短

        UART通信对于时钟同步非常敏感。在长距离传输中,时钟信号可能会因为传播延迟而引起时序问题。同时随着传输距离的增加,电信号可能会受到噪声、信号失真和衰减等影响,导致信号质量下降。这可能导致接收端无法正确解读发送端的信号,从而限制了传输距离。

三、实验目标

        用开发板与上位机通过串口通信,完成数据环回实验

        上位机是指可以直接发送操作指令的计算机或单片机,一般提供用户操作交互界面并向用户展示反馈数据。典型设备类型:电脑,手机,平板,面板,触摸屏。

        数据环回实验是一种用于测试和验证数据通信链路的方法。在数据环回测试中,发送的数据经过通信链路后被重新接收,然后进行比较以确保数据传输的正确性。

四、硬件设计

       上位机通过USB接口CH340N/P将差分信号传输出去,使用CH340C芯片将USB通信协议转成UART串口通信协议,从RXD端口将数据输送到FPGA,FPGA将数据处理完成后将数据发送回TXD,然后经过CH340芯片和USB将数据传回到上位机,将发送的数据和返回的数据进行对比,如果一样,则说明UART串口通信实验正常。

五、程序设计

(一)时序图

        假设波特率为115200bps,系统时钟为50Mhz,则每传输一比特数据需要(1/115200/20ns=434)个时钟周期,rx_cnt用于统计接受了多少位数据,他每隔434个时钟周期自增1。

(二)接受模块

        需要有外界端口的输入数据uart_rxd,还要有数据寄存器rx_data。当捕捉到uart_rxd的下降沿时,说明有效数据已经可以开始输入了,这个时候对clk_cnt和rx_cnt时钟进行计数。最后将数据给到uart_data端口,uart_data端口将数据给到回环模块。

module receive(
	input 		      clk,
	input 		      rst_n,
				      
	input 		      uart_rxd,
	
	output  		  start_flag,
	output reg 		  rx_flag,
	output reg [3:0]  rx_cnt,
	output reg        uart_done,
	output reg [7:0]  uart_data

);


reg [8:0]  clk_cnt;
reg 	   uart_rxd1;
reg        uart_rxd2;
reg [7:0]  rx_data;//接受数据寄存器

wire       add_clk_cnt;
wire       end_clk_cnt;
wire       add_rx_cnt;
wire       end_rx_cnt;

//捕捉输入数据的下降沿,若捕捉到uart_rxd下降沿,则start_flag拉高

//对输入的uart_rxd数据打两拍,消除亚稳态
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)begin
		uart_rxd1   <= 1'b0;
		uart_rxd2   <= 1'b0;
	end
	else begin
		uart_rxd1   <= uart_rxd;
		uart_rxd2   <= uart_rxd1;
	end
end 

//捕捉输入数据的下降沿,若捕捉到uart_rxd下降沿,则start_flag拉高
assign start_flag = (~uart_rxd) & uart_rxd2;

//当star_flag被拉高时,接受过程标志信号rx_flag被拉高,直到uart_rxd到停止位
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		rx_flag <= 1'b0;
	else begin
		if(start_flag)
			rx_flag <= 1'b1;
		else if((rx_cnt == 4'd9) && (clk_cnt == 9'd267))//计数到停止位中间时,停止接收过程
			rx_flag <= 1'b0;
		else 
			rx_flag <= rx_flag;
	end
end	
	
//clk_cnt计数模块,计数到434
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		clk_cnt <= 9'd0;
	else if(add_clk_cnt)begin
		if(end_clk_cnt)
			clk_cnt <= 9'd0;
		else
			clk_cnt <= clk_cnt +1'b1;
	end
	else
		clk_cnt <= 9'd0;
end
assign add_clk_cnt = rx_flag && (clk_cnt < 9'd434);
assign end_clk_cnt = (clk_cnt == 9'd434) ? 1'b1 : 1'b0; 

//rx_cnt计数模块,当clk_cnt增加到434时,rx_cnt加1,直到9
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		rx_cnt <= 4'd0;
	else if(add_rx_cnt)begin
		if(end_rx_cnt)
			rx_cnt <= 4'd0;
		else
			rx_cnt <= rx_cnt + 1'b1;
	end 
	else
		rx_cnt <= 4'd0;
end
assign add_rx_cnt = rx_flag && (rx_cnt < 4'd9);
assign end_rx_cnt = (rx_cnt == 4'd9) ? 1'b1 : 1'b0;

//当rx_cnt计数到9时,说明该帧数据已传输完毕,uart_done拉高
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		uart_done <= 1'b0;
	else if (rx_cnt == 4'd9)
		uart_done <= 1'b1;
	else
		uart_done <= 1'b0;
end

//接受数据到寄存器,串行数据转并行
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		rx_data <= 8'd0;
	else if(rx_flag)//由于数据传输是串行的,所以无需等到uart_done拉高后再赋值
		if(clk == 8'd267)begin//到时钟中间进行数据赋值
			case(rx_cnt)
				4'd1 : rx_data[0] <= uart_rxd2;
				4'd2 : rx_data[1] <= uart_rxd2;
				4'd3 : rx_data[2] <= uart_rxd2;
				4'd4 : rx_data[3] <= uart_rxd2;
				4'd5 : rx_data[4] <= uart_rxd2;
				4'd6 : rx_data[5] <= uart_rxd2;
				4'd7 : rx_data[6] <= uart_rxd2;
				4'd8 : rx_data[7] <= uart_rxd2;
			endcase
		end
		else
			rx_data <= rx_data;
	else
		rx_data <= 8'd0;
end

//将寄存器中的数据传输到输出端口
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		uart_data <= 8'd0;
	else if (rx_cnt == 4'd9)//寄存器接受数据完成
		uart_data <= rx_data;
	else
		uart_data <= 8'd0;
end

endmodule

(三)发送模块

module send(
	input              clk,
	input              rst_n,
				       
	input              send_en,
	input       [7:0]  data,//从接收端来的数据
	
	output  	       uart_tx_busy, //发送忙状态标志 
	output  reg [7:0]  tx_data,
	output  	       en_flag,
	output  reg        tx_flag,
	output  reg [3:0]  tx_cnt,
	output  reg        uart_tx
);

reg  [8:0]clk_cnt;
reg  send_en1;
reg  send_en2;
wire add_clk_cnt;
wire end_clk_cnt;
wire add_tx_cnt;
wire end_tx_cnt;

//对发送使能信号打两拍
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		send_en1 <= 1'b0;
	    send_en2 <= 1'b0;
	end
	else begin
		send_en1 <= send_en;
		send_en2 <= send_en1;
	end
end

//捕捉使能信号send_en上升沿后
assign en_flag = send_en && (~send_en2);

//当捕捉到使能信号后,发送标志信号被拉高,直到数据被发送完且时钟到停止位
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		tx_flag <= 1'b0;
	else if(en_flag)
		tx_flag <= 1'b1;
	else if((tx_cnt == 4'd9) && (clk_cnt == 9'd400))
		tx_flag <= 1'b0;
	else
		tx_flag <= tx_flag;
end

//当tx_flag信号被拉高后,说明信号正在在发送
assign uart_tx_busy =tx_flag;

//当捕捉到使能信号后,将输入的数据给到输出寄存器tx_data
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		tx_data <= 8'b0;
	else if(en_flag)
		tx_data <= data;
	else if((tx_cnt == 4'd9) && (clk_cnt == 9'd400))
		tx_data <= 1'b0;
	else
		tx_data <= tx_data;
end

//当发送标志信号开始拉高后,计数器clk_cnt开始计数,直到434
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		clk_cnt <= 9'd0;
	else if(add_clk_cnt)begin
		if(end_clk_cnt)
			clk_cnt <= 9'd0;
		else
			clk_cnt <= clk_cnt +1'b1;
	end
	else
		clk_cnt <= 9'd0;
end
assign add_clk_cnt = tx_flag && (clk_cnt < 9'd434);
assign end_clk_cnt = (clk_cnt == 9'd434) ? 1'b1 : 1'b0; 

//当发送标志信号被拉高后,计数器tx_cnt开始计数直到9
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		tx_cnt <= 4'd0;
	else if(add_tx_cnt)begin
		if(end_tx_cnt)
			tx_cnt <= 4'd0;
		else
			tx_cnt <= tx_cnt + 1'b1;
	end 
	else
		tx_cnt <= 4'd0;
end
assign add_tx_cnt = tx_flag && (tx_cnt < 4'd9);
assign end_tx_cnt = (tx_cnt == 4'd9) ? 1'b1 : 1'b0;

//将寄存器tx_data中的数据传给端口uart_tx。并行数据转串行
always @(posedge clk or negedge rst_n)begin
	if(! rst_n)
		uart_tx <= 1'b0;
	else if(tx_flag)begin
		case(tx_cnt)
			4'd0 : uart_tx <= 1'd0;
			4'd1 : uart_tx <= tx_data[0];
			4'd2 : uart_tx <= tx_data[1];
			4'd3 : uart_tx <= tx_data[2];
			4'd4 : uart_tx <= tx_data[3];
			4'd5 : uart_tx <= tx_data[4];
			4'd6 : uart_tx <= tx_data[5];
			4'd7 : uart_tx <= tx_data[6];
			4'd8 : uart_tx <= tx_data[7];
			4'd9 : uart_tx <= 1'd1;
			default:;
		endcase
	end
	else
		uart_tx <= 1'd1;
end

endmodule

(四)回环模块

module loop(
    input	         clk,                  
    input            rst_n,                
     
    input            recv_done,  //接收一帧数据完成标志
    input      [7:0] recv_data,  //接收的数据
     
    input            tx_busy,    //发送忙状态标志      
    output reg       send_en,    //发送使能信号
    output reg [7:0] send_data   //待发送数据

);

reg recv_done1;
reg recv_done2;   
reg tx_ready;   
                                      
//对recv_done打两拍
always @(posedge clk or negedge rst_n) begin         
    if (!rst_n) begin
        recv_done1 <= 1'b0;                                  
        recv_done2 <= 1'b0;
    end                                                      
    else begin                                               
        recv_done1 <= recv_done;                               
        recv_done2 <= recv_done1;                            
    end
end

//捕获recv_done上升沿
assign recv_done_flag = (~recv_done2) & recv_done;

//判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
always @(posedge clk or negedge rst_n) begin         
    if (!rst_n) begin
        tx_ready  <= 1'b0; 
        send_en   <= 1'b0;
        send_data <= 8'd0;
    end                                                      
    else begin                                               
        if(recv_done_flag)begin                 //检测串口接收到数据
            tx_ready  <= 1'b1;                  //准备启动发送过程
            send_en   <= 1'b0;
            send_data <= recv_data;             //寄存串口接收的数据
        end
        else if(tx_ready && (~tx_busy)) begin   //检测串口发送模块空闲
            tx_ready <= 1'b0;                   //准备过程结束
            send_en  <= 1'b1;                   //拉高发送使能信号
        end
    end
end

endmodule

(五)顶层模块

module uart(
	input 			sys_clk,
	input 			sys_rst_n,

    input           uart_rxd,           //UART接收端口
    output          uart_txd            //UART发送端口
);
wire send_en_tmp;
wire tx_busy_tmp;
wire uart_data_tmp;
wire send_data_tmp;
wire uart_done_tmp;


//对输入模块例化
receive u_receive(

	.clk             (sys_clk),
	.rst_n           (sys_rst_n),
					 
	.uart_rxd        (uart_rxd),//在顶层模块的端口接受数据
	.uart_done       (uart_done_tmp),//将接受完数据信号给到回环模块
	.uart_data       (uart_data_tmp)//将数据输出给回环模块
);

//对输出模块进行例化
send u_send(
					
	.clk             (sys_clk),
	.rst_n           (sys_rst_n),
				
	.send_en         (send_en_tmp),//从回环模块接受发送使能信号
	.data            (send_data_tmp),//从回环模块接受数据
             
	.uart_tx_busy    (tx_busy_tmp),//将发送忙给到回环模块
	.uart_tx         (uart_txd)//输出数据给顶层模块
);

//对数据回环模块进行例化
loop u_loop(

	.clk            (sys_clk),      
	.rst_n          (sys_rst_n),      
	              
	.recv_done      (uart_done_tmp),//从输入模块接受输入完成信号
	.recv_data      (uart_data_tmp),//从输出模块接受数据

	.tx_busy        (tx_busy_tmp),//从输出模块接受发送忙信号
	.send_en        (send_en_tmp),//将发送使能信号给到输出模块
	.send_data      (send_data_tmp)//把数据输出到输出模块
);             
     
endmodule

六、仿真

(一)测试文件

`timescale 1 ns/ 1 ps
module tb_uart();

reg sys_clk;
reg sys_rst_n;
reg uart_rxd;
                                             
wire uart_txd;


uart u_uart (

	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.uart_rxd(uart_rxd),
	.uart_txd(uart_txd)
);
initial                                                
begin                                                  
    sys_clk <= 1'b0;
	sys_rst_n <= 1'b0;
	uart_rxd <= 1'b1;
	#40 sys_rst_n <=1'b1;
    #20 uart_rxd <= 1'b0; //起始位
	
	//有效数据位
	#8680 uart_rxd <= 1'b1;//每比特数据需要持续434×20ns
	#8680 uart_rxd <= 1'b0;
	#8680 uart_rxd <= 1'b1;
	#8680 uart_rxd <= 1'b1;
	#8680 uart_rxd <= 1'b0;
	#8680 uart_rxd <= 1'b1;
	#8680 uart_rxd <= 1'b0;
	#8680 uart_rxd <= 1'b0;
	
	#8680 uart_rxd <= 1'b1;//停止位
	
end                                                    
always #10 sys_clk = ~sys_clk;                                                
                                                 
endmodule

(二)管脚约束文件

package require ::quartus::project

#system clock:50Mhz
set_location_assignment PIN_M2 -to sys_clk

#system reset
set_location_assignment PIN_M1 -to sys_rst_n

#USB UART
set_location_assignment PIN_N5  -to uart_rxd
set_location_assignment PIN_M7  -to uart_txd


(三)仿真波形图

        由于在例化文件中给的uart_rxd数据按比特给0和1 ,因此波形图uart_rxd也会以8680ns为周期进行跳变。

        参考正点原子视频,最后调试结果如下:

七、写在最后

        不知不觉已经到了2023年的尾声了,先在这里祝各位朋友新年快乐。我现在坐在日本北九州市早稻田IPS的宿舍里面敲下这段话。无数人质疑我出国念书的决定,无数人质疑我将专业从电气工程及其自动化换到IC领域,这段时间也确实吃了不少的苦头,孤独,不被理解,被质疑,但既然选好了的路,那还是要尽量去把他走好,况且我认为结果并不会差。一开始是觉得数字IC设计HC太少,且学习难度高,因此想先以FPGA入门,我想着把FPGA学的差不多了还是要学学IC设计,毕竟行业天花板更高一些。经过半个来月的系统学习,发现自己对于代码逻辑,硬件设计等方面有极大的欠缺,能听懂顶层模块的设计原理,但是具体到实际的代码,往往感觉力不从心。我知道我学习方法有问题,不喜欢刨根问底,不喜欢反复去看,去重复代码,别人不催我我很难主动学习。因此开始在优快云上面写博客的初衷就在于希望自己能够不断更,这样也有一个外部的力量催着自己赶紧学,再有就是想要以后可以比较方便的复习,当然,如果能够帮到各位朋友那就再好不过啦。接下来的一年中要好好努力呀。在新的一年里,希望能够尽快补全自己的技术栈,然后找两个项目来练练,最好回国找一段实习,希望明年这个时候,我已经手握心仪的offer!

###本文参考正点原子视频,如有侵权,请联系删除。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值