通讯协议之路主要分为两部分,第一部分从理论上面讲解各类协议的通讯原理以及通讯格式,第二部分从具体运用上讲解各类通讯协议的具体应用方法。
后续文章会同时发表在个人博客(jason1016.club)、CSDN;视频会发布在bilibili(UID:399951374)
序、个人简述
- UART通讯为异步全双工通讯
- UART可以转化为RS232、RS485、RS422等协议
- UART数据包:
- 空闲位:1
- 起始位:0
- 数据位:7位,高电平代表’1’,低电平代表’0’(如果没有奇偶位为8位)
- 奇偶校验位:1位
- 停止位:1
START(1)+数据位(7)+奇偶校验位(1)+结束位(1位)
一、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来缓存我们接收的数据,以免造成数据丢失
参考文章:【精选】通信协议详解(一):UART串口(协议+数据格式+设计实现)_串口数据格式-CSDN博客