04_串口 RS485

1. 理论学习

1.1 单端传输

单端传输是指在传输过程中,在一根导线上传输对地之间的电平差,用这个电平差值来表示逻辑“0”和“1”(RS232 即为单端传输方式)。
在这里插入图片描述

1.2 差分传输

差分传输是使用两根线进行传输信号,这两根线上的信号振幅相等,相位相差 180 度,极性相反。在这两根线上传输的信号就是差分信号,信号接收端比较这两个信号电压的差值来判断发送端发送的逻辑“0”和逻辑“1”。
在这里插入图片描述

1.3 RS485 简介

RS485 通信协议和 RS232 是一样的
RS485 是 UART 的一种,在上一章节我们详细地介绍了 UART 串口通信以及 RS-232接口标准。RS-485 是针对 RS-232 接口的不足而出现的一种接口标准,本章节将为大家详细的介绍 RS-485 标准。RS-485 是双向、半双工通信协议,允许多个驱动器和接收器挂接在总线上,其中每个驱动器都能够脱离总线。所谓半双工就是指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。RS-485 采用差分传输方式.

1.4 RS485 收发器电路图

在这里插入图片描述
即当 RS485_RE 为高时发送过程,为低时接收过程。

1.5 双 RS485 通信连接图

在这里插入图片描述

2. 实验目标

在前面我们已经学习了流水灯和呼吸灯的控制方法,本章节就利用 RS-485 接口,使用两块 FPGA 开发板实现两块开发板之间的流水灯、呼吸灯控制。具体为:当按下其中一块开发板的按键 1 时,另一块开发板的流水灯亮,再次按下按键 1 时,流水灯灭;同理呼吸灯也是如此。需要注意的是我们开发板中的 led 只能显示一种状态(流水灯或呼吸灯),所以当显示流水灯/呼吸灯时,按下呼吸灯按键/流水灯按键后,led 会显示呼吸灯/流水灯。控制要求如图 31-3 所示。
在这里插入图片描述

3. 模块框图

3.1 顶层模块框图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 按键消抖模块

在这里插入图片描述

3.3 呼吸灯模块

在这里插入图片描述

3.4 流水灯模块

在这里插入图片描述

3.5 串口接收模块

在这里插入图片描述

3.6 串口发送模块

这个模块与《RS232》章节中的 uart_tx 模块大致相同,唯一不同的是《RS232》章节中的 uart_tx 模块的 work_en(发送工作使能信号),我们需要将其作为输出连接到MAX3485 收发器中去控制信息的发送和接收。同时还要注意在《RS232》章节中的 uart_tx模块的 work_en 信号是拉高到数据位的最后一位,停止位并没有拉高。通过测试发现这会导致开发板在向另一块开发板发送信息时,rx 在没有接受到数据时会拉低几个时钟,而uart_rx 模块是通过 rx 的下降沿来判断是否传来数据的,这样就会导致开发板误以为另一块开发板发送过来了信息,导致结果出现错误。所以我们需要将 work_en(发送工作使能信号)拉高至停止位来解决这个问题。
在这里插入图片描述
波形图的不同
在这里插入图片描述

4. 波形图

4.1 led 灯控制模块

在这里插入图片描述
在这里插入图片描述

4.2 Uart_tx

在这里插入图片描述

5. RTL

5.1 led_ctrl

`timescale  1ns/1ns



module  led_ctrl
(
    input   wire            sys_clk         ,   //模块时钟,50MHz 
    input   wire            sys_rst_n       ,   //复位信号,低有效
    input   wire            water_key_flag  ,   //流水灯按键有效信号
    input   wire            breath_key_flag ,   //呼吸灯按键有效信号
    input   wire    [3:0]   led_out_w       ,   //流水灯
    input   wire            led_out_b       ,   //呼吸灯
    input   wire    [7:0]   po_data         ,   //接收数据

    output  wire            pi_flag         ,   //发送标志信号
    output  wire    [7:0]   pi_data         ,   //发送数据
    output  reg     [3:0]   led_out             //输出led灯
    
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//reg   define
reg     water_led_flag  ;   //流水灯标志信号,作为pi_data[0]发送
reg     breath_led_flag ;   //呼吸灯标志信号,作为pi_data[1]发送

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//按下呼吸灯按键或流水灯按键时,开始发送数据
assign  pi_flag =   water_key_flag | breath_key_flag;

//低两位数据为led控制信号
assign  pi_data =   {
    
    6'd0,breath_led_flag,water_led_flag};

//water_key_flag:串口发送的控制信号,高时流水灯,低时停止(按键控制)
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        water_led_flag  <=  1'b0;
    else    if(breath_key_flag == 1'b1)
        water_led_flag  <=  1'b0;
    else    if(water_key_flag == 1'b1)
        water_led_flag  <=  ~water_led_flag;

//breath_key_flag:串口发送的控制信号,高时呼吸灯灯,低时停止(按键控制)
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        breath_led_flag  <=  1'b0;
    else    if(water_key_flag == 1'b1)
        breath_led_flag  <=  1'b0;
    else    if(breath_key_flag == 1'b1)
        breath_led_flag  <=  ~breath_led_flag;

//led_out:当传入的流水灯有效时,led灯为流水灯,同理呼吸灯也是如此
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out <=  4'b1111;
    else    if(po_data[0] == 1'b1 )
        led_out <=  led_out_w;
    else    if(po_data[1] == 1'b1 )
    //使四个led灯都显示呼吸灯状态
        led_out <=  {
    
    led_out_b,led_out_b,led_out_b,led_out_b};
    else
        led_out <=  4'b1111; 

endmodule

5.2 Uart_tx

`timescale  1ns/1ns


module  uart_tx
#(
    parameter   UART_BPS    =   'd9600,           //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000      //时钟频率
)
(
    input   wire            sys_clk     ,   //系统时钟50Mhz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire    [7:0]   pi_data     ,   //并行数据
    input   wire            pi_flag     ,   //并行数据有效标志信号
            
    output  reg             work_en     ,   //发送使能,高有效
    output  reg             tx              //串口发送数据
);

//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;
        
//reg   define
reg     [12:0]  baud_cnt    ;   
reg             bit_flag    ;   
reg     [3:0]   bit_cnt     ;    

//************************************************************************//
//******************************* Main Code ******************************//
//************************************************************************//

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else    if(pi_flag == 1'b1)	
        work_en <= 1'b1;
    else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
        work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到5207
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 13'b0;
    else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
        baud_cnt <= 13'b0;
    else    if(work_en == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else    if( /* baud_cnt == 13'd1 */baud_cnt == BAUD_CNT_MAX - 1 )
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))	
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (work_en == 1'b1))
        bit_cnt <= bit_cnt + 1'b1;
                                
//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tx <= 1'b1;	//空闲状态时为高电平
    else    if(/* bit_flag == 1'b1 */work_en == 1'b1)
        case(bit_cnt)
            0       : tx <= 1'b0;
            1       : tx <= pi_data[0];
            2       : tx <= pi_data[1];
            3       : tx <= pi_data[2];
            4       : tx <= pi_data[3];
            5       : tx <= pi_data[4];
            6       : tx <= pi_data[5];
            7       : tx <= pi_data[6];
            8       : tx <= pi_data[7];
            9       : tx <= 1'b1;
            default : tx <= 1'b1;
        endcase
                
endmodule

5.3 RS485

`timescale  1ns/1ns


module  rs485
(
    input   wire            sys_clk     ,   //系统时钟,50MHz
    input   wire            sys_rst_n   ,   //复位信号,低有效
    input   wire            rx          ,   //串口接收数据
    input   wire    [1:0]   key         ,   //两个按键

    output  wire            work_en     ,   //发送使能,高有效
    output  wire            tx          ,   //串口接收数据
    output  wire    [3:0]   led             //led灯

);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//parameter define
parameter   UART_BPS    =   14'd9600;       //比特率
parameter   CLK_FREQ    =   26'd50_000_000; //时钟频率

//wire  define
wire    [7:0]   po_data         ;   //接收数据
wire    [7:0]   pi_data         ;   //发送数据
wire            pi_flag         ;   //发送标志信号
wire            water_key_flag  ;   //流水灯按键有效信号
wire            breath_key_flag ;   //呼吸灯按键有效信号
wire    [3:0]   led_out_w       ;   //流水灯
wire            led_out_b       ;   //呼吸灯

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//--------------------uart_rx_inst------------------------
uart_rx
#(
    .UART_BPS    (UART_BPS  ),   //串口波特率
    .CLK_FREQ    (CLK_FREQ  )    //时钟频率
)
uart_rx_inst(
    .sys_clk     (sys_clk   ),   //系统时钟50Mhz
    .sys_rst_n   (sys_rst_n ),   //全局复位
    .rx          (rx        ),   //串口接收数据

    .po_data     (po_data   ),   //串转并后的8bit数据
    .po_flag     (          )    //接收数据完成标志信号没用到可不接
);

//--------------------uart_tx_inst------------------------
uart_tx
#(
    .UART_BPS    (UART_BPS  ),   //串口波特率
    .CLK_FREQ    (CLK_FREQ  )    //时钟频率
)
uart_tx_inst(
    .sys_clk     (sys_clk   ),   //系统时钟50Mhz
    .sys_rst_n   (sys_rst_n ),   //全局复位
    .pi_data     (pi_data   ),   //并行数据
    .pi_flag     (pi_flag   ),   //并行数据有效标志信号

    .work_en     (work_en   ),   //发送使能,高有效
    .tx          (tx        )    //串口发送数据
);

//--------------------key_filter_inst------------------------
//两个按键信号例化两次
key_filter  key_filter_w
(
    .sys_clk        (sys_clk        ),    //系统时钟50Mhz
    .sys_rst_n      (sys_rst_n      ),    //全局复位
    .key_in         (key[0]         ),    //按键输入信号

    .key_flag       (water_key_flag)  //key_flag为1时表示消抖后按键有效

);
key_filter  key_filter_b
(
    .sys_clk        (sys_clk        ),    //系统时钟50Mhz
    .sys_rst_n      (sys_rst_n      ),    //全局复位
    .key_in         (key[1]         ),    //按键输入信号

    .key_flag       (breath_key_flag) //key_flag为1时表示消抖后按键有效

);

//--------------------key_ctrl_inst------------------------
led_ctrl    led_ctrl_inst
(
    .sys_clk         (sys_clk        ),   //模块时钟,50MHz
    .sys_rst_n       (sys_rst_n      ),   //复位信号,低有效
    .water_key_flag  (water_key_flag ),   //流水灯按键有效信号
    .breath_key_flag (breath_key_flag),   //呼吸灯按键有效信号
    .led_out_w       (led_out_w      ),   //流水灯
    .led_out_b       (led_out_b      ),   //呼吸灯
    .po_data         (po_data        ),   //接收数据

    .pi_flag         (pi_flag        ),   //发送标志信号
    .pi_data         (pi_data        ),   //发送数据
    .led_out         (led            )    //输出led灯

);

//--------------------water_led_inst------------------------
water_led   water_led_inst
(
    .sys_clk         (sys_clk   ),   //系统时钟50Mh
    .sys_rst_n       (sys_rst_n ),   //全局复位

    .led_out         (led_out_w )    //输出控制led灯

);

//--------------------breath_led_inst------------------------
breath_led  breath_led_inst
(
    .sys_clk         (sys_clk   ),   //系统时钟50Mhz
    .sys_rst_n       (sys_rst_n ),   //全局复位

    .led_out         (led_out_b )    //输出信号,控制led灯

);

endmodule

6. Testbench

6.1 tb_rs485

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/03/12
// Module Name   : tb_rs485
// Project Name  : rs485
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : RS485仿真文件
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  tb_rs485();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//wire  define
wire            rx1         ;
wire            work_en1    ;
wire            tx1         ;
wire    [3:0]   led1        ;
wire            work_en2    ;
wire            tx2         ;
wire    [3:0]   led2        ;

//reg   define
reg             sys_clk     ;
reg             sys_rst_n   ;
reg     [1:0]   key1        ;
reg     [1:0]   key2        ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//对sys_clk,sys_rst赋初值,并模拟按键抖动
initial
    begin
            sys_clk     =   1'b1 ;
            sys_rst_n   <=  1'b0 ;
            key1        <=  2'b11;
            key2        <=  2'b11;
    #200    sys_rst_n   <=  1'b1 ;
//按下流水灯按键
    #2000000    key1[0]      <=  1'b0;//按下按键
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #200        key1[0]      <=  1'b1;//松开按键
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
//按下呼吸灯按键
    #2000000    key1[1]      <=  1'b0;//按下按键
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #200        key1[1]      <=  1'b1;//松开按键
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
//按下呼吸灯按键
    #2000000    key1[1]      <=  1'b0;//按下按键
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #200        key1[1]      <=  1'b1;//松开按键
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
//按下呼吸灯按键
    #2000000    key1[1]      <=  1'b0;//按下按键
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #200        key1[1]      <=  1'b1;//松开按键
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
//按下流水灯灯按键
    #2000000    key1[0]      <=  1'b0;//按下按键
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #200        key1[0]      <=  1'b1;//松开按键
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
//按下流水灯灯按键
    #2000000    key1[0]      <=  1'b0;//按下按键
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #200        key1[0]      <=  1'b1;//松开按键
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    end

//sys_clk:模拟系统时钟,每10ns电平取反一次,周期为20ns,频率为50Mhz
always #10 sys_clk = ~sys_clk;

//重新定义参数值,缩短仿真时间仿真
//发送板参数
defparam    rs485_inst1.key_filter_w.CNT_MAX         =   5      ;
defparam    rs485_inst1.key_filter_b.CNT_MAX         =   5      ;
defparam    rs485_inst1.uart_rx_inst.UART_BPS        =   1000000;
defparam    rs485_inst1.uart_tx_inst.UART_BPS        =   1000000;
defparam    rs485_inst1.water_led_inst.CNT_MAX       =   4000   ;
defparam    rs485_inst1.breath_led_inst.CNT_1US_MAX  =   4      ;
defparam    rs485_inst1.breath_led_inst.CNT_1MS_MAX  =   9      ;
defparam    rs485_inst1.breath_led_inst.CNT_1S_MAX   =   9      ;
//接收板参数
defparam    rs485_inst2.key_filter_w.CNT_MAX         =   5      ;
defparam    rs485_inst2.key_filter_b.CNT_MAX         =   5      ;
defparam    rs485_inst2.uart_rx_inst.UART_BPS        =   1000000;
defparam    rs485_inst2.uart_tx_inst.UART_BPS        =   1000000;
defparam    rs485_inst2.water_led_inst.CNT_MAX       =   4000   ;
defparam    rs485_inst2.breath_led_inst.CNT_1US_MAX  =   4      ;
defparam    rs485_inst2.breath_led_inst.CNT_1MS_MAX  =   99     ;
defparam    rs485_inst2.breath_led_inst.CNT_1S_MAX   =   99     ;


//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//

//发送板
//-------------rs485_inst1-------------
rs485   rs485_inst1
(
    .sys_clk     (sys_clk    ),   //系统时钟,50MHz
    .sys_rst_n   (sys_rst_n  ),   //复位信号,低有效
    .rx          (rx1        ),   //串口接收数据
    .key         (key1       ),   //两个按键

    .work_en     (work_en1   ),   //发送使能,高有效
    .tx          (tx1        ),   //串口发送数据
    .led         (led_tx1    )    //led灯

);

//接收板
//-------------rs485_inst2-------------
rs485   rs485_inst2
(
    .sys_clk     (sys_clk    ),   //系统时钟,50MHz
    .sys_rst_n   (sys_rst_n  ),   //复位信号,低有效
    .rx          (tx1        ),   //串口接收数据
    .key         (key2       ),   //两个按键

    .work_en     (work_en2   ),   //发送使能,高有效
    .tx          (tx2        ),   //串口发送数据
    .led         (led_rx2    )    //led灯

);
endmodule

猜你喜欢

转载自blog.csdn.net/HeElLose/article/details/131424491