串口应用:遵循uart协议,发送多个字节的数据(状态机)
阅读原文时间:2023年07月09日阅读:1

  上一节中,我们遵循uart协议,它发送一次只能发送6/7/8位数据,我们不能随意更改位数(虽然在代码上可行),不然就不遵循uart协议了,会造成接收端无法接收。

  在现实生活中,我们有时候要发的数据不止8位,这时候就得多次发送了。分多段发送,就是说发送一次数据的时间里发送系统有多个状态,这便是状态机。即有限状态自动机,通常体现为一张流程图。一般包含state(状态),event(事件),action(动作),transition(转换)四个要素。

如在此情景下,有以下几个状态:

像这种有多个状态的情景,我们可以设置状态变量state,使能端enable,结束位tx_done(一般在底层)来控制状态的转换。

所以关键的点在于,把问题逻辑抽象化。最好是画出一个流程图来,大部分问题便迎刃而解了。

注意:

1.仿真时给的脉冲20ns可能会使判断条件无效,设置为21ns即可。或者给个200ns的延迟后再给20ns的脉冲。

2.通过观察其他变量的波形,来设置tx_done变化的判定条件,从而优化了tx——done的持续时间。

3.可以加入一个signal信号来控制仿真合适结束(通过@(negedge signal))

4.隐式例化:例化时不改变端口的数量,顺序,便是隐式例化,只需要定义端口的reg类型即可。

不足:

1.状态太多,代码很长。

2.适应范围不广。

思考:

1.如何优化状态机,做到只用3个状态就能实现上述功能。

2.优化代码使得可以十分简单的修改代码从而达到发送任意字节的数据的功能(字节有上限)。

module uart_4_nbyte(//用ifelse的方法传送五个字节的数据
clk,
reset,
data40,
send_pulse,
uart_tx,
sign
);
input clk ;
input reset ;
input send_pulse ;
input [39:0]data40 ;
output uart_tx ;
output sign ;

reg send\_en ;  
wire tx\_done ;  
reg \[7:0\]data ;

uart\_1\_1    uart\_4\_nbyte1(  //设计输入  
.clk(clk),//时钟  
.reset(reset),//复位  
.data(data),//数据  
.send\_en(send\_en),//使能  
.baud\_rate(3'd5),//波特率  
.uart\_tx(uart\_tx),//串口输出  
.tx\_done(tx\_done)//结束信号  

);

reg \[2:0\]state ;  
reg sign ;

always@(posedge clk or negedge reset)  
if (!reset)  
    begin  
    state <= 1'b0 ;  
    sign <= 1'b0 ;  
    data <= 0 ;  
    end  
 else if(send\_pulse == 1'b1)  
    begin  
    state <= 1'b1 ;  
    send\_en <= 1'b1 ;  
    sign <= 1 ;  
    end  
else if (state == 1 )  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[7:0\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 2 ;  
        send\_en <= 0 ;  
        end  
    end  
else if (state == 2 )  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[15:8\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 3 ;  
        send\_en <= 0 ;  
        end  
    end  
else if (state == 3 )  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[23:16\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 4 ;  
        send\_en <= 0 ;  
        end  
    end  
else if (state == 4 )  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[31:24\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 5 ;  
        send\_en <= 0 ;  
        end  
    end  
else if (state == 5 )  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[39:32\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 6 ;  
        send\_en <= 0 ;  
        end  
    end  
else if (state == 6 )  
    begin  
    state <= 1'b0 ;  
    sign <= 1'b0 ;  
    end

endmodule

module uart_1_1( //设计输入
clk,//时钟
reset,//复位
data,//数据
send_en,//使能
baud_rate,//波特率
uart_tx,//串口输出
tx_done//结束信号
);
input clk;
input reset;
input [7:0]data;
input send_en;
input [2:0]baud_rate;
output reg uart_tx;
output reg tx_done;

 reg \[17:0\]bit\_tim;

//设计逻辑  
//把波特率转化为一位的持续时间  //单位时间内通过信道传输的码元数称为码元传输速率,即波特率,码元/s,一个码元可能由多个位组成。而比特率即 ‘位/s’  
always@(baud\_rate)  //在这里一个 码元由一位组成,所以波特率=比特率  
    begin  
        case(baud\_rate)         //常见的串口传输波特率  
        3'd0 : bit\_tim = 1000000000/300/20 ; //波特率为300  
        3'd1 : bit\_tim = 1000000000/1200/20 ; //波特率为1200  
        3'd2 : bit\_tim = 1000000000/2400/20 ; //波特率为2400  
        3'd3 : bit\_tim = 1000000000/9600/20 ; //波特率为9600  
        3'd4 : bit\_tim = 1000000000/19200/20 ; //波特率为19200  
        3'd5 : bit\_tim = 1000000000/115200/20 ; //波特率为115200  
        default bit\_tim = 1000000000/9600/20 ;   //多余的寄存器位置放什么:默认速率  
        endcase  
    end

reg \[17:0\]counter1 ;//用来计数每一位的持续时间  
always@(posedge clk or negedge reset)  
    begin  
        if(!reset)//复位清零  
            counter1 <=17'b0 ;  
        else if (send\_en )//使能端有效,计数  
            begin  
            if( counter1 == bit\_tim - 1'b1 )//位持续时间到达时归零  
                counter1 <= 17'b0 ;  
            else  
                counter1 <= counter1 + 1'b1 ;//位持续时间没达到时继续进行  
            end  
        else counter1 <= 17'b0 ;            //使能端无效时,清零  
    end 

reg \[3:0\]counter2 ; //输出第几位。如果忘了考虑归零,那么计数器会出现溢出归零,在这里是加到15然后归零  
always@(posedge clk or negedge reset)  
    begin  
        if(!reset)//复位  
            counter2 <= 4'b0 ;  
        else if ( send\_en )//使能端有效  
            begin  
            if(counter2 == 0)//消耗20ns,进入起始位。这个挺重要的,没有这个的话得消耗一位的时间进入起始位  
                counter2 <= counter2 +1'b1 ;  
            else if( counter1 == bit\_tim - 1'b1 )//开始进行位移  
                counter2 <= counter2 + 4'b1 ;  
            else  
                counter2 <= counter2 ;  
            end  
        else//使能端无效,归零,进入空闲位  
            counter2 <= 4'b0 ;  
    end                

always@(posedge clk or negedge reset)  
    begin  
        if(!reset)//复位  
            begin  
                uart\_tx <= 4'b1 ;  
            end  
        else if ( send\_en )//使能端有效,输出每一位  
                case(counter2)  
                    0:begin uart\_tx <= 1'b1 ; end//设定第一位为空闲位。没有空闲位的话,使能端无效时,counter停留在0,不能保持输出高电平(取决于要输出的数据),不符合要求。  
                    1:uart\_tx <= 1'b0 ;//起始位  
                    2:uart\_tx <= data\[0\] ;  
                    3:uart\_tx <= data\[1\] ;  
                    4:uart\_tx <= data\[2\] ;  
                    5:uart\_tx <= data\[3\] ;  
                    6:uart\_tx <= data\[4\] ;  
                    7:uart\_tx <= data\[5\] ;  
                    8:uart\_tx <= data\[6\] ;  
                    9:uart\_tx <= data\[7\] ;  
                    10:uart\_tx <= 1'b1 ;//结束位  
                    11:begin uart\_tx <= 1'b1 ;  end//为了让结束位跑满,设置11,作为第11个点,定第十位长度。  
                    default uart\_tx <= 1'b1 ;  
                endcase  
       else  
            uart\_tx <= 1'b1 ;  
    end 

    always@(posedge clk or negedge reset)  
    begin  
        if(!reset)//复位清零  
            tx\_done <= 1'b0 ;  
        else if (send\_en )//使能端有效  
            begin  
            if( counter2 == 0 )//  
                 tx\_done <= 1'b0 ;  
            else if (( counter2 == 10 ) && ( counter1 == bit\_tim - 1'b1 ))  
                 tx\_done <= 1'b1 ;  
            else if (tx\_done == 1'b1)  
                 tx\_done <= 1'b0 ;  
            end  
        else if (tx\_done == 1'b1)  
            tx\_done <= 1'b0 ;  
    end  

endmodule

`timescale 1ns / 1ns
module uart_4_tb(
);
reg clk ;
reg reset ;
reg [39:0]data40 ;
reg send_pulse ;
wire uart_tx ;
wire sign ;

uart\_4\_nbyte    uart\_4\_sim(//隐式例化  
clk,  
reset,  
data40,  
send\_pulse,  
uart\_tx,  
sign  
);

initial clk = 1;  
always #10 clk = ! clk ;  
initial begin  
reset = 0 ;  
data40  = 40'd0 ;  
send\_pulse = 1'b0 ;  
#201 ;  
reset = 1 ;  
data40  = 40'h123456789a ;  
#200 ;  
send\_pulse = 1'b1 ;  
#20;  
send\_pulse = 1'b0 ;  
@(negedge sign) ;  
data40  = 40'ha987654321 ;  
#200 ;  
send\_pulse = 1'b1 ;  
#20;//设置为20ns时识别不出,会出错。设置21可以解决//多给200ns延迟后错误消失,用20ns也可以  
send\_pulse = 1'b0 ;  
@(negedge sign) ;  
#200 ;  
$stop ;  
end

endmodule

module uart_5_nbyte(//用case的方法传送五个字节的数据
clk,
reset,
data40,
send_pulse,
uart_tx,
sign
);
input clk ;
input reset ;
input send_pulse ;
input [39:0]data40 ;
output uart_tx ;
output sign ;

reg send\_en ;  
wire tx\_done ;  
reg \[7:0\]data ;

uart\_1\_1    uart\_5\_nbyte1(  //设计输入  
.clk(clk),//时钟  
.reset(reset),//复位  
.data(data),//数据  
.send\_en(send\_en),//使能  
.baud\_rate(3'd5),//波特率  
.uart\_tx(uart\_tx),//串口输出  
.tx\_done(tx\_done)//结束信号  

);

reg \[2:0\]state ;  
reg sign ;

always@(posedge clk or negedge reset)  
if (!reset)  
    begin  
    state <= 1'b0 ;  
    sign <= 1'b0 ;  
    data <= 0 ;  
    end  
 else if(send\_pulse == 1'b1)  
    begin  
    state <= 1'b1 ;  
    send\_en <= 1'b1 ;  
    sign <= 1 ;  
    end  
else case(state)  
 1:  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[7:0\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 2 ;  
        send\_en <= 0 ;  
        end  
    end  
2:  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[15:8\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 3 ;  
        send\_en <= 0 ;  
        end  
    end  
3:  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[23:16\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 4 ;  
        send\_en <= 0 ;  
        end  
    end  
4:  
    begin  
    if(!tx\_done)  
        begin  
        data <= data40\[31:24\] ;  
        send\_en <= 1 ;  
        end  
    else if(tx\_done)  
        begin  
        state <= 5 ;  
        send\_en <= 0 ;  
        end  
    end  

5:
begin
if(!tx_done)
begin
data <= data40[39:32] ;
send_en <= 1 ;
end
else if(tx_done)
begin
state <= 6 ;
send_en <= 0 ;
end
end
6:
begin
state <= 1'b0 ;
sign <= 1'b0 ;
end
endcase

endmodule