linux下的串口通信原理及編程實例
一、串口的基本原理
1 串口通訊
串口通訊(Serial Communication),是指外設和計算機間,通過數據信號線、地線等,按位進行傳輸數據的一種通訊方式。串口是一種接口標准,它規定了接口的電氣標准,沒有規定接口插件電纜以及使用的協議。
2 串口通訊的數據格式
一個字符一個字符地傳輸,每個字符一位一位地傳輸,並且傳輸一個字符時,總是以“起始位”開始,以“停止位”結束,字符之間沒有固定的時間間隔要求。
每一個字符的前面都有一位起始位(低電平),字符本身由7位數據位組成,接着字符后面是一位校驗位(檢驗位可以是奇校驗、偶校驗或無校驗位),最后是一位或一位半或二位停止位,停止位后面是不定長的空閑位,停止位和空閑位都規定為高電平。實際傳輸時每一位的信號寬度與波特率有關,波特率越高,寬度越小,在進行傳輸之前,雙方一定要使用同一個波特率設置。
3 通訊方式
單工模式(Simplex Communication)的數據傳輸是單向的。通信雙方中,一方固定為發送端,一方則固定為接收端。信息只能沿一個方向傳輸,使用一根傳輸線。
半雙工模式(Half Duplex)通信使用同一根傳輸線,既可以發送數據又可以接收數據,但不能同時進行發送和接收。數據傳輸允許數據在兩個方向上傳輸,但是,在任何時刻只能由其中的一方發送數據,另一方接收數據。因此半雙工模式既可以使用一條數據線,也可以使用兩條數據線。半雙工通信中每端需有一個收發切換電子開關,通過切換來決定數據向哪個方向傳輸。因為有切換,所以會產生時間延遲,信息傳輸效率低些。
全雙工模式(Full Duplex)通信允許數據同時在兩個方向上傳輸。因此,全雙工通信是兩個單工通信方式的結合,它要求發送設備和接收設備都有獨立的接收和發送能力。在全雙工模式中,每一端都有發送器和接收器,有兩條傳輸線,信息傳輸效率高。
顯然,在其它參數都一樣的情況下,全雙工比半雙工傳輸速度要快,效率要高。
4 偶校驗與奇校驗
在標准ASCII碼中,其最高位(b7)用作奇偶校驗位。所謂奇偶校驗,是指在代碼傳送過程中用來檢驗是否出現錯誤的一種方法,一般分奇校驗和偶校驗兩種。奇校驗規定:正確的代碼一個字節中1的個數必須是奇數,若非奇數,則在最高位b7添1;偶校驗規定:正確的代碼一個字節中1的個數必須是偶數,若非偶數,則在最高位b7添1。
5 停止位
停止位是按長度來算的。串行異步通信從計時開始,以單位時間為間隔(一個單位時間就是波特率的倒數),依次接受所規定的數據位和奇偶校驗位,並拼裝成一個字符的並行字節;此后應接收到規定長度的停止位“1”。所以說,停止位都是“1”,1.5是它的長度,即停止位的高電平保持1.5個單位時間長度。一般來講,停止位有1,1.5,2個單位時間三種長度。
6 波特率
波特率就是每秒鍾傳輸的數據位數。
波特率的單位是每秒比特數(bps),常用的單位還有:每秒千比特數Kbps,每秒兆比特數Mbps。串口典型的傳輸波特率600bps,1200bps,2400bps,4800bps,9600bps,19200bps,38400bps。
PLC/PC與稱重儀表通訊時,最常用的波特率是9600bps,19200bps。PLC/PC或儀表與大屏幕通訊時,最常用的波特率是600bps。
7 典型的串口通訊標准
EIA RS232(通常簡稱“RS232”): 1962年由美國電子工業協會(EIA)制定。
EIA RS485(通常簡稱“RS485”): 1983年由美國電子工業協會(EIA)制定。
8 RS232串口
RS232是計算機與通信工業應用中最廣泛一種串行接口。它以全雙工方式工作,需要地線、發送線和接收線三條線。RS232只能實現點對點的通信方式。
8.1 RS232串口缺點
●接口信號電平值較高,接口電路芯片容易損壞。
●傳輸速率低,最高波特率19200bps。
●抗干擾能力較差。
●傳輸距離有限,一般在15m以內。
●只能實現點對點的通訊方式。
8.2 RS232串口接口定義
RXD:接收數據,TXD:發送數據,GND/SG:信號地。
8.3 電腦DB9針接口定義
電腦DB9針接口是常見的RS232串口,其引腳定義如下:
2號腳:RXD(接收數據)
3號腳:TXD(發送數據)
5號腳:SG或GND(信號地)
其它腳:我們不用
電腦RS232串口與儀表串口連接圖:
9 RS485串口
9.1 RS485串口特點
●RS485采用平衡發送和差分接收,具有良好的抗干擾能力,信號能傳輸上千米。
●RS485有兩線制和四線制兩種接線。采用四線制時,只能實現點對多的通訊(即只能有一個主設備,其余為從設備)。四線制現在很少采用,現在多采用兩線制接線方式。
●兩線制RS485只能以半雙式方式工作,收發不能同時進行。
●RS485在同一總線上最多可以接32個結點,可實現真正的多點通訊,但一般采用的是主從通信方式,即一個主機帶多個從機。
●因RS485接口具有良好的抗干擾能力,長的傳輸距離和多站能力等優點使其成為首選的串行接口。
10 串口通訊硬件常見的注意事項
●通訊電纜端子一定接牢,不可有任何松動,否則,可能會燒壞儀表或上位機的通訊板。
●不可帶電拔插通訊端子,否則,可能會燒壞儀表或上位機的通訊板,一定要關閉儀表電源后才能去拔插通訊端子或接通訊線。
●通訊用的屏蔽電纜最好選用雙層隔離型屏蔽電纜,其次選用單層屏蔽電纜,最好不要選用無屏蔽層的電纜,且電纜屏蔽層一定要能完全屏蔽,有些質量差的電纜,屏蔽層很松散,根本起不到屏蔽的作用。單層屏蔽的電纜屏蔽層應一端接地,雙層屏蔽的電纜屏蔽層其外層(含鎧裝)應兩端接地,內層屏蔽則應一端接地。
●儀表使用RS232通訊時,通訊電纜長度不得超過15米。
●一般RS485協議的接頭沒有固定的標准,可能根據廠家的不同引腳順序和管腳功能可能不盡相同,用戶可以查閱相關產品RS485的引腳圖。
●RS485通訊電纜最好選用阻阬匹配、低衰減的RS485專用通訊電纜(雙絞線),不要使用普通的雙絞電纜或質量較差的通訊電纜。因為普通電纜或質量差的通訊電纜,可能阻抗不匹配、衰減大、絞合度不夠、屏蔽層太松散,這樣會導致干擾將非常大,會造成通訊不暢,甚至通訊不上。
●儀表使用RS485通訊時,每台儀表必須手牽手地串下去,不可以有星型連接或者分叉,如果有星型連接或者分叉,干擾將非常大,會造成通訊不暢,甚至通訊不上。
●485總線結構理論上傳輸距離達到1200米,一般是指通訊線材優質達標,波特率9600,只有一台485設備才能使得通訊距離達到1200米,而且能通訊並不代表每次通訊都正常,所以通常485總線實際的穩定通訊距離遠遠達不到1200米。負載485設備多,線材阻抗不同時,通訊距離更短。
●儀表使用RS485通訊時,必要時,請接入終端電阻,以增強系統的抗干擾性,典型的終端電阻阻值是120歐。
11 串口通訊軟件設置要點
11.1 有關通訊的一些基本概念
●主機與從機:在通訊系統中起主要作用、發布主要命令的稱為主機,接受命令的稱為從機。
●連續方式:指主機不需要發布命令,從機就能自動地向主機發送數據。
●指令方式:指主機向從機發布命令,從機根據指令執行動作,並將結果“應答”給主機的模式。
●輸出數據類型:指在連續方式通訊時,從機輸出給主機的數據類型。
●通訊協議:指主機與從機通訊時,按哪一種編碼規則來通訊。
●波特率:主從機之間通訊的速度。
●數據位:每次傳輸數據時,數據由幾位組成。
●校驗位:數據傳輸錯誤檢測,可以是奇校驗、偶校驗或無校驗。
●地址:每一台從機的編號。
11.2 主從機之間通訊設置要點
●要點一:主/從RS232/485硬件有無設置正確,通訊線有無接對。有些通訊板卡是RS422與RS485共用的,依靠板上跳線來實現的,有些儀表RS232/485也需要通訊跳線來實現。
●要點二:主機上的通訊端口有無設置正確;超時(一般設置為2s)、通訊延時(一般設置為5~20ms)、ACK信號延時(一般設置為0ms)有無設置正確。
●要點三:主/從機通訊協議有無選擇正確。
●要點四:主/從機波特率有無選擇正確。
●要點五:主/從機數據位有無選擇正確。數據位可以選擇7位,8位。
●要點六:主/從機校驗位有無選擇正確。校驗位一般可選擇偶校驗、奇校驗、無校驗。
●要點七:主/從機停止位有無選擇正確。停止位可以選擇1位、1.5位還是2位。
●要點八:從機地址有無選擇正確。
●要點九:主/從機的通訊方式有無選擇正確。
進行通訊測試的時候經常會進行線路測試,測試所用的串口線是否可用,方法有二如下:
1> 把串口線接到不同的串口,用串口調試工具從一個串口發數據,另一個能正常收到說明串口線是OK的。
2> 把串口線的一端短接(用金屬把2,3號腳連通),用萬用表測另一端的2,3號如果正常的話會有嘀嘀的短接報警聲。
二、linux下串口的基本操作
1、串口的操作
1.1打開:fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY | O_NDELAY);
O_RDWR 讀寫方式打開;
O_NOCTTY 不允許進程管理串口(不太理解,一般都選上);
O_NDELAY 非阻塞(默認為阻塞,打開后也可以使用fcntl()重新設置)
1.2寫入:n = write(fd, "linux", 5);
n實際寫入字節數;
1.3讀取:res = read(fd,buf,len);
res 讀取的字節數;
1.4設置:fcntl(fd, F_SETFL, FNDELAY); //非阻塞
fcntl(fd, F_SETFL, 0); // 阻塞
1.5關閉:close(fd);
2、串口配置
struct termios options; // 串口配置結構體
tcgetattr(fd,&options); //獲取當前設置
bzero(&options,sizeof(options));
options.c_cflag |= B115200 | CLOCAL | CREAD; // 設置波特率,本地連接,接收使能
options.c_cflag &= ~CSIZE; //屏蔽數據位
options.c_cflag |= CS8; // 數據位為 8 ,CS7 for 7
options.c_cflag &= ~CSTOPB; // 一位停止位, 兩位停止為 |= CSTOPB
options.c_cflag &= ~PARENB; // 無校驗
//options.c_cflag |= PARENB; //有校驗
//options.c_cflag &= ~PARODD // 偶校驗
//options.c_cflag |= PARODD // 奇校驗
options.c_cc[VTIME] = 0; // 等待時間,單位百毫秒 (讀)。后有詳細說明
options.c_cc[VMIN] = 0; // 最小字節數 (讀)。后有詳細說明
tcflush(fd, TCIOFLUSH); // TCIFLUSH刷清輸入隊列。
TCOFLUSH刷清輸出隊列。
TCIOFLUSH刷清輸入、輸出隊列。
tcsetattr(fd, TCSANOW, &options); // TCSANOW立即生效;
TCSADRAIN:Wait until everything has been transmitted;
TCSAFLUSH:Flush input and output buffers and make the change
3、VTIME 和 VMIN
VTIME 定義要求等待的零到幾百毫秒的值(通常是一個8位的unsigned char變量)。
VMIN 定義了要求等待的最小字節數, 這個字節數可能是0。
只有設置為阻塞時這兩個參數才有效,僅針對於讀操作。
說起來比較復雜,舉個例子吧,設置為阻塞狀態,寫操作未進行實驗,這里僅討論讀操作,
read(fd,&buf,8); // 讀串口
3.1
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 0;
VMIN = 0,當緩沖區字節數 >= 0 時進行讀操作,實際上這時讀串口操作並未被阻塞,因為條件始終被滿足。
3.2
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
VMIN = 1,當緩沖區字節數 >= 1 時進行讀操作,當沒有數據時讀串口操作被阻塞。
3.3
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 4;
VMIN = 4,當緩沖區字節數 >= 4 時進行讀操作,否則讀串口操作被阻塞。每次讀出的最大字節數由read函數中第三個參數決定。直到緩沖區剩下的數據< read 第三個參數 並且< 4 (如果這時read第三參數為 1 則進行4次讀操作直至讀完緩沖區,如read第三參數為2,連續進行讀操作,直至緩沖區空或還剩一個字符)。沒有設置VTIME,剩下的字符沒有確定的期限,直到下次滿足讀條件的時候才被讀出。
----------------------------------考慮VTIME-----------------------------
3.4
options.c_cc[VTIME] = 10; //單位百毫秒
options.c_cc[VMIN] = 4;
同3.3的區別就是,沒滿足條件或讀緩沖區中剩下的數據會在1秒(10百毫秒)后讀出。另外特別注意的是當設置VTIME后,如果read第三個參數小於VMIN ,將會將VMIN 修改為read的第三個參數,即使用read(fd,&buf,2);,以上設置變為:
options.c_cc[VTIME] = 10;
options.c_cc[VMIN] = 2;
=====================================================================
1>打開串口函數open_port()中要實現的函數:
(1)open("/dev/ttys0",O_RDWR | O_NOCTTY | O_NDELAY);/*打開串口0*/
(2)fcntl(fd,F_SETFL,0)/*恢復串口為阻塞狀態*/
(3)isatty(STDIN_FILENO) /*測試是否為中斷設備 非0即是中斷設備*/
2> 配置串口參數函數set_opt()中要實現的函數:
(1)保存原先有串口配置
tcgetattr(fd,&oldtio);
(2)先將新串口配置清0
bzore(&newtio,sizeof(newito));
(3)激活選項CLOCAL和CREAD 並設置數據位大小
newtio.c_cflag |=CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |=CS8;
(4)設置奇偶校驗
奇校驗:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
偶校驗:
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PAREND;
newtio.c_cflag &= ~PARODD;
無奇偶校驗:
newtio.c_cflag &= ~PARENB;
(5) 設置停止位
newtio.c_cflag &= ~CSTOPB; /*停止位為1*/
newtio.c_cflag |= CSTOPB;/*停止位為0*/
(6)設置波特率:
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
(7)設置等待時間和最小接受字符:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
(8)處理為接收字符:
tcflush(fd,TCIFLUSH);
(9)激活新配置:
tcsetattr(fd,TCSANOW,&newtio);
3.讀寫串口
write(fd,buff,8);
read(fd,buff,8);
三、串口編程實例:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
/* 五個參量 fd打開文件 speed設置波特率 bit數據位設置 neent奇偶校驗位 stop停止位 */
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
printf("set done!\n");
return 0;
}
int open_port(int fd,int comport)
{
/* fd 打開串口 comport表示第幾個串口 */
char *dev[]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2"};
long vdisable;
if (comport==1)
{ fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
if (-1 == fd){
perror("Can't Open Serial Port");
return(-1);
}
else
printf("open ttyS0 .....\n");
}
else if(comport==2)
{ fd = open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY);
if (-1 == fd){
perror("Can't Open Serial Port");
return(-1);
}
else
printf("open ttyS1 .....\n");
}
else if (comport==3)
{
fd = open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY);
if (-1 == fd){
perror("Can't Open Serial Port");
return(-1);
}
else
printf("open ttyS2 .....\n");
}
if(fcntl(fd, F_SETFL, 0)<0)
printf("fcntl failed!\n");
else
printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
if(isatty(STDIN_FILENO)==0)
printf("standard input is not a terminal device\n");
else
printf("isatty success!\n");
printf("fd-open=%d\n",fd);
return fd;
}
int main(void)
{
int fd;
int nread,i;
char buff[]="Hello\n";
if((fd=open_port(fd,1))<0){
perror("open_port error");
return;
}
if((i=set_opt(fd,115200,8,'N',1))<0){
perror("set_opt error");
return;
}
printf("fd=%d\n",fd);
// fd=3;
nread=read(fd,buff,8);
printf("nread=%d,%s\n",nread,buff);
close(fd);
return;
}
四、Linux 多線程串口通信
大概流程就是打開一個串口、然后進行串口設置。開啟二個線程,一個線程寫數據,另一個線程讀數據。
代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <sys/time.h>
#define MAX 2
pthread_t thread[2];
pthread_mutex_t mut;
int fd;
int set_port(int fd,int nbits)
{
struct termios newtio,oldtio;
if(tcgetattr(fd,&oldtio)!=0)
{
perror("pei zhi cuo wu1\n");
return -1;
}
bzero(&newtio,sizeof(newtio)); //清零
newtio.c_cflag |=CLOCAL|CREAD;//用於本地連接和接收使能
newtio.c_cflag &=~CSIZE;//設置數據位
switch(nbits)
{
case 7:
newtio.c_cflag |=CS7;break;
case 8:
newtio.c_cflag |=CS8;break;
}
//設置奇校驗位
newtio.c_cflag |=PARENB;
//設置波特率
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
//設置停止位
newtio.c_cflag &=~PARENB;
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("pei zhi cuo wu2\n");
return -1;
}
printf("bao cun wan bi \n");
return 0;
}
void *thread1()
{
int i;
printf ("thread1 \n");
for( i=0;i<MAX;i++){
pthread_mutex_lock(&mut);
if(i==0){
printf("write %d\n",i+1);
char buf1[]="AT+FCLASS=0\r\n";
int length=sizeof(buf1);
int j=write(fd,buf1,length);
puts(buf1);
if(j<0)printf("fa song shi bai\n");
printf("%d n",j);
}
else if(i==1){
printf("write %d\n",i+1);
char buf2[]="AT+CBST=7,0,0\r\n";
int length=sizeof(buf2);
int j=write(fd,buf2,length);
puts(buf2);
if(j<0)printf("fa song shi bai\n");
printf("%d \n",j)
}
sleep(3);
pthread_mutex_unlock(&mut);
}
printf("thread1 stop\n");
pthread_exit(NULL);
}
void *thread2()
{
int j;
sleep(1);
printf("thread2\n");
char buf[100];
for (j = 0; j< MAX; j++)
{
pthread_mutex_lock(&mut);
sleep(3);
printf("read %d\n",j+1);
int k=read(fd,buf,100);
printf("k+%d\n",k);
puts(buf);
pthread_mutex_unlock(&mut);
sleep(2);
}
printf("thread2 :stop\n");
pthread_exit(NULL);
}
void thread_create(void)
{
int temp;
memset(&thread, 0, sizeof(thread)); //comment1
/*創建線程*/
if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
printf("xian chegn 1 faile\n");
else
printf("xian cheng 1 chegn gong\n");
if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
printf("2 faile\n");
else
printf("2 surcess\n");
}
void thread_wait(void)
{
/*等待線程結束*/
if(thread[0] !=0) { //comment4
pthread_join(thread[0],NULL);
printf("1 stop \n");
}
if(thread[1] !=0) { //comment5
pthread_join(thread[1],NULL);
printf("2 stop \n");
}
}
int main(void) {
int i,j,k;
fd=open("/dev/ttyS2",O_RDWR|O_NOCTTY|O_NDELAY);
if(-1==fd)printf("mei da kai tong xin duan kou hao\n");
else
{
i=set_port(fd, 8);
if(i<0)
{
perror("pei zhi cuo wu3\n");
return 0;
}
pthread_mutex_init(&mut,NULL);
printf("creat preadth\n");
thread_create();
printf("chu li \n");
thread_wait();
close(fd);
}
return 0;
}
五、用select查詢串口數據
select讀主要實現的功能是,在一定時間內不停地看串口有沒有數據,有數據則進行讀,當時間過去后還沒有數據,則返回超時錯誤。
具體的函數如下;
int read_datas_tty(int fd,char *rcv_buf,int sec,int usec)
{
int retval;
unsigned char tempchar2;
fd_set rfds;
struct timeval tv;
int ret,pos;
tv.tv_sec = sec;//set the rcv wait time
tv.tv_usec = usec;//100000us = 0.1s
while(1){
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
retval = select(fd+1,&rfds,NULL,NULL,&tv);
if(retval ==-1)
{
perror("select()");
break;
}
else if(retval)
{
ret= read(fd,rcv_buf,1);
tempchar2 = rcv_buf;
printf("rcv_buf is %s\n",rcv_buf);
}
else
{
break;
}
}
return 1;
}
在前面的普通讀寫里面加上這個函數就可以了
它的調用方式為:
read_datas_tty(fd,buff,10,10);
這就表示等待時間為10S+10us
Linux下直接用read讀串口可能會造成堵塞,或數據讀出錯誤。然而用select先查詢com口,再用read去讀就可以避免,並且當com口延時時,程序可以退出,這樣就不至於由於com口堵塞,程序就死了。我的代碼如下:
bool ReadDevice( int hComm, unsigned long uLen, char* pData )
{
int nread = 0;
char inbuf[uLen];
char buff[uLen];
memset( inbuff, '\0', uLen );
memset( buff, '\0', uLen );
fd_set readset;
struct timeval tv;
int MaxFd = 0;
int c = 0;
int z;
do
{
FD_ZERO( &readset );
if( hComm >= 0 )
FD_SET( hComm, &readset );[A1]
/**
FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的全部位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否為真
**/
MaxFd = hComm + 1;
tv.tv_sec = 0;
tv.tv_usec = 500000;
do
{
z = select( MaxFd, &readset, 0, 0, &tv);
}while( z==-1 && errno==EINTR[c1] );
/**
(1)當監視的相應的文件描述符集中滿足條件時,比如說讀文件描述符集中有數據到來時,內核(I/O)根據狀態修改文件描述符集,並返回一個大於0的數。
(2)當沒有滿足條件的文件描述符,且設置的timeval監控時間超時時,select函數會返回一個為0的值。
(3)當select返回負值時,發生錯誤。
**/
if( z == -1 )
printf("select(2)\n");
if( z == 0 )
{
hComm = -1;
}
if( hComm>=0 && FD_ISSET(hComm, &readset) )
{
z = read( hComm, buff, uLen - c );
c += z;
if( z == -1 )
{
hComm = -1;
}
if( z > 0 )
{
buff[ z + 1 ] = '\0';
strcat( inbuff, buff );
memset( buff, 0x00, uLen );
}
else
{
hComm = -1;
}
}
}while( hComm >= 0 );
memcpy( pData, inbuff, c );
return true;
}