uart串口通信协议标准_串口通信协议

uart串口通信协议标准_串口通信协议通信协议篇——UART串口通信

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新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

Jetbrains全家桶1年46,售后保障稳定

接收模块:

`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账号...

(0)


相关推荐

  • 输入法个性化怎么设置_手机输入键盘怎么个性化设置

    输入法个性化怎么设置_手机输入键盘怎么个性化设置个性化设置技巧(补充输入法)子墨居士前言本次的内容大部分为推送。本打算自己推荐几款比较好用的桌面整理软件,然而最近事情越来越多,这部分的内容分享已经有很多不错的文章。就允许我偷一次懒呗(…

    2022年10月29日
  • [Android] Bitmap的内存计算

    本文聚焦的问题1、Bitmap中像素数据占用多大内存?如何计算?2、不同图片来源对内存大小有什么影响?Bitmap bitmap = Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);依然以如此声明一个bitmap为例,参数就决定了bitmap的大小。(以Android 8.0+平台为例,这行代码执行后占用的总内存大小=bitmap在…

  • [Protel99SE]打印PDF「建议收藏」

    [Protel99SE]打印PDF「建议收藏」写在前面Protel仍然占据着不少工程师的手心,最近在接触一些产品转生产的事宜。从研发拿到一些工程资料后,需要转化出来给生产使用。于是就遇上了将Protel设计文件输出成PDF文件的问题。准备工作想要打印输出PDF文件,最方便的就是装一个PDF打印机。推荐安装Foxit阅读器,自动全安装PDF打印机。之后用任何软件做文档,想输出PDF文件,可以直接调用打印功能,然后打印到Fox

  • c语言系统主函数流程图,c语言流程图【调解方式】

    c语言系统主函数流程图,c语言流程图【调解方式】虽然电脑已经很普遍了,但是一些年长的人对电脑的操作不是很熟悉,比如在使用win7系统时一旦遇到c语言流程图时就懵了,对于c语言流程图处理起来相对来说较简单,按照我们的步骤处理c语言流程图很容易上手,c语言流程图具体处理方法如下:c语言的流程图怎么画?答:如果会编程序而不会画流程图,建议先把自己的程序研究一遍。若是画主程序流程图,那就需看懂主函数的程序,按照main()函数中的具体书写过程来画,例…

    2022年10月31日
  • php字符串大小写转换函数

    php字符串大小写转换函数php字符串大小写转换函数

  • 激活函数的作用「建议收藏」

    激活函数的作用「建议收藏」激活函数是用来加入非线性因素的,解决线性模型所不能解决的问题首先我们有这个需求,就是二分类问题,如我要将下面的三角形和圆形点进行正确的分类,如下图:利用我们单层的感知机,用它可以划出一条线,把平面分割开:上图直线是由得到,那么该感知器实现预测的功能步骤如下,就是我已经训练好了一个感知器模型,后面对于要预测的样本点,带入模型中,如果y>0,那么就说明是直线的右侧,也就…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号