【正點原子FPGA連載】 第十七章 RS485串列埠通訊實驗 -摘自【正點原子】領航者ZYNQ之FPGA開發指南_V2.0

2022-01-05 10:00:14

1)實驗平臺:正點原子領航者ZYNQ開發板
2)平臺購買地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套實驗原始碼+手冊+視訊下載地址:http://www.openedv.com/thread-301505-1-1.html
4)對正點原子FPGA感興趣的同學可以加群討論:994244016
5)關注正點原子公眾號,獲取最新資料更新
在這裡插入圖片描述

第十七章 RS485串列埠通訊實驗

RS-485是針對UART串列埠的一種介面標準,它定義了序列通訊系統中傳送器和接收器的一系列電氣特性。相比於RS-232,RS-485標準的通訊系統抗干擾能力較強,可實現長距離資料傳輸,同時支援多個收發器連線到同一個通訊網路中。因此,RS-485在工業控制領域以及有類似需求的系統中得到了廣泛的應用。
本章包括以下幾個部分:
1.1 RS-485簡介
1.2 實驗任務
1.3 硬體設計
1.4 程式設計
1.5 下載驗證 
1.1 RS-485簡介
在「串列埠通訊實驗」章節我們詳細地介紹了UART串列埠通訊以及RS-232介面標準。實際上,除了RS-232之外,RS-422和RS-485也都是常用的序列通訊介面標準,它們定義了介面不同的電氣特性,如RS-232是單端輸入輸出,而RS-422/485為差分輸入輸出等。
在介紹RS-485之前,我們先來了解一下串列埠通訊過程中單端傳輸與差分傳輸的差別。單端傳輸是指在傳送或接收過程中,用訊號線對地線的電壓值來表示邏輯「0」和「1」。而差分傳輸使用兩根訊號線來傳輸一路訊號,這兩根訊號線上傳輸的訊號幅值相等,相位相差180度(極性相反),用它們的差值來表示邏輯「0」和「1」,如下圖所示。
在這裡插入圖片描述

圖 7.5.13.1 差分傳輸方式
在傳輸過程中,當訊號線上疊加了頻率、幅值和相位都相同的干擾訊號時(共模干擾),對於單端傳輸而言,由於地線電位為0,則傳輸的訊號就包含了干擾訊號;而在差分傳輸方式下,干擾可以通過兩個訊號線上電壓的差值抵消,相當於抑制了共模干擾,如下圖所示。因此相對於單端傳輸方式,差分傳輸大大提高了訊號在傳輸過程中的抗干擾能力,但是需要多餘的訊號線來實現訊號傳輸。
在這裡插入圖片描述

圖 7.5.13.2 差分傳輸抑制共模干擾
RS-232介面標準出現較早,訊號採用負邏輯電平、單端傳輸方式工作。通過一根訊號線傳送,一根訊號線接收,加上一根地線,RS-232可實現全雙工通訊。由於單端傳輸方式抗干擾能力差,導致RS-232標準通訊距離短(小於15米),資料傳輸速率低等問題。另外RS-232僅支援一對一通訊,存在無法實現多個裝置互聯的缺點。
RS-422由RS-232發展而來,它是為彌補RS-232之不足而提出的。RS-422採用差分傳輸(又稱平衡傳輸)方式,將最大傳輸速率提高到10Mbps;當傳輸速率在100kbps以下時,傳輸距離可達1200米。由於採用差分傳輸方式,RS-422需要4根訊號線來實現全雙工通訊,兩根用於傳送、兩根用於接收,一般會再加上一根地線。RS-422允許在一條傳輸匯流排上連線最多10個接收器,從而實現單個裝置傳送,多個裝置接收的功能。
為擴充套件應用範圍,在RS-422基礎上又制定了RS-485標準。RS-485同樣採用差分傳輸方式,但是RS-485只有2根訊號線,由傳送和接收共用,因此傳送和接收不能同時進行,只能實現半雙工通訊。RS-485增加了多點、雙向通訊能力,即允許多個傳送器連線到同一條匯流排上,各裝置通過使能訊號控制傳送和接收過程。
1.2 實驗任務
本節實驗任務是使用兩塊領航者開發板通過RS-485埠互聯,由各自開發板上的兩個按鍵分別控制對方開發板上兩個LED燈的亮滅。當按鍵按下時,對方開發板上對應的LED燈點亮;按鍵釋放時,對應的LED燈熄滅。
1.3 硬體設計
RS485串列埠部分的原理圖如下圖所示。由於ZYNQ PL側串列埠輸入輸出引腳為TTL電平,用3.3V代表邏輯「1」,0V代表邏輯「0」;而RS-485電平標準採用差分訊號的差值電壓來代表邏輯「0」和「1」。因此當FPGA與RS485介面標準的裝置通訊時,需要加電平轉換晶片SP3485,實現RS485電平與TTL電平的轉換。
在這裡插入圖片描述

圖 7.5.13.1 RS485串列埠原理圖
由於RS-485為半雙工通訊方式,需要通過使能訊號來控制傳送和接收過程。在下圖中,電平轉換晶片SP3485的2號引腳為低電平接收使能,3號引腳為高電平傳送使能。在這裡我們將兩個引腳連線在一起,只需要通過一個訊號RS485_DE即可控制收發過程:當RS485_DE為高電平時,SP3485處於傳送過程;當RS485_DE為低電平時,SP3485處於接收過程。
在這裡插入圖片描述

圖 7.5.13.2 RS232/RS485選擇介面
下圖為RS232/RS485的選擇介面,由上圖可知,SP3485晶片埠的RS485_RX和RS485_TX並沒有直接和ZYNQ的引腳相連線,而是連線到開發板的P1口,RS232串列埠和RS485串列埠共用P1口的UART2_TX和UART2_RX,UART2_TX和UART2_RX是直接和FPGA的引腳相連線的,這樣的設計方式實現了有限IO的多種複用功能。因此,在做RS485串列埠通訊實驗時,需要使用杜邦線或者跳帽將RS485_RX和UART2_TX連線在一起,RS485_TX和UART2_RX連線在一起。
除此之外,領航者開發板上還包括了RS485收發方向自動控制電路,如下圖所示:
在這裡插入圖片描述

圖 7.5.13.3 RS485收發方向自動控制電路
其中,「RS485_RX」網路由ZYNQ輸出的UART2_TX驅動。當UART2_TX為高,即不傳送時,三極體導通,「RD485_DE」被拉低,此時SP3485晶片工作在接收狀態,RS485差分匯流排的電平被外部電阻強制拉高,達到了輸出高電平的的狀態。當UART2_TX為低,即開始傳送時,三極體截止,「RD485_DE」被拉高,此時SP3485晶片工作在傳送狀態,RS485差分匯流排的電平由UART2_TX來驅動。這樣就實現了RS485收發狀態的自動控制。
本實驗中,各埠訊號的管腳分配如下表所示:
表 17.3.1 RS485串列埠通訊實驗管腳分配
訊號名 方向 管腳 埠說明 電平標準
sys_clk input U18 系統時鐘,50M LVCMOS33
sys_rst_n input N16 系統復位,低有效 LVCMOS33
rs485_uart_rxd input K14 RS485串列埠接收 LVCMOS33
rs485_uart_txd output M15 RS485串列埠傳送 LVCMOS33
key[1] input K16 按鍵1 LVCMOS33
key[0] input L14 按鍵0 LVCMOS33
led[1] output L15 LED燈1 LVCMOS33
led[0] output H15 LED燈0 LVCMOS33
對應的約束語句如下所示:

create_clock -period 20.000 -name sys_clk  [get_ports sys_clk]
   
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports {key[0]}]
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports {key[1]}]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {led[0]}]
set_property -dict {PACKAGE_PIN L15 IOSTANDARD LVCMOS33} [get_ports {led[1]}]
set_property -dict {PACKAGE_PIN K14 IOSTANDARD LVCMOS33} [get_ports rs485_uart_rxd]
set_property -dict {PACKAGE_PIN M15 IOSTANDARD LVCMOS33} [get_ports rs485_uart_txd]

1.4 程式設計
根據實驗任務,我們可以大致規劃出系統的控制流程:當檢測到有按鍵按下或釋放時,將按鍵資料通過RS485串列埠傳送出去;而當RS485串列埠接收到對方傳送的按鍵資料時,根據接收到的資料改變LED燈的顯示狀態。由此畫出系統的功能框圖如下所示:
在這裡插入圖片描述

圖 7.5.13.1 RS485串列埠實驗系統框圖
由系統總體框圖可知,FPGA部分包括五個模組,頂層模組(rs485_uart_top)、接收模組(uart_recv)、傳送模組(uart_send)、按鍵消抖模組(key_debounce)和LED燈控制模組(led_ctrl)。其中在頂層模組中完成對另外四個模組的例化。
由於RS-485只是對介面標準的定義,資料的傳輸仍然是按照UART串列埠通訊協定進行。因此我們可以直接呼叫「串列埠通訊實驗」中的串列埠傳送和接收模組。在這裡我們仍然設定資料位為8位元,停止位為1位,無校驗位,波特率為115200bps。
各模組埠及訊號連線如下圖所示:
在這裡插入圖片描述

圖 7.5.13.2 頂層模組原理圖
key_debounce為按鍵消抖模組,在檢測到有按鍵按下或釋放時對按鍵資料進行消抖處理,在按鍵資料穩定後給出通知訊號key_flag,並將資料由串列埠傳送模組uart_send傳送出去。uart_recv為串列埠接收模組,它負責接收對方傳送的按鍵資料,並在一幀資料(8位元)接收結束後給出通知訊號uart_done。當LED燈控制模組led_ctrl檢測到該通知訊號時,根據接收到的按鍵資料改變板卡上LED燈的顯示狀態。
頂層模組的程式碼如下:

1   module rs485_uart_top(
2       input           sys_clk,           //外部50M時鐘
3       input           sys_rst_n,         //外部復位訊號,低有效
4       
5       input  [1:0]    key,               //按鍵
6       output [1:0]    led,               //led燈
7       
8       //uart介面
9       input           rs485_uart_rxd,    //rs485串列埠接收埠
10      output          rs485_uart_txd     //rs485串列埠傳送埠
11      );
12      
13  //parameter define
14  parameter  CLK_FREQ = 50000000;        //定義系統時脈頻率
15  parameter  UART_BPS = 115200;          //定義串列埠波特率
16      
17  //wire define   
18  wire       tx_en_w;                    //UART傳送使能
19  wire       rx_done_w;                  //UART接收完畢訊號
20  wire [7:0] tx_data_w;                  //UART傳送資料
21  wire [7:0] rx_data_w;                  //UART接收資料
22  wire [1:0] key_value_w;                //消抖後的按鍵資料
23  
24  //*****************************************************
25  //**                    main code
26  //*****************************************************   
27  assign tx_data_w = {6'd0,key_value_w}; //將按鍵消抖後的值送到傳送模組
28  
29  uart_recv #(                           //串列埠接收模組
30      .CLK_FREQ       (CLK_FREQ),        //設定系統時脈頻率
31      .UART_BPS       (UART_BPS))        //設定串列埠接收波特率
32  u_uart_recv(                 
33      .sys_clk        (sys_clk), 
34      .sys_rst_n      (sys_rst_n),
35      
36      .uart_rxd       (rs485_uart_rxd),
37      .uart_done      (rx_done_w),
38      .uart_data      (rx_data_w)
39      );
40      
41  uart_send #(                           //串列埠傳送模組
42      .CLK_FREQ       (CLK_FREQ),        //設定系統時脈頻率
43      .UART_BPS       (UART_BPS))        //設定串列埠傳送波特率
44  u_uart_send(                 
45      .sys_clk        (sys_clk),
46      .sys_rst_n      (sys_rst_n),
47       
48      .uart_en        (tx_en_w),
49      .uart_din       (tx_data_w),
50      .uart_txd       (rs485_uart_txd)
51      );
52      
53  key_debounce u_key_debounce(
54      .sys_clk        (sys_clk), 
55      .sys_rst_n      (sys_rst_n),
56      
57      .key            (key),
58      .key_flag       (tx_en_w),         //按鍵有效通知訊號
59      .key_value      (key_value_w)      //按鍵消抖後的資料
60      );
61      
62  led_ctrl u_led_ctrl(
63      .sys_clk        (sys_clk), 
64      .sys_rst_n      (sys_rst_n),
65      
66      .led_en         (rx_done_w),       //led控制使能
67      .led_data       (rx_data_w[1:0]),  //led控制資料
68      .led            (led)
69  );
70  
71  endmodule 

頂層模組中主要完成對其餘模組的例化,需要注意的是程式第27行:由於板卡上只有2個按鍵,而串列埠通訊過程中資料位為8位元,因此需要將消抖後得到的2按鍵位資料高位補6個零,然後再給到串列埠傳送模組。同樣,在將接收的按鍵資料用於LED燈控制時,僅將低2位有效位賦值給LED燈控制模組,如第67行所示。
串列埠接收程式和串列埠傳送程式與「串列埠通訊實驗」章節中的程式碼完全相同。
有關串列埠收發過程更詳細的介紹請大家參考「串列埠通訊實驗」,下面我們來介紹一下另外兩個模組:按鍵消抖模組和LED燈控制模組。
在機械按鍵按下和釋放的過程中,由於機械觸點的彈性作用,按鍵開關在閉合的瞬間不會立即穩定地導通,在釋放時也不是立刻就能完全斷開。因此,在閉合及斷開的瞬間均伴隨有一連串的抖動,如下圖所示。按鍵的抖動過程體現在數位電路中就是不斷變化的高低電平,為避免在抖動過程中採集到錯誤的按鍵狀態,我們需要對按鍵資料進行消除抖動處理。
在這裡插入圖片描述

圖 7.5.13.3 機械按鍵抖動過程
按鍵抖動的時間長短由按鍵的機械特性決定,一般為5ms~10ms,在抖動時間內按鍵狀態可能會不斷的發生變化。由於按鍵的抖動過程持續時間較短,很快就趨於穩定狀態。因此在按鍵按下及釋放之後,若按鍵能穩定在同一狀態且持續時間達20ms,我們就認為抖動過程已經結束,此時的採集的按鍵資料有效。
按鍵消抖模組的程式碼如下所示:

1   module key_debounce(
2       input            sys_clk,          //外部50M時鐘
3       input            sys_rst_n,        //外部復位訊號,低有效
4       
5       input      [1:0] key,              //外部按鍵輸入
6       
7       output reg       key_flag,         //按鍵資料有效訊號
8       output reg [1:0] key_value         //按鍵消抖後的資料
9       );
10  
11  //reg define    
12  reg [31:0] delay_cnt;
13  reg [ 1:0] key_reg;
14  
15  //*****************************************************
16  //**                    main code
17  //*****************************************************
18  always @(posedge sys_clk or negedge sys_rst_n) begin 
19      if (!sys_rst_n) begin 
20          key_reg   <= 2'b11;
21          delay_cnt <= 32'd0;
22      end
23      else begin
24          key_reg <= key;
25          if(key_reg != key)             //一旦檢測到按鍵狀態發生變化(有按鍵被按下或釋放)
26              delay_cnt <= 32'd1000000;  //給延時計數器重新裝載初始值(計數時間為20ms)
27          else if(key_reg == key) begin  //在按鍵狀態穩定時,計數器遞減,開始20ms倒計時
28                   if(delay_cnt > 32'd0)
29                       delay_cnt <= delay_cnt - 1'b1;
30                   else
31                       delay_cnt <= delay_cnt;
32               end           
33      end   
34  end
35  
36  always @(posedge sys_clk or negedge sys_rst_n) begin 
37      if (!sys_rst_n) begin 
38          key_flag  <= 1'b0;
39          key_value <= 2'b11;          
40      end
41      else begin
42          if(delay_cnt == 32'd1) begin   //當計數器遞減到1時,說明按鍵穩定狀態維持了20ms
43              key_flag  <= 1'b1;         //此時消抖過程結束,給出一個時鐘週期的標誌訊號
44              key_value <= key;          //並寄存此時按鍵的值
45          end
46          else begin
47              key_flag  <= 1'b0;
48              key_value <= key_value; 
49          end  
50      end   
51  end
52      
53  endmodule 

程式中第25行不斷檢測按鍵狀態,一旦發現按鍵狀態發生改變時,就給計數器delay_cnt賦初值1000000。在按鍵狀態不發生改變時delay_cnt遞減從而實現倒計時的功能,在倒計時過程中,一旦檢測到按鍵狀態發生改變,則說明有抖動產生,此時重新給delay_cnt賦初值,並開始新一輪倒計時。在50Mhz時鐘驅動下,delay_cnt若能由1000000遞減至1,則說明按鍵狀態保持穩定時間達20ms,此時輸出一個時鐘週期的通知訊號key_flag,並將此時的按鍵資料寄存輸出。
串列埠接收模組在接收對方傳送的按鍵資料後,將資料低4位元(高4位元為零)給到LED控制模組,並輸出通知訊號uart_done。LED燈控制模組在檢測到uart_done的上升沿時,利用接收到的按鍵資料改變LED燈的顯示狀態。
LED燈控制模組的程式碼如下:

1   module led_ctrl(
2       input            sys_clk,          //外部50M時鐘
3       input            sys_rst_n,        //外部復位訊號,低有效
4       
5       input            led_en,           //led控制使能
6       input      [1:0] led_data,         //led控制資料
7       
8       output reg [1:0] led               //led燈
9       );
10  
11  //reg define
12  reg led_en_d0;
13  reg led_en_d1;
14  
15  //wire define
16  wire led_en_flag;
17  
18  //*****************************************************
19  //**                    main code
20  //*****************************************************
21  //捕獲led_en上升沿,得到一個時鐘週期的脈衝訊號
22  assign led_en_flag = (~led_en_d1) & led_en_d0;
23  
24  always @(posedge sys_clk or negedge sys_rst_n) begin 
25      if (!sys_rst_n) begin 
26          led_en_d0 <= 1'b0;
27          led_en_d1 <= 1'b0;
28      end
29      else begin
30          led_en_d0 <= led_en;
31          led_en_d1 <= led_en_d0;
32      end
33  end
34  
35  always @(posedge sys_clk or negedge sys_rst_n) begin 
36      if (!sys_rst_n) 
37          led <= 2'b00;
38      else if(led_en_flag)               //在led_en上升沿到來時,改變led燈的狀態
39              led <= ~led_data;          //按鍵按下時為低電平,而led高電平時點亮
40          else
41              led <= led;
42  end
43      
44  endmodule 

由於領航者開發板上的按鍵在按下時為低電平,而LED為高電平時點亮,因此為了實現按鍵按下時點亮對應LED燈的功能,需要將按鍵資料取反後賦值給LED輸出埠暫存器,如程式碼中第39行所示。
1.5 下載驗證
編譯工程並生成位元流.bit檔案。接下來我們將兩個領航者開發板上的RS485介面用兩根杜邦線連線起來,如下圖所示。連線時注意介面位置一一對應,不要接反了,兩塊開發板的RS485埠的A連線A,B連線B。另外領航者開發板上的CAN介面與RS485介面十分相像,使用時請注意區分。還有一點需要注意的是,兩塊開發板的P1口都需要使用杜邦線或者跳帽進行連線選擇RS485口,否則無法進行RS485串列埠通訊。然後分別將兩塊開發板上的下載器一端連電腦,另一端與開發板上的JTAG下載埠連線,最後連線電源線並開啟電源開關。
在這裡插入圖片描述

圖 7.5.13.1 領航者開發板實物圖
接下來我們下載程式,驗證兩個領航者開發板上的按鍵通過RS-485通訊埠控制對方LED燈亮滅的功能。需要說明的是,由於本次實驗使用兩塊開發板進行通訊,所以可以在開始通訊之前,先將程式固化至開發板中,這個需要在嵌入式SDK軟體中完成,ZYNQ晶片無法單獨固化位元流檔案。在《領航者ZYNQ之嵌入式開發指南.pdf》檔案中「第七章 程式固化實驗」,會有一個單獨的章節向大家介紹程式固化的方法。
兩塊開發板程式固化完成後,重新給領航者開發板上電,依次按下任意一個開發板上的兩個按鍵,可以觀察到另外一個開發板上對應的LED燈在按下時點亮,釋放時熄滅,說明程式下載驗證成功。