学习任务:今天实现了串口控制led的亮灭切换,以及发送一个特定字符的功能。
问题的产生及解决:
在书写led切换亮灭条件的时候,最开始只写了 rx_data == 49 ,导致led没能按照预期发送特定字符亮起,再发送特定字符,熄灭。 这是因为,当 没有新值传入的情况下,rx_data会一直保持最后传入的那个值,也即:如果最后一个是49,则会一直保持49,也就是说,这个if条件会持续成立,内部的语句led <= ~led 会不断执行。导致每有一个时钟电平到来,led就会翻转一次。
为了解决这个问题,也即 想要,只有一瞬间满足该条件,然后执行内部语句。则很自然想到,,后面就再添加一个条件,也即 数据接收完成的标志。 当接收完成,且接收的值为49 才会进行内部语句。这样就实现了 发送一次翻转 一次led。
下面是相关代码:
led_control
module led_control(
input rst_n,
input clk,
input [7:0] rx_data,
input dout_vld,
output reg tx_req_flag,
output reg led
);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 0;
tx_req_flag <= 0;
end //下面这排不能少条件,若只写条件1,则会让每个时钟电平到来的时候都会翻转led。
else if(rx_data == 49 && dout_vld==1)begin //接收到的是ascii码对应值
led <= ~led;
tx_req_flag <= 1;
end
else begin
led <= led;
tx_req_flag <= 0;
end
end
endmodule
数据接收模块
`include "param.v"
module uart_rx(
input clk,
input rst_n,
input rx_din,
output [7:0] rx_dout,
output reg dout_vld
);//开始的标志是,数据接收端下降沿产生,结束标志是,数据包传完,即cnt_bit == 9
reg [12:0] cnt_bps;//波特率计数寄存器
wire add_cnt_bps;//波特率计数开始
wire end_cnt_bps;//波特率计数结束
reg [3:0] cnt_bit;//比特计数寄存器
wire add_cnt_bit;//比特计数开始
wire end_cnt_bit;//比特计数结束
reg rx_flag;
reg rx_din_r0;
reg rx_din_r1;
reg [9:0] rx_data;
wire nedge;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_din_r0 <= 1'b1;
rx_din_r1 <= 1'b1;
end
else begin
rx_din_r0 <= rx_din;
rx_din_r1 <= rx_din_r0;
end
end
assign nedge = ~rx_din_r0 && rx_din_r1;//下降沿产生,是开始接收的标志。 因为在发送端,第一位是0,所以这里正好从1->0,即下降沿产生
//波特率计数器 (接收一个Bit需要的时间)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps)begin
if(end_cnt_bps)begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps + 1;
end
end
else begin
cnt_bps <= cnt_bps;
end
end
assign add_cnt_bps = rx_flag;
assign end_cnt_bps = add_cnt_bps && cnt_bps == (`SYS_FRQ / `BAUD_MAX) - 1'd1;
//计数发送了多少位数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt_bps;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd9;
//判断是否开始接收数据(下降沿产生,则开始,几位数据位遍历完,则结束)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_flag <= 1'b0;
end
else if(nedge)begin//数据开始输入
rx_flag <= 1'b1;
end
else if(end_cnt_bit)begin
rx_flag <= 1'b0;
end
else begin
rx_flag <= rx_flag;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 10'b0;
end //下面这个判断条件很巧妙
//(考虑到了 0.1数据位之间临界处并不是理想的垂直跳变,所以就慢半拍(电平完全稳定了)读取数据值,
else if(rx_flag && cnt_bps == (`SYS_FRQ / `BAUD_MAX)>>1)begin
rx_data[cnt_bit] <= rx_din_r0;//注意cnt_bit要cnt_bps==(`SYS_FRQ / `BAUD_MAX)才会+1,所以第一次赋值的rx_data[0]会被覆盖一次
end
else begin
rx_data <= rx_data;
end
end
assign rx_dout = rx_data[8:1];
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_vld <= 1'b0;
end
else if(end_cnt_bit)begin
dout_vld <= 1'b1;
end
else begin
dout_vld <= 1'b0;
end
end
endmodule
数据发送模块
`include "param.v"
module uart_tx(
input clk,
input rst_n,
input tx_req,
input [7:0] tx_din,//输入的并行数据(真正需要发送的数据串)(数据不够这么多位,自动补零?)
output reg tx_dout,//输出的串行数据
output dout_vld //发送完成的标志
);//开始的标志是 tx_req为1,结束的标志是 数据发送完,tx_flag随即置0
reg [12:0] cnt_bps;//波特率计数寄存器
wire add_cnt_bps; //波特率计数开始标志
wire end_cnt_bps; //波特率计数结束
reg [3:0] cnt_bit;//比特计数寄存器
wire add_cnt_bit;//比特计数开始标志
wire end_cnt_bit;//比特计数结束
reg tx_flag;
reg [9:0] tx_data;//需要发送的数据 (实际发送的数据串,即:包括包头包尾)
//波特率计数器(发送1bit需要的时间)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps)begin
if (end_cnt_bps) begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps +1;
end
end
else begin
cnt_bps <= cnt_bps ;
end
end
assign add_cnt_bps = tx_flag;
assign end_cnt_bps = add_cnt_bps && cnt_bps == (`SYS_FRQ/`BAUD_MAX)-1'd1 ;
//计数已经发送了多少位数据(确定当前应该发送第几位了)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if (end_cnt_bit) begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt_bps;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd9;//达到条件后,结束发送
//发送开始,结束的判断
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_flag <= 0;
end
else if(tx_req)begin //收到发送请求,则 将发送位(即tx_flag)置1
tx_flag <= 1'b1;
end
else if (end_cnt_bit) begin
tx_flag <= 1'b0;
end
else begin
tx_flag <= tx_flag;
end
end
//拼接(打包)需要发送的数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data <= 10'b0;
end
else if(tx_req)begin
tx_data <= {1'b1,tx_din,1'b0};//注意:传送的时候 tx_data[0]=0 而不是1。 这里最后一位是1,是因为 要让接收端的默认信号变回高电平,便于下次低电平到来的时候,下降沿的产生
end
else begin
tx_data <= tx_data;
end
end
//开始发送数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_dout <= 1'b1;
end
//发送端为什么不考虑0.1衔接不理想的问题?
//,因为发送端是发送,而不是读取,读取要判断该电平表示1还是0,而发送端不用读取、所以不用考虑0.1电平理想的问题
else if(tx_flag && cnt_bps == 1)begin
tx_dout <= tx_data[cnt_bit];
end
else begin
tx_dout <= tx_dout;
end
end
assign dout_vld = ~tx_flag;
endmodule
顶层文件:
module uart_led(
input clk,
input rst_n,
input rx,
output tx_dout,
output led
);
wire [7:0] rx_reg;
wire dout_vld;
wire tx_req_flag;
reg [7:0] message = "t";
uart_rx u_uart_rx(
.clk(clk),
.rst_n(rst_n),
.rx_din(rx),
.rx_dout(rx_reg),//rx_reg在没有新值产生的情况下,一直保持该时刻的值
.dout_vld(dout_vld)
);
led_control u_led_control(
.clk(clk),
.rst_n(rst_n),
.rx_data(rx_reg),
.dout_vld(dout_vld),
.tx_req_flag(tx_req_flag),
.led(led)
);
uart_tx u_uart_tx(
.clk(clk),
.rst_n(rst_n),
.tx_req(tx_req_flag),
.tx_din(message),
.tx_dout(tx_dout)
);
endmodule