通信协议详解(一):UART串口(协议+数据格式+设计实现)

本文详细介绍了UART串口通信协议,包括数据包格式、波特率和整体架构。通过Verilog实现了UART的发送和接收模块,重点展示了如何进行串并转换以完成数据的发送和接收。在发送模块中,利用计数器实现波特率控制,而在接收模块中,通过检测下降沿开始传输并锁定数据。最后,通过仿真验证了模块的正确性。
摘要由CSDN通过智能技术生成

uart串口通信协议及verilog实现

一、uart串口通信简介

通用异步收发器 UART(Universal Asynchronous Receiver/Transmitter),是一种串行、异步、全双工的通信协议,将所需传输的数据一位接一位地传输,在UART通讯协议中信号线上的状态位高电平代表’1’,低电平代表’0’。其特点是通信线路简单,只要一对传输线就可以实现双向通信,大大降低了成本,但传送速度较慢。

二、串口传输

1、数据协议

在这里插入图片描述
在串口通信中,尤其需要关注的是数据流以及波特率。一个数据流由10个数据位组成,包含1位起始位,7位有效数据位,1位奇偶校验位,1位停止位。uart串口信号线上空闲时常驻高电平,当检测到低电平下降沿时认为数据传输开始,到停止位时数据传输结束,一共10位数据位组成一个数据包。
起始位:通信线路上空闲时为“1”,当检测到“0”即下降沿时,认为数据传输开始
有效数据位:传输开始后传递的需要接收和发送的数据值,可以表示指令或数据
奇偶校验位:奇偶校验,通过来校验传输数据中“1”的个数为奇数个(奇校验)或偶数个(偶校验)来指示传输数据是否正确
停止位:数据传输结束,传输线恢复常“1”状态
此外,还需关注数据传输波特率,波特率表示一秒内传输了多少个码元数量,一般波特率为300,1200,2400,9600,19200,38400,115200等。例如9600 Baud表示一秒内传输了9600个码元信息,当一个码元只含1 bit信息时,波特率=比特率

2、整体架构

串口协议用于与其他模块之间的信息交互,包含接收模块发送模块,信号传输线上根据波特率完成码元的接收与发送,因而接收模块主要完成并串转换,串并转换是接收和发送模块必备的基本功能,发送模块完成并串转换,接收模块完成串并转换。
在这里插入图片描述

波特率与时钟频率关系如下(码元为单bit时):

三、串口传输实现

1、发送模块

代码如下

//===============
//author:LGYSSS
//proj:uart_transimitter
//===============
module uart_tx(
    clk    ,
    rst_n  ,
    data_vld , //有效信号
    data_in,   //输入信号
    data_out,  //并行转串行输出信号
    rdy        //模块有效信号,示意模块准备接收数据
    );


parameter WIDTH	=8;
parameter CLK_CNT= 5208;     //波特率baud=9600下单码元传输 码元宽度为104166  50MHz下一个时钟周期为20ns,传输一个数据位104166/20=5208个clk
parameter  NUM_CNT=10;     //数据位个数


input clk;
input rst_n;
input [WIDTH-1:0]data_in;
input data_vld;
output data_out;
output rdy;
 
reg data_out; 
reg [WIDTH-1:0]data_in_reg;
reg start_tx;
wire [WIDTH-1+2:0] data;

reg   [12:0]     cnt0;
wire             add_cnt0;
wire             end_cnt0;
reg   [3:0]      cnt1;
wire             add_cnt1;
wire             end_cnt1;
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		start_tx<=0;
	else if(start_tx==0&&data_vld==1)
		start_tx<=1;
	else if(end_cnt1)
		start_tx<=0;
end



always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt0 <= 0;
        end
        else if(add_cnt0)begin
            if(end_cnt0)
                cnt0 <= 0;
            else
                cnt0 <= cnt0 + 1;
        end
end

assign add_cnt0 = start_tx;
assign end_cnt0 = add_cnt0 && cnt0== CLK_CNT-1;

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1== NUM_CNT-1;

always  @(posedge clk or negedge rst_n)begin  //在数据有vld效时进行数据锁存,进行下一步串并转换
	if(!rst_n)begin
		data_in_reg<=0;
	end
	else if(start_tx==0&&data_vld==1)begin
		data_in_reg<=data_in;
	end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_out <= 1'b1;
    end
    else if(add_cnt0 && cnt0==1-1)begin
        data_out <= data[cnt1];
    end
end

assign data={1'b1,data_in_reg,1'b0}; //起始位,停止位拼接

assign rdy=((data_vld==1)||(start_tx==1))?1'b0:1'b1;

endmodule

2、接收模块

代码如下:

//===============
//author:LGYSSS
//proj:uart_receiver
//===============
module uart_rx(
    clk    ,
    rst_n  ,
    data_vld ,
    rx_data_in,
    rx_data_out
    );

parameter WIDTH	=8;
parameter CLK_CNT= 5208;     //波特率baud=9600下单码元传输 码元宽度为104166  50MHz下一个时钟周期为20ns,传输一个数据位104166/20=5208个clk
parameter CLK_CNT_MID=2604;
parameter  NUM_CNT=10;

input clk;
input rst_n;
input rx_data_in;

output reg [WIDTH-1:0] rx_data_out;
output reg data_vld;
wire               start_rx;

reg [19:0]         cnt0;
wire               add_cnt0;
wire               end_cnt0;

reg [3:0]          cnt1;
wire               add_cnt1;
wire               end_cnt1;
reg               flag;
reg rx_data_in_reg0;
reg rx_data_in_reg1;
reg rx_data_in_reg2;


always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		rx_data_in_reg0<=0;
		rx_data_in_reg1<=0;
		rx_data_in_reg2<=0;
	end
	else begin
		rx_data_in_reg0<=rx_data_in;
		rx_data_in_reg1<=rx_data_in_reg0;
		rx_data_in_reg2<=rx_data_in_reg1;
	end
end

assign start_rx=(rx_data_in_reg2&&~rx_data_in_reg1)==1; //下降沿检测


always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 1'b0;
        end
        else if(start_rx)begin
            flag <= 1'b1;
        end
        else if(end_cnt1)begin
            flag <= 1'b0;
        end
end
 
always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt0 <= 0;
        end
        else if(add_cnt0)begin
            if(end_cnt0)
                cnt0 <= 0;
            else
                cnt0 <= cnt0 + 1;
        end
end

assign add_cnt0 = flag;
assign end_cnt0 = add_cnt0 && cnt0== CLK_CNT-1;

always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt1 <= 0;
        end
        else if(add_cnt1)begin
            if(end_cnt1)
                cnt1 <= 0;
            else
                cnt1 <= cnt1 + 1;
        end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1== NUM_CNT-1-1;
 
always @(posedge clk or negedge rst_n)begin 
	if(!rst_n)begin
		rx_data_out<=0;
	end
	else if(cnt0==CLK_CNT_MID-1&&cnt1!=0&&flag==1)begin
		rx_data_out[cnt1-1]<=rx_data_in_reg2;
	end
end
 
always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            data_vld <= 1'b0;
        end
        else if(end_cnt1)begin
            data_vld <= 1'b1;
        end
        else begin
            data_vld <= 1'b0;
        end    
end
 
endmodule
 
 

四、串口收发仿真

串口发送模块仿真波形:
在这里插入图片描述

串口接收模块仿真波形:
在这里插入图片描述


总结

看到这里模块功能其实很清晰了,两个模块完成的功能其实就是收发数据的串并转换,传输的内容具体是什么我们是不用关心的,只需要关心数据收发格式,检测到下降沿时开始传输,传输一定数据位时结束当前传输,就完成了一个数据帧的传输,即完成了uart串口通信的一次收/发。.
同时,在本模块设计中笔者是采用锁存器来锁存当vld有效时的信号,当数据流传输速度较大时,我们也可以采取fifo来缓存我们接收的数据,以免造成数据丢失


评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

俩个圆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值