串口通信原理
1.串口通信指串口按位(bit)發(fā)送和接收字節(jié)。盡管比按字節(jié)(byte)的并行通信慢,但是串口可以在使用一根線發(fā)送數(shù)據(jù)的同時(shí)用另一根線接收數(shù)據(jù)。
2.串口是計(jì)算機(jī)上一種非常通用的設(shè)備通信協(xié)議(不要與通用串行總線Universal SerialBus或者USB混淆)
3.地,串口用于ASCII碼字符的傳輸。通信使用3根線完成:(1)地線,(2)發(fā)送,(3)接收。由于串口通信是異步的,端口能夠在一根線上發(fā)送數(shù)據(jù)同時(shí)在另一根線上接收數(shù)據(jù)。其他線用于握手,但是不是必須的。串口通信最重要的參數(shù)是比特率、數(shù)據(jù)位、停止位和奇偶校驗(yàn)。對(duì)于兩個(gè)進(jìn)行通信的端口,這些參數(shù)必須匹配
4.-232(ANSI/EIA-232標(biāo)準(zhǔn))是IBM-PC及其兼容機(jī)上的串行連接標(biāo)準(zhǔn)、RS-422(EIA RS-422-AStandard)是Apple的Macintosh計(jì)算機(jī)的串口連接標(biāo)準(zhǔn)。RS-485(EIA-485標(biāo)準(zhǔn))是RS-422的改進(jìn)。
說(shuō)到開(kāi)源,恐怕很少有人不挑大指稱(chēng)贊。學(xué)生通過(guò)開(kāi)源代碼學(xué)到了知識(shí),程序員通過(guò)開(kāi)源類(lèi)庫(kù)獲得了別人的成功經(jīng)驗(yàn)及能夠按時(shí)完成手頭的工程,商家通過(guò)開(kāi)源軟件賺到了錢(qián)……,總之是皆大歡喜。然而開(kāi)源軟件或類(lèi)庫(kù)的首要缺點(diǎn)就是大多缺乏詳細(xì)的說(shuō)明文檔和使用的例子,或者就是軟件代碼隨便你用,就是文檔,例子和后期服務(wù)收錢(qián)。這也難怪,畢竟就像某個(gè)著名NBA球員說(shuō)的那樣:“我還要養(yǎng)家,所以千萬(wàn)美元以下的合同別找我談,否則我寧可待業(yè)”。是啊,支持開(kāi)源的人也要養(yǎng)家,收點(diǎn)錢(qián)也不過(guò)分。要想既不花錢(qián)又學(xué)到知識(shí)就只能借助網(wǎng)絡(luò)和了,我只是想拋磚引玉,為開(kāi)源事業(yè)做出點(diǎn)微薄共獻(xiàn),能為你的工程解決哪怕一個(gè)小問(wèn)題,也就足夠了。
雖然我的這個(gè)系列介紹的東西不是什么Web框架,也不是什么開(kāi)源服務(wù)器,但是我相信,作為一個(gè)程序員,什么樣的問(wèn)題都會(huì)遇到。有時(shí)候越是簡(jiǎn)單的問(wèn)題反而越棘手;越是小的地方就越是找不到稱(chēng)手的家伙。只要你不是整天只與“架構(gòu)”、“構(gòu)件”、“框架”打交道的話(huà),相信我所說(shuō)的東西你一定會(huì)用到。
1 串口通信簡(jiǎn)介
1.1 常見(jiàn)的Java串口包
1.2 串口包的安裝(Windows下)
2 串口API概覽
2.1 javax.comm.CommPort
2.2 javax.comm.CommPortIdentifier
2.3 javax.comm.SerialPort
2.4 串口API實(shí)例
2.4.1 列舉出本機(jī)所有可用串口
2.4.2 串口參數(shù)的配置
2.4.3 串口的讀寫(xiě)
3 串口通信的通用模式及其問(wèn)題
3.1 事件監(jiān)聽(tīng)模型
3.2 串口讀數(shù)據(jù)的線程模型
3.3 第三種方法
4 結(jié)束語(yǔ)
1 串口通信簡(jiǎn)介
嵌入式系統(tǒng)或傳感器網(wǎng)絡(luò)的很多應(yīng)用和測(cè)試都需要通過(guò)PC機(jī)與嵌入式設(shè)備或傳感器節(jié)點(diǎn)進(jìn)行通信。其中,最常用的接口就是RS-232串口和并口(鑒于USB接口的復(fù)雜性以及不需要很大的數(shù)據(jù)傳輸量,USB接口用在這里還是顯得過(guò)于奢侈,況且目前除了SUN有一個(gè)支持USB的包之外,我還沒(méi)有看到其他直接支持USB的Java類(lèi)庫(kù))。SUN的CommAPI分別提供了對(duì)常用的RS232串行端口和IEEE1284并行端口通訊的支持。RS-232-C(又稱(chēng)EIA RS-232-C,以下簡(jiǎn)稱(chēng)RS232)是在1970年由美國(guó)電子工業(yè)協(xié)會(huì)(EIA)聯(lián)合貝爾系統(tǒng)、調(diào)制解調(diào)器廠家及計(jì)算機(jī)終端生產(chǎn)廠家共同制定的用于串行通訊的標(biāo)準(zhǔn)。RS232是一個(gè)全雙工的通訊協(xié)議,它可以同時(shí)進(jìn)行數(shù)據(jù)接收和發(fā)送的工作。
1.1 常見(jiàn)的Java串口包
目前,常見(jiàn)的Java串口包有SUN在1998年發(fā)布的串口通信API:comm2.0.jar(Windows下)、comm3.0.jar(Linux/Solaris);IBM的串口通信API以及一個(gè)開(kāi)源的實(shí)現(xiàn)。鑒于在Windows下SUN的API比較常用以及IBM的實(shí)現(xiàn)和SUN的在API層面都是一樣的,那個(gè)開(kāi)源的實(shí)現(xiàn)又不像兩家大廠的產(chǎn)品那樣讓人放心,這里就只介紹SUN的串口通信API在Windows平臺(tái)下的使用。
1.2 串口包的安裝(Windows下)
到SUN的網(wǎng)站下載javacomm20-win32.zip,包含的東西如下所示:
按照其使用說(shuō)明(Readme.html)的說(shuō)法,要想使用串口包進(jìn)行串口通信,除了設(shè)置好環(huán)境變量之外,還要將win32com.dll復(fù)制到《JDK》/bin目錄下;將comm.jar復(fù)制到《JDK》/lib;把javax.comm.properties也同樣拷貝到《JDK》/lib目錄下。然而在真正運(yùn)行使用串口包的時(shí)候,僅作這些是不夠的。因?yàn)橥ǔ.?dāng)運(yùn)行“java MyApp”的時(shí)候,是由JRE下的虛擬機(jī)啟動(dòng)MyApp的。而我們只復(fù)制上述文件到JDK相應(yīng)目錄下,所以應(yīng)用程序?qū)?huì)提示找不到串口。解決這個(gè)問(wèn)題的方法很簡(jiǎn)單,我們只須將上面提到的文件放到JRE相應(yīng)的目錄下就可以了。
值得注意的是,在網(wǎng)絡(luò)應(yīng)用程序中使用串口API的時(shí)候,還會(huì)遇到其他更復(fù)雜問(wèn)題。有興趣的話(huà),你可以查看CSDN社區(qū)中“關(guān)于網(wǎng)頁(yè)上Applet用javacomm20讀取客戶(hù)端串口的問(wèn)題”的帖子。
2 串口API概覽
2.1 javax.comm.CommPort
這是用于描述一個(gè)被底層系統(tǒng)支持的端口的抽象類(lèi)。它包含一些高層的IO控制方法,這些方法對(duì)于所有不同的通訊端口來(lái)說(shuō)是通用的。SerialPort 和ParallelPort都是它的子類(lèi),前者用于控制串行端口而后者用于控這并口,二者對(duì)于各自底層的物理端口都有不同的控制方法。這里我們只關(guān)心SerialPort。
2.2 javax.comm.CommPortIdentifier
這個(gè)類(lèi)主要用于對(duì)串口進(jìn)行管理和設(shè)置,是對(duì)串口進(jìn)行訪問(wèn)控制的核心類(lèi)。主要包括以下方法
l 確定是否有可用的通信端口
l 為IO操作打開(kāi)通信端口
l 決定端口的所有權(quán)
l 處理端口所有權(quán)的爭(zhēng)用
l 管理端口所有權(quán)變化引發(fā)的事件(Event)
2.3 javax.comm.SerialPort
這個(gè)類(lèi)用于描述一個(gè)RS-232串行通信端口的底層接口,它定義了串口通信所需的最小功能集。通過(guò)它,用戶(hù)可以直接對(duì)串口進(jìn)行讀、寫(xiě)及設(shè)置工作。
2.4 串口API實(shí)例
大段的文字怎么也不如一個(gè)小例子來(lái)的清晰,下面我們就一起看一下串口包自帶的例子---SerialDemo中的一小段代碼來(lái)加深對(duì)串口API核心類(lèi)的使用方法的認(rèn)識(shí)。
2.4.1 列舉出本機(jī)所有可用串口
void listPortChoices() {
CommPortIdentifier portId;
Enumeration en = CommPortIdentifier.getPortIdentifiers();
// iterate through the ports.
while (en.hasMoreElements()) {
portId = (CommPortIdentifier) en.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
System.out.println(portId.getName());
}
}
portChoice.select(parameters.getPortName());
}
以上代碼可以列舉出當(dāng)前系統(tǒng)所有可用的串口名稱(chēng),我的機(jī)器上輸出的結(jié)果是COM1和COM3。
2.4.2 串口參數(shù)的配置
串口一般有如下參數(shù)可以在該串口打開(kāi)以前配置進(jìn)行配置:
包括波特率,輸入/輸出流控制,數(shù)據(jù)位數(shù),停止位和齊偶校驗(yàn)。
SerialPort sPort;
try {
sPort.setSerialPortParams(BaudRate,Databits,Stopbits,Parity);
//設(shè)置輸入/輸出控制流
sPort.setFlowControlMode(FlowControlIn | FlowControlOut);
} catch (UnsupportedCommOperationException e) {}
2.4.3 串口的讀寫(xiě)
對(duì)串口讀寫(xiě)之前需要先打開(kāi)一個(gè)串口:
CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(PortName);
try {
SerialPort sPort = (SerialPort) portId.open(“串口所有者名稱(chēng)”, 超時(shí)等待時(shí)間);
} catch (PortInUseException e) {//如果端口被占用就拋出這個(gè)異常
throw new SerialConnectionException(e.getMessage());
}
//用于對(duì)串口寫(xiě)數(shù)據(jù)
OutputStream os = new BufferedOutputStream(sPort.getOutputStream());
os.write(int data);
//用于從串口讀數(shù)據(jù)
InputStream is = new BufferedInputStream(sPort.getInputStream());
int receivedData = is.read();
讀出來(lái)的是int型,你可以把它轉(zhuǎn)換成需要的其他類(lèi)型。
這里要注意的是,由于Java語(yǔ)言沒(méi)有無(wú)符號(hào)類(lèi)型,即所有的類(lèi)型都是帶符號(hào)的,在由byte到int的時(shí)候應(yīng)該尤其注意。因?yàn)槿绻鸼yte的最高位是1,則轉(zhuǎn)成int類(lèi)型時(shí)將用1來(lái)占位。這樣,原本是10000000的byte類(lèi)型的數(shù)變成int型就成了1111111110000000,這是很?chē)?yán)重的問(wèn)題,應(yīng)該注意避免。
3 串口通信的通用模式及其問(wèn)題
終于嘮叨完我最討厭的基礎(chǔ)知識(shí)了,下面開(kāi)始我們本次的重點(diǎn)--串口應(yīng)用的研究。由于向串口寫(xiě)數(shù)據(jù)很簡(jiǎn)單,所以這里我們只關(guān)注于從串口讀數(shù)據(jù)的情況。通常,串口通信應(yīng)用程序有兩種模式,一種是實(shí)現(xiàn)SerialPortEventListener接口,監(jiān)聽(tīng)各種串口事件并作相應(yīng)處理;另一種就是建立一個(gè)獨(dú)立的接收線程專(zhuān)門(mén)負(fù)責(zé)數(shù)據(jù)的接收。由于這兩種方法在某些情況下存在很?chē)?yán)重的問(wèn)題(至于什么問(wèn)題這里先賣(mài)個(gè)關(guān)子J),所以我的實(shí)現(xiàn)是采用第三種方法來(lái)解決這個(gè)問(wèn)題。
3.1 事件監(jiān)聽(tīng)模型
現(xiàn)在我們來(lái)看看事件監(jiān)聽(tīng)模型是如何運(yùn)作的
:
l 首先需要在你的端口控制類(lèi)(例如SManager)加上“implements SerialPortEventListener”
l 在初始化時(shí)加入如下代碼:
try {
SerialPort sPort.addEventListener(SManager);
} catch (TooManyListenersException e) {
sPort.close();
throw new SerialConnectionException(“too many listeners added”);
}
sPort.notifyOnDataAvailable(true);
l 覆寫(xiě)public void serialEvent(SerialPortEvent e)方法,在其中對(duì)如下事件進(jìn)行判斷:
BI -通訊中斷。
CD -載波檢測(cè)。
CTS -清除發(fā)送。
DATA_AVAILABLE -有數(shù)據(jù)到達(dá)。
DSR -數(shù)據(jù)設(shè)備準(zhǔn)備好。
FE -幀錯(cuò)誤。
OE -溢位錯(cuò)誤。
OUTPUT_BUFFER_EMPTY -輸出緩沖區(qū)已清空。
PE -奇偶校驗(yàn)錯(cuò)。
RI -振鈴指示。
一般最常用的就是DATA_AVAILABLE--串口有數(shù)據(jù)到達(dá)事件。也就是說(shuō)當(dāng)串口有數(shù)據(jù)到達(dá)時(shí),你可以在serialEvent中接收并處理所收到的數(shù)據(jù)。然而在我的實(shí)踐中,遇到了一個(gè)十分嚴(yán)重的問(wèn)題。
首先描述一下我的實(shí)驗(yàn):我的應(yīng)用程序需要接收傳感器節(jié)點(diǎn)從串口發(fā)回的查詢(xún)數(shù)據(jù),并將結(jié)果以圖標(biāo)的形式顯示出來(lái)。串口設(shè)定的波特率是115200,川口每隔128毫秒返回一組數(shù)據(jù)(大約是30字節(jié)左右),周期(即持續(xù)時(shí)間)為31秒。實(shí)測(cè)的時(shí)候在一個(gè)周期內(nèi)應(yīng)該返回4900多個(gè)字節(jié),而用事件監(jiān)聽(tīng)模型我最多只能收到不到1500字節(jié),不知道這些字節(jié)都跑哪里去了,也不清楚到底丟失的是那部分?jǐn)?shù)據(jù)。值得注意的是,這是我將serialEvent()中所有處理代碼都注掉,只剩下打印代碼所得的結(jié)果。數(shù)據(jù)丟失的如此嚴(yán)重是我所不能忍受的,于是我決定采用其他方法。
3.2 串口讀數(shù)據(jù)的線程模型
這個(gè)模型顧名思義,就是將接收數(shù)據(jù)的操作寫(xiě)成一個(gè)線程的形式:
public void startReadingDataThread() {
Thread readDataProcess = new Thread(new Runnable() {
public void run() {
while (newData != -1) {
try {
newData = is.read();
System.out.println(newData);
//其他的處理過(guò)程
………。
} catch (IOException ex) {
System.err.println(ex);
return;
}
}
readDataProcess.start();
}
在我的應(yīng)用程序中,我將收到的數(shù)據(jù)打包放到一個(gè)緩存中,然后啟動(dòng)另一個(gè)線程從緩存中獲取并處理數(shù)據(jù)。兩個(gè)線程以生產(chǎn)者—消費(fèi)者模式協(xié)同工作,數(shù)據(jù)的流向如下圖所示:
這樣,我就圓滿(mǎn)解決了丟數(shù)據(jù)問(wèn)題。然而,沒(méi)高興多久我就又發(fā)現(xiàn)了一個(gè)同樣嚴(yán)重的問(wèn)題:雖然這回不再丟數(shù)據(jù)了,可是原本一個(gè)周期(31秒)之后,傳感器節(jié)電已經(jīng)停止傳送數(shù)據(jù)了,但我的串口線程依然在努力的執(zhí)行讀串口操作,在控制臺(tái)也可以看見(jiàn)收到的數(shù)據(jù)仍在不斷的打印。原來(lái),由于傳感器節(jié)點(diǎn)發(fā)送的數(shù)據(jù)過(guò)快,而我的接收線程處理不過(guò)來(lái),所以InputStream就先把已到達(dá)卻還沒(méi)處理的字節(jié)緩存起來(lái),于是就導(dǎo)致了明明傳感器節(jié)點(diǎn)已經(jīng)不再發(fā)數(shù)據(jù)了,而控制臺(tái)卻還能看見(jiàn)數(shù)據(jù)不斷打印這一奇怪的現(xiàn)象。唯一值得慶幸的是最后收到數(shù)據(jù)確實(shí)是4900左右字節(jié),沒(méi)出現(xiàn)丟失現(xiàn)象。然而當(dāng)處理完最后一個(gè)數(shù)據(jù)的時(shí)候已經(jīng)快1分半鐘了,這個(gè)時(shí)間遠(yuǎn)遠(yuǎn)大于節(jié)點(diǎn)運(yùn)行周期。這一延遲對(duì)于一個(gè)實(shí)時(shí)的顯示系統(tǒng)來(lái)說(shuō)簡(jiǎn)直是災(zāi)難!
后來(lái)我想,是不是由于兩個(gè)線程之間的同步和通信導(dǎo)致了數(shù)據(jù)接收緩慢呢?于是我在接收線程的代碼中去掉了所有處理代碼,僅保留打印收到數(shù)據(jù)的語(yǔ)句,結(jié)果依然如故??磥?lái)并不是線程間的通信阻礙了數(shù)據(jù)的接收速度,而是用線程模型導(dǎo)致了對(duì)于發(fā)送端數(shù)據(jù)發(fā)送速率過(guò)快的情況下的數(shù)據(jù)接收延遲。這里申明一點(diǎn),就是對(duì)于數(shù)據(jù)發(fā)送速率不是如此快的情況下前面者兩種模型應(yīng)該還是好用的,只是特殊情況還是應(yīng)該特殊處理。
3.3 第三種方法
痛苦了許久(Boss天天催我L)之后,偶然的機(jī)會(huì),我聽(tīng)說(shuō)TinyOS中(又是開(kāi)源的)有一部分是和我的應(yīng)用程序類(lèi)似的串口通信部分,于是我下載了它的1.x版的Java代碼部分,參考了它的處理方法。解決問(wèn)題的方法說(shuō)穿了其實(shí)很簡(jiǎn)單,就是從根源入手。根源不就是接收線程導(dǎo)致的嗎,那好,我就干脆取消接收線程和作為中介的共享緩存,而直接在處理線程中調(diào)用串口讀數(shù)據(jù)的方法來(lái)解決問(wèn)題(什么,為什么不把處理線程也一并取消?----都取消應(yīng)用程序界面不就鎖死了嗎?所以必須保留)于是程序變成了這樣:
public byte[] getPack(){
while (true) {
// PacketLength為數(shù)據(jù)包長(zhǎng)度
byte[] msgPack = new byte[PacketLength];
for(int i = 0; i 《 PacketLength; i++){
if( (newData = is.read()) != -1){
msgPack[i] = (byte) newData;
System.out.println(msgPack[i]);
}
}
return msgPack;
}
}
在處理線程中調(diào)用這個(gè)方法返回所需要的數(shù)據(jù)序列并處理之,這樣不但沒(méi)有丟失數(shù)據(jù)的現(xiàn)象行出現(xiàn),也沒(méi)有數(shù)據(jù)接收延遲了。這里唯一需要注意的就是當(dāng)串口停止發(fā)送數(shù)據(jù)或沒(méi)有數(shù)據(jù)的時(shí)候is.read()一直都返回-1,如果一旦在開(kāi)始接收數(shù)據(jù)的時(shí)候發(fā)現(xiàn)-1就不要理它,繼續(xù)接收,直到收到真正的數(shù)據(jù)為止。
4 結(jié)束語(yǔ)
本文介紹了串口通信的基本知識(shí),以及常用的幾種模式。通過(guò)實(shí)踐,提出了一些問(wèn)題,并在最后加以解決。值得注意的是對(duì)于第一種方法,我曾將傳感器發(fā)送的時(shí)間由128毫秒增加到512毫秒,仍然有很?chē)?yán)重的數(shù)據(jù)丟失現(xiàn)象發(fā)生,所以如果你的應(yīng)用程序需要很精密的結(jié)果,傳輸數(shù)據(jù)的速率又很快的話(huà),就最好不要用第一種方法。對(duì)于第二種方法,由于是線程導(dǎo)致的問(wèn)題,所以對(duì)于不同的機(jī)器應(yīng)該會(huì)有不同的表現(xiàn),對(duì)于那些處理多線程比較好的機(jī)器來(lái)說(shuō),應(yīng)該會(huì)好一些。但是我的機(jī)器是Inter 奔四3.0雙核CPU+512DDR內(nèi)存,這樣都延遲這么厲害,還得多強(qiáng)的CPU才行???所以對(duì)于數(shù)據(jù)量比較大的傳輸來(lái)說(shuō),還是用第三種方法吧。不過(guò)這個(gè)世界問(wèn)題是很多的,而且未知的問(wèn)題比已知的問(wèn)題多的多,說(shuō)不定還有什么其他問(wèn)題存在,歡迎你通過(guò)下面的聯(lián)系方式和我一起研究。
-
usb
+關(guān)注
關(guān)注
60文章
7936瀏覽量
264457 -
JAVA
+關(guān)注
關(guān)注
19文章
2966瀏覽量
104700 -
串口通信
+關(guān)注
關(guān)注
34文章
1624瀏覽量
55507
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論