赞
踩
最近做了两种通信协议的实现的练习(uart,spi),此文介绍uart串口协议(串口发送)的verilog实现和testbench的编写,考虑到还有部分同学使用vhdl,vhdl版本会随后发布。在以后的系列里,还会有介绍spi协议的文章,把我自己学习中遇到的困难和正确的解决方案记录下来,仿真环境为vivado 2018.3.
串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
此文目前只是实现串口发送过程,即将并行数据转化成串行数据发送给其他设备。
作为新手,我觉得串口通信主要要注意以下几点:
1.什么叫异步?异步,即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。而我们后面讲的SPI协议就是同步方式,是通过一个sclk信号来协同主从设备。
2.物理层,如下图所示。我们其实只用关心RXD、TXD、GND端口,其他其实不用知道太多,有兴趣的同学可以自行百度。
3.看懂uart发送的时序图。(非常重要!!!!)
1.首先要了解 串口发送的数据格式,因为是异步方式,所以数据格式就显得尤为重要,就是要知道通信的起始标志和终止标志,即通信是什么时候开始,是什么时候结束。
UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如上图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。
起始位:起始位标志。低有效。
数据位:一帧数据中的有效数据。注意,串口协议规定了数据位长度只能位 6 7 8 位。要想发多位数据,只能将其切分为多个过程发送。
奇偶校验位:是用来验证数据的正确性。奇偶校验一般不使用,如果 使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。其实就是发的时候如果奇校验位为1,表面数据中1的个数为奇数个,再看接受端的奇校验位是否依旧为1。如果是的话,校验成功。在一定概率上保证了传输的正确性。当然,只是概率上的传输正确。此次试验不同奇偶校验。
停止位:停止位标志着一帧数据的结束,高有效。
空闲时,传输 1 。
波特率设置 : 由于是异步通信,所以没有一个统一的时钟,需要双方约定传输传输速率。常见的波特率有:300, 1200, 2400, 9600, 19200, 115200 等.即一秒发送多少位数据。
所以我们需要实现下面的时序图。当使能信号到来时,发送起始位,数据位,停止位,一帧数据发送结束。
模块框图:
设计文件:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 2022/04/28 09:19:27
- // Design Name:
- // Module Name: uart_tx
- // Project Name:
- // Target Devices:
- // Tool Versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
-
- module uart_byte_tx (
- clk,
- reset,
- send_go, // 改成单脉冲启动信号 启动一个内部 send_en 信号
- data,
- baud_set,
- uart_tx,
- tx_done
- );
-
- input clk;
- input reset;
- input send_go;
- input [7:0]data;
- input [2:0]baud_set;
- output reg uart_tx;
- output reg tx_done;
- // every data remain time
-
- // baud_set = 0 bps = 9600 bps_max = 1000 000 000 / 9600 / 20
-
- reg [17:0]bps_max;
- always@(*)
- case(baud_set)
- 0:bps_max = 1000000000/9600/20; // clk = 50MHZ
- 0:bps_max = 1000000000/19200/20;
- 0:bps_max = 1000000000/38400/20;
- 0:bps_max = 1000000000/57600/20;
- 0:bps_max = 1000000000/115200/20;
- default:bps_max = 1000000000/9600/20;
- endcase
-
- reg send_en;
-
- always@(posedge clk or negedge reset)
- if (!reset)
- send_en <= 0;
- else if(send_go)
- send_en <= 1;
- else if(tx_done)
- send_en <= 0;
- // 一旦开始发送,send_go 有效,就将外部的data 锁存起来,,这样防止外部数据变化而采错data【i】
- reg [7:0]r_data;
- always@(posedge clk or negedge reset)
- if (send_go)
- r_data <= data;
- else
- r_data <= r_data;
-
- wire bps_clk;
- assign bps_clk = (div_cnt == 1);
- reg [17:0]div_cnt;
- always @(posedge clk or negedge reset) begin
- if(!reset)
- div_cnt <= 0;
- else if(send_en)
- begin
- if(div_cnt == bps_max - 1)
- div_cnt <= 0;
- else
- div_cnt <= div_cnt + 1'b1;
- end
- else
- div_cnt <= 0;
- end
-
- reg [3:0]bps_cnt; // 11
- always @(posedge clk or negedge reset) begin
- if(!reset)
- bps_cnt <= 0;
- else if(send_en)begin
- if(bps_clk)begin
- //if(div_cnt == 1)begin // 当send_en有效事 bps_cnt 才开始加 bps_cnt 不要等到最大再加一 这样会滞后
- if(bps_cnt == 12)
- bps_cnt <= 0;
- else
- bps_cnt <= bps_cnt + 1'b1;
- end
- end
- else
- bps_cnt <= 0;
- end
-
- // send
- always @(posedge clk or negedge reset)
- if(!reset)begin
- uart_tx <= 1'b1;
- tx_done <= 1'b0;
- end
- else begin
- case(bps_cnt)
- // 0:begin uart_tx <= 1'b0;tx_done <= 1'b0;end 这样写,bps_cnt 上面是 send_en 为零的时候就 为0 用0 不科学
- // 1:uart_tx <= data[0];
- // 2:uart_tx <= data[1];
- // 3:uart_tx <= data[2];
- // 4:uart_tx <= data[3];
- // 5:uart_tx <= data[4];
- // 6:uart_tx <= data[5];
- // 7:uart_tx <= data[6];
- // 8:uart_tx <= data[7];
- // 9:uart_tx <= 1'b1;
- // 10:begin uart_tx <= 1'b1;tx_done <= 1'b1;end
- // default:uart_tx <= 1'b1;
- 0:tx_done<=1'b0;
- 1:uart_tx <= 1'b0;
- 2:uart_tx <= r_data[0];
- 3:uart_tx <= r_data[1];
- 4:uart_tx <= r_data[2];
- 5:uart_tx <= r_data[3];
- 6:uart_tx <= r_data[4];
- 7:uart_tx <= r_data[5];
- 8:uart_tx <= r_data[6];
- 9:uart_tx <= r_data[7];
- 10:uart_tx <= 1'b1;
- 11:begin uart_tx <= 1'b1;end
- default:uart_tx <= 1'b1;
- endcase
- end
- // 为了保证 tx_done 只持续一个时钟周 单独写一个process
- always @(posedge clk or negedge reset)
- if(!reset)
- tx_done <= 1'b0;
- else if ((bps_clk == 1) && (bps_cnt == 10))
- tx_done <= 1'b1;
- else
- tx_done <= 1'b0;
- endmodule
testbench文件:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 2022/05/01 14:23:59
- // Design Name:
- // Module Name: uart_byte_tx_tb
- // Project Name:
- // Target Devices:
- // Tool Versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
-
-
- module uart_byte_tx_tb(
- );
- reg clk;
- reg reset;
- reg send_go;
- reg [7:0]data;
- wire uart_tx;
- wire tx_done;
-
- uart_byte_tx uart_byte_tx (
- .clk(clk),
- .reset(reset),
- .send_go(send_go), // 改成单脉冲启动信号 启动一个内部 send_en 信号
- .data(data),
- .baud_set(3'd4),
- .uart_tx(uart_tx),
- .tx_done(tx_done)
- );
- initial clk = 1;
- always#10 clk = ~clk;
-
- initial begin
- reset = 0;
- #201;
- reset = 1;
- send_go = 1;
- data = 8'h57;
- #20
- send_go = 0;
-
- @(posedge tx_done);
- #201;
- reset = 1;
- send_go = 1;
- data = 8'h75;
- #20
- send_go = 0;
-
- @(posedge tx_done);
- #20000;
- $stop;
- end
- endmodule
仿真结果:
由上图结果我们可以看到: 当send_go信号来临时(单脉冲),至于为什么设计成单脉冲,因为单脉冲更符合模块与模块之间的握手习惯。这个主要参考B站上小梅哥的视频。现在我们继续看这个仿真结果,首先发送01010111这个数据,串口规定从低位到高位,所以加上起始标志和结束标志,就是0111010101十位数据,证明时序的正确性。每发送完一个数据,产生一个tx_done单脉冲。工程文件可以直接下载我的资源(0积分)串口通信实现(verilog带testbench文件)-嵌入式文档类资源-CSDN文库。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。