【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
uart之所以被称之为最简单的通信协议,一方面是因为它没有时钟信号,另外一方面是因为接收和发送是两条线分开来的。它不像一些协议,例如iic、sdio等等,数据位既可以用做输入、也可以用做输出。uart这样设计起来,就会简单很多。不过,很多sdio协议,也支持spi输入输出,当然这是后话了。
1、uart接收的顺序
前面说过,uart的发送一般是10个bit,即起始位、数据位和停止位,默认没有校验位。起始位就是从1拉到0,而停止位则是恢复为1。数据位的发送,是从低到高依次发送。那么这里接收其实也是一样的,首先接收起始位,然后是数据位,最后是停止位。数据位也是从低到高接收。
2、uart接收的时机
因为没有clock作为保护,所以这里这能靠数据的边沿触发作为接收的一个时机。平时uart的tx都是高电平,因此接收的时候如果发现从高到低拉低,那就是代表数据开始进行发送,这边就要做好接收的准备了。
3、uart接收的波特率
即使双方商量好了接收的起始时间,但是接收的速度还是没有办法定下来的,因此这种发送、接收的速度只能靠提前的约定来进行处理。比如大家都约定好,发送、接收的帧率是4800、9600,或者是115200。如果没有按照约定的频率来发送、接收,就会出现两种情况,要么采样慢了漏掉数据,要么是采样快了,采样了多余的数据。
4、采样的时机
实际uart的采样频率要比fpga时钟慢很多,一个周期内理论上什么时候采样都可以。不过为了保证数据的稳定性,我们一般还是在采样周期的中间那个点,去获取对应的bit信息,这是最好的处理方式。
5、uart接收的实现
本身uart的实现不复杂。需要注意的主要就是状态机和开始接收、接收时机的处理。首先是设计好状态机,
always@(posedge clk or negedge rst)
if(!rst)
state <= 3'h0;
else
state <= next_state;
always@(*)
if(!rst)
next_state = 3'h0;
else if(state == 3'h0 && (rx2 && !rx1))
next_state = 3'h1;
else if(state == 3'h1 && counter == HALF_CYCLE) // skip first data
next_state = 3'h2;
else if(state == 3'h2 && counter == HALF_CYCLE && num == 7) // middle 8 data
next_state = 3'h3;
else if(state == 3'h3 && counter == HALF_CYCLE) // last data
next_state = 3'h0;
else
next_state = state;
处理好了状态机,就要弄清楚什么时候开始接收,并且接收的周期是多少,
always@(posedge clk or negedge rst)
if(!rst) begin
rx1 <= 1'b1;
rx2 <= 1'b1;
end
else begin
rx1 <= rx;
rx2 <= rx1;
end
always @(posedge clk or negedge rst)
if(!rst) // reset state
counter <= 16'h0;
else if(state != 3'h0) begin // first data and middle 8 data
if((counter == FULL_CYCLE) || (state == 3'h3 && counter == HALF_CYCLE))
counter <= 16'h0;
else
counter <= counter + 1;
end
这些都定下来之后,最后就是接收数据、接收数量以及提示什么时候数据可用。
always @(posedge clk or negedge rst)
if(!rst)
num <= 3'h0;
else if(state == 3'h2 && counter == HALF_CYCLE) // total 8 data, select data from the middle of the wave
num <= num + 1;
always @(posedge clk or negedge rst)
if(!rst)
out_data <= 8'h0;
else if(state == 3'h2 && counter == HALF_CYCLE) // save data to reg
out_data[num] <= rx2;
always @(*)
if(!rst)
data_valid = 1'b0;
else if(state == 3'h3 && counter == HALF_CYCLE)
data_valid = 1'b1;
else
data_valid = 1'b0;
一切都没有问题之后,就可以添加testbench,做好仿真测试即可。这是准备的testbench文件,最重要的部分就是rx下降沿的设计,因为只有有了明显的下降沿,才会触发状态机的运转。
`timescale 1ns/1ps
module test();
reg clk;
reg rst;
reg rx;
wire data_valid;
wire[7:0] data;
initial begin
clk = 0;
rst = 1;
rx = 1;
#10 rst = 0;
#30 rst = 1;
#50 rx = 0;
#300 rx = 1;
#10000 $finish;
end
initial begin
while(1)
clk = #5 ~clk;
end
uart_rx uart_rx(
.clk(clk),
.rst(rst),
.rx(rx),
.data_valid(data_valid),
.out_data(data)
);
initial begin
$dumpfile("hello.vcd");
$dumpvars(0, test);
end
endmodule
没有什么问题之后,就可以直接利用iverilog、vvp、gtkwave编译、仿真和查看波形了,