基于FPGA的通用串口通讯设计

三、串口通讯

        本章节将会介绍串口通讯内容,串口通讯是常见通讯接口之一,与之还有的是IIC通讯、SPI等低速通讯接口,其分为串口接收和串口发送两部分,本章节将制作一个通用时钟与波特率的串口通讯模块,并制作寄存器手册通过串口进行收发,框架流程如下:

        通用时钟与波特率模块主要是通过parameter的方式直接计算出来,通过计数器进行波特率时钟波形的产生,提供两种输出方式,分别是波特率计数最大值和波特率时钟,其代码如下:

module uart_bps #
(
   parameter            DWID = 32            ,
   parameter            FREQ = 125000000     ,
   parameter            UBPS = 115200
)
(
   input                clk_sys              ,
   input                rst_sys              ,
   //
   output reg           bps_clk = 0          ,
   output    [DWID-1:0] bps_max
);

// ********************************************************
// localparam
// ********************************************************
localparam CLK_PERIOD = 1000_000_000 / FREQ ;
localparam BPS_PERIOD = 1000_000_000 / UBPS ;
localparam BPS_CNT = BPS_PERIOD/CLK_PERIOD  ;


// ********************************************************
// signal
// ********************************************************
reg   [31:0]            cnt_bps = 0 ;

// ********************************************************
// process
// ********************************************************
assign bps_cnt = CLK_PERIOD / BPS_PERIOD ;

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      cnt_bps <= 0 ;
   else if( cnt_bps == BPS_CNT - 1 )
      cnt_bps <= 0 ;
   else
      cnt_bps <= cnt_bps + 1 ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      bps_clk <= 0 ;
   else if( cnt_bps == BPS_CNT - 1 )
      bps_clk <= ~bps_clk ;
   else ;
end

assign bps_max = BPS_CNT ;

endmodule

        串口通讯主要分为串口接收和串口发送,串口发送波形如下图1,而串口接收波形如下图2,为什么串口接收波形需要移至数据的中心或者尾端?在教材中其实有讲解,这是因为防止串口抖动导致的亚稳态状态,在串口接收中采用16倍采样,而发送时FPGA自己组装发出,所以不必进行16倍采样发出。

        对于串口发送与串口接收而言,个人认为串口接收会理解较复杂,且教材采用计数器+case语句方式进行设计,会较为逻辑不连贯,故本章节依据自己理解设计代码,采用中心采样方式已避免绝大多数的亚稳态,串口接收模块如下:

module uart_recv #
(
   parameter               DWID = 32
)
(
   input                   clk_sys           , 
   input                   rst_sys           ,  
   input      [DWID-1:0]   bps_max           ,  

   input                   uart_rxd          ,  //串行数据输入
   output reg [7:0]        recv_data         ,  //数据接收完成
   output reg              recv_done            //数据接收结束
);

// ********************************************************
// signal
// ********************************************************
reg                        uart_rxd_nedge  = 1'b0  ;  
reg                        uart_rxd_reg    = 1'b0  ;  
reg                        uart_rxd_ena    = 1'b0  ;  

reg   [7:0]                data_reg = 8'b0         ;  //接收数据寄存器
reg   [3:0]                data_bit = 4'd0         ;  
reg   [DWID-1:0]           bps_cnt                 ;  //分频计数


// ********************************************************
// process
// ********************************************************

always @ ( posedge clk_sys ) 
begin
   uart_rxd_reg    <= uart_rxd; // 起始条件判断
   uart_rxd_nedge  <= ( uart_rxd_reg && uart_rxd == 1'b0 ) ? 1'b1 : 1'b0;
end

always @( posedge clk_sys ) 
begin
   if( rst_sys )
      uart_rxd_ena <= 1'b0 ;
   else if( uart_rxd_nedge )//接收开始
      uart_rxd_ena <= 1'b1 ;
   else if( data_bit == 4'd0 && bps_cnt == bps_max/2 && uart_rxd == 1'b1 )
      uart_rxd_ena <= 1'b0 ;//防止低电平毛刺被当作起始位,采用中心采样
   else if( recv_done )
      uart_rxd_ena <= 1'b0 ;
end

always @( posedge clk_sys )
begin
   if( rst_sys || !uart_rxd_ena)
      bps_cnt <= 1;
   else if( uart_rxd_ena )
   begin
      if( bps_cnt == bps_max )
         bps_cnt <= 1;
      else
         bps_cnt <= bps_cnt + 1;
   end
end

always @ ( posedge clk_sys ) //位计数器,对应当前接收位数
begin
   if( rst_sys )
      data_bit <= 4'd0;
   else if( bps_cnt == bps_max ) // 表示一个数据计数完成
   begin
      if(data_bit == 4'd9)
         data_bit <= 4'd0;
      else
         data_bit <= data_bit + 4'd1;
   end
end


always @ ( * ) // 串行接收数据(从低位接收,采样中间值)
begin
    if( rst_sys || !uart_rxd_ena )
      data_reg <= 8'b0 ;
    else if( bps_cnt == bps_max/2 ) //接收采样自rxd每位中间值
    begin
      case(data_bit)
         1:  data_reg[0] <= uart_rxd ;
         2:  data_reg[1] <= uart_rxd ;
         3:  data_reg[2] <= uart_rxd ;
         4:  data_reg[3] <= uart_rxd ;
         5:  data_reg[4] <= uart_rxd ;
         6:  data_reg[5] <= uart_rxd ;
         7:  data_reg[6] <= uart_rxd ;
         8:  data_reg[7] <= uart_rxd ;
         default: data_reg <= data_reg;
      endcase
   end
end

always @ ( * )
begin
   if( rst_sys )
      recv_data <= 8'b0;
   else if( recv_done )
      recv_data <= data_reg;
end

always @ ( * )
begin 
   if( rst_sys )
     recv_done <= 1'b0;
   else if( data_bit == 9 && bps_cnt == bps_max )
      recv_done <= 1'b1;
   else
      recv_done <= 1'b0;
end

endmodule

串口发送模块如下:

module uart_send #
(
   parameter                  DWID = 32
)
(
   input                      clk_sys              ,            
   input                      rst_sys              ,   
   //
   input                      send_dena            ,        //开始发送标志信号
   input       [7:0]          send_data            ,        //待发送数据
   //
   input       [DWID-1:0]     bps_max              ,        //设置波特率
   output reg                 uart_txd             ,        //串行数据输出
   output reg                 send_done                     //发送结束标志信号
);

// ********************************************************
// signal
// ********************************************************
reg                           send_star = 1'b0     ;  //发送使能信号
reg [7:0]                     data_reg  = 8'b0     ;  //待发送数据寄存器
reg [3:0]                     data_bit  = 4'd0     ;  //计数当前输出位数
reg [DWID-1:0]                bps_cnt   = 1        ;  //分频计数

// ********************************************************
// process
// ******************************************************** 

always @ ( posedge clk_sys )
begin 
    if( rst_sys ) 
        data_reg <= 8'b0;
    else if(send_dena)
        data_reg <= send_data;
    else ;
end
 
always @ ( posedge clk_sys )
begin 
   if( rst_sys ) 
      send_star <= 1'b0 ;
   else if( send_dena )
      send_star <= 1'b1 ;
   else if( send_done )
      send_star <= 1'b0 ;
   else ;
end
 
always @ ( posedge clk_sys )
begin 
   if( rst_sys || !send_star )       //复位或未使能
      bps_cnt  <= 1;
   else if( send_star )
   begin
      if( bps_cnt == bps_max )
         bps_cnt <= 1;
      else 
         bps_cnt <= bps_cnt + 1;
   end
end
 
always @ ( posedge clk_sys )
begin 
    if( rst_sys )
      data_bit <= 4'd0;          //1-10为有效状态
    else if( bps_cnt == bps_max )
    begin
      if(data_bit == 4'd9)
         data_bit <= 4'd0;
      else 
         data_bit <= data_bit + 4'd1;
    end
end
 
always @ ( * ) 
begin 
    if( rst_sys || !send_star )
        uart_txd <= 1'b1; 
    else begin
        case(data_bit)
            0:  uart_txd <= 1'b0;             
            1:  uart_txd <= data_reg[0];  
            2:  uart_txd <= data_reg[1];  
            3:  uart_txd <= data_reg[2]; 
            4:  uart_txd <= data_reg[3];  
            5:  uart_txd <= data_reg[4];  
            6:  uart_txd <= data_reg[5];  
            7:  uart_txd <= data_reg[6]; 
            8:  uart_txd <= data_reg[7];
            9:  uart_txd <= 1'b1;
            default: uart_txd <= 1'b1;           
        endcase
    end
end
 
always @ ( * ) 
begin 
    if( rst_sys )
      send_done <= 1'b0;
    else if( data_bit == 9 && bps_cnt == bps_max )
      send_done <= 1'b1;
    else 
      send_done <= 1'b0;
end

endmodule

        两个代码的实现流程均相差不多,先检测下降沿,通过uart_bps模块计算出波特率周期,通过计数器计算达到周期值时或者半周期时将数据发出或者采样,这里的复位后的cnt值为1是因为为0的话,bps_cnt 得等于 bps_mux - 1 ,尽量不在条件语句上进行逻辑运算减小组合逻辑。

        验证功能代码如下,可在仿真中产生需要发送的数据送入串口发送模块,串口发送的数据经过串口接收模块提取,从而可以看出发送和接收数值是都一致,一致就说明代码正确:

localparam CLK_PERIOD_BPS         = 1_000_000_000/115200/CLK_PERIOD_125M ;
always #(CLK_PERIOD_BPS/2) bps_clk = ~bps_clk ;


// ****************************************************************************
// uart
// ****************************************************************************

   
always @ ( posedge bps_clk )
begin
   if( rst_sys )
      cnt_uart <= 0;
   else if( reg_uart_txd_dena )
      cnt_uart <= 0 ;
   else
      cnt_uart <= cnt_uart + 1 ;
end
   
always @ ( posedge bps_clk )
begin
   if( rst_sys )
      reg_uart_txd_dena <= 0 ;
   else if( &cnt_uart  )
      reg_uart_txd_dena <= 1 ;
   else
      reg_uart_txd_dena <= 0 ;
end

always @ ( posedge bps_clk )
begin
   if( rst_sys )
      reg_uart_txd_data <= 8'h0 ;
   else if( &cnt_uart  )
      reg_uart_txd_data <= reg_uart_txd_data + 1 ;
   else ;
end

        将上诉reg_uart_txd_dena和reg_uart_txd_data 送入uart_send.v中即可,本工程采用工程仿真,故仿真搭建较多,等工程完毕后会上传完整仿真。

        本工程的晶振时钟是50M,但一般速率不会取的那么慢,特别是千兆以太网,故在本工程会通过MMCM产生125M时钟,并且通过1s时钟统计,计算出统计值后通过串口发出,MMCM的使用方式以及时钟统计模块均在本章不进行讲解,具体代码待工程完毕后上传github,寄存器代码如下:

module acx720_register #
(
   parameter                  DWID = 32
)
(
   input                      clk_sys              ,
   input                      rst_sys              ,
   // ctrl
   input                      uart_rxd             ,
   output                     uart_txd             ,
   // register
   input       [31:0]         cnt_125m             ,
   input       [31:0]         cnt_50m              
);

// ********************************************************
// signal
// ********************************************************
wire     [7:0]               data_byte             ;
wire                         data_dena             ;

reg                          addr_dena = 0         ;
reg      [7:0]               tx_data               ;
wire     [31:0]              bps_max               ;
// ********************************************************
// process
// ********************************************************
always @ ( posedge clk_sys )
begin
   if( data_dena )
      addr_dena <= 1 ;
   else
      addr_dena <= 0 ;
end

always @ ( posedge clk_sys )
begin
   case ( data_byte )
      //read
      8'h00 : tx_data <= cnt_125m[7:0]   ;
      8'h01 : tx_data <= cnt_125m[15:8]  ;
      8'h02 : tx_data <= cnt_125m[23:16] ;
      8'h03 : tx_data <= cnt_125m[31:24] ;
      8'h04 : tx_data <= cnt_50m[7:0]   ;
      8'h05 : tx_data <= cnt_50m[15:8]  ;
      8'h06 : tx_data <= cnt_50m[23:16] ;
      8'h07 : tx_data <= cnt_50m[31:24] ;
      default: tx_data <= 8'hff ;
   endcase
end


// ********************************************************
// UART
// ********************************************************
uart_bps #
(  
   .DWID                ( DWID                     ),
   .FREQ                ( 125000000                ),
   .UBPS                ( 115200                   )
)u_uart_bps
(
   .clk_sys             ( clk_sys                  ),
   .rst_sys             ( rst_sys                  ),
   //
   .bps_clk             ( bps_clk                  ),
   .bps_max             ( bps_max                  )
);

uart_recv #
(
   .DWID                ( DWID                     )
)u_uart_recv
(
   .clk_sys             ( clk_sys                  ), 
   .rst_sys             ( rst_sys                  ),  
   .bps_max             ( bps_max                  ),  

   .uart_rxd            ( uart_rxd                 ),  //串行数据输入
   .recv_data           ( data_byte                ),  //数据接收完成
   .recv_done           ( data_dena                )   //数据接收结束
);

uart_send #
(
   .DWID                ( DWID                     )
) u_uart_send
(
   .clk_sys             ( clk_sys                  ),
   .rst_sys             ( rst_sys                  ),
   .bps_max             ( bps_max                  ),
   // 
   .send_dena           ( addr_dena                ),
   .send_data           ( tx_data                  ),
   .send_done           (                          ),
   .uart_txd            ( uart_txd                 )
);



endmodule

        上板验证结果如下,通过分别输入03、02、01、00可得出数值分别为07、73、59、3F;拼接转换城10进制为124999999,寄存器工作就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值