UART通信
- UART通信简介
- verilog实现
- 顶层模块
- 接收模块
- 发送模块
- 仿真波形
- 实测结果
UART通信简介
即通用异步收发器(Universal Asynchronous Receiver/Transmitter),是一种串行、异步、全双工的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。
数据传输速率:波特率(单位:baud,波特)
常见波特率有:1200、2400、4800、19200、38400、57600等,最常用的是9600和115200。
数据通信格式如下:包含一个起始位、n个数据位(通常为8位,即一个字节)、1个校验位、1个结束位
其中各位的意义如下:
空闲位:
UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据传输。
起始位:
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
数据位:
起始位之后就是我们所要传输的数据,数据位可以是5、6、7、8,9位等,构成一个字符(一般都是8位)。如ASCII码(7位),扩展BCD码(8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
奇偶校验位:
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。校验位其实是调整个数,串口校验分几种方式:
- 1、无校验(no parity)。
- 2、奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果“1”的数目是奇数,校验位为“0”。
- 3、偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果为奇数,校验位为“1”。
- 4、mark parity:校验位始终为1(不常用)。
- 5、parity:校验位始终为0(不常用)。
停止位:
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。
verilog实现
本次UART工程实现采用环路测试,为了达到通用模块的标准,采用参数传递的形式来实现工程,可修改的参数有:
1.时钟频率。对应不同的开发平台,其芯片的工作频率不同,可直接通过修改参数CLK_FRE来实现频率匹配,参数缺省为50MHz。
2. UART传输数据长度。UART的数据长度可修改,通常采用byte的形式,即8位数据长度,缺省也为8位。
3. 奇偶校验位。通过PARITY_ON的参数传递实现是否需要奇偶校验的功能,缺省为不需要奇偶校验。
4. 奇偶校验类型。可选择为奇校验或偶校验,缺省为奇校验。
5. 波特率。波特率类型可自行调整,一般常用为9600,115200。
亲测可用。既可用于学习,也可用于快速开发。废话完了,开始正文……
顶层模块
顶层模块直接调用uart_rx和uart_tx两个模块,前者实现接收数据,后者实现发送数据,从而形成环路。代码(附注释)如下:
module uart_loop(input i_clk_sys,input i_rst_n,input i_uart_rx,output o_uart_tx,output o_ld_parity);localparam DATA_WIDTH = 8;localparam BAUD_RATE = 9600;localparam PARITY_ON = 1;localparam PARITY_TYPE = 1;wire w_rx_done;wire[DATA_WIDTH-1 : 0] w_data;uart_rx #(.CLK_FRE(50), //时钟频率,默认时钟频率为50MHz.DATA_WIDTH(DATA_WIDTH), //有效数据位,缺省为8位.PARITY_ON(PARITY_ON), //校验位,1为有校验位,0为无校验位,缺省为0.PARITY_TYPE(PARITY_TYPE), //校验类型,1为奇校验,0为偶校验,缺省为偶校验.BAUD_RATE(BAUD_RATE) //波特率,缺省为9600) u_uart_rx(.i_clk_sys(i_clk_sys), //系统时钟.i_rst_n(i_rst_n), //全局异步复位,低电平有效.i_uart_rx(i_uart_rx), //UART输入.o_uart_data(w_data), //UART接收数据.o_ld_parity(o_ld_parity), //校验位检验LED,高电平位为校验正确.o_rx_done(w_rx_done) //UART数据接收完成标志);uart_tx#(.CLK_FRE(50), //时钟频率,默认时钟频率为50MHz.DATA_WIDTH(DATA_WIDTH), //有效数据位,缺省为8位.PARITY_ON(PARITY_ON), //校验位,1为有校验位,0为无校验位,缺省为0.PARITY_TYPE(PARITY_TYPE), //校验类型,1为奇校验,0为偶校验,缺省为偶校验.BAUD_RATE(BAUD_RATE) //波特率,缺省为9600) u_uart_tx( .i_clk_sys(i_clk_sys), //系统时钟.i_rst_n(i_rst_n), //全局异步复位.i_data_tx(w_data), //传输数据输入.i_data_valid(w_rx_done), //传输数据有效.o_uart_tx(o_uart_tx) //UART输出);endmodule
接收模块
接收模块采用三段式状态机的形式编写,附带基本注释说明。
module uart_rx
#(parameter CLK_FRE = 50, //时钟频率,默认时钟频率为50MHzparameter DATA_WIDTH = 8, //有效数据位,缺省为8位parameter PARITY_ON = 0, //校验位,1为有校验位,0为无校验位,缺省为0parameter PARITY_TYPE = 0, //校验类型,1为奇校验,0为偶校验,缺省为偶校验parameter BAUD_RATE = 9600 //波特率,缺省为9600
)
(input i_clk_sys, //系统时钟input i_rst_n, //全局异步复位,低电平有效input i_uart_rx, //UART输入output reg[DATA_WIDTH-1 :0] o_uart_data, //UART接收数据output reg o_ld_parity, //校验位检验LED,高电平位为校验正确output reg o_rx_done //UART数据接收完成标志);/*UART输入是异步输入,最好是同步到FPGA内部的时钟域可以省略,但在异步电路中,保持时钟域同步是一个良好习惯*/reg sync_uart_rx;always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)sync_uart_rx <= 1'b1;elsesync_uart_rx <= i_uart_rx;end/*连续采样五个接收路电平来判断rx是否有信号传来用五个采样信号来作为判断标准可以有效排除毛刺噪声带来的误判*/reg [4:0] r_flag_rcv_start;wire w_rcv_start;always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)r_flag_rcv_start <= 5'b11111;elser_flag_rcv_start <= {r_flag_rcv_start[3:0], sync_uart_rx};end//状态机定义reg [2:0] r_current_state; //当前状态reg [2:0] r_next_state; //次态localparam STATE_IDLE = 3'b000; //空闲状态localparam STATE_START = 3'b001; //开始状态localparam STATE_DATA = 3'b011; //数据接收状态localparam STATE_PARITY = 3'b100; //数据校验状态localparam STATE_END = 3'b101; //结束状态localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; //波特计数周期reg baud_valid; //波特计数有效位reg [15:0] baud_cnt; //波特率计数器 reg baud_pulse; //波特率采样脉冲reg [3:0] r_rcv_cnt; //接收数据位计数//波特率计数器always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)baud_cnt <= 16'h0000;else if(!baud_valid)baud_cnt <= 16'h0000;else if(baud_cnt == CYCLE - 1)baud_cnt <= 16'h0000;elsebaud_cnt <= baud_cnt + 1'b1;end//波特采样脉冲always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)baud_pulse <= 1'b0;else if(baud_cnt == CYCLE/2-1)baud_pulse <= 1'b1;elsebaud_pulse <= 1'b0;end//状态机状态变化定义always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)r_current_state <= STATE_IDLE;else if(!baud_valid)r_current_state <= STATE_IDLE;else if(baud_valid && baud_cnt == 16'h0000)r_current_state <= r_next_state;end//状态机次态定义always@(*)begincase(r_current_state)STATE_IDLE: r_next_state <= STATE_START;STATE_START: r_next_state <= STATE_DATA;STATE_DATA:if(r_rcv_cnt == DATA_WIDTH)beginif(PARITY_ON == 0)r_next_state <= STATE_END;elser_next_state <= STATE_PARITY; //校验位开启时进入校验状态endelsebeginr_next_state <= STATE_DATA;endSTATE_PARITY: r_next_state <= STATE_END;STATE_END: r_next_state <= STATE_IDLE;default:;endcaseendreg[DATA_WIDTH - 1 :0] r_data_rcv;reg r_parity_check;//状态机输出逻辑always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)beginbaud_valid <= 1'b0;r_data_rcv <= 'd0;r_rcv_cnt <= 4'd0;r_parity_check <= 1'b0;o_uart_data <= 'd0;o_ld_parity <= 1'b0;o_rx_done <= 1'b0;endelsecase(r_current_state)STATE_IDLE:begin//闲置状态下对寄存器进行复位r_rcv_cnt <= 4'd0;r_data_rcv <= 'd0;r_parity_check <= 1'b0;o_rx_done <= 1'b0;//连续检测到低电平时认为UART传来数据,拉高baud_validif(r_flag_rcv_start == 5'b00000)baud_valid <= 1'b1;endSTATE_START:beginif(baud_pulse && sync_uart_rx) //波特率采样脉冲到来时再次检测是否为低电平,如果不为低电平,认为前期误检测,重新进入IDLE状态baud_valid <= 1'b0;endSTATE_DATA:beginif(baud_pulse)beginr_data_rcv <= {sync_uart_rx, r_data_rcv[DATA_WIDTH-1 : 1]}; //数据移位存储r_rcv_cnt <= r_rcv_cnt + 1'b1; //数据位计数r_parity_check <= r_parity_check + sync_uart_rx; //校验位做加法验证高电平的奇偶endendSTATE_PARITY:beginif(baud_pulse)begin//校验检测,正确则o_ld_parity拉高,可输出给led检测,如果闪烁则表示有错误数据发生if(r_parity_check + sync_uart_rx == PARITY_TYPE)o_ld_parity <= 1'b1;elseo_ld_parity <= 1'b0;endelseo_ld_parity <= o_ld_parity;endSTATE_END:beginif(baud_pulse)begin//没有校验位或者校验位正确时才输出数据,否则直接丢弃数据if(PARITY_ON == 0 || o_ld_parity)begino_uart_data <= r_data_rcv;o_rx_done <= 1'b1;endendelsebegino_rx_done <= 1'b0;endif(baud_cnt == 16'h0000)baud_valid <= 1'b0;enddefault:;endcaseendendmodule
发送模块
发送模块和接收模块基本写法一致,只有细微的参数改动实现逆过程。重点区别在于三段状态机的状态机输出逻辑不同。
module uart_tx
#(parameter CLK_FRE = 50, //时钟频率,默认时钟频率为50MHzparameter DATA_WIDTH = 8, //有效数据位,缺省为8位parameter PARITY_ON = 0, //校验位,1为有校验位,0为无校验位,缺省为0parameter PARITY_TYPE = 0, //校验类型,1为奇校验,0为偶校验,缺省为偶校验parameter BAUD_RATE = 9600 //波特率,缺省为9600
)
( input i_clk_sys, //系统时钟input i_rst_n, //全局异步复位input [DATA_WIDTH-1 : 0] i_data_tx, //传输数据输入input i_data_valid, //传输数据有效output reg o_uart_tx //UART输出);//状态机定义reg [2:0] r_current_state; //当前状态reg [2:0] r_next_state; //次态localparam STATE_IDLE = 3'b000; //空闲状态localparam STATE_START = 3'b001; //开始状态localparam STATE_DATA = 3'b011; //数据发送状态localparam STATE_PARITY = 3'b100; //数据校验计算和发送localparam STATE_END = 3'b101; //结束状态localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; //波特计数周期reg baud_valid; //波特计数有效位reg [15:0] baud_cnt; //波特率计数器 reg baud_pulse; //波特率采样脉冲reg [3:0] r_tx_cnt; //接收数据位计数//波特率计数器always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)baud_cnt <= 16'h0000;else if(!baud_valid)baud_cnt <= 16'h0000;else if(baud_cnt == CYCLE - 1)baud_cnt <= 16'h0000;elsebaud_cnt <= baud_cnt + 1'b1;end//波特采样脉冲always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)baud_pulse <= 1'b0;else if(baud_cnt == CYCLE/2-1)baud_pulse <= 1'b1;elsebaud_pulse <= 1'b0;end//状态机状态变化定义always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)r_current_state <= STATE_IDLE;else if(!baud_valid)r_current_state <= STATE_IDLE;else if(baud_valid && baud_cnt == 16'h0000)r_current_state <= r_next_state;end//状态机次态定义always@(*)begincase(r_current_state)STATE_IDLE: r_next_state <= STATE_START;STATE_START: r_next_state <= STATE_DATA;STATE_DATA:if(r_tx_cnt == DATA_WIDTH)beginif(PARITY_ON == 0)r_next_state <= STATE_END;elser_next_state <= STATE_PARITY; //校验位开启时进入校验状态endelsebeginr_next_state <= STATE_DATA;endSTATE_PARITY: r_next_state <= STATE_END;STATE_END: r_next_state <= STATE_IDLE;default:;endcaseendreg [DATA_WIDTH-1 : 0] r_data_tx;reg r_parity_check;//状态机输出逻辑always@(posedge i_clk_sys or negedge i_rst_n)beginif(!i_rst_n)beginbaud_valid <= 1'b0;r_data_tx <= 'd0;o_uart_tx <= 1'b1;r_tx_cnt <= 4'd0;r_parity_check <= 1'b0;endelsecase(r_current_state)STATE_IDLE:begino_uart_tx <= 1'b1;r_tx_cnt <= 4'd0;r_parity_check <= 4'd0;if(i_data_valid)beginbaud_valid <= 1'b1;r_data_tx <= i_data_tx;endendSTATE_START:beginif(baud_pulse)o_uart_tx <= 1'b0;endSTATE_DATA:beginif(baud_pulse)beginr_tx_cnt <= r_tx_cnt + 1'b1;o_uart_tx <= r_data_tx[0];r_parity_check <= r_parity_check + r_data_tx[0];r_data_tx <= {1'b0 ,r_data_tx[DATA_WIDTH-1:1]};endendSTATE_PARITY:beginif(baud_pulse)beginif(PARITY_TYPE == 1)o_uart_tx <= r_parity_check;elseo_uart_tx <= r_parity_check + 1'b1;endendSTATE_END:beginif(baud_pulse)begino_uart_tx <= 1'b1;baud_valid <= 1'b0;endenddefault:;endcaseendendmodule
以上便是整个工程的完整代码,至于FPGA的xdc约束,不同开发平台不同,这里就不贴出来了。
仿真波形
testbench的编写如下:
module uart_loop_tb();reg clk_sys;reg rst_n;reg uart_in;wire uart_out;wire parity;uart_loop u_uart_loop(.i_clk_sys(clk_sys),.i_rst_n(rst_n),.i_uart_rx(uart_in),.o_uart_tx(uart_out),.o_ld_parity(parity));initial beginclk_sys = 1'b0;rst_n = 1'b0;uart_in = 1'b1;endalways #10 clk_sys = ~clk_sys;localparam ELEMENT_TIME = 104160;reg [7:0] DATA = 8'hAC;initial begin#100 rst_n = 1'b1;#20000;uart_in = 1'b0;#ELEMENT_TIMEuart_in = DATA[0];#ELEMENT_TIMEuart_in = DATA[1];#ELEMENT_TIMEuart_in = DATA[2];#ELEMENT_TIMEuart_in = DATA[3];#ELEMENT_TIMEuart_in = DATA[4];#ELEMENT_TIMEuart_in = DATA[5];#ELEMENT_TIMEuart_in = DATA[6];#ELEMENT_TIMEuart_in = DATA[7];#ELEMENT_TIMEuart_in = 1'b1;#ELEMENT_TIMEuart_in = 1'b1;endendmodule
此处我们只进行一次数据传输过程的测试,读者可自行编写测试程序。
传输的字节内容为8’hAC即8’b10101100。
仿真波形如下:
仿真波形中蓝色部分为rx端的主要波形,橙色部分为tx端的主要波形。
有兴趣了解细致波形的可以直接建立一个工程,这样子想看哪个波形就能看哪个波形啦~
实测结果
实测直接通过串口CH340实现TTL电平转换,与PC端的串口调试助手进行测试。调试助手采用友善串口调试助手(老实说这个调试助手界面做的不怎么样哈哈),设置条件可查看左边串口设置:
测试结果如下:
可以看到,红色为发送数据,蓝色为接收数据,串口回环实现了将接收数据再次转发给PC端。
以上就是本次分享的内容啦~有机会再更新其他的协议。