大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全家桶1年46,售后保障稳定
通信协议篇——UART串口通信
1.简介
UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。
2.原理
通信方式
串口的通信方式为串行通信,按位发送和接收字节,将并行数据转换为串行数据流发送出去,将接受的串行数据流转换为并行数据。完成串口通信,只需要三根线,接收(rx)、发送(tx)和地线。确定通信双方的波特率和数据格式一致,是实现串口通信的前提。
一般情况下,设备之间的通信方式可以分为并行通信和串行通信。它们的区别是:
并行通信 | 串行通信 | |
---|---|---|
传输原理 | 数据各个位同时传输 | 数据按位顺序传输 |
优点 | 速度快 | 占用引脚资源少 |
缺点 | 占用引脚资源多 | 速度相对较慢 |
数据格式
UART通信按位发送和接收数据包,每个数据包有固定的格式:
起始位 | 数据位 | 奇偶校验位 | 停止位 | |
---|---|---|---|---|
长度 | 1 | 8 | 1 | 1、1.5、2 |
波特率
这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比,波特率越高,信号能够有效传输的距离越短。
在串口通信中,可以把波特率理解为每秒钟传输的数据包的个数,把比特率理解为每秒钟传输的bit数。但在实际应用中,波特率和比特率常常被混淆,通常在串口通信中常说的波特率,其实指的是比特率,串口通信用比特率描述数据传输速率。例如,常用的比特率有9600bps、14400bps、115200bps等。实际使用中,通常会把单位bps省略,希望大家能够知道这其实指的是比特率。
数据位
数据位传输的是有效的信息,具体内容由用户自己定义。在串口通信中,标准的数据位长度为8位,代表一个字节的长度。扩展的ASCII码是0~255,刚好占8位长度,所以利用8位数据位几乎就可以传输所有字符了。
起始位和停止位
串口通信的两根数据线rx和tx,在空闲状况下都是处于高电平的。所以,一个数据包的起始位通常是‘0’。当检测到接收数据线rx的下降沿时,即表明线路上来了一个数据包,请做好接收准备。起始位非常重要,它使得通信双方能够区分有效数据位和空闲位,这是实现通信的前提。
而停止位则在数据包的末尾,通常是‘1’,使得线路恢复高电平,即空闲状态。停止位也常被当做分隔开两个数据包的关键点,只有当线路恢复高电平,下一个起始位来临时,才能够检测到下降沿。一般规定两个数据包之间至少需要有1到2个空闲位(即停止位长度可为1到2位)。
奇偶校验位
奇偶校验是串口通信中的一种简单的检错方式,它的原理是使得数据位与奇偶校验位的总的‘1’的个数为奇数或偶数,若为奇数则是奇校验,若为偶数则是偶校验。例如,如果数据是“001”,那么对于奇校验,校验位为’0‘;对于偶校验,校验位为’1‘。当然,不使用奇偶校验位也是可以的。
传输时序
即:
起始位 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 校验位 | 停止位 | |
---|---|---|---|---|---|---|---|---|---|---|---|
Value | 0 | X | X | X | X | X | X | X | X | X | 1 |
(数据位采用低位在前,高位在后的传输方式,即LSB First,MSB Last)
具体通信过程
发送过程:
当检测到发送命令的上升沿,启动数据发送。先发送起始位‘0’;再按照低位在前、高位在后的顺序发送数据位;然后根据数据位异或计算得到的奇(偶)校验值,作为校验位发送;最后发送停止位‘1’。具体时序如下:
外部:数据准备→发送命令置‘1’;
内部:检测发送命令上升沿→计数器启动→发送起始位、数据位、奇偶校验位、停止位→计数器清零→检测发送命令上升沿。
接收过程:
当检测到接收数据线rx的下降沿时,表示一个数据包的起始位到来,启动数据接收。先接收起始位,仅作检测作用;再接收数据位,存储在数据寄存器中,当接受完最后一位数据位时,数据读取完成标志置‘1’;再检验奇偶校验位,若不正确则数据错误标志置‘1’;再接收停止位‘1’,若不正确则帧错误标志置‘1’。具体时序如下:
内部:检测rx下降沿→计数器启动→接收起始位‘0’→接收数据位→接收完最后一位数据,数据读取完成标志置‘1’→检验奇偶校验位→接收停止位‘1’→计数器清零→检测rx下降沿;
外部:检测数据读取完成标志上升沿→读取所接收数据。
计数器:
串口通信中,时钟一般设置为波特率的多倍频(如16倍),这是因为只有在每一位的中心位置读取数据,才能确保数据的正确性,靠近位的边沿读取数据的话,容易受到器件本身电平跳变快慢的影响,具有很大的不稳定性。当时钟是16倍的波特率时,每一位的长度有16个时钟周期,我们可以选择在第8个时钟上升沿读取数据,或者取第7、8、9个时钟所读取数据相与,以保证每一位数据的准确性。
计数器的计数范围需要根据时钟和数据包的长度来设置,若一帧数据有11位长,时钟是波特率的16倍,则最大计数值应当设定为176(由于取每一位的中值,所以一般设定为176-8=168),当达到计数器最大值时,表示一帧数据发送或接收完成,此时将所有控制信号复位,计数器清零。
标准接口:
name | description | length |
---|---|---|
clk | 时钟 | 1 |
rst | 复位信号 | 1 |
rx | 接收数据线 | 1 |
dataout | 接收数据 | 8 |
rdsig | 数据读取完成标志 | 1 |
data_error | 数据错误标志 | 1 |
frame_error | 帧错误标志 | 1 |
tx | 发送数据线 | 1 |
datain | 发送数据 | 8 |
wrsig | 数据发送命令 | 1 |
3.程序实现
一个串口模块可以分成3个小模块——时钟模块、接收模块和发送模块。
RTL视图:
时钟模块:
`timescale 1ns / 1ps
//
//Module Name: clkdiv
//说明:串口波特率为9600,采样时钟为154.6KHz(16倍频)
//
module clkdiv
(
input clk50, //系统时钟
input rst_n, //收入复位信号
output reg clkout //采样时钟输出
);
reg [15:0] cnt;
//分频进程, 50Mhz的时钟326分频,得到波特率9600的16倍频
always @(posedge clk50 or negedge rst_n)
begin
if (!rst_n) begin
clkout <=1'b0;
cnt<=0;
end
else if(cnt == 16'd162) begin
clkout <= 1'b1;
cnt <= cnt + 16'd1;
end
else if(cnt == 16'd325) begin
clkout <= 1'b0;
cnt <= 16'd0;
end
else begin
cnt <= cnt + 16'd1;
end
end
endmodule
接收模块:
`timescale 1ns / 1ps
///
// Module name: uartrx.v
// 说明:16个clock接收一个bit,16个时钟采样,取中间的采样值
///
module uartrx
(
input clk, //采样时钟
input rst_n, //复位信号
input rx, //UART数据输入
output reg[7:0] dataout, //接收数据输出
output reg rdsig, //数据读取完成标志
output reg dataerror, //数据出错指示
output reg frameerror //帧出错指示
);
reg [7:0] cnt;
reg rxbuf, rxfall, receive;
parameter paritymode = 1'b0;
reg presult, idle;
always @(posedge clk) //检测线路的下降沿
begin
rxbuf <= rx;
rxfall <= rxbuf & (~rx);
end
//启动串口接收程序
always @(posedge clk)
begin
if (rxfall && (~idle)) begin//检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
receive <= 1'b1;
end
else if(cnt == 8'd168) begin //接收数据完成
receive <= 1'b0;
end
end
//串口接收程序, 16个时钟接收一个bit
always @(posedge clk or negedge rst_n)
begin
if (!rst_n) begin
idle<=1'b0;
cnt<=8'd0;
rdsig <= 1'b0;
frameerror <= 1'b0;
dataerror <= 1'b0;
presult<=1'b0;
end
else if(receive == 1'b1) begin
case (cnt)
8'd0:begin
idle <= 1'b1;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd24:begin //接收第0位数据
idle <= 1'b1;
dataout[0] <= rx;
presult <= paritymode^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd40:begin //接收第1位数据
idle <= 1'b1;
dataout[1] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd56:begin //接收第2位数据
idle <= 1'b1;
dataout[2] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd72:begin //接收第3位数据
idle <= 1'b1;
dataout[3] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd88:begin //接收第4位数据
idle <= 1'b1;
dataout[4] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd104:begin //接收第5位数据
idle <= 1'b1;
dataout[5] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd120:begin //接收第6位数据
idle <= 1'b1;
dataout[6] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd136:begin //接收第7位数据
idle <= 1'b1;
dataout[7] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b1;
end
8'd152:begin //接收奇偶校验位
idle <= 1'b1;
if(presult == rx)
dataerror <= 1'b0;
else
dataerror <= 1'b1; //如果奇偶校验位不对,表示数据出错
cnt <= cnt + 8'd1;
rdsig <= 1'b1;
end
8'd168:begin
idle <= 1'b1;
if(1'b1 == rx)
frameerror <= 1'b0;
else
frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错
cnt <= cnt + 8'd1;
rdsig <= 1'b1;
end
default: begin
cnt <= cnt + 8'd1;
end
endcase
end
else begin
cnt <= 8'd0;
idle <= 1'b0;
rdsig <= 1'b0;
end
end
endmodule
发送模块:
`timescale 1ns / 1ps
//
// Module Name: uarttx
// 说明:16个clock发送一个bit, 一个起始位,8个数据位,一个校验位,一个停止位
//
module uarttx
(
input clk, //UART时钟
input rst_n, //系统复位
input[7:0] datain, //需要发送的数据
input wrsig, //发送命令,上升沿有效
output reg idle, //线路状态指示,高为线路忙,低为线路空闲
output reg tx //发送数据信号
);
reg send;
reg wrsigbuf, wrsigrise;
reg presult; //奇偶校验位
reg[7:0] cnt; //计数器
parameter paritymode = 1'b0; //偶校验
//检测发送命令wrsig的上升沿
always @(posedge clk)
begin
wrsigbuf <= wrsig;
wrsigrise <= (~wrsigbuf) & wrsig;
end
//启动串口发送程序
always @(posedge clk)
begin
if (wrsigrise && (~idle)) //当发送命令有效且线路为空闲时,启动新的数据发送进程
begin
send <= 1'b1;
end
else if(cnt == 8'd168) //一帧数据发送结束
begin
send <= 1'b0;
end
end
//串口发送程序, 16个时钟发送一个bit
always @(posedge clk or negedge rst_n)
begin
if (!rst_n) begin
tx <= 1'b1;
idle <= 1'b0;
cnt<=8'd0;
presult<=1'b0;
end
else if(send == 1'b1) begin
case(cnt) //产生起始位
8'd0: begin
tx <= 1'b0;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd16: begin
tx <= datain[0]; //发送数据0位
presult <= datain[0]^paritymode;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd32: begin
tx <= datain[1]; //发送数据1位
presult <= datain[1]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd48: begin
tx <= datain[2]; //发送数据2位
presult <= datain[2]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd64: begin
tx <= datain[3]; //发送数据3位
presult <= datain[3]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd80: begin
tx <= datain[4]; //发送数据4位
presult <= datain[4]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd96: begin
tx <= datain[5]; //发送数据5位
presult <= datain[5]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd112: begin
tx <= datain[6]; //发送数据6位
presult <= datain[6]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd128: begin
tx <= datain[7]; //发送数据7位
presult <= datain[7]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd144: begin
tx <= presult; //发送奇偶校验位
presult <= datain[0]^paritymode;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd160: begin
tx <= 1'b1; //发送停止位
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd168: begin
tx <= 1'b1;
idle <= 1'b0; //一帧数据发送结束
cnt <= cnt + 8'd1;
end
default: begin
cnt <= cnt + 8'd1;
end
endcase
end
else begin
tx <= 1'b1;
cnt <= 8'd0;
idle <= 1'b0;
end
end
endmodule
顶层模块:
`timescale 1ns/1ps
//module name:uart_demo
//说明: 串口波特率为9600
// 接收数据,rx下降沿开始读取,rdsig上升沿表示数据包读取完成
// 发送数据,检测到wrsig上升沿,开始发送数据
module uart_demo
(
input clk_50MHz,
input rst,
input rx,
input[7:0] datain,
input wrsig,
output clk_uart,
output tx,
output rdsig,
output[7:0] dataout
);
//clkdiv
clkdiv clkdiv_inst
(
.clk50(clk_50MHz),
.rst_n(rst),
.clkout(clk_uart)
);
//uart rx
uartrx uartrx_inst
(
.clk(clk_uart),
.rst_n(rst),
.rx(rx),
.dataout(dataout),
.rdsig(rdsig)
);
//uart tx
uarttx uarttx_inst
(
.clk(clk_uart),
.rst_n(rst),
.datain(datain),
.wrsig(wrsig),
.tx(tx)
);
endmodule
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/210064.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...