串口发送模块与验证
baud_set
|
波特率
|
波特率周期
|
波特率分频计数值
|
System_clk_period = 20计数值
(从0开始计算所以-1)
|
0
|
9600
|
104167ns
|
104167/ System_clk_period
|
5208-1
|
1
|
19200
|
52083ns
|
52083/ System_clk_period
|
2604-1
|
2
|
38400
|
26041ns
|
26041/ System_clk_period
|
1302-1
|
3
|
57600
|
17361ns
|
17361/ System_clk_period
|
868-1
|
4
|
115200
|
8680ns
|
8680/ System_clk_period
|
434-1
|
下一个时钟来临之后,[MUX2_1] 取 [MUX2_2] 的状态,由于 [MUX2_2] 取自Tx_Done信号,而Tx_Done为0,所以 [MUX2_2] 取的是en_cnt的信号,即UART_state == en_cnt == [MUX2_2] == 1。
只要Tx_Done信号为1,则 [MUX2_2] 就会选择输出0,从而改变 UART_state、en_cnt信号,注意 [bsp_cnt] 模块的clr信号也受Tx_Done控制。
module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, uart_state);
input clk; // 系统时钟
input rst_n; // 复位
input[7:0] data_byte; // 要发送的数据
input send_en; // 启动发送
input[2:0] baud_set; // 波特率选择
output reg rs232_tx;
output reg tx_done; // 发送完毕通知 1:发送完毕 0:正在发送
output reg uart_state; // 发送状态 1:正在发送数据 0:空闲状态
reg bps_clk; // 波特率时钟
wire en_cnt; // 计数使能 1:使能 0:失能
reg[15:0] div_cnt; // 分频计数器
reg[15:0] bps_dr; // 分频计数最大值
reg[3:0] bps_cnt; // 波特率时钟计数器
wire clr; // 清零信号
reg[7:0] r_data_byte_buff; // 缓冲区,用于存储需要发送的数据,避免在发送过程中数据突然改变
localparam START_BIT = 1'b0;
localparam STOP_BIN = 1'b1;
// 串口工作状态
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(tx_done) // 发送完毕
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
assign en_cnt = uart_state;
// 用于启动发送时锁存即将要发送的数据
// 这样就可以避免在发送的过程中数据突然改变导致发送的数据不正确。
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
r_data_byte_buff <= 8'd0;
else if(send_en)
r_data_byte_buff <= data_byte; // 启动发送则锁存最新的数据
else
r_data_byte_buff <= r_data_byte_buff;
end
// 【DR_LUT】 通过查表的方式将波特率转换为对应的分频计数最大值
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_dr <= 16'd5207; // 9600bps
else begin
case(baud_set) // 查找表
0:bps_dr <= 16'd5207; // 9600bps
1:bps_dr <= 16'd2603; // 19200bps
2:bps_dr <= 16'd1301; // 38400bps
3:bps_dr <= 16'd0867; // 57600bps
4:bps_dr <= 16'd0433; // 115200bps
default:bps_dr <= 16'd5207; // 9600bps
endcase
end
end
// 【Div_Cnt】 计数功能
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
div_cnt <= 16'd0;
else if(en_cnt) begin
if(div_cnt == bps_dr)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end else
div_cnt <= 16'd0;
end
// 【Div_Cnt】 bps_clk 时钟产生
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1) // 当计数器刚开始计数时就产生一个时钟
bps_clk <= 1'b1; // 这样就相当于启动发送时就立即开始发送数据
else
bps_clk <= 1'b0;
end
// 【bps_cnt】 bps 计数(即发送的数据位数计数)
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_cnt <= 4'd0;
else if(clr)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
// 【MUX10】、【r_R232_Tx】 尽量避免组合逻辑直接输出,输出是有毛刺的可能会出现不太稳定的情况
// 发送数据模块
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
rs232_tx <= STOP_BIN; // 起始位为低电平,所以空闲时为高电平即停止位
else begin
case(bps_cnt)
0:rs232_tx <= STOP_BIN; // 空闲时 bps_cnt 会一直为 0
1:rs232_tx <= START_BIT; // 起始位
2:rs232_tx <= r_data_byte_buff[0];
3:rs232_tx <= r_data_byte_buff[1];
4:rs232_tx <= r_data_byte_buff[2];
5:rs232_tx <= r_data_byte_buff[3];
6:rs232_tx <= r_data_byte_buff[4];
7:rs232_tx <= r_data_byte_buff[5];
8:rs232_tx <= r_data_byte_buff[6];
9:rs232_tx <= r_data_byte_buff[7];
10:rs232_tx <= STOP_BIN; // 停止位
default:rs232_tx <= STOP_BIN;
endcase
end
end
// 检测一帧数据是否发送完成
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
end
assign clr = tx_done; // 当完成一帧数据发送之后清除 bps 计数器
问题:tx_done、bps_cnt 会分别维持两个时钟周期的 1 和 11
module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, uart_state);
input clk; // 系统时钟
input rst_n; // 复位
input[7:0] data_byte; // 要发送的数据
input send_en; // 启动发送
input[2:0] baud_set; // 波特率选择
output reg rs232_tx;
output wire tx_done; // 发送完毕通知 1:发送完毕 0:正在发送
output reg uart_state; // 发送状态 1:正在发送数据 0:空闲状态
reg bps_clk; // 波特率时钟
wire en_cnt; // 计数使能 1:使能 0:失能
reg[15:0] div_cnt; // 分频计数器
reg[15:0] bps_dr; // 分频计数最大值
reg[3:0] bps_cnt; // 波特率时钟计数器
wire clr; // 清零信号
reg[7:0] r_data_byte_buff; // 缓冲区,用于存储需要发送的数据,避免在发送过程中数据突然改变
localparam START_BIT = 1'b0;
localparam STOP_BIN = 1'b1;
// 串口工作状态
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(tx_done) // 发送完毕
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
assign en_cnt = uart_state;
// 用于启动发送时锁存即将要发送的数据
// 这样就可以避免在发送的过程中数据突然改变导致发送的数据不正确。
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
r_data_byte_buff <= 8'd0;
else if(send_en)
r_data_byte_buff <= data_byte; // 启动发送则锁存最新的数据
else
r_data_byte_buff <= r_data_byte_buff;
end
// 【DR_LUT】 通过查表的方式将波特率转换为对应的分频计数最大值
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_dr <= 16'd5207; // 9600bps
else begin
case(baud_set) // 查找表
0:bps_dr <= 16'd5207; // 9600bps
1:bps_dr <= 16'd2603; // 19200bps
2:bps_dr <= 16'd1301; // 38400bps
3:bps_dr <= 16'd0867; // 57600bps
4:bps_dr <= 16'd0433; // 115200bps
default:bps_dr <= 16'd5207; // 9600bps
endcase
end
end
// 【Div_Cnt】 计数功能
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
div_cnt <= 16'd0;
else if(en_cnt) begin
if(div_cnt == bps_dr)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end else
div_cnt <= 16'd0;
end
// 【Div_Cnt】 bps_clk 时钟产生
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1) // 当计数器刚开始计数时就产生一个时钟
bps_clk <= 1'b1; // 这样就相当于启动发送时就立即开始发送数据
else
bps_clk <= 1'b0;
end
// 【bps_cnt】 bps 计数(即发送的数据位数计数)
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
bps_cnt <= 4'd0;
else if(clr)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
// 【MUX10】、【r_R232_Tx】 尽量避免组合逻辑直接输出,输出是有毛刺的可能会出现不太稳定的情况
// 发送数据模块
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
rs232_tx <= STOP_BIN; // 起始位为低电平,所以空闲时为高电平即停止位
else begin
case(bps_cnt)
0:rs232_tx <= STOP_BIN; // 空闲时 bps_cnt 会一直为 0
1:rs232_tx <= START_BIT; // 起始位
2:rs232_tx <= r_data_byte_buff[0];
3:rs232_tx <= r_data_byte_buff[1];
4:rs232_tx <= r_data_byte_buff[2];
5:rs232_tx <= r_data_byte_buff[3];
6:rs232_tx <= r_data_byte_buff[4];
7:rs232_tx <= r_data_byte_buff[5];
8:rs232_tx <= r_data_byte_buff[6];
9:rs232_tx <= r_data_byte_buff[7];
10:rs232_tx <= STOP_BIN; // 停止位
default:rs232_tx <= STOP_BIN;
endcase
end
end
/*
// 检测一帧数据是否发送完成 // 采用此种方式会导致 tx_done、bps_cnt 会分别维持两个时钟周期的 1 和 11
// 因为当 bps_cnt 变为 11 的时候,需要等第2个时钟周期才会被采样到。当采样到之后 tx_done = 1,而 clr 也立即变为 1 ,
// 而 clr 为 1 的时候也需要等第3个时钟周期才能被 bps_cnt 采样到变为 0
// 而 bps_cnt 为 0 时,需要等到第4个时钟周期才能被 tx_done 采样,才会变为 0
[email protected](posedge clk, negedge rst_n) begin
if(!rst_n)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
end
*/
// 【r_Tx_Done】 为了避免 tx_done 这里采用直接赋值的方式来避免出现延迟一个时钟的现象
assign tx_done = bps_cnt == 4'd11 ? 1'b1 : 1'b0;
assign clr = tx_done; // 当完成一帧数据发送之后清除 bps 计数器