2019-11-27
關鍵字:485串口通信
SP3485 是一款半雙工的遵循RS485與RS422通信協議的傳輸芯片。
SP3485的芯片封裝如下圖所示:
其中 1 腳、4 腳分別接 rk3288 的 RX 腳與 TX 腳。
第 2 腳、第 3 腳為收發控制位,通常這兩個腳都是接同一個電平信號的,因為通過芯片封裝圖可知這兩個腳是互為取反設計的。當給這兩個腳高電平時芯片處於“發送”模式,當給它們低電平時則處於“接收”模式。這款芯片的收發控制既可以通過軟件來控制電平高低以切換收發模式,也可以直接通過硬件電路來自動切換收發模式。二者的區別在於對收發模式切換延時的要求不同。使用軟件控制模式切換存在一定的延時,極限大約在 100us 左右,這個延時時長其實已經能應對大多數場景下的通信了,但還是有極少數高速通信場景接受不了這個延時時長。這種情況下就得考慮硬件電路自動切換收發模式了。如何通過硬件自動切換收發模式呢?網上有一篇文章:
http://www.elecfans.com/dianlutu/app/20180117617635_2.html
本篇文章記述的是通過軟件來切換收發模式的方式。
上圖第 6 腳、第 7 腳是差分信號輸出引腳。因為 485 通信必須要有兩條差分信號線才能進行通信,而這款芯片又僅有一組輸出引腳,因此這在硬件上就限制了這款芯片只能是半雙工通信模式的芯片。
本篇文章記述的是通過軟件控制收發模式切換的方式,在筆者的開發板里,它的DE、RE腳所接的引腳為:GPIO7_B7
它的 RX、TX 接到了 UART1 中:
在 rk3288 中,共有 5 個 UART 串口,它們通常在 /dev 目錄下以 ttyS* 的方式注冊成串口驅動設備:
UART2 默認是被配置成調試串口,它是不能被當作普通串口來用的。當然,具體要開放多少個通信串口可以在 dts 中去配置。總而言之,筆者手里的環境就是通過 ttyS1 來收發 485 通信數據的。
既然已經知道是通過 rk 的普通串口 UART1 來做 485 通信的,那我們直接去改動 rk3288 的串口驅動程序就好了。
rk3288 的普通串口默認是不支持 485 通信的,因此我們這里需要自己去改一下驅動代碼。筆者手里的開發板串口驅動對應的源碼文件為:
./kernel/driver/tty/serial/rk_serial.c
rk3288 的串口驅動默認是有“回顯”功能的,我們需要將它的回顯功能去掉,就是在注冊 uart 驅動以后修改一下驅動結構體,如下圖所示:
然后由於我們只將 UART1 用作 485 通信,因此需要在 probe() 函數中將連接着 sp3485 收發模式控制位的引腳給申請成為普通 GPIO 口:
上圖所示代碼中的宏 "GPIO_485_EN" 指的是前面我們提到的那個 GPIO7_B7 引腳,這個引腳在驅動代碼里必須轉換成整型數字來表示,它在這里的值是 239,計算方式為:7 * 32 + 15 = 239。詳細的計算方式可以參閱筆者的另一篇博文:RK平台如何計算GPIO腳編號
在將模式控制引腳設置為GPIO輸出模式后還將默認電平拉為了低電平,即上電就是“接收”模式。
為了實現收發功能,我們必須要在適當的時機拉低或置高收發模式控制位電平。由於筆者這邊是將控制位默認拉低電平的,因此這個適當的時機就是在 rk3288 芯片的 UART1 即將要發送數據時將控制位電平置高,在 rk3288 芯片的 UART1 將最后一位數據發送完畢后再將控制位電平拉低。
怎么來實現這個目的呢?
rk3288 串口的數據收發是通過中斷來做的。在筆者的 rk_serial.c 源碼中,有兩個函數被指定為串口數據的收發中斷處理函數:
因為前面已經將這個控制位引腳設置為普通輸出型 GPIO 口了,所以在一進入 transmit_chars() 函數時就將引腳電平置高,並延時 100us 以等待電平完成拉高操作:
需要強調的是,transmit_chars() 函數是中斷函數,是不能延時太久的,不然系統會報 BUG。
另外,由於串口的通信速率通常比較慢,如 9600bps, 11520bps 等,當發送的數據較長時就會需要比較多的時間。而我們拉低控制位的邏輯就是通過查詢串口的中斷標志寄存器,等它發送完成后就拉。而我們又不能在中斷函數里等,所以需要一個工作隊列來幫忙。在串口通信進入到 start_up() 階段時初始化工作隊列,在 transmit_chars() 函數即將跑完時推一個工作隊列入棧,在隊列函數中做死等操作。
在這里筆者選擇 tasklet 隊列。
rk3288 在驅動代碼中將串口設備描述為結構體 struct uart_rk_port,我們需要在這個結構體中添加一個 tasklet 變量:
在 start_up() 函數中初始化 tasklet 隊列:
在 transmit_chars() 結束前推入這個 tasklet 隊列:
隊列響應函數中啥事也不干,就查詢相關標志位寄存器並死等它的結束,並在查詢到相關標志位完成以后立即將控制位引腳電平拉低:
這里必須強調一點:上圖的 while() 代碼塊中不要加任何延時函數,最好不要加任何語句。不然的話極有可能會將 cpu 完全搶占導致內核崩潰。
這種方式可以滿足大多數場景下的 485 通信需求。
不過仍然要注意的是,這種軟件切換 sp3485 收發模式的方式,在串口發送完最后一位數據以后,仍然會有約 100us 的延時 GPIO_485_EN 腳的電平才會被拉低。換句話說就是 sp3485 在發送完成以后,至少要間隔 100us 才能接收數據。
要想縮短這一時間間隔還得通過前面提到的硬件控制自動切換收發模式的方式。