uart协议FPGA实现
1.uart协议是什么???
如图是简化版的uart协议,包括start位,数据位(Bit0-Bit7),stop位;说白了就是用来传递数据的。
2.uart主要参数
- 波特率:每s传输的bit数;常见波特率有9600,115200等
- 起始位:uart协议以拉低作为起始位
- 终止位:uart协议以最终拉高作为终止位
3.信号设计:
信号设计中,我主要按照“输入”,“输出”,“内部信号”,“系统信号”进行分类:
说明一下,send_en是内部信号,send_en==1时,表示数据可以发送,是uart的数据可以发送,不是外部的数据可以输入进来!!!
4.设计思想
主要是需要领悟到send_en,tx_done,num_cnt,send_go这几个信号互相之间的关系,我的习惯是把每个信号单独列出来,然后变化过程列出来:
5.verilog代码
模块设计:
module uart_byte_send(
data,
Boud,
clk,
rst_n,
send_go,
uart_tx,
tx_done
);
parameter TBS=1_000_000_000;
input [7:0] data;//数据传入
input [2:0] Boud;//波特率选择,共8种
input clk;
input rst_n;
input send_go;//使能发送
output reg uart_tx;
output reg tx_done;//发送结束
reg send_en;
//Boud选择--计数时间
reg [17:0] boud_time;
always@(*) begin
case (Boud) //波特率问中间的数字,/20是根据板载晶振50MHz
3'd0: boud_time = TBS/600/20;
3'd1: boud_time = TBS/1200/20;
3'd2: boud_time = TBS/2400/20;
3'd3: boud_time = TBS/4800/20;
3'd4: boud_time = TBS/9600/20;
3'd5: boud_time = TBS/19200/20;
3'd6: boud_time = TBS/38400/20;
default: boud_time = TBS/9600/20;//默认9600;
endcase
end
//boud计数器,计数单位数据的时间
reg [17:0] boud_cnt;
always@(posedge clk or negedge rst_n or negedge send_en)
if(!rst_n)
boud_cnt<=0;
else if(boud_cnt==boud_time-1)
boud_cnt<=0;
else if (send_en==0)
boud_cnt<=0;
else
boud_cnt<=boud_cnt+1'b1;
//num计数器,计数多少帧,这里的开始帧和结束帧以及8位数据帧共10帧
reg [4:0] num_cnt;
always@(posedge clk or negedge rst_n or posedge send_en)
if(!rst_n)
num_cnt<=0;
else if(num_cnt==10)
num_cnt<=0;
else if (send_en==0)
num_cnt<=0;
else if(send_en) begin
if(boud_cnt==1)
num_cnt<=num_cnt+1'b1;
else
num_cnt<=num_cnt;
end
//send_en判断能否发送数据
always@(posedge clk or negedge tx_done or negedge rst_n)
if(!rst_n)
send_en<=0;
else if (num_cnt==10)
send_en<=0;
else if (tx_done==0)
send_en<=1;
//判断tx_done信号何时表示发送完成
always@(posedge clk or posedge send_go or negedge rst_n)
if(!rst_n)
tx_done<=1;
else if (num_cnt==10 )
tx_done<=1;
else if (send_go==1)
tx_done<=0;
//uart_tx发送数据
always@(posedge clk or negedge rst_n or posedge send_en)
if(!rst_n)
uart_tx<=1'b1;
else if(send_en) begin
case (num_cnt)
4'd0:uart_tx<=1'b0;
4'd1:uart_tx<=data[7];
4'd2:uart_tx<=data[6];
4'd3:uart_tx<=data[5];
4'd4:uart_tx<=data[4];
4'd5:uart_tx<=data[3];
4'd6:uart_tx<=data[2];
4'd7:uart_tx<=data[1];
4'd8:uart_tx<=data[0];
4'd9:uart_tx<=1'b1;
default:uart_tx<=1'b1;
endcase
end
else
uart_tx<=1'b1;
endmodule
激励设计:
`timescale 1ns / 1ps
module uart_byte_send_tb;
// Inputs
reg [7:0] data;
reg clk;
reg rst_n;
reg send_go;
// Outputs
wire uart_tx;
wire tx_done;
// Instantiate the Unit Under Test (UUT)
uart_byte_send uut (
.data(data),
.Boud(4),
.clk(clk),
.rst_n(rst_n),
.send_go(send_go),
.uart_tx(uart_tx),
.tx_done(tx_done)
);
initial clk=1;
always #10 clk=~clk;//20ns一个周期
//对输入进行赋值
initial begin
rst_n=0;
data=0;
send_go=0;
#201;
rst_n=1;
#100;
data=8'h57;
send_go=1;
#20
@(posedge tx_done)//死循环,一直等待TX_done的到来,再执行下一语句
send_go=0;
#20000
data=8'h75;
send_go=1;
#20
@(posedge tx_done)
send_go=0;
#20000
$stop;
end
endmodule
6.调试过程
调试真的。。。难受的一匹,一开始的代码都跑不出循环,接下来慢慢说明调试的过程和方法!!!
Ⅰ.调试方法:
我觉得调试的话可以按照信号传入的先后顺序,列出先后顺序图,我这里的调试顺序就是如下:
从send_go开始看,同时看tx_done和send_en的变化状态,注意在发数据的时候也要注意这三个信号的变化。
Ⅱ.一些小bug
①第一次做的时候,一直跑不出循环,然后波形图出来之后,发现一直等不到tx_done的上升沿,然后发现uart_tx一直是0,找到控制元素num_cnt也一直是0,所以就是num_cnt的问题了!那他为什么不+1呢???原来是刚开始只检测了send_en的上升沿,忘记检测clk的上升沿了!
②噢对,还有就是为什么要在uart_tx检测send_en的上升沿,实际上是因为num_cnt的常态是“0”,也就是说他平常就是0,但是我们是需要“0”这个状态来发起始位的,所以就需要另外一个条件,当然你可以选择放弃“0”态来发起始位,用“1”态来发,小梅哥就是这么做的,但是我觉得这样子后面写循环的时候就很乱,而且也不合逻辑。所以找另外条件的话可以考虑,怎么把平常的状态给区分开,我选择的就是用send_en,因为send_en置1的时候是代表系统在传数据,那正好就跟常态区分开了!
③还有一个地方,是看了小梅哥学的,就是把num_cnt在boud_cnt为1的时候就+1,这样子可以避免等一个整数据位