赞
踩
UART (universal asynchronous receiver-transmitter)是一种采用异步串行通信方式的通用异步收发传输器;它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。UART串口通信需要两根信号线来实现,一根用于发送,另外一根接收(表明是异步全双工通信)。
①协议层:通信协议(包括数据格式、传输速率等)。
②物理层:接口类型、电平标准等。
协议层:数据格式,一帧数据由4部分组成(用代码设计串口用到的就是协议层):
·起始位( 1bit)
·数据位(6/7/8bit)
·奇偶校验位(1bit)
·停止位(1bit/1.5bit/2bit)
起始位,数据位,停止位是必要的。
数据位的数据高位在后,低位在前,从左只有从低到高,即bit[0]至bit[n].
串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,
单位是bit/s (位/秒),简称bps;
常用的波特率有9600、19200、 38400、 57606以 及115200等。
.TTL电平的串口(3.3V)
·RS232电平的串口(+5~+12V为低电平,-12~-5V为高电平)
串口按电气标准分包括:
·RS-232-C:TXD/RXD/GND、15米/9600bps
·RS-422:TX+/TX-/RX+/RX-/GND
.RS485:A/B/G 、1200米/9600bps
数据传输时,串行数据经过接收端进行串转并的处理变为并行数据,再经过发送端并转串处理传输串行数据。
首先找到起始位才能开始数据接收(检测下降沿,进行同步打拍;检测下降沿时为r0低电平,r1高电平)
ps:为什么要做同步?因为传输过程中是没有时钟信号的,首先需要将其与时钟边沿同步,然后打拍检测边沿
起始位传输占据1bit,求波特率为9600传输1bit需要的时钟周期,即起始位所占用的时间,计算方法为:链接(https://blog.csdn.net/weixin_45388202/article/details/116465712),需要计数大约5208次。
传输一帧数据的数据位如图所示:
从低位到高位传输,所以数据位数据为11111001.
rx_data接收数据进行中间采样(中间信号稳定),如果接收的时候检测到了下降沿但采样的起始位为1(接收数据包含了起始位),说明rxd可能产生了抖动过后又回到高电平,因此需要进行判断其是否为起始位,采样到1说明产生了抖动,此时将bit计数器清零,rx_data不是有效数据不能作为输出。
输入串行数据rxd,进入FPGA后,一帧数据里包含停止位和起始位,但只有数据位这8位是需要的有效数据。
输入串行数据rxd,输出8位数据rx_data,加入标志信号rx_data_vld判断输出的rx_data是否为8位数据位,是则有效。
对rxd进行同步打拍,边沿检测下降沿(起始位为一帧数据的开始)
定义参数及信号,flag为接收一帧数据的标志信号,作为开启bps计数器的标志,即在一帧数据到来时,起始位检测下降沿bps计数器开启计数flag拉高,记完1bit后end_cnt_bps拉高结束这一比特计时,此处flag则拉低。除开end_cnt_bps的其他时刻均保持flag拉高,知道最后1bit数据传输完毕。
对于end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]),它必须满足计数完第十个bit或者起始位data_in[0]为1,此时才是bit结束计数标志,两钟情况满足任一即可。
进行数据采样时最好是选择之间的稳定信号,为什么加入data_in信号?我的理解是此信号是经过串行信号通过FPGA进行并行处理,但我们只需要数据位的8位有效信号,因此需要对有效信号进行提取做准备
将有效数据位数据赋予rx_data,并进行是否是有效数据的判断,判断条件结束bit计数和存在起始位必须同时满足,这样才能有一帧完整的10bit数据,然后去除的data_in[8:1]中间的8位数据才是数据位信息。
- module uart_rx (
- input clk ,
- input rst_n ,
- input rxd ,//输入串行数据
- output reg [7:0] rx_data ,
- output reg rx_data_vld//判断标志信号,判断rx_data的数据是否有效
- );
- //参数定义
- parameter BPS_9600 = 5208 ;
-
- //信号定义
- reg rx_r0 ;
- reg rx_r1 ;
-
- wire nedge ;//检测起始位
-
- reg [12:0] cnt_bps ;//计数1bit时钟周期
- wire add_cnt_bps ;
- wire end_cnt_bps ;
- reg flag ;//接收标志
-
- reg [3:0] cnt_bit ;//计数bit数
- wire add_cnt_bit ;
- wire end_cnt_bit ;
-
- reg [9:0] data_in ;//一帧数据,起始位(1bit),数据位(8bit),停止位(1bit)
- //进行串并转换
-
- //同步,打拍,边沿检测
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- rx_r0 <= 1'b1 ;
- rx_r1 <= 1'b1 ;
- end
- else begin
- rx_r0 <= rxd ;//同步数据
- rx_r1 <= rx_r0 ;//打拍
- end
- end
-
- assign nedge = rx_r1 & ~rx_r0 ;//下降沿检测
-
- //flag
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- flag <= 1'b0 ;
- end
- else if(nedge) begin
- flag <= 1'b1 ;
- end
- else if(end_cnt_bit) begin
- flag <= 1'b0;
- end
- end
-
- //cnt_bps
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- cnt_bps <= 'd0;
- end
- else if (add_cnt_bps) begin
- if (end_cnt_bps) begin
- cnt_bps <= 'd0 ;
- end
- else begin
- cnt_bps <= cnt_bps + 1'b1 ;
- end
- end
- end
-
- assign add_cnt_bps = flag ;
- assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;
-
-
-
- //cnt_bit
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- cnt_bit <= 'd0;
- end
- else if (add_cnt_bit) begin
- if (end_cnt_bit) begin
- cnt_bit <= 'd0 ;
- end
- else begin
- cnt_bit <= cnt_bit + 1'b1 ;
- end
- end
- end
-
- assign add_cnt_bit = end_cnt_bps ;
- assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]) ;//data_in[0]是存的起始位
-
- //data_in采样数据(包含起始位和停止位)
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- data_in <= 0;
- end
- else if (cnt_bps == BPS_9600 >> 1) begin//为了稳定采样中间的值
- data_in[cnt_bit] <= rx_r1 ;
- end
- end
-
- //取数据位rx_data
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- rx_data <= 0 ;
- end
- else if (end_cnt_bit) begin
- rx_data <= data_in[8:1] ;//只需要给数据位
- end
- else begin
- rx_data <= 0 ;
- end
- end
-
- //判断rx_data是否有效
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- rx_data_vld <= 0 ;
- end
- else begin
- rx_data_vld <= end_cnt_bit && (data_in[0] == 0);
- end
- end
-
-
- endmodule
定义系统时钟周期,例化模块,产生时钟,上电复位并赋予初值,然后分配起始位,数据位,停止位并进行延时处理。
- `timescale 1ns/1ps
- module tb_uart_rx ();
- reg clk ;
- reg rst_n ;
- reg rxd ;
-
- wire [7:0] rx_data ;
- wire rx_data_vld ;
-
- //参数定义
- parameter CYCLE = 20 ;
-
- //例化
- uart_rx u_uart_rx(
- .clk (clk ),
- .rst_n (rst_n ),
- .rxd (rxd ),
- .rx_data (rx_data ),
- .rx_data_vld (rx_data_vld)
- );
-
- //时钟
- initial begin
- clk = 1'b1 ;
- forever begin
- #(CYCLE/2);
- clk = ~clk ;
- end
- end
-
- integer i;//数据个数
-
- initial begin
- rst_n = 1'b1 ;
- #(CYCLE);
- rst_n = 1'b0 ;
- rxd = 1'b1 ;
- #22;
- rst_n = 1'b1 ;
-
- #(CYCLE*200);
-
- //模拟一帧数据的格式
- rxd = 1'b0 ;//起始位
- #(CYCLE*5208);
-
- //数据位
- for (i=0;i<8;i=i+1) begin
- case (i)//赋值rxd = 1111_1001
- 1: rxd = 1'b0 ;
- 2: rxd = 1'b0 ;
- default: rxd = 1'b1 ;//只有第一位和第二位为0
- endcase
- #(CYCLE*5208);
- end
-
- rxd = 1'b1 ;//停止位
- #(CYCLE*5208);
-
- #(CYCLE*200);
- $stop;
- end
- endmodule
test.do文件:
- vlib work
- vmap work work
-
- #编译testbench文件
- vlog tb_uart_rx.v
- #编译 设计文件
- vlog ../rtl/uart_rx.v
-
-
- #vlog altera_mf.v
-
-
- #指定仿真顶层
- vsim -novopt work.tb_uart_rx
- #添加信号到波形窗
- add wave -position insertpoint sim:/tb_uart_rx//*
-
- run -all
观测到最后8位数据位为1111_1001符合要求。
此处没有理解透彻5802为啥右移一位就变成中间部分的数据了?将打拍的数据赋予data_in内部为什么应bit计数器表示?
答:右移一一位相当于除以2,符合在中间值采样原则
和发送端类似,由上设计框图
输入并行信号tx_data,将rx_data的数据传输给它,tx_data_vld为传输8位数据位有效信号标志,输出串行数据txd
必须保持波特率一致,定义参数
flag在这里作为发送数据标志,同时也是计时器bps的开启条件,其在数据有效时拉高进行发送,所有bit的数据发送完后拉低
同样用data_out寄存一帧10bit的数据(并行数据),通过发送端转化为串行数据,在数据发送有效时,添加起始位和停止位赋值于它进行寄存
输出串行数据txd,仅在发送数据标志flag拉高时发送数据,数据来自于寄存于data_out的10bit并行数据。
- module uart_tx (
- input clk ,
- input rst_n ,
- input [7:0] tx_data ,
- input tx_data_vld ,//发送有效信号标志
- output reg txd
- );
- //参数定义
- parameter BPS_9600 = 5208 ;
-
- //信号定义
-
- reg [12:0] cnt_bps ;//计数1bit时钟周期
- wire add_cnt_bps ;
- wire end_cnt_bps ;
- reg flag ;//发送数据标志,同时也是bps计数的开启条件
-
- reg [3:0] cnt_bit ;//计数bit数
- wire add_cnt_bit ;
- wire end_cnt_bit ;
-
- reg [9:0] data_out ;
-
- //flag
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- flag <= 1'b0 ;
- end
- else if(tx_data_vld) begin //在发送有效信号时,flag作为标志拉高
- flag <= 1'b1 ;
- end
- else if(end_cnt_bit) begin//bit计数结束时,flag拉低
- flag <= 1'b0;
- end
- end
-
- //cnt_bps
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- cnt_bps <= 'd0;
- end
- else if (add_cnt_bps) begin
- if (end_cnt_bps) begin
- cnt_bps <= 'd0 ;
- end
- else begin
- cnt_bps <= cnt_bps + 1'b1 ;
- end
- end
- end
-
- assign add_cnt_bps = flag ;
- assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;
-
-
-
- //cnt_bit
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- cnt_bit <= 'd0;
- end
- else if (add_cnt_bit) begin
- if (end_cnt_bit) begin
- cnt_bit <= 'd0 ;
- end
- else begin
- cnt_bit <= cnt_bit + 1'b1 ;
- end
- end
- end
-
- assign add_cnt_bit = end_cnt_bps ;
- assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9) ;
-
- //寄存数据至data_out(包含起始位和停止位)
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- data_out <= 0;
- end
- else if (tx_data_vld) begin
- data_out <= {1'b1,tx_data,1'b0} ;
- end
- end
-
- //输出txd
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- txd <= 1'b1 ;
- end
- else if (flag) begin
- txd <= data_out[cnt_bit] ;
- end
- end
-
- endmodule
只需要加上接收端的例化模块即可,中间信号端口互通,在最后再增加10bit的周期延迟。
- `timescale 1ns/1ps
- module tb_uart_rx ();
- reg clk ;
- reg rst_n ;
- reg rxd ;
-
- wire [7:0] rx_data ;
- wire rx_data_vld ;
-
- //参数定义
- parameter CYCLE = 20 ;
-
- //例化
- uart_rx u_uart_rx(
- .clk (clk ),
- .rst_n (rst_n ),
- .rxd (rxd ),
- .rx_data (rx_data ),
- .rx_data_vld (rx_data_vld)
- );
-
- uart_tx u_uart_tx(
- .clk (clk ) ,
- .rst_n (rst_n ) ,
- .tx_data (rx_data ) ,
- .tx_data_vld (rx_data_vld) ,
- .txd (txd )
- );
-
- //时钟
- initial begin
- clk = 1'b1 ;
- forever begin
- #(CYCLE/2);
- clk = ~clk ;
- end
- end
-
- integer i;//数据个数
-
- initial begin
- rst_n = 1'b1 ;
- #(CYCLE);
- rst_n = 1'b0 ;
- rxd = 1'b1 ;
- #22;
- rst_n = 1'b1 ;
-
- #(CYCLE*200);//增加间隔
-
- //模拟一帧数据的格式
- rxd = 1'b0 ;//起始位
- #(CYCLE*5208);
-
- //数据位
- for (i=0;i<8;i=i+1) begin
- case (i)//赋值rxd = 1111_1001
- 1: rxd = 1'b0 ;
- 2: rxd = 1'b0 ;
- default: rxd = 1'b1 ;//只有第一位和第二位为0
- endcase
- #(CYCLE*5208);
- end
-
- rxd = 1'b1 ;//停止位
- #(CYCLE*5208);
-
- #(CYCLE*200);//增加间隔
-
- #(CYCLE*10*5208);//10bit时钟周期延迟
-
-
- $stop;
- end
- endmodule
test.do文件
- vlib work
- vmap work work
-
- #编译testbench文件
- vlog tb_uart_rx.v
- #编译 设计文件
- vlog ../rtl/uart_rx.v
- vlog ../rtl/uart_tx.v
-
-
- #vlog altera_mf.v
-
-
- #指定仿真顶层
- vsim -novopt work.tb_uart_rx
- #添加信号到波形窗
- add wave -position insertpoint sim:/tb_uart_rx//*
-
- run -all
只需要改动例化模块即可
- `timescale 1ns/1ps
- module tb_uart ();
- reg clk ;
- reg rst_n ;
- reg rxd ;
-
- wire txd ;
-
- //参数定义
- parameter CYCLE = 20 ;
-
- //例化
- uart u_uart(
- .clk (clk ),
- .rst_n (rst_n),
- .rxd (rxd ),
- .txd (txd )
- );
-
- //时钟
- initial begin
- clk = 1'b1 ;
- forever begin
- #(CYCLE/2);
- clk = ~clk ;
- end
- end
-
- integer i;//数据个数
-
- initial begin
- rst_n = 1'b1 ;
- #(CYCLE);
- rst_n = 1'b0 ;
- rxd = 1'b1 ;
- #22;
- rst_n = 1'b1 ;
-
- #(CYCLE*200);//增加间隔
-
- //模拟一帧数据的格式
- rxd = 1'b0 ;//起始位
- #(CYCLE*5208);
-
- //数据位
- for (i=0;i<8;i=i+1) begin
- case (i)//赋值rxd = 1111_1001
- 1: rxd = 1'b0 ;
- 2: rxd = 1'b0 ;
- default: rxd = 1'b1 ;//只有第一位和第二位为0
- endcase
- #(CYCLE*5208);
- end
-
- rxd = 1'b1 ;//停止位
- #(CYCLE*5208);
-
- #(CYCLE*200);//增加间隔
-
- #(CYCLE*10*5208);//10bit时钟周期延迟
-
-
- $stop;
- end
- endmodule
仿真图
另外一种测试方法:
用FPGA的tx端去模拟PC端的tx端发送数据,然后FPGA接收端rx进行接收
代码:
直接建立task发送三个数据
- `timescale 1ns/1ps
- module tb_uart1 ();
- reg clk ;
- reg rst_n ;
- reg [7:0] data ;
- reg data_vld ;
-
- wire dout ; //中间连线
- wire txd ;//tx输出
-
- //参数定义
- parameter CYCLE = 20 ;
-
- //例化
- uart_tx u_uart_tx(
- .clk (clk ) ,
- .rst_n (rst_n ) ,
- .tx_data (data ) ,
- .tx_data_vld (data_vld) ,
- .txd (dout )
- );
-
- uart u_uart(
- .clk (clk ),
- .rst_n (rst_n ),
- .rxd (dout ),
- .txd (txd )
- );
-
- //时钟
- initial begin
- clk = 1'b1 ;
- forever begin
- #(CYCLE/2);
- clk = ~clk ;
- end
- end
-
-
- initial begin
- rst_n = 1'b1 ;
- #(CYCLE);
- rst_n = 1'b0 ;
- data = 0 ;
- data_vld = 1'b0 ;
- #22;
- rst_n = 1'b1 ;
- #(CYCLE*200);//增加间隔
-
- //发送几个数据
- Send(8'hf9);
- Send(8'ha0);
- Send(8'hff);
-
- #(CYCLE*10*5208);//10bit时钟周期延迟
- #(CYCLE*200);
- $stop;
- end
-
- task Send;
- input [7:0] send_data ;
- begin
- data = send_data ;
- data_vld = 1'b1 ;
- #(CYCLE);
- data_vld = 1'b0 ;
- #(10*5208*CYCLE);
- end
- endtask
-
-
-
- endmodule
能够在波特率为9600时进行发送和接收
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。