1.多字节串口发送原理与设计思路
错误示例 :8N1 (八个数据,没有奇偶效检位,一个停止位),认为只要填充起始信号和停止信号就可以了,例如串口发送Ox1234,串口模拟输出0_0010_1100_0100_1000_1 ;(LSB)小端读取方式。
这种数据按照8N1进行解析的话,提取其中的数据位,一次解析要接收10位数据,如果解析正确的话,提取中间的8位有效的数据。要解析到16位的Ox1234的话就要解析两次,也就是需要20位数据。模拟串口时,仅仅发送了18位数据,所以无论最终解析出来是什么,都不会得到我们期望收到的Ox1234。
所以正确的传输需要分成两次,第一次传输0_0100_1000_1 , 第二次传输0_0010_0110_1,传输过程如图所示
将两个8位的数据拼接后得到Ox1234
所以我们可以把多字节的串口发送,看成连续的多个单字节数据串口发送的过程。设计思路只需要将待发送的数据拆分成多个单字节,然后调用之前写好的单字节发送模块逐个进行发送,就可以实现多字节串口数据的发送。
2.多字节串口发送原理
为了保证发送数据的连续性,可以改使用单字节串口发送完成信号byte_tx_done 作为条件信号,
1.byte_tx_done 拉高时候,说明一个字节的数据发送完成 ,此时就输出下一字节数据给单字节串口发送模块。 一次多字节串口发送,发送数据量是确定的。串口也不可能一直发送下去,因此我2每次发送完成,判断数据是否发送完成。3 信号计算当前已经发送的数据再与信号位宽进行进行比较,判断整个信号发送过程是否完成。产生串口发送完成信号,本次传输结束。设计可以使用状态机来判断字节是否发送完成。下图是整体设计框架图
从上图可以看出,uart_data_tx设计框架中包含两个模块,一个是调用直接调用uart_byte_tx模块,然后利用有限状态机来设计信号数据发送的具体流程。
2.1总输入输出信号
module uart_data_tx(
clk ,
reset_n ,
baud_set ,
send_en ,
data ,
uart_tx ,
uart_state ,
tx_done
);
2.2例化data_byte_tx模块
uart_byte_tx uart_byte_tx_inst1 (
.send_en (byte_send_en ) ,
.data_byte (data_byte) ,
.baud_set (baud_set ) ,
.clk (clk ) ,
.reset_n (reset_n ) ,
.uart_tx (uart_tx ) ,
.tx_done (byte_tx_done ) ,
.uart_state (uart_state)
);
2.3状态机设计FSM
1.发送使能,等待数据发送。
2.byte_send_en 拉高,单个字节发送,分为两种接收数据模式,一个是MSB(大端字节序),一个是小端字节序(LSB)。
3,传送完成后,判断是否单个字节是否发送完成,拉高byte_tx_done信号电平,同时字节计数器累加传输位宽。
4.计数器计数的位宽数据值与传输位宽进行对比判断数据是否传输完成。没有完成的haul数据就继续传送。
//FSM module
localparam S0 = 0 ; //SO状态是等待发送使能状态
localparam S1 = 1 ; //发起单字节数据发送
localparam S2 = 2 ; //等待单字节数据发送完成
localparam S3 = 3 ; //检查所有数据是否传输完成
reg [1 : 0 ] state ; //定义传输状态
reg [ 8 : 0 ] cnt ; //计数
always @ (posedge clk or negedge reset_n) begin
if (!reset_n) begin
byte_send_en <= 1'b0 ;
data_byte [ 7 :0 ] <= 8'b0 ;
tx_done <= 1'b0 ;
cnt <= 8'b0 ;
end
else begin
case (state )
S0 : begin
data_byte <= 8'b0 ;
tx_done <= 1'b0 ;
cnt <= 8'b0 ;
if (send_en) begin
state <= S1 ;
data_r <= data ;
end
else begin
state <= S0 ;
data_r <= data_r ;
end
end
S1 : begin
byte_send_en <= 1'b1 ;
if (MSB_FIRST == 1 ) begin // 大端字节序
data_byte <= data_r[DATA_WIDTH - 1 : DATA_WIDTH - 8 ] ;
data_r <= data_r << 8 ;
end
else begin
data_byte <=data_r [7 : 0 ] ;
data_r <= data_r >> 8 ; //小端传送
end
state <= S2 ;
end
S2: begin
byte_send_en <= 1'b0;
if (byte_tx_done == 1 ) begin
state <= S3 ;
cnt <= cnt + 9 'd8 ;
end
else
state <= S2 ;
end
S3 : begin
if (cnt >= DATA_WIDTH ) begin //大于位宽说明传输完成
state <= S0 ;
cnt <= 0 ;
tx_done <= 1'b1 ;
end
else begin
state <= S1 ;
tx_done <= 1'b0 ;
end
end
default : state <= S0 ;
endcase
end
end
3.testbench仿真
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/10/17 15:58:55
// Design Name:
// Module Name: uart_data_tx_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
`define CLK_PERIOD 20
//时钟周期为20ns
module uart_data_tx_tb();
parameter DATA_WIDTH = 32 ;
parameter MSB_FIRST = 0 ; //小端字节序
reg clk ;
reg reset_n ;
reg send_en ; // 发送使能
reg [DATA_WIDTH - 1 : 0 ] data ; //输入数据
wire uart_tx ;
wire uart_state ;
wire tx_done ;
//module instiation
uart_data_tx # (
.DATA_WIDTH (DATA_WIDTH) ,
.MSB_FIRST(MSB_FIRST)
)
uart_data_inst(
.clk (clk ) ,
.reset_n (reset_n ) ,
.baud_set (3'd4 ) , //波特率115200
.send_en (send_en ) ,
.data (data ) ,
.uart_tx (uart_tx ) ,
.uart_state (uart_state ) ,
.tx_done (tx_done )
);
initial clk = 1 ;
always #(`CLK_PERIOD / 10) clk = !clk ; //时钟翻转
initial begin
reset_n = 0 ;
data = 0 ;
send_en = 0 ;
#(`CLK_PERIOD * 10 + 1) ;
reset_n = 1 ;
#(`CLK_PERIOD * 100) ;
data = 32'haabbccdd;
send_en = 1'b1 ;
#(`CLK_PERIOD ) ;
send_en = 1'b0 ;
#(`CLK_PERIOD ) ;
@(posedge tx_done ) ;
#1
data = 32'h11223344 ;
send_en = 1'b1 ;
#(`CLK_PERIOD )
send_en = 1'b0 ;
#(`CLK_PERIOD) ;
@(posedge tx_done )
#1 ;
#2000 ;
$stop ;
end
endmodule
仿真结果图
小端字节序(LSB)仿真结果图
发送数据aabbccdd ,和11223344,data_byte的发送为 dd cc bb aa , 和 44 33 22 11 说明发送阶段完成。
同理大端字节序(MSB)结果图
aa , bb ,cc ,dd 11 22 33 44 。
总结:
本次设计运用了合理的拆分字节发送,将多个字节的发送拆分成单个字节的发送,通过FSM状态机的设计来判断字节发送中各种情况和步骤,设计思路很巧妙,层次化设计明显。