1.UART 概述 (通用异步收发传输器)
1. 基本定义
UART(Universal Asynchronous Receiver/Transmitter)是一种常见的串行通信协议,用于在设备间通过异步串行通信传输数据。它不依赖独立的时钟信号,而是通过预定义的波特率(Baud Rate) 同步数据的收发。 核心特点:
- 异步通信:无需共享时钟信号,仅通过数据线传输。
- 全双工模式:收发双方可同时发送和接收数据(需独立TX、RX线)。
- 灵活配置:支持自定义波特率、数据位长度、校验位、停止位等。
2. 工作原理
(1) 数据传输格式
每一帧数据包含以下部分(以典型8N1格式为例):
起始位 | 数据位(8位) | 校验位(可选) | 停止位(1位) |
---|---|---|---|
1位,低电平 | LSB先发 | 奇偶校验(可选) | 1位,高电平 |
- 起始位:低电平(0)表示数据传输开始。
- 数据位:传输的有效数据(5~9位),通常低位(LSB)先发送(如用户之前的案例)。
- 校验位:用于简单错误检测(奇校验/偶校验/无校验)。
- 停止位:高电平(1)表示一帧数据结束,并允许接收端校准时序。
(2) 异步同步机制
- 波特率校准:收发双方必须使用相同的波特率(如115200、9600)。 例如:
复制
波特率115200 → 每位持续时间 ≈ 1/115200 ≈ 8.68μs
- 数据采样:接收端在起始位下降沿触发,并在数据位中间点采样,抵消时钟偏移影响。
3. 典型应用场景
- 微控制器与外围模块通信:如ESP8266(Wi-Fi)、GPS模块、传感器(温湿度)。
- 调试接口:通过UART输出调试信息(常见于嵌入式开发板)。
- 有线设备互联:旧式打印机、工业设备(如Modbus RTU协议)。
- 信号转换:结合电平转换芯片(如MAX232)实现RS232、RS485等长距离通信。
4. 优缺点分析
优点 | 缺点 |
---|---|
硬件简单(仅需两根数据线) | 传输距离短(通常<1米) |
成本低(无需复杂协议栈) | 无硬件错误恢复机制(需软件纠错) |
广泛兼容性(几乎所有MCU支持) | 需严格匹配波特率(误差<3%) |
5. 硬件实现关键点
- 发送端(TX):
- 将并行数据转为串行,按波特率逐位发送。
- 使用分频器生成波特率时钟(如50MHz主频 → 115200波特率需分频系数:50e6 / 115200 ≈ 434)。
- 接收端(RX):
- 检测起始位下降沿,启动同步采样。
- 通过移位寄存器重组串行数据为并行数据。
6. 常见问题与解决方案
- 波特率失配: 若收发波特率差超过3%,会导致采样偏移,需重新校准。
- 电磁干扰: 长距离使用需加屏蔽线或转换为差分信号(如RS485)。
- 数据冲突: 全双工通信需避免同时发送,可通过流控信号(RTS/CTS)解决。
7. 主流扩展协议
- RS-232:电平标准(±3~15V),支持更长距离(<15米)。
- RS-485:差分信号,可多点通信(工业现场总线)。
- USB转UART:通过芯片(如CH340、CP2102)实现USB与串口无缝衔接。
2.verilog编写
这里需要讲解下,这里使用到50mhz的时钟, 波特率为115200,这里的50mhz的时钟是在1秒内有50_000_000个周期的数据,波特率115200是在1秒内有115200bit的传输。
50_000_000/115200 指的是传输1bit需要传输多少个时钟周期
在写测试代码的时候,#8680 是因为 在 50_000_000/115200= 434 个时钟周期传输1bit, 而434个时钟周期每个时钟周期为20ns 434*20= 8680.
1,波形图
接收和发送都根据这个图编写就行
2.1接收模块代码
module uart_rx (
input clk,
input rst,
input rx_en,
input data_in,
output reg [7:0] data_out,
output uart_rx_done
);localparam CLK = 50_000_000,BOTE = 115200,CNT = CLK / BOTE ;reg rx_en_d1;
reg rx_en_d2;
reg rx_flag ;
reg rx_valid;
reg [3:0] rx_cnt ;
reg [15:0] clk_cnt ;
reg [7:0] data_out_r;
reg uart_rx_done_r;assign uart_rx_done = uart_rx_done_r;always @(posedge clk or negedge rst )beginif (rst == 1'b1)beginrx_en_d1 <= 1'b0;rx_en_d2 <= 1'b0;endelse beginrx_en_d1 <= rx_en;rx_en_d2 <= rx_en_d1;end
endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)rx_flag <= 1'b0;else if (rx_en_d1 == 1'b1 && rx_en_d2 == 1'b0)rx_flag <= 1'b1;else rx_flag <= 1'b0;
endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)rx_valid <= 1'b0;else if (rx_cnt == 4'd9 && clk_cnt == CNT /2 )rx_valid <= 1'b0;else if (rx_flag == 1'b1)rx_valid <= 1'b1;else;
endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)clk_cnt <= 16'd0;else if (clk_cnt == CNT )clk_cnt <= 16'd0;else if (rx_valid == 1'b1)clk_cnt <= clk_cnt +1'b1;else clk_cnt <= 16'd0;
endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)rx_cnt <= 4'd0;else if (rx_en_d1 == 1'b1 && rx_en_d2 == 1'b0)rx_cnt <= 4'd0;else if (clk_cnt == CNT) rx_cnt <= rx_cnt +1'b1;else;
endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)uart_rx_done_r <= 1'b0;else if (rx_valid == 1'b1) beginif (rx_cnt == 4'd9)uart_rx_done_r <= 1'b1;else uart_rx_done_r <= 4'd0;endelseuart_rx_done_r <= 1'b0;
endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)data_out_r <= 8'd0;else if (rx_valid == 1'b1 ) beginif (clk_cnt == CNT /4) case (rx_cnt )4'd1 : data_out_r[0] = data_in ; 4'd2 : data_out_r[1] = data_in ; 4'd3 : data_out_r[2] = data_in ; 4'd4 : data_out_r[3] = data_in ; 4'd5 : data_out_r[4] = data_in ; 4'd6 : data_out_r[5] = data_in ; 4'd7 : data_out_r[6] = data_in ; 4'd8 : data_out_r[7] = data_in ; default : ;endcaseelse data_out_r <= data_out_r;endelse data_out_r <= 8'd0; endalways @ (posedge clk or negedge rst )beginif (rst == 1'b1)data_out <= 8'd0;else if (rx_cnt == 4'd9)data_out <= data_out_r;else data_out <= 8'd0;
endendmodule
2.2发送模块代码
module uart_tx(
input clk,
input rst,input tx_en,
input [7:0] data_din,
output data_out,
output uart_tx_done );
localparam CLK = 50_000_000, //时钟 50_000_000 一秒 50000000个时钟周期 数据 波特率 9600 一秒9600个数据bit 5000000/9600 一个bit需要多少时钟周期 BOTE =115200, CNT = CLK /BOTE ;reg tx_en_d1;
reg tx_en_d2;
reg start_flag;
reg tx_valid;
reg [3:0] tx_cnt;
reg [15:0] clk_cnt;
reg uart_done_r;
reg uart_dout_r;
reg [7:0] data_din_r;assign data_out = uart_dout_r;
assign uart_tx_done = uart_done_r;always @ (posedge clk or negedge rst) beginif (rst == 1'b1) begintx_en_d1 <= 1'b0;tx_en_d2 <= 1'b0;endelse begintx_en_d1 <=tx_en;tx_en_d2 <= tx_en_d1;end
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)data_din_r <= 8'd0;else if (tx_cnt ==4'd9 && clk_cnt == CNT /2)data_din_r <= 8'd0;else if ( tx_en_d1 == 1'b1 && tx_en_d2 == 1'b0 ) data_din_r <= data_din;else;
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)start_flag <= 1'b0;else if ( tx_en_d1 == 1'b1 && tx_en_d2 == 1'b0 ) start_flag <= 1'b1;else start_flag <= 1'b0;
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)tx_valid <= 1'b0;else if (start_flag == 1'b1)tx_valid <= 1'b1;else if (tx_cnt ==4'd9 && clk_cnt == CNT /2 )tx_valid <= 1'b0;else;
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)clk_cnt <= 16'd0;else if (clk_cnt == CNT )clk_cnt <= 16'd0;else if (tx_valid == 1'b1)clk_cnt <= clk_cnt +1'b1;else clk_cnt <= 16'd0;
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)tx_cnt <= 4'd0;else if (tx_en_d1 == 1'b1 && tx_en_d2 == 1'b0)tx_cnt <= 4'd0;else if (clk_cnt == CNT)tx_cnt <= tx_cnt +1'b1;else;
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)uart_done_r <= 1'b0;else if (tx_valid == 1'b1) beginif (tx_cnt == 4'd9) uart_done_r <= 1'b1;elseuart_done_r <= 1'b0; endelseuart_done_r <= 1'b0;
endalways @ (posedge clk or negedge rst) beginif (rst == 1'b1)uart_dout_r <= 1'b0; else if (tx_valid == 1'b1) begincase (tx_cnt)4'd0 : uart_dout_r <= 1'b0;4'd1 : uart_dout_r <=data_din_r[0]; 4'd2 : uart_dout_r <=data_din_r[1];4'd3 : uart_dout_r <=data_din_r[2];4'd4 : uart_dout_r <=data_din_r[3];4'd5 : uart_dout_r <=data_din_r[4];4'd6 : uart_dout_r <=data_din_r[5];4'd7 : uart_dout_r <=data_din_r[6];4'd8 : uart_dout_r <=data_din_r[7]; 4'd9 : uart_dout_r <=1'b1;default : ;endcaseendelseuart_dout_r <= 1'b1;
endendmodule
2.3顶层模块代码
module uart_top (
input clk,
input rst,
input uart_data_rx,
input rx_en,
output uart_data_out
);wire uart_tx_done;
wire uart_rx_done;
wire [7:0] data_out;uart_rx uart_rx (
.clk (clk ), //input
.rst ( rst ), //input
.rx_en ( rx_en ), //input
.data_in ( uart_data_rx ), //input
.data_out ( data_out ), //output reg [7:0]
.uart_rx_done ( uart_rx_done ) //output
);uart_tx uart_tx(
.clk ( clk ), //input
.rst ( rst ), //input
.tx_en ( uart_rx_done ), //input
.data_din ( data_out ), //input [7:0]
.data_out ( uart_data_out ), //output
.uart_tx_done ( uart_tx_done ) //output );endmodule
2.4仿真模块代码
module uart_tb();reg clk;
reg rst;
reg rx_en;
reg uart_data_rx;
wire uart_data_out;initial beginrst = 1'b1;clk = 1'b1;uart_data_rx = 1'b1;rx_en = 1'b0;#20rst = 1'b0;#4340uart_data_rx = 1'b0;rx_en = 1'b1;#20rx_en = 1'b0;#8680 uart_data_rx = 1'b1;#8680 uart_data_rx = 1'b0;#8680 uart_data_rx = 1'b1;#8680 uart_data_rx = 1'b0;#8680 uart_data_rx = 1'b1;#8680 uart_data_rx = 1'b0;#8680 uart_data_rx = 1'b1;#8680 uart_data_rx = 1'b0;#8680 uart_data_rx = 1'b1;endalways #10 clk = !clk;uart_top uart_top_inst (.clk(clk),.rst(rst),.rx_en(rx_en),.uart_data_rx(uart_data_rx),.uart_data_out(uart_data_out)
);endmodule
3.仿真波形
3.1 接收模块仿真波形
数据输入先放到低位依次传入
3.2 发送模块仿真波形
发送的时候,把需要发送的数据低位先发