FPGA学习笔记之UART

   一、概念原理   

        异步通信(UART):按照自己的节奏发送、接收(发送端、接收端的时钟不一致)

        同步通信(USART):在通信中的发送端和接收端,时钟能够保持一致。

        IIC:为低速设备通信而生,传输速率比不上SPI。两线式串行总线,用于连接微控制器以及外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。

 注:IIC是半双工,判断哪个在控制时,要跟他&,

        SPI:

这里补充 全双工、半双工、单工。

全双工:可以同时收发,互不干扰。例如,uart。

半双工:数据不可以同时收发,例如,rs232,485.iic

单工:只有一个方向,例如,spi

        UART协议:(作图说明)

        解释:奇偶校验位在这里

 奇校验:时刻保持9bit里1为奇数个。例如,8个数据位,0000_0001,有1个1,则校验位为0;

若有偶数个1,则校验位为1;

偶校验:与奇校验正好相反,时刻保持9bit里1的个数为偶数个。

校验的方式通常是用异或来判断;相同为0,不同为1;

二、设计框图

 注:频率=时钟个数*波特率。uart:一个时钟一个周期,所以这里频率就是9600;那么分频就是50MHZ/9600,取一个大致值即可。

三、UART通用模块思想     

          1.波特率可配置;

          2.输入时钟;

          3.数据位;

          4.停止位

          5.校验位

四、程序编写

 第一部分:驱动部分(上层)

module uart_drive#(
        parameter           P_SYSTEM_CLK    = 50_000_000     ,//时钟频率
        parameter           P_UART_BUADRATE = 9600           ,//波特率
        parameter           P_UART_DATA_WIDTH = 8            ,//数据宽度
        parameter           P_UART_STOP_WIDTH = 1            ,//停止位
        parameter           P_UART_CHECK      = 0             //0为无校验,1为奇校验,2为偶校验
     


)(
        input               i_clk,
        input               i_rst,

        input               i_uart_rx,
        output              i_uart_tx,

        input  [P_UART_DATA_WIDTH-1:0]  i_user_tx_data,
        input                           i_user_tx_valid,
        output                          o_user_tx_ready,


        output [P_UART_DATA_WIDTH-1:0]   o_user_rx_data,
        output                           o_user_rx_valid
       
    );

第一,顶层设计思想,通用模块配置好,顶层参数;第二,输入50MHZ,复位信号;第三,输入接收数据,输出发送数据;第四,用户数据、准备握手、传输数据

第二部分:接收模块

 uart_rx#(
        .           P_SYSTEM_CLK       (P_SYSTEM_CLK     )     ,
        .           P_UART_BUADRATE    (P_UART_BUADRATE  )     ,
        .           P_UART_DATA_WIDTH  (P_UART_DATA_WIDTH)   ,//数据宽度
        .           P_UART_STOP_WIDTH  (P_UART_STOP_WIDTH)   //停止位:1or2
 


)

uart_rx_u0
(
    . i_clk(w_uart_buadclk) ,
    . i_rst(w_uart_buadclk_rst),

    . i_uart_rx( i_uart_rx),
     

    . o_user_rx_data(o_user_rx_data),
    . o_user_rx_valid(o_user_rx_valid)
    );

第一,顶层参数的设置, 但这里为啥缺少一个检查位? ;第二,端口定义声明,输入,复位,接收信号,接收数据,接收反馈;

 rst_gen_module#(
    .                  P_RST_CYCLE  (1)

)
rst_gen_module_u0
(
    .               i_clk(w_uart_buadclk),
    .               o_rst(w_uart_buadclk_rst)

    );

module rst_gen_module#(
            parameter                  P_RST_CYCLE   =1

)(
    input                   i_clk,
    output                  o_rst

    );


    reg                 ro_rst = 0;
    reg  [7:0]          r_cnt;


    assign              o_rst=ro_rst;    
    always@(posedge i_clk)
     begin
         if(r_cnt == P_RST_CYCLE-1||P_RST_CYCLE == 0)
         r_cnt<=r_cnt;
         else
         r_cnt<=r_cnt+1;
    end      
     always@(posedge i_clk)
     begin
         if(r_cnt == P_RST_CYCLE-1||P_RST_CYCLE == 0)
         ro_rst<='d0;
         else
         ro_rst<='d1;
    end       
endmodule

⚠️这里复位没有用板子的值,而是又重新分频了一个复位值rst_gen,原因就是不能跨时钟复位。

 第三部分:发送模块设计

CLK_DIV_module#(
   .     p_CLK_DIV_CNT ( P_CLK_DIV_NUMBER)//16位宽:最大65535
)

CLK_DIV_module_u0

(
    .             i_clk(i_clk),
    .             i_rst(i_rst),
   .             o_clk_div(w_uart_buadclk)
    );


    uart_tx#(
                .  P_SYSTEM_CLK       (P_SYSTEM_CLK     )     ,
                .  P_UART_BUADRATE    (P_UART_BUADRATE  )     ,
                .  P_UART_DATA_WIDTH  (P_UART_DATA_WIDTH)   ,//数据宽度
                .  P_UART_STOP_WIDTH  (P_UART_STOP_WIDTH)   //停止位


)
uart_tx_u0

(
        .              i_clk(w_uart_buadclk),
        .              i_rst(w_uart_buadclk_rst),

     
        .             o_uart_tx(  o_uart_tx),

        .   i_user_tx_data (i_user_tx_data ),
        .                          i_user_tx_valid(i_user_tx_valid),
        .                          o_user_tx_ready(o_user_tx_ready) 
    );

 第三部分:发送模块设计


module uart_tx#(
        parameter           P_SYSTEM_CLK    = 50_000_000     ,
        parameter           P_UART_BUADRATE = 9600           ,
        parameter           P_UART_DATA_WIDTH = 8            ,//数据宽度
        parameter           P_UART_STOP_WIDTH = 1           , //停止位
        parameter           P_UART_CHECK      = 0              //0为无校验,1为奇校验,2为偶校验


)(
        input               i_clk,
        input               i_rst,
        output              o_uart_tx,
        input  [P_UART_DATA_WIDTH-1:0]  i_user_tx_data,
        input                           i_user_tx_valid,
        output                          o_user_tx_ready    
    );

 第一,顶层参数的设置;第二,声明端口以及定义端口,特别注意的是,o_uart_tx是往外输出的数据信号,随后依次是往外发送的数据,发送信号值以及时刻准备值(来张波形图说明下关系)

reg                     ro_uart_tx;     //面对一个新的要求,不知该从何入手时的一个解决办法;记得整理下笔记
reg                     ro_user_tx_ready;
reg   [15:0]            r_cnt;//计数器位宽高于16bit时,组合逻辑的级数过高,请谨慎使用。  
reg   [P_UART_DATA_WIDTH-1:0]   r_tx_data;     //正常-1.现在+1,实则把校验位,停止位都包含在内。笔记       
reg                             r_tx_check;
/***************wire******************/
wire                    w_tx_active;
/***************component*************/

/***************assign****************/
assign  o_uart_tx = ro_uart_tx;
assign  o_user_tx_ready = ro_user_tx_ready;
assign  w_tx_active = i_user_tx_valid & o_user_tx_ready;      
/***************always****************/

注意:我们在面对一个新的设计要求时,首先要从输出处入手,先设计一个寄存器,做赋值处理;

一旦有赋值就会有计数器的存在(也不一定)

always@(posedge i_clk,posedge i_rst) //准备      形式固定。
begin
    if(i_rst)
        ro_user_tx_ready <= 'd1;
    else if(w_tx_active)
          ro_user_tx_ready <= 'd0;
        else if(r_cnt == 2 +P_UART_DATA_WIDTH + P_UART_STOP_WIDTH -1)
                ro_user_tx_ready <= 'd1;
            else
                ro_user_tx_ready <=  ro_user_tx_ready ;     
    end

老规矩:时序逻辑一上来,先降龙十八掌,if......else块搞上三个。然后填充内容。空闲位置为高电平,当握手成功,也就是value为1时,起始位的值为0;当计数器计满(这里没搞懂,在哪里计数),再拉高,停止位,最后进入空闲位。

always@(posedge i_clk,posedge i_rst) //握手
begin
    if(i_rst)
      r_cnt <= 'd0; 
    else if(r_cnt == 2 +P_UART_DATA_WIDTH + P_UART_STOP_WIDTH -1)
      r_cnt <= 'd0;
        else if(!ro_user_tx_ready)//作下图:vaule、ready、data  笔记
       r_cnt <= r_cnt+ 'd1;     
            else
            r_cnt <= r_cnt; 
end

 计数器:复位,计满都为清零,当准备信号下拉为0,开始传输值时,计数器开始计数。

always@(posedge i_clk,posedge i_rst) //接货
begin
   if(i_rst)
        r_tx_data <= 'd0;
   else if(w_tx_active)//用户寄存
        r_tx_data <= i_user_tx_data;//1.奇校验:
    else if(!ro_user_tx_ready)
        r_tx_data <= r_tx_data >> 1;//移位,方便下面送货。笔记
   else 
        r_tx_data <= r_tx_data;
end

 w_tx_active是与值,当为1时,就进入发送数据的状态。储存数据的寄存器开始将数据赋值,从低位开始传输。

always@(posedge i_clk,posedge i_rst)//送货
begin
    if(i_rst)
        ro_uart_tx <= 'd1;
    else if(w_tx_active)
        ro_uart_tx <= 'd0;
    else if(r_cnt == P_UART_DATA_WIDTH )//倒数第三位校验位,会慢一拍
        ro_uart_tx <= r_tx_check;
    else if(r_cnt >= 3 +P_UART_DATA_WIDTH -2)//倒数第二位发停止位
        r_tx_check <= 'd1;
    else if(!ro_user_tx_ready)
        ro_uart_tx <= r_tx_data[0];//先发低位
    else 
        ro_uart_tx <= 'd1;    
end

 送货:在检查校验位,停止位;

always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)
        r_tx_check <= 'd0;
    else if(!ro_user_tx_ready && P_UART_CHECK == 1)
         r_tx_check <=~(r_tx_check ^ r_tx_data[0]);
    else if(!ro_user_tx_ready && P_UART_CHECK == 2)
         r_tx_check <=r_tx_check ^ r_tx_data[0];
    else
        r_tx_check <= r_tx_check;
end
endmodule

这个块在检查奇偶校验位,若是奇校验,;若是偶校验,。

module uart_rx#(
        parameter           P_SYSTEM_CLK    = 50_000_000     ,
        parameter           P_UART_BUADRATE = 9600           ,
        parameter           P_UART_DATA_WIDTH = 8            ,//数据宽度
        parameter           P_UART_STOP_WIDTH = 1            , //停止位
        parameter           P_UART_CHECK      = 0   


)(
        input               i_clk,
        input               i_rst,

        input               i_uart_rx,//接收信号,关键!
     

        output [P_UART_DATA_WIDTH-1:0]   o_user_rx_data,
        output                           o_user_rx_valid
       
    );






/***************function**************/

/***************parameter*************/

/***************port******************/             

/***************mechine***************/

/***************reg*******************/
reg [P_UART_DATA_WIDTH-1:0]    ro_user_rx_data;
reg                            ro_user_rx_valid;
reg [1:0]                      r_uart_rx;//定义一个两级寄存器,准备打两拍
reg [1:0]                      r_cnt;//计数器是命脉;要是不用计数器,就得考虑状态机的使用。
reg                            r_rx_check;
/***************wire******************/

/***************component*************/

/***************assign****************/
assign  o_user_rx_data  = ro_user_rx_data;
assign  o_user_rx_valid = ro_user_rx_valid;
/***************always****************/
always@(posedge i_clk,posedge i_rst)
begin
     if(i_rst)
        r_uart_rx <= 'd0;
     else
        r_uart_rx <= {r_uart_rx[0],i_uart_rx};/*打两拍是无条件一直需要做的事情,故不需要条件触发;
                                               信号不断地从低位读取进来,形成信号输入左移,构成了r_uart_rx[1]打两拍的情况。*/
end 

always@(posedge i_clk,posedge i_rst) //计数器共有四个状态,上电清零;计满清零;计数;保持。输入数据,起始位来了 开始计数,用计数器获取数据。
begin
        if(i_rst)
           r_cnt <= 'd0;
        else if(1+P_UART_DATA_WIDTH+1+P_UART_STOP_WIDTH-1)//从0开始,减去1是必要的
           r_cnt <= 'd0;
        else if(r_uart_rx[1] == 0 || r_cnt > 0)
           r_cnt <= r_cnt +1;
        else
           r_cnt <= r_cnt;
end

always@(posedge i_clk,posedge i_rst)
begin
      if(i_rst)
        ro_user_rx_data <= 'd0;
      else if(r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH)
        ro_user_rx_data <= {r_uart_rx[1],ro_user_rx_data[7:1]};//先发低位
      else
        ro_user_rx_data <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
        if(i_rst)
          ro_user_rx_valid <= 'd0;
        else if(r_cnt == P_UART_DATA_WIDTH && P_UART_CHECK == 0)
          ro_user_rx_valid <= 'd1;//只持续一个周期
        else if(r_cnt == P_UART_DATA_WIDTH && P_UART_CHECK == 1 &&  r_rx_check )
          ro_user_rx_valid <= 'd1;
        else if(r_cnt == P_UART_DATA_WIDTH && P_UART_CHECK == 2 && !r_rx_check)
          ro_user_rx_valid <= 'd1;
        else
          ro_user_rx_valid <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
        if(i_rst)
           r_rx_check <= 'd0;
        else if(r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH)//接收数据期间
           r_rx_check <=r_rx_check^r_uart_rx[1];//异或判断接收的1是奇数还是偶数,奇数则为1,偶数则为0
        else
           r_rx_check <= r_rx_check;
end

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值