前言
今天学习正点原子领航者开发板UART串口通信模块,以下是记录的笔记和重写的代码。
1:UART串口通信
1.1 基础知识
串行通信:优点:占用的引脚IO少,成本低 缺点:传输速率低
并行通信:优点:传输速率快 缺点:占用的引脚IO较多,成本高
单工通信:数据只能沿一个方向传输
半双工通信:数据可以沿两个方向传输,但需要分时进行
全双工通信:数据可以同时进行双向传输
同步通信:带时钟端口的数据传输
异步通信:没有时钟端口,发送方和接收方使用各自的时钟控制数据的收发过程
1.2 串行通信接口
1.3 UART 串口通信
1.3.1物理层
接口类型:串行通信接口有RS232/RS485/RS422等
RS232电平:逻辑1:-15V ~ -3V 逻辑0:+3V ~ +15V
1.3.2协议层
注:BAUD_CNT_MAX = CLK_FREQ/UART_BPS
即:计数器最大值 = 晶振周期 / 比特率
相关位置协议如下图:
1.4 亚稳态
首先,亚稳态是当正常采集数据时候,由于触发器信号会有延时的问题,很难正常采集其是0还是1。所以,这里采用打三拍的方式,如下代码,打三拍减少了tmet的时间,每打一拍,其时间降低为原来的1/2。
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
uart_rxd_d2 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
uart_rxd_d2 <= uart_rxd_d1;
end
end
2:波形图——写代码
2.1 系统框图
2.2 uart_rx波形图
注: 停止位标志位必须在时段周期一半的时候进行停止,卡沿的话,如果停止位后立刻进行开始标志位,那么开始标志位会被直接错过(忽视),所以取一半的计数(0~216)。
代码分析:
根据波形从上往下逐一信号有逻辑的书写和分析
1:start_en(由uart_rxd_d1,uart_rxd_d2,rx_flag波形推出):他是捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号,简单理解为既获得了接收信号标志位rx_flag为0,且获得打三拍后的数据uart_rxd_d2为1和打两拍的数据uart_rxd_d1为0的信号(捕获下降沿)。
2:uart_rxd_d2(由uart_rxd_d0,uart_rxd_d1,uart_rxd_d2波形推出):写打三拍的信号。
3:接收标志rx_flag赋值(由start_en波形推出):检测到起始位start_en为1后,标志信号rx_flag拉高为1(为baud_cnt计数做准备)。在停止位一半的时候(baud_cnt计数的一半且rx_cnt为9),即接收过程结束,标志信号rx_flag拉低为0。
4:波特率的计数器baud_cnt赋值(由标志位rx_flag推出):标志位rx_flag为1,开始计数,标志位rx_flag为0,清零。计数到(BAUD_CNT_MAX-1)时,清零。
5:对接收数据计数器(rx_cnt)进行赋值(由波特率的计数器baud_cnt和标志位rx_flag推出):标志位rx_flag为1,开始计数,标志位rx_flag为0,清零。baud_cnt到(BAUD_CNT_MAX-1)时,rx_cnt就+1。
6:根据rx_cnt来寄存rxd端口的数据rx_data_t(由接收数据计数器rx_cnt,波特率的计数器baud_cnt和标志位rx_flag推出):标志位rx_flag为1,开始寄存数据,标志位rx_flag为0,寄存数据rx_data_t清零。baud_cnt计数到数据位的中间时,rx_data_t开始寄存uart_rxd_d2的数据。
7:给接收完成信号uart_rx_done和接收到的数据uart_rx_data赋值(由接收数据计数器rx_cnt和波特率的计数器baud_cnt推出):当接收数据计数器rx_cnt到停止位的一半(rx_cnt为9即且波特率的计数器baud_cnt一半的时候),将rx_data_t的值赋给uart_rx_data,且uart_rx_done赋1,否则的话rx_data_t不变,uart_rx_done赋0。
module uart_rx(
input sys_clk ,
input sys_rst_n ,
input uart_rxd ,
output reg uart_rx_done,
output reg [7:0] uart_rx_data
);
parameter CLK_FREQ = 50000000 ;
parameter UART_BPS = 115200 ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
wire start_en ;
reg uart_rxd_d0 ;
reg uart_rxd_d1 ;
reg uart_rxd_d2 ;
reg rx_flag ;
reg [15:0] baud_cnt ;
reg [3:0] rx_cnt ;
reg [7:0] rx_data_t ;
// 1:start_en(由uart_rxd_d1,uart_rxd_d2,rx_flag波形推出)
assign start_en = (~rx_flag) & uart_rxd_d2 & (~uart_rxd_d1);
// 2:uart_rxd_d2(由uart_rxd_d0,uart_rxd_d1,uart_rxd_d2波形推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
uart_rxd_d2 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd ;
uart_rxd_d1 <= uart_rxd_d0 ;
uart_rxd_d2 <= uart_rxd_d1 ;
end
end
//3:接收标志rx_flag赋值(由start_en波形推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_flag <= 1'b0;
else if(start_en)
rx_flag <= 1'b1;
else if(baud_cnt == (BAUD_CNT_MAX/2 - 1'b1) && rx_cnt == 4'd9)
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
// 4:波特率的计数器baud_cnt赋值(由标志位rx_flag推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
baud_cnt <= 16'd0;
else if(baud_cnt < (BAUD_CNT_MAX - 1'b1) && rx_flag)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 16'd0;
end
// 5:对接收数据计数器(rx_cnt)进行赋值
//(由波特率的计数器baud_cnt和标志位rx_flag推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_cnt <= 1'b0;
else if(rx_flag) begin
if(baud_cnt == (BAUD_CNT_MAX - 1'b1))
rx_cnt <= rx_cnt + 1'b1;
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 1'b0;
end
//6:根据rx_cnt来寄存rxd端口的数据rx_data_t
//(由接收数据计数器rx_cnt,波特率的计数器baud_cnt和标志位rx_flag推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_data_t <= 8'd0;
else if(rx_flag) begin
if(baud_cnt == (BAUD_CNT_MAX/2 - 1'b1)) begin
case(rx_cnt)
4'd1: rx_data_t[0] <= uart_rxd_d2;
4'd2: rx_data_t[1] <= uart_rxd_d2;
4'd3: rx_data_t[2] <= uart_rxd_d2;
4'd4: rx_data_t[3] <= uart_rxd_d2;
4'd5: rx_data_t[4] <= uart_rxd_d2;
4'd6: rx_data_t[5] <= uart_rxd_d2;
4'd7: rx_data_t[6] <= uart_rxd_d2;
4'd8: rx_data_t[7] <= uart_rxd_d2;
default:rx_data_t <= rx_data_t;
endcase
end
else rx_data_t <= rx_data_t;
end
else
rx_data_t <= 8'd0;
end
//7:给接收完成信号uart_rx_done和接收到的数据uart_rx_data赋值
//(由接收数据计数器rx_cnt和波特率的计数器baud_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
uart_rx_done <= 1'b0;
uart_rx_data <= 8'd0;
end
else if(rx_cnt == 8'd9 && baud_cnt == (BAUD_CNT_MAX/2 - 1'b1)) begin
uart_rx_done <= 1'b1 ;
uart_rx_data <= rx_data_t ;
end
else begin
uart_rx_done <= 1'b0 ;
uart_rx_data <= uart_rx_data;
end
end
endmodule
2.3 uart_tx波形图
注: 如图,根据工程经验,其停止位是在整个周期的15/16处进行停止较为合适,不易发生数据堵塞。
代码分析:
根据波形从上往下逐一信号有逻辑的书写和分析
1:当使能信号uart_tx_en为高时,寄存输入的并行数据tx_data_t,并拉高uart_tx_busy信号(由uart_tx_en,tx_cnt,baud_cnt推出):当uart_tx_en为1时,并行数据tx_data_t寄存来自uart_tx_data的数据且拉高uart_tx_busy信号为1。当计数到停止位结束时,停止发送过程,即清空发送数据寄存器,并拉低BUSY信号。其他情况下,信号保持不变。
2:波特率的计数器baud_cnt赋值(由使能信号uart_tx_en和忙信号uart_tx_busy推出):uart_tx_busy为1,开始计数,uart_tx_busy为0,清零。计数到(BAUD_CNT_MAX-1)时,清零。
3:发送数据计数器tx_cnt进行赋值(由波特率的计数器baud_cnt和使能信号uart_tx_en推出):使能信号uart_tx_en为1,开始计数,tx_cnt清零。uart_tx_busy为1,且baud_cnt到(BAUD_CNT_MAX-1)时,tx_cnt就+1。uart_tx_busy为0,tx_cnt清零。
4:根据tx_cnt来给发送端口uart_txd赋值(由忙信号uart_tx_busy和发送数据计数器tx_cnt推出):当忙信号uart_tx_busy为1,根据协议对信号进行发送。当tx_cnt为0时,起始位uart_txd为0。当tx_cnt为1~8时,分别给uart_txd发送数据。当tx_cnt为1时,停止位uart_txd为1。其他时间段,全是停止位。
注:uart_txd空闲的时候,其发送端口是高电平1。
module uart_tx(
input sys_clk ,
input sys_rst_n ,
input uart_tx_en ,
input [7:0] uart_tx_data,
output reg uart_txd ,
output reg uart_tx_busy
);
parameter CLK_FREQ = 50000000 ;
parameter UART_BPS = 115200 ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
reg [7:0] tx_data_t ;
reg [15:0] baud_cnt ;
reg [3:0] tx_cnt ;
//1:当使能信号uart_tx_en为高时,寄存输入的并行数据tx_data_t,并拉高uart_tx_busy信号
//(由uart_tx_en,tx_cnt,baud_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
tx_data_t <= 8'd0 ;
uart_tx_busy <= 1'b0;
end
else if(uart_tx_en) begin
uart_tx_busy <= 1'b1 ;
tx_data_t <= uart_tx_data ;
end
else if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX*15/16 - 1'b1)) begin
uart_tx_busy <= 1'b0 ;
tx_data_t <= 8'd0 ;
end
else begin
uart_tx_busy <= uart_tx_busy;
tx_data_t <= tx_data_t ;
end
end
//2:波特率的计数器baud_cnt赋值(由使能信号uart_tx_en和忙信号uart_tx_busy推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
baud_cnt <= 16'd0;
else if(baud_cnt < (BAUD_CNT_MAX - 1'b1) && uart_tx_busy)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 16'd0;
end
//3:发送数据计数器tx_cnt进行赋值(由波特率的计数器baud_cnt和使能信号uart_tx_en推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tx_cnt <= 4'b0;
else if(uart_tx_busy) begin
if(baud_cnt == (BAUD_CNT_MAX - 1'b1))
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'b0;
end
//4:根据tx_cnt来给发送端口uart_txd赋值(由忙信号uart_tx_busy和发送数据计数器tx_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
uart_txd <= 1'b1;
else if(uart_tx_busy) begin
case(tx_cnt)
4'd0: uart_txd <= 1'b0 ; //开始位
4'd1: uart_txd <= tx_data_t[0] ;
4'd2: uart_txd <= tx_data_t[1] ;
4'd3: uart_txd <= tx_data_t[2] ;
4'd4: uart_txd <= tx_data_t[3] ;
4'd5: uart_txd <= tx_data_t[4] ;
4'd6: uart_txd <= tx_data_t[5] ;
4'd7: uart_txd <= tx_data_t[6] ;
4'd8: uart_txd <= tx_data_t[7] ;
4'd9: uart_txd <= 1'b1 ; //停止位
default:uart_txd <= 1'b1 ;
endcase
end
else
uart_txd <= 1'b1;
end
endmodule
3:DEBUG解决丢包数据
3.1 Vivado调试
采样深度:1,000,000,000/115200*10*3/20=13020
1s除以比特率为115200,要观察十位(协议上0~9),所以乘以10,观察三个字节(传_收_传),乘以3,晶振是50Mhz,所以再除以周期20ns,这就是应设置采样深度13020。
这里uart_rx_done两次时间间隔实际为4322,使得在uart_tx_busy降为0之前就发生了uart_rx_done,导致上一个数据还没发完,就让发下一个数据了,只有当下个uart_rx_done的信号来临时,才会发送,所以完全错过了上一个uart_rx_done的信号。
理论值:1,000,000,000/115200*10/20 = 4340,即1s除以比特率,乘以10个位,除以周期20ns,与实际值差了18个周期。
3.2 解决方法
思路:无论怎么改变接收位的代码,这里他都会使得uart_rx_done的间隔时间变短,数据更加丢包。所以,这里必须改变发送位的代码,来调整uart_rx_done的间隔时间,具体解决方案就是当发送为接收到了uart_rx_done的代码,就立刻去发送下一段通信,剩下没发完的直接省略。
代码:当接收到uart_tx_en的信号,就对立刻对baud_cnt和tx_cnt都清零,并且发送完整停止位,这样就可以重新计数,发送下一次发送的数据。
module uart_tx(
input sys_clk ,
input sys_rst_n ,
input uart_tx_en ,
input [7:0] uart_tx_data,
output reg uart_txd ,
output reg uart_tx_busy
);
parameter CLK_FREQ = 50000000 ;
parameter UART_BPS = 115200 ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
reg [7:0] tx_data_t ;
reg [15:0] baud_cnt ;
reg [3:0] tx_cnt ;
//1:当使能信号uart_tx_en为高时,寄存输入的并行数据tx_data_t,并拉高uart_tx_busy信号
//(由uart_tx_en,tx_cnt,baud_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
tx_data_t <= 8'd0 ;
uart_tx_busy <= 1'b0;
end
else if(uart_tx_en) begin
uart_tx_busy <= 1'b1 ;
tx_data_t <= uart_tx_data ;
end
else if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX - 1'b1)) begin
uart_tx_busy <= 1'b0 ;
tx_data_t <= 8'd0 ;
end
else begin
uart_tx_busy <= uart_tx_busy;
tx_data_t <= tx_data_t ;
end
end
//2:波特率的计数器baud_cnt赋值(由使能信号uart_tx_en和忙信号uart_tx_busy推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
baud_cnt <= 16'd0;
else if(uart_tx_en)
baud_cnt <= 16'd0;
else if(baud_cnt < (BAUD_CNT_MAX - 1'b1) && uart_tx_busy)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 16'd0;
end
//3:发送数据计数器tx_cnt进行赋值(由波特率的计数器baud_cnt和使能信号uart_tx_en推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tx_cnt <= 4'b0;
else if(uart_tx_en)
tx_cnt <= 16'd0;
else if(uart_tx_busy) begin
if(baud_cnt == (BAUD_CNT_MAX - 1'b1))
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'b0;
end
//4:根据tx_cnt来给发送端口uart_txd赋值(由忙信号uart_tx_busy和发送数据计数器tx_cnt推出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
uart_txd <= 1'b1;
else if(uart_tx_busy) begin
case(tx_cnt)
4'd0: uart_txd <= 1'b0 ; //开始位
4'd1: uart_txd <= tx_data_t[0] ;
4'd2: uart_txd <= tx_data_t[1] ;
4'd3: uart_txd <= tx_data_t[2] ;
4'd4: uart_txd <= tx_data_t[3] ;
4'd5: uart_txd <= tx_data_t[4] ;
4'd6: uart_txd <= tx_data_t[5] ;
4'd7: uart_txd <= tx_data_t[6] ;
4'd8: uart_txd <= tx_data_t[7] ;
4'd9: uart_txd <= 1'b1 ; //停止位
default:uart_txd <= 1'b1 ;
endcase
end
else
uart_txd <= 1'b1;
end
endmodule
参考内容
- 正点原子 领航者ZYNQ开发板资料盘(http://www.openedv.com/docs/)
- ZYNQ领航者V2开发板