目录
1.先定义部分端口 + 捕捉rxd下降沿确定start_flag
2.2然后实现两个计数器:clk_cnt + rx_cnt,clk_cnt是根据rx_flag拉高才开始计数
2.3 实现rx_cnt——rx_flag拉高情况下,clk_cnt每数到434个时钟周期,rx_cnt + 1
3.1在真正赋值给uart_data输出前,需要定义一个临时的寄存器rx_data——保存接收的数据,防止数据丢失或者出现一些其他的亚稳态现象!
3.2最后得到输出uart_data——定义output uart_data,以及完成信号uart_done
1.捕捉回环模块传过来使能信号en的上升沿——得到en_flag
2.做好tx_flag,并将数据保存到临时寄存器tx_data
3.1然后实现两个计数器:clk_cnt + tx_cnt——类似发送模块
4.最后:将寄存器中的数据,一位一位送到UART发送端口,并转串
任务时序图:
1.编写UART接收模块
1.先定义部分端口 + 捕捉rxd下降沿确定start_flag
2.1做好rx_flag——通过start_flag决定rx_flag,rx_flag要保持到第9位(停止位拉高半个波特率周期)才拉低——整个接收状态都是在rx_flag有效时才执行,因此下面所有操作都要首先判定rx_flag!
其实CLK_cnt也应该位宽尽量的大,因为不同的波特率,就可能不止434了(暂时没动)
//为了修改波特率后,代码不用改动,额外定义parameter常量!
//如果rx_flag未到9/未拉高0.5个波特率,就让rx_flag保持住
2.2然后实现两个计数器:clk_cnt + rx_cnt,clk_cnt是根据rx_flag拉高才开始计数
CLK_cnt计数到434个时钟时,就清零
2.3 实现rx_cnt——rx_flag拉高情况下,clk_cnt每数到434个时钟周期,rx_cnt + 1
3.赋值操作——uart_rxd赋值给uart_data
3.1在真正赋值给uart_data输出前,需要定义一个临时的寄存器rx_data——保存接收的数据,防止数据丢失或者出现一些其他的亚稳态现象!
因为如果不保存就直接输出,那接收模块的意义在哪?
//判断rx_cnt,去clk_cnt正中间,进行赋值
//异步通信,为了防止亚稳态,(因为不同时钟域)采用寄存两拍的值作为有效复制更好
异步通信延迟两拍: http://t.csdn.cn/iR8jJ
3.2最后得到输出uart_data——定义output uart_data,以及完成信号uart_done
临时寄存器不能作为真正的输出
最后将输出信号——uart_done以及uart_data完成
当到达停止位,说明寄存器中有一帧数据了,uart_done拉高 以及 uart_data输出!
2. 编写UART发送模块——发送的对称结构
0.发送模块说明+先定义部分端口
端口:时钟、复位、uart_txd、uart_en(开始发送的指令)、uart_tx_busy(告诉环回模块此时的状态,同时连接到上位机,告诉外界在发送数据,此时不能接收数据)
input uart_en, //环回模块的输出,告诉发送模块要发数据了
1.捕捉回环模块传过来使能信号en的上升沿——得到en_flag
2.做好tx_flag,并将数据保存到临时寄存器tx_data
同样需要临时寄存器tx_data(对应接收端的rx_data),保存数据输入uart_din,防止数据丢失或者出现一些其他的亚稳态现象!
根据上面的问题,所以我们又要定义tx_cnt,在tx_cnt计数到9的时候(并持续大于半个波特率周期(15/16个)拉低)——因为上位机时钟存在不稳定、数据线传输到上位机存在延迟等等原因
3.1然后实现两个计数器:clk_cnt + tx_cnt——类似发送模块
都是在tx_flag拉高的前提下,才能进行计数
3.2实现tx_cnt
4.最后:将寄存器中的数据,一位一位送到UART发送端口,并转串
//UART发送端口一般保持拉高,等起始位来就拉低,给上位机接收提醒
//发送必须从4'd0开始,形成完整的一个数据帧,要给接收端提示 //没有发送数据,uart_txd拉高
5.额外的:发送忙信号uart_tx_busy
发送忙信号uart_tx_busy直接用tx_flag即可,用于告诉环回模块正发送忙!
3.至此,接收、发送模块完成
1.接收代码
module uart_recv(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output reg [7:0] uart_data, //因为要在always语句块中赋值reg
output reg uart_done
);
//增加常量
parameter CLK_FREQ = 5000_0000; //时钟频率常量
parameter UART_BPS = 115200; //波特率常量
localparam BPS_cnt = CLK_FREQ/UART_BPS; //局部参数,不能作为参数传递,BPS_cnt持续一个波特率周期(434)
//1.抓取uart_rxd的下降沿——得到start_flag
reg uart_rxd_d0;
reg uart_rxd_d1;
wire start_flag;
assign start_flag = ~uart_rxd_d0 & uart_rxd_d1;
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;
end
else begin
//双寄存器锁存,且d0 d1永远差一个时钟
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
end
end
//2.1做好rx_flag,
//2.2然后实现两个计数器:clk_cnt + rx_cnt
reg rx_flag;
reg[3:0] rx_cnt; //数到9,位宽为4即可
reg[8:0] clk_cnt; //数到433<512对应位宽9位
//2.1 做好rx_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rx_flag <= 1'b0;
end
else begin
if (start_flag)
rx_flag <= 1'b1;
//补一个拉低:在rx_cnt = 第9位数据时拉低
//而且拉低的时间是在第九位持续:0.5个波特率周期后(434*0.5=217)
//而且为了修改波特率后,代码不用改动,额外定义parameter常量!
else if ((rx_cnt == 9) && clk_cnt == BPS_cnt/2)
rx_flag <= 1'b0;
else
//如果rx_flag未到9/未拉高0.5个波特率,就让rx_flag保持住
rx_flag <= rx_flag;
end
end
//2.2然后实现两个计数器:clk_cnt + rx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 9'd0;
//clk_cnt是根据rx_flag拉高才开始计数
else if (rx_flag) begin
if (clk_cnt < BPS_cnt - 1) //434 - 1
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 9'd0;
end
//rx_flag没有拉高
else
clk_cnt <= 9'd0;
end
//2.3 实现rx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_cnt <= 4'b0;
//首先rx_flag需要拉高
else if (rx_flag) begin
//其次clk_cnt=434个时钟周期,rx_cnt才+1
if (clk_cnt == BPS_cnt - 1)
rx_cnt <= rx_cnt + 1'b1;
//没到434就保持
else
rx_cnt <= rx_cnt;
end
//最后如果没有rx_flag,清零等待状态
else
rx_cnt <= 4'b0;
end
//3.赋值操作——uart_rxd赋值给uart_data
//在真正赋值给uart_data输出前,需要定义一个临时的寄存器rx_data——保存接收的数据
//因为如果不保存就直接输出,那接收模块的意义在哪?
reg[7:0] rx_data; //和输出uart_data位宽一致
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_data <= 8'b0;
else if (rx_flag) begin
//判断rx_cnt,去clk_cnt正中间,进行赋值
if (clk_cnt == BPS_cnt/2) begin
case (rx_cnt)
//异步通信,为了防止亚稳态,(因为不同时钟域)采用寄存两拍的值作为有效复制更好
//从4'd1开始,就是从bit0——有效数据开始
4'd1: rx_data[0] <= uart_rxd_d1;
4'd2: rx_data[1] <= uart_rxd_d1;
4'd3: rx_data[2] <= uart_rxd_d1;
4'd4: rx_data[3] <= uart_rxd_d1;
4'd5: rx_data[4] <= uart_rxd_d1;
4'd6: rx_data[5] <= uart_rxd_d1;
4'd7: rx_data[6] <= uart_rxd_d1;
4'd8: rx_data[7] <= uart_rxd_d1;
default:;
endcase
end
else
rx_data <= rx_data; //时钟计数器没到217时,寄存器数据不变
end
else
rx_data <= 8'b0; //rx_flag没有拉高
end
//3.2最后将输出信号——uart_done以及uart_data完成
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_data <= 8'b0;
uart_done <= 1'b0;
end
else if (rx_cnt == 4'd9) begin
uart_data <= rx_data;
uart_done <= 1'b1;
end
else begin
uart_data <= 8'b0;
uart_done <= 1'b0;
end
end
endmodule
2.发送代码
module uart_send(
input sys_clk,
input sys_rst_n,
input uart_en, //环回模块的输出,告诉发送模块要发数据了
input[7:0] uart_din, //环回模块传过来的数据
output reg uart_txd, //发送数据,和上位机通信(且并串转换)
output uart_tx_busy //告诉环回模块此时正在忙——发送数据
);
//增加常量
parameter CLK_FREQ = 5000_0000; //时钟频率常量
parameter UART_BPS = 115200; //波特率常量
localparam BPS_cnt = CLK_FREQ/UART_BPS; //局部参数,不能作为参数传递,BPS_cnt持续一个波特率周期(434)
//1.捕捉回环模块传过来使能信号en的上升沿——得到en_flag
reg uart_en_d0;
reg uart_en_d1;
wire en_flag;
assign en_flag = uart_en_d0 & ~uart_en_d1;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
//双寄存器锁存,且d0 d1永远差一个时钟
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//2.1做好tx_flag,
//2.2然后实现两个计数器:clk_cnt + rx_cnt
reg tx_flag;
reg[3:0] tx_cnt; //数到9,位宽为4即可
reg[8:0] clk_cnt; //数到433<512对应位宽9位
//同样需要临时寄存器tx_data,保存数据输入uart_din
//防止数据丢失或者出现一些其他的亚稳态现象
reg[7:0] tx_data;
//2.1 做好tx_flag,寄存数据tx_data
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_flag <= 1'b0;
tx_data <= 8'b0;
end
else if (en_flag) begin
tx_flag <= 1'b1;
tx_data <= uart_din;
end
//定义tx_cnt,在tx_cnt计数到9的时候(并持续大于半个波特率周期(15/16个)拉低)
//同时数据清零——因为数据
else if ((tx_cnt == 4'd9) && clk_cnt == (BPS_cnt - BPS_cnt/16)) begin
tx_flag <= 1'b0;
tx_data <= 8'b0;
end
//其他情况——包括没到9就保持原状态
else begin
tx_flag <= tx_flag;
tx_data <= tx_data; //数据保持
end
end
//3.1然后实现两个计数器:clk_cnt + tx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 9'd0;
//clk_cnt是根据tx_cnt拉高才开始计数
else if (tx_flag) begin
if (clk_cnt < BPS_cnt - 1) //434 - 1
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 9'd0;
end
//tx_cnt没有拉高
else
clk_cnt <= 9'd0;
end
//3.2实现tx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
tx_cnt <= 4'b0;
//首先rx_flag需要拉高
else if (tx_flag) begin
//其次clk_cnt=434个时钟周期,rx_cnt才+1
if (clk_cnt == BPS_cnt - 1)
tx_cnt <= tx_cnt + 1'b1;
//没到434就保持
else
tx_cnt <= tx_cnt;
end
//最后如果没有rx_flag,清零等待状态
else
tx_cnt <= 4'b0;
end
//4.最后一步:将寄存器中的数据,一位一位送到UART发送端口,并转串
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
uart_txd <= 1'b1; //UART发送端口一般保持拉高,等起始位来就拉低,给上位机接收提醒
else (tx_flag) begin
case (tx_cnt)
//发送必须从4'd0开始,形成完整的一个数据帧,要给接收端提升
4'd0: uart_txd <= 1'b0; //起始位——形成下降沿
4'd1: uart_txd <= tx_data[0];
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7];
4'd9: uart_txd <= 1'b1; //停止位——拉高
default: ;
endcase
end
else
//没有发送数据,uart_txd拉高
uart_txd <= 1'b1;
end
//额外的,发送忙信号uart_tx_busy直接用tx_flag即可,用于告诉环回模块正发送忙!
assign uart_tx_busy = tx_flag;
endmodule