串口通信

小梅哥资料第五章

1.协议::UART(通用异步收发传输器) (最为基础的就是 UART,因其 电路结构简单、成本较低,所以在注重性价比的情况下,使用非常广泛),IIC(双向两线总线),SPI(串行外围总线), USB2.0/3.00(通 用串行总线), Ethernet(以太网)

2.UART: 发送时将并转串传输,接收时串转并,可以实现全双工(=打电话,同时收发)传输和接收。

包括 RS232、RS449、RS423、 RS422 和 RS485 等接口标准规范和总线标准规范

3.RS232

 

 使用前设置 数据位数、波特率大小(一般通信两端设备都要设为 相同的波特率,但有些设备也可设置为自动检测波特率)、奇偶校验类型(验证数据的正确性)和停止位数(标志着一次数据传输完成)。

4.设计

4.1设计输入。根据上图设计即可。

4.2.设计逻辑:

  功能描述:

  1.发送八位数据位,一个起始位,一个结束位,共十位。

  2.空闲时输出高电平

  3.有一个使能端,高电平时传输数据,低电平时空闲(不传送)。

  4.用户可以选择波特率。

  细节:

   1.用户可选择的波特率为常用速率,他只需要选择0,1..等按键就可以直接选择某个对应速率:9600,126000等,所以这里对应有一个寄存器用来存对应的速率。

   2.为了区别空闲时和工作时,管理输出位的计数器应该留一个位给空闲状态,在使能端生效时立刻从空闲位进入起始位。使能端无效时停留在空闲位输出对应电平。(20ns的误差,让空闲位进入起始位,是可以接受的)

     3.空闲位跟工作起始位的电平必然是不同的,这样可直观区分。

     4. 仿真时可以用 @(posedge/negedge 信号),功能是:等到信号变化沿到来时再进行下一条语句,否则一直等待。用这条语句就不用在仿真时去计算发送一串数据需要多少时间。

5.代码

module uart_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 )//使能端有效,计数
                if( counter1 == bit_tim - 1'b1 )//位持续时间到达时归零
                    counter1 <= 17'b0 ;
                else
                    counter1 <= counter1 + 1'b1 ;//位持续时间没达到时继续进行
            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 )//使能端有效
                if(counter2 == 0)//消耗20ns,进入起始位。这个挺重要的,没有这个的话得消耗一位的时间进入起始位
                    counter2 <= counter2 +1'b1 ;
                else if( counter1 == bit_tim - 1'b1 )//开始进行位移
                    counter2 <= counter2 + 4'b1 ;   
                else
                    counter2 <= counter2 ;
            else//使能端无效,归零,进入空闲位
                counter2 <= 4'b0 ;    
        end                

    always@(posedge clk or negedge reset)
        begin
            if(!reset)//复位
                begin
                    uart_tx <= 4'b1 ;
                    tx_done <= 1'b0 ;
                end  
            else if ( send_en )//使能端有效,输出每一位
                    case(counter2)
                        0:uart_tx <= 1'b1 ;//设定第一位为空闲位。没有空闲位的话,使能端无效时,counter停留在0,不能保持输出高电平(取决于要输出的数据),不符合要求。
                        1:begin uart_tx <= 1'b0 ; tx_done <= 1'b0 ; end//起始位
                        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 ; tx_done <= 1'b1 ; end//为了让结束位跑满,设置11,作为第11个点,定第十位长度。
                        default uart_tx <= 1'b1 ;
                    endcase
           else 
                uart_tx <= 1'b1 ;         

        end 
endmodule

仿真测试代码

`timescale 1ns / 1ns

module uart_1_tb(    );
    reg s_clk ;
    reg s_reset ;
    reg [7:0]s_data ;
    reg s_send_en ;
    reg [2:0]s_baud_rate ;
    wire s_uart_tx ;
    wire s_tx_done ;
    uart_1 uart_1_sim(
        .clk(s_clk),
        .reset(s_reset),
        .data(s_data),
        .send_en( s_send_en ),
        .baud_rate(s_baud_rate),
        .uart_tx( s_uart_tx),
        .tx_done(s_tx_done )
        );
    
    initial s_clk = 1 ;
    always #10 s_clk = ! s_clk ;
    initial 
        begin
            s_reset = 0 ;
            s_data = 8'b0000_0000 ;
            s_send_en = 0 ;
            s_baud_rate =3'b000 ;
            #201 ;
            s_reset = 1 ;
            s_data = 8'b0110_1001 ;
            s_baud_rate = 3'b101 ;
            s_send_en = 1 ;
            #20 ;
            @(posedge s_tx_done);
            s_send_en = 0 ;
            #8000 ;
            s_data = 8'b1010_0101 ;
            s_send_en = 1 ;
            #20 ;
            @(posedge s_tx_done) ;
            s_send_en = 0 ;
            #1000 ;
            $stop ;
        end
       
endmodule

问题:如何设计,使得使能端一直有效时,可以输入多端数据时,都可以输出,(上面的代码是需要使能端归零再变1来重置计数器counter2,从而输出第二段数据。如果没有重置,则需要计数器计满溢出才开始传输第二段数据)

posted @ 2022-05-27 20:48  little_breeze  阅读(390)  评论(0)    收藏  举报