chg0823 发表于 2022-1-17 13:40

[国产FPGA高云GW1N 系列开发板测评]——(4)串口实验

本帖最后由 chg0823 于 2022-1-13 18:43 编辑

<p>&nbsp; &nbsp;<span style="font-size:10.5pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">1</span></span></span><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">、简介</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">串口是一种比较常见的通信接口,有同步串行接口和异步串行接口之分,同步和异步串行接口主要区别在于,异步串行通信是按照字节来传输的,即每一次数据是按一个字节一个字节进行传输,传输速度较低;同步串行通信是需要将数据按照字节组合起来一起发送即按照信息块的方式进行传输,同时同步串行通信是需要与设备的时钟信号进行通信达到&ldquo;同步&rdquo;的效果。</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">通常异步串行通信(UART)包含TTL电平的串口和RS232电平的串口。 TTL电平是3.3V,而RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平;同步串行通信也是一种常用的工业通信接口。按照电气标准及协议来分串行接口包括RS-232-C、RS-422、RS485等等比较常用的接口。</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">本例是按照异步串行通信的TTL电平实验,然后外接一个TTL转USB的小模块,实现UART通信。</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">2</span></span></span><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">、原理</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">UART</span></span></span><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">常见的串口通信波特率有9600、38400、115200等等,而且发送和接收波特率必须保持一致才能正确通信。波特率是指 1 秒最大传输的数据位数,包括起始位、数据位、校验位、停止位。假如通信波特率设定为 9600,那么一个数据位的时间长度是1/9600 秒。</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">UART</span></span></span><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">传输时序如下图所示:</span></span></span></span></span></p>

<p align="center" style="text-align:center"> &nbsp;</p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">3</span></span></span><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">、程序设计</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">(1)数据发射模块</span></span></span></span></span></p>

<pre>
<code>assign uart_tx_busy = tx_flag;

//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) &amp; uart_en_d0;

//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
      uart_en_d0 &lt;= 1'b0;                                 
      uart_en_d1 &lt;= 1'b0;
    end                                                      
    else begin                                             
      uart_en_d0 &lt;= uart_en;                              
      uart_en_d1 &lt;= uart_en_d0;                   
    end
end

//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程         
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                                 
      tx_flag &lt;= 1'b0;
      tx_data &lt;= 8'd0;
    end
    else if (en_flag) begin               //检测到发送使能上升沿                     
            tx_flag &lt;= 1'b1;                //进入发送过程,标志位tx_flag拉高                               
            tx_data &lt;= uart_din;            //寄存待发送的数据
      end
                                          //计数到停止位结束时,停止发送过程
      else if ((tx_cnt == 4'd9) &amp;&amp; (clk_cnt == BPS_CNT - (BPS_CNT/16))) begin                                       
            tx_flag &lt;= 1'b0;                //发送过程结束,标志位tx_flag拉低                               
            tx_data &lt;= 8'd0;
      end
      else begin
            tx_flag &lt;= tx_flag;
            tx_data &lt;= tx_data;
      end
end

//进入发送过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                           
      clk_cnt &lt;= 16'd0;                                 
    else if (tx_flag) begin               //处于发送过程
      if (clk_cnt &lt; BPS_CNT - 1)
            clk_cnt &lt;= clk_cnt + 1'b1;
      else
            clk_cnt &lt;= 16'd0;               //对系统时钟计数达一个波特率周期后清零
    end
    else                           
      clk_cnt &lt;= 16'd0;                                   //发送过程结束
end

//进入发送过程后,启动发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                           
      tx_cnt &lt;= 4'd0;
    else if (tx_flag) begin               //处于发送过程
      if (clk_cnt == BPS_CNT - 1)                        //对系统时钟计数达一个波特率周期
            tx_cnt &lt;= tx_cnt + 1'b1;                //此时发送数据计数器加1
      else
            tx_cnt &lt;= tx_cnt;      
    end
    else                              
      tx_cnt&lt;= 4'd0;                                  //发送过程结束
end

//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin      
    if (!sys_rst_n)
      uart_txd &lt;= 1'b1;      
    else if (tx_flag)
      case(tx_cnt)
            4'd0: uart_txd &lt;= 1'b0;         //起始位
            4'd1: uart_txd &lt;= tx_data;   //数据位最低位
            4'd2: uart_txd &lt;= tx_data;
            4'd3: uart_txd &lt;= tx_data;
            4'd4: uart_txd &lt;= tx_data;
            4'd5: uart_txd &lt;= tx_data;
            4'd6: uart_txd &lt;= tx_data;
            4'd7: uart_txd &lt;= tx_data;
            4'd8: uart_txd &lt;= tx_data;   //数据位最高位
            4'd9: uart_txd &lt;= 1'b1;         //停止位
            default: ;
      endcase
    else
      uart_txd &lt;= 1'b1;                   //空闲时发送端口为高电平
end
</code></pre>

<p style="text-indent:24.0pt; text-align:justify">(2)<span style="font-size:12.0pt"><span style="background:white"><span style="font-family:等线"><span style="color:#333333">数据接收模块</span></span></span></span></p>

<pre>
<code>assignstart_flag = uart_rxd_d1 &amp; (~uart_rxd_d0);   

//对UART接收端口的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
      uart_rxd_d0 &lt;= 1'b0;
      uart_rxd_d1 &lt;= 1'b0;         
    end
    else begin
      uart_rxd_d0&lt;= uart_rxd;                  
      uart_rxd_d1&lt;= uart_rxd_d0;
    end   
end

//当脉冲信号start_flag到达时,进入接收过程         
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                                 
      rx_flag &lt;= 1'b0;
    else begin
      if(start_flag)                        //检测到起始位
            rx_flag &lt;= 1'b1;                  //进入接收过程,标志位rx_flag拉高
                                                //计数到停止位中间时,停止接收过程
      else if((rx_cnt == 4'd9) &amp;&amp; (clk_cnt == BPS_CNT/2))
            rx_flag &lt;= 1'b0;                  //接收过程结束,标志位rx_flag拉低
      else
            rx_flag &lt;= rx_flag;
    end
end

//进入接收过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                           
      clk_cnt &lt;= 16'd0;                                 
    else if ( rx_flag ) begin                   //处于接收过程
      if (clk_cnt &lt; BPS_CNT - 1)
            clk_cnt &lt;= clk_cnt + 1'b1;
      else
            clk_cnt &lt;= 16'd0;                       //对系统时钟计数达一个波特率周期后清零
    end
    else                                                             
      clk_cnt &lt;= 16'd0;                                                //接收过程结束,计数器清零
end

//进入接收过程后,启动接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                           
      rx_cnt&lt;= 4'd0;
    else if ( rx_flag ) begin                   //处于接收过程
      if (clk_cnt == BPS_CNT - 1)                                //对系统时钟计数达一个波特率周期
            rx_cnt &lt;= rx_cnt + 1'b1;                        //此时接收数据计数器加1
      else
            rx_cnt &lt;= rx_cnt;      
    end
       else
      rx_cnt&lt;= 4'd0;                                                //接收过程结束,计数器清零
end

//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin
    if ( !sys_rst_n)
      rxdata &lt;= 8'd0;                                    
    else if(rx_flag)                            //系统处于接收过程
      if (clk_cnt == BPS_CNT/2) begin         //判断系统时钟计数器计数到数据位中间
            case ( rx_cnt )
             4'd1 : rxdata &lt;= uart_rxd_d1;   //寄存数据位最低位
             4'd2 : rxdata &lt;= uart_rxd_d1;
             4'd3 : rxdata &lt;= uart_rxd_d1;
             4'd4 : rxdata &lt;= uart_rxd_d1;
             4'd5 : rxdata &lt;= uart_rxd_d1;
             4'd6 : rxdata &lt;= uart_rxd_d1;
             4'd7 : rxdata &lt;= uart_rxd_d1;
             4'd8 : rxdata &lt;= uart_rxd_d1;   //寄存数据位最高位
             default:;                                    
            endcase
      end
      else
            rxdata &lt;= rxdata;
    else
      rxdata &lt;= 8'd0;
end

//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin      
    if (!sys_rst_n) begin
      uart_data &lt;= 8'd0;                              
      uart_done &lt;= 1'b0;
    end
    else if(rx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时         
      uart_data &lt;= rxdata;                  //寄存输出接收到的数据
      uart_done &lt;= 1'b1;                      //并将接收完成标志位拉高
    end
    else begin
      uart_data &lt;= 8'd0;                                 
      uart_done &lt;= 1'b0;
    end   
end
</code></pre>

<p style="text-indent:24.0pt; text-align:justify">(3)顶层模块</p>

<pre>
<code>//捕获recv_done上升沿,得到一个时钟周期的脉冲信号
assign recv_done_flag = (~recv_done_d1) &amp; recv_done_d0;
                                                
//对发送使能信号recv_done延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
      recv_done_d0 &lt;= 1'b0;                                 
      recv_done_d1 &lt;= 1'b0;
    end                                                      
    else begin                                             
      recv_done_d0 &lt;= recv_done;                              
      recv_done_d1 &lt;= recv_done_d0;                           
    end
end

//判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
      tx_ready&lt;= 1'b0;
      send_en   &lt;= 1'b0;
      send_data &lt;= 8'd0;
    end                                                      
    else begin                                             
      if(recv_done_flag)begin               //检测串口接收到数据
            tx_ready&lt;= 1'b1;                  //准备启动发送过程
            send_en   &lt;= 1'b0;
            send_data &lt;= recv_data;             //寄存串口接收的数据
      end
      else if(tx_ready &amp;&amp; (~tx_busy)) begin   //检测串口发送模块空闲
            tx_ready &lt;= 1'b0;                   //准备过程结束
            send_en&lt;= 1'b1;                   //拉高发送使能信号
      end
    end
end

//串口接收模块   
uart_recv #(                        
    .CLK_FREQ       (CLK_FREQ),         //设置系统时钟频率
    .UART_BPS       (UART_BPS))         //设置串口接收波特率
u_uart_recv(               
    .sys_clk      (sys_clk),
    .sys_rst_n      (sys_rst_n),
   
    .uart_rxd       (uart_rxd),
    .uart_done      (recv_done),
    .uart_data      (recv_data)
    );

//串口发送模块   
uart_send #(                        
    .CLK_FREQ       (CLK_FREQ),         //设置系统时钟频率
    .UART_BPS       (UART_BPS))         //设置串口发送波特率
u_uart_send(               
    .sys_clk      (sys_clk),
    .sys_rst_n      (sys_rst_n),
   
    .uart_en      (send_en),
    .uart_din       (send_data),
    .uart_tx_busy   (tx_busy),
    .uart_txd       (uart_txd)
    );</code></pre>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">4</span></span></span><span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">、验证</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:等线">&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-size:12.0pt"><span style="background:white"><span style="color:#333333">用PC端的串口进行测试,结果如下:</span></span></span></span></span></p>

<p style="text-indent:24.0pt; text-align:justify"> &nbsp;</p>

<p style="text-indent:24.0pt; text-align:justify">&nbsp;</p>

<p style="text-align:justify">&nbsp;</p>

Jacktang 发表于 2022-1-17 22:39

<p>PC端的串口进行测试是正常的,说明程序设计正确</p>
页: [1]
查看完整版本: [国产FPGA高云GW1N 系列开发板测评]——(4)串口实验