UART协议及串口回环
- 一、异步通信的分类
- 1、UART(通用异步收发器)
- 2、RS422
- 3、RS485
- 4、Modbus
- 5、接口标准
- 二、UART协议要求
- 1、空闲状态
- 2、起始位
- 3、数据位
- 4、校验位
- 5、停止位
- 6、波特率
- 7、比特率
- 三、汉字发送
- 四、串口回环
- uart_tx
- uart_rx
- ctrl
- top
- 五、参考
- 六、源码
一、异步通信的分类
1、UART(通用异步收发器)
一般是指元器件之间的通信,用一位低电平作为起始位,再来8位数据位后面加校验和高电平的停止位,信号一般对地电压5V、3.3V等等都可以,这就是uart通信,
2、RS422
后来人们发现这种通信方案传输距离不长,于是将这种电平信号改成差分信号,每个差分信号需要两条线,那rx和tx就一共需要四条线,不需要gnd了,这种通信就叫RS422,
3、RS485
但人们发现一般情况下发送和接收很少一起于是就掐掉一路变成一对差分,这就是RS485。
4、Modbus
在实践中人们又发现在这每家都通信协议都不一样很难集成到一起,于是就统一定义一下发送的协议,每个设备有一个地址,在一个网络中主机首先发送从机地址然后再发送功能码字节长度和实际数据以及CRC校验,这样不仅稳定而且还能兼容很多产品,于是modbus就诞生了,也就是说modbus是建立在uart或者422或者485之上的一种协议,其实modbus还可以建立在tcp/ip上面
5、接口标准
二、UART协议要求
1、空闲状态
保持高电平
2、起始位
master设备由空闲状态的高电平拉为低电平,且持续一个bit数据的保持时间(跟波特率有关),用于告知slave设备准备接受数据
3、数据位
支持5~8bit的有效数据传输,由通信双方约定好通信的格式。先发送数据的低位,LSB表示低位,MSB表示高位
格式:起始位,bit0~bit7,校验位,停止位
4、校验位
用于数据校验的,校验的方式分为奇偶校验,校验位的作用是平衡数据中高电平数据的个数是奇数个/偶数个。
缩位异或(^A) 用于判断1的个数,0:偶数 1:奇数
缩位同或(~^A) 用于判断0的个数,0:奇数 1:偶数
奇校验(odd):保证数据(数据位+校验位)中奇数个逻辑高电平
偶校验(even):保证数据中偶数个逻辑高电平
例如:传输的数据为0100_0011
奇校验的校验位:0
偶校验的校验位:1
5、停止位
两台设备通信可能会存在小段时间的不同步情况,从而影响数据的传输结果,因此master必须保证有停止位,即数据线拉高一段时间。停止位不仅仅表示数据传输结束,也是矫正时钟同步的机会,让slave设备能够正确的识别下一轮数据的起始位。
6、波特率
码元:携带数据信息的信号单元(uart是单根线传输,所以码元就是一个二进制数)
波特率:每秒钟信道传输的码元数(二进制数据),符号Baud,单位波特每秒(Bps)
计算方法:50M时钟,周期为20ns
(1_000_000_000/115200)/20 = 434
传输1bit信号,在115200的波特率下,需要持续434个时钟周期
7、比特率
比特率:每秒钟信道传输的信息量,单位每秒比特数(bps)
公式:比特率 = 波特率*码元(对于一次传输一个bit数据而言,波特率=比特率)
三、汉字发送
UART每次只能发送8bit的数据,发送汉字信息需要连续发送16bit的信息
(一个汉字2个字节)发送的时ASCII码,需要使用软件查询汉字的ASCII码。发送汉字,先发送汉字对应16bit的ASCII码中的高位8bit,再发送低8bit
四、串口回环
uart_tx
`include "param.v"module uart_tx( //发送数据 并串转换input clk ,input rst_n ,input [1:0] baud_sel,input [7:0] din ,input din_vld ,output tx_dout ,output tx_busy //发送状态指示
);//信号定义reg [12:0] cnt0 ;//波特率计数器wire add_cnt0 ;wire end_cnt0 ;reg [3:0] cnt1 ;//bit计数器wire add_cnt1 ;wire end_cnt1 ;reg add_flag ;reg [`DATA_W+1:0] tx_data ;reg [12:0] baud ;//选择分频系数reg tx_bit ;//计数器always @(posedge clk or negedge rst_n) begin if (rst_n==0) begincnt0 <= 0; endelse if(add_cnt0) beginif(end_cnt0)cnt0 <= 0; elsecnt0 <= cnt0+1 ;endendassign add_cnt0 = (add_flag);assign end_cnt0 = add_cnt0 && cnt0 == (baud)-1 ;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begincnt1 <= 0; endelse if(add_cnt1) beginif(end_cnt1)cnt1 <= 0; elsecnt1 <= cnt1+1 ;endendassign add_cnt1 = (end_cnt0);assign end_cnt1 = add_cnt1 && cnt1 == (`DATA_W)+1;//add_flag 计数器使能信号always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginadd_flag <= 1'b0;endelse if(din_vld)beginadd_flag <= 1'b1;endelse if(end_cnt1)begin add_flag <= 1'b0;endendalways @(*)begincase(baud_sel)0:baud = `BAUD_RATE_115200;1:baud = `BAUD_RATE_57600;2:baud = `BAUD_RATE_38400;3:baud = `BAUD_RATE_9600;default:baud = `BAUD_RATE_115200;endcase end//输出`ifdef PARITY_ENABLE //使能校验功能//reg [`DATA_W+1:0] tx_data ;wire parity_bit ;assign parity_bit = (`PARITY_TYPE == 1'b1) ? (~^din) : (^din);always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begintx_data <= 0;endelse if(din_vld)begin //收到请求时,将 停止位 校验位 数据 起始位 拼接tx_data <= {1'b1,parity_bit,din,1'b0};endend`else //未使能校验功能//reg [`DATA_W+1:0] tx_data ;always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begintx_data <= 0;endelse if(din_vld)begin //收到请求时,将 校验位 数据 起始位 拼接tx_data <= {1'b1,din,1'b0};endend`endif always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begintx_bit <= 1'b1;endelse if(add_cnt0 && cnt0 == 1-1)begintx_bit <= tx_data[cnt1];endendassign tx_busy = din_vld | add_flag;assign tx_dout = tx_bit;endmodule
uart_rx
`include "param.v"module uart_rx ( //接收串行数据 串并转换input clk ,input rst_n ,input [1:0] baud_sel,input rx_din ,output [7:0] rx_dout ,output rx_vld
);//定义信号reg [12:0] cnt0 ;//波特率计数器wire add_cnt0 ;wire end_cnt0 ;reg [3:0] cnt1 ;//bit计数器wire add_cnt1 ;wire end_cnt1 ;reg add_flag ;reg [12:0] baud ;//选择分频系数reg [2:0] rx_din_r ;//同步、打拍寄存器wire n_edge ;//下降沿检测reg [`DATA_W:0] rx_data ;//采样数据寄存器//计数器always @(posedge clk or negedge rst_n) begin if (rst_n==0) begincnt0 <= 0; endelse if(add_cnt0) beginif(end_cnt0)cnt0 <= 0; elsecnt0 <= cnt0+1 ;endendassign add_cnt0 = (add_flag);assign end_cnt0 = add_cnt0 && cnt0 == (baud)-1 ;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begincnt1 <= 0; endelse if(add_cnt1) beginif(end_cnt1)cnt1 <= 0; elsecnt1 <= cnt1+1 ;endendassign add_cnt1 = (end_cnt0);assign end_cnt1 = add_cnt1 && (cnt1 == (`DATA_W) || rx_data[0]);//add_flag 计数器使能信号always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginadd_flag <= 1'b0;endelse if(n_edge)beginadd_flag <= 1'b1;endelse if(end_cnt1)beginadd_flag <= 1'b0;endendalways @(*)begincase(baud_sel)0:baud = `BAUD_RATE_115200;1:baud = `BAUD_RATE_57600;2:baud = `BAUD_RATE_38400;3:baud = `BAUD_RATE_9600;default:baud = `BAUD_RATE_115200;endcase end//下降沿检测 同步打拍always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrx_din_r <= 3'b111;endelse beginrx_din_r <= {rx_din_r[1:0],rx_din};//rx_din_r[0] <= rx_din; //同步//rx_din_r[1] <= rx_din_r[0]; //打拍//rx_din_r[2] <= rx_din_r[1]; //打拍endendassign n_edge = rx_din_r[2] & ~rx_din_r[1];//采样数据always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrx_data <= 0;endelse if(add_flag && cnt0 == (baud >> 1))begin//rx_data <= {rx_din,rx_data[`DATA_W:1]};//右移rx_data[cnt1] <= rx_din;endend//校验`ifdef PARITY_ENABLE //开启校验wire parity_result ;assign parity_result = ^rx_data[`DATA_W:1];assign rx_dout = rx_data[`DATA_W-1:1];assign rx_vld = end_cnt1 & ~rx_data[0] & parity_result == `PARITY_TYPE;`elsif //不开启校验assign rx_dout = rx_data[`DATA_W:1];assign rx_vld = end_cnt1 & ~rx_data[0];`endif //输出// assign rx_dout = rx_data[8:1];
// assign rx_vld = end_cnt0 & cnt1 == (9)-1 & ~rx_data[0];endmodule
ctrl
module control ( //缓存input clk ,input rst_n ,input [7:0] din ,input din_vld ,input busy ,output [7:0] dout ,output dout_vld
);//信号定义reg rd_flag ;reg [7:0] tx_data ;reg tx_data_vld ;wire fifo_rdreq ;wire fifo_wrreq ;wire fifo_empty ;wire fifo_full ;wire [7:0] fifo_qout ;wire [3:0] fifo_usedw ;always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_flag <= 1'b0;endelse if(fifo_usedw >= 8)beginrd_flag <= 1'b1;endelse if(fifo_empty)begin rd_flag <= 1'b0;end endalways @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begintx_data <= 0;tx_data_vld <= 1'b0; endelse begintx_data <= fifo_qout;tx_data_vld <= fifo_rdreq;endend//FIFO例化fifo u_fifo(.aclr (~rst_n ),.clock (clk ),.data (din ),.rdreq (fifo_rdreq ),.wrreq (fifo_wrreq ),.empty (fifo_empty ),.full (fifo_full ),.q (fifo_qout ),.usedw (fifo_usedw ));assign fifo_wrreq = din_vld & ~fifo_full;assign fifo_rdreq = rd_flag & ~busy; //发送模块 非忙状态下 给发送请求// assign dout_vld = fifo_rdreq;
// assign dout = fifo_qout;assign dout_vld = tx_data_vld; //时序逻辑输出assign dout = tx_data;endmodule
top
module top(input clk ,input rst_n ,input uart_rxd,output uart_txd
);//信号定义wire [7:0] rx_byte ;wire rx_byte_vld ;wire [7:0] tx_byte ;wire tx_byte_vld ;wire tx_busy ;wire [1:0] baud_sel ;assign baud_sel = 2'd0; //选择波特率//模块例化uart_rx u_rx( //接收串行数据 串并转换/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input [1:0] */.baud_sel(baud_sel ),/*input */.rx_din (uart_rxd ),/*output [7:0] */.rx_dout (rx_byte ),/*output */.rx_vld (rx_byte_vld ) );control u_ctrl( //缓存/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input [7:0] */.din (rx_byte ),/*input */.din_vld (rx_byte_vld ),/*input */.busy (tx_busy ),/*output [7:0] */.dout (tx_byte ),/*output */.dout_vld(tx_byte_vld ) );uart_tx u_tx( //发送数据 并串转换/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input [1:0] */.baud_sel(baud_sel ),/*input [7:0] */.din (tx_byte ),/*input */.din_vld (tx_byte_vld ),/*output */.tx_dout (uart_txd ),/*output */.tx_busy (tx_busy ) //发送状态指示 );endmodule
五、参考
UART的fpga实现
六、源码
https://github.com/IvanXiang/FPGA_UART