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)關注正點原子公眾號,獲取最新資料更新
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燈在按下時點亮,釋放時熄滅,說明程式下載驗證成功。