Bootstrap

FPGA学习记录_RS485串口通信_正点原子领航者开发板

前言

        今天学习正点原子领航者开发板RS485串口通信模块,以下是记录的笔记和重写的代码。


1:RS485串口通信

        RS-485是针对UART串口的一种接口标准,接口为两线、半双工,信号采用差分传输方式,通过两根线传输数据(差为2 ~ 6v为1,差为-6 ~ -2v为0),RS-432采用单端的传输方式(根据电平来判断0或1)。

        差分传输优点在于,抗干扰能力强,单端很容易受影响,但是差分两根线都会受影响,所以差值不变。差分传输相比于单端传输抗干扰能力强,传输距离远,对外界电磁干扰小,时序定位准确;但对布线要求高,适用于高速场景USB/MIPI/PCIE等。


2:波形图——写代码

2.1 按键消抖模块

        代码分析:

        1:打两拍判断按键是否改变。

        2:按键数据有效标志位key_data_valid:当检测到按键数据发生改变时,key_data_valid拉高为1。

        3:分别调用key_debounce模块消抖:输入按键key0和按键key1消抖后,输出消抖后的按键key_data。

<font color="#FF8C0" size=3 >
module key_trig(
    input     [1:0] key         ,
    input           sys_rst_n   ,
    input           sys_clk     ,
    output    [1:0] key_data    ,
    output  reg     key_data_valid
);
reg   [1:0] key_data_d0;
reg   [1:0] key_data_d1;



//打两拍判断按键是否改变。
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        key_data_d0 <= 2'b11;
        key_data_d1 <= 2'b11;
    end
    else begin
        key_data_d0 <= key_data     ;
        key_data_d1 <= key_data_d0  ;
    end 
end

//按键数据有效标志位key_data_valid
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        key_data_valid <= 1'b0;
    else if(key_data_d0 != key_data_d1)
        key_data_valid <= 1'b1;
    else
        key_data_valid <= 1'b0;
end

//分别调用key_debounce模块消抖
key_debounce u_key_debounce0(
    .sys_clk        (sys_clk    ),
    .sys_rst_n      (sys_rst_n  ),
    .key            (key[0]     ),
    .key_filter     (key_data[0])
);

key_debounce u_key_debounce1(
    .sys_clk        (sys_clk    ),
    .sys_rst_n      (sys_rst_n  ),
    .key            (key[1]     ),
    .key_filter     (key_data[1])
);



endmodule
</font>

2.2 LED灯控制模块

        代码分析:

        控制led灯的变化:在led_en使能(接收完成信号uart_rx_done)为1,led灯的状态改变为接收到的数据led_data相反(与接收到的按键电平uart_rx_data[1:0] = uart_tx_data[1:0] = key_data 相反),按键按下改变uart_rx_data[1:0],控制led亮灭。

module led_ctrl(
    input               led_en   ,
    input               sys_clk  ,
    input               sys_rst_n,
    input      [1:0]    led_data ,
    output reg [1:0]    led      
);

//控制led灯的变化
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if(!sys_rst_n)
        led <= 2'b00;
    else if(led_en)
        led <= ~led_data ;
    else
        led <= led  ;
end

endmodule

2.3 顶层模块

        代码分析:

        1:串口接收和串口发送模块几乎不变,与之前不同的是,本次发送模块uart_tx是的发送数据uart_tx_data由{6‘b0,key_data[1:0]}两部分组成。

        2:led控制模块:led_en对应uart_rx_done,led_data对应uart_rx_data[1:0],led模块不变。

        3:按键触发模块:按键有效信号key_data_valid和发送完成信号uart_tx_done(uart_tx_en)信号一致,其他不变。

module rs485_key_led(
    input             sys_clk   ,
    input             sys_rst_n ,
       
    input      [1:0]  key       ,
    output     [1:0]  led       ,
       
    input             rs485_rxd ,
    output            rs485_txd 
);

parameter       CLK_FREQ = 50000000 ;
parameter       UART_BPS = 115200   ;


wire        uart_rx_done;
wire  [7:0] uart_rx_data;
wire        uart_tx_done;
wire  [7:0] uart_tx_data;
wire  [1:0] key_data    ;
                       
assign uart_tx_data = {6'b0,key_data};
uart_rx #(
    .CLK_FREQ   (CLK_FREQ),
    .UART_BPS   (UART_BPS))
    u_uart_rx(
    .sys_clk     (sys_clk     ),
    .sys_rst_n   (sys_rst_n   ),
    .uart_rxd    (rs485_rxd   ),
    .uart_rx_done(uart_rx_done),
    .uart_rx_data(uart_rx_data)
);

//led控制模块
led_ctrl u_led_ctrl(
    .led_en     (uart_rx_done       ),
    .sys_clk    (sys_clk            ),
    .sys_rst_n  (sys_rst_n          ),
    .led_data   (uart_rx_data[1:0]  ),
    .led        (led                )
);   


uart_tx #(
    .CLK_FREQ   (CLK_FREQ),
    .UART_BPS   (UART_BPS))
    u_uart_tx(
    .sys_clk     (sys_clk     ),
    .sys_rst_n   (sys_rst_n   ),
    .uart_tx_en  (uart_tx_done),
    .uart_tx_data(uart_tx_data),
    .uart_txd    (rs485_txd   ),
    .uart_tx_busy(            )
);               

//按键触发模块
key_trig u_key_trig(
    .key            (key           ),
    .sys_rst_n      (sys_rst_n     ),
    .sys_clk        (sys_clk       ),
    .key_data       (key_data      ),
    .key_data_valid (uart_tx_done  )
);


endmodule

2.4 仿真文件

        代码分析:

        1:按键初始为高电平2'b11,把所有的发送的数据设为0,然后仿真按键按下后抖动。

        2:defparam:先是仿真里面的例化模块u_rs485_key_led,寻找u_rs485_key_led中的例化模块u_key_trig,寻找例化模块u_key_trig的例化模块u_key_debounce0,寻找例化模块u_key_debounce0中的常量CNX。简单来说,就是这个CNX是一开始设置的常量,但由于我们连续例化了三到四层子模块,但并没层层例化常量,这里没有办法直接用之前#(.******** (******))这种方式例化的,所以得用defparam来找到这个常量的位置进行例化。

        注:defparam不可综合,只适用于仿真(tb文件)

 `timescale 1ns/1ns   //仿真单位/仿真精读
module tb_uart_loopback();

parameter CLK_PERIOD = 20;

defparam u_rs485_key_led.u_key_trig.u_key_debounce0.CNX=10;
defparam u_rs485_key_led.u_key_trig.u_key_debounce1.CNX=10;

reg         sys_clk;
reg         sys_rst_n;
reg         uart_rxd;
wire        uart_txd;
reg  [1:0]  key     ;
wire [1:0]  led     ;    


initial begin
    sys_clk <=1'b0;
    sys_rst_n <= 1'b0;
    uart_rxd <= 1'b1;
    key <= 2'b11    ;
    #200
    sys_rst_n <= 1'b1;
    #1000
    uart_rxd  <= 1'b0;  //起始位
    #8680
    uart_rxd  <= 1'b0;  //D0
    #8680
    uart_rxd  <= 1'b0;  //D1
    #8680
    uart_rxd  <= 1'b0;  //D2
    #8680
    uart_rxd  <= 1'b0;  //D3
    #8680
    uart_rxd  <= 1'b0;  //D4
    #8680
    uart_rxd  <= 1'b0;  //D5
    #8680
    uart_rxd  <= 1'b0;  //D6
    #8680
    uart_rxd  <= 1'b0;  //D7
    #8680
    uart_rxd  <= 1'b1;  //停止位
    #8680
    uart_rxd  <= 1'b1;  //空闲状态
    #1000
    key <= 2'b00     ;
    #100             
    key <= 2'b11     ;
    #100             
    key <= 2'b00     ;
end

always #(CLK_PERIOD/2) sys_clk = ~sys_clk;

rs485_key_led u_rs485_key_led(
    .sys_clk     (sys_clk   ), 
    .sys_rst_n   (sys_rst_n ),
    .key         (key       ),
    .led         (),
    .rs485_rxd   (uart_rxd  ),
    .rs485_txd   ()
    );             
                   
endmodule


3:烧录

        Flash和SD卡可以固化程序,但这些都是PS端的内容,不能单独固化PL程序比特流,具体操作需看后续Linux部分。


参考内容

;