這個話題,大家可能再熟悉不過了,網上資料很多,因為這是linux下編程比較重要的一個方面,懂這方面的人很多;這裡我只是想給初學者簡單的介紹下這方面的知識: 串口編程其實說白了, 是拿根串口線把電腦和所要控制的機器串連起來,然後在通過編程的方式對下位機進行發送指定的資料或進行控制,或進行傳輸,然後在接受下位機反饋回來的資訊提示是否已經正確。是不是好俗! 串口是電腦上一種非常通用裝置通訊的協議,常用PC機上包含的是RS232規格的串口,當然,除了RS232 ,還有RS485和RS422兩種規格,用於不同的裝置通訊;這裡主要是介紹RS232串口編程。 在串口編程中,比較重要的是串口的設定,我們要設定的部分包括傳輸速率,資料位元,停止位,同位位元;要注意的是,每台機器的串口預設設定可能是不同的,如 果你沒設定這些,僅僅按照預設設定進行發送資料,很可能出現n 多異想不到而又查不出來的情況;所以,在真正通訊前,我們必須設定這些: 下面就開始介紹這些基本設定的函數,(其實都是些固定架構,程式中稍微改改就行)~o~ 1.設定傳輸速率 注意每台機器都有輸出和輸入接受資訊的速度 ,所以用cfsetispeed 和cfsetospeed來分別設定;注意到struct termios 這樣一個結構,它包括了串口端所有的設定,下面還要用到。它在termios.h中被定義。。還有一個地方比較難以理解,為什麼設定了speed_arr 和name_arr兩個數組,這是因為在linuxe下,系統為傳輸速率專門準備了一張表用B38400,B19200......代替,而我們實際上傳進 去的只能是38400,19200這些值,所以我們拿我們傳進去的和name_arr進行比較,如果相等則從系統對照表中取出相應值進行設定,如果不等證 明傳的值在系統對照表中沒有,則不進行設定。 int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, // B38400, B19200, B9600, B4800, B2400, B1200, B300, }; int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, }; void set_speed(int fd, int speed) { nt i; int status; struct termios Opt; //定義了這樣一個結構 tcgetattr(fd, &Opt); //用來得到機器原連接埠的預設設定 for ( i= 0; i 判斷傳進來是否相等 { tcflush(fd, TCIOFLUSH); //重新整理輸入輸出緩衝 cfsetispeed(&Opt, speed_arr); //這裡分別設定 cfsetospeed(&Opt, speed_arr); status = tcsetattr(fd, TCSANOW, &Opt); //這是立刻把bote rates設定真正寫到串口中去 if (status != 0) perror("tcsetattr fd1"); //設定錯誤 return; } tcflush(fd,TCIOFLUSH); //同上 } } 2。設定同位,資料,停止位 這三個參數通常放在一起設定,databits是資料位元,stopbits是停止位,parity是校正位。 串口的這些設定是很複雜很複雜的,Termios成員中共定義c_cflag 控制項 c_lflag 線路項 c_iflag 輸入項 c_oflag 輸出項 c_cc 控制字元 c_ispeed 輸入傳輸速率 c_ospeed 輸出傳輸速率 那麼多項,對於每一項都有很多的設定,這裡我們不講的那麼複雜,就一個通用的串口架構進行解釋,主要進行同位,資料,停止位的設定。而其他的一些控制 項,在程式中用到時穿插講解: int set_Parity(int fd,int databits,int stopbits,int parity) { struct termios options; //定義一個結構 if ( tcgetattr( fd,&options) != 0) //首先讀取系統預設設定options中,必須 { perror("SetupSerial 1"); return(FALSE); } options.c_cflag &= ~CSIZE; //這是設定c_cflag選項不按位元據位元遮罩 switch (databits) /*設定資料位元數*/ { case 7: options.c_cflag |= CS7; //設定c_cflag選項資料位元為7位 break; case 8: options.c_cflag |= CS8; //設定c_cflag選項資料位元為8位 break; default: fprintf(stderr,"Unsupported data size\n"); //其他的都不支援 return (FALSE); } /* PARENB Enable parity generation on output and parity checking for input. INPCK Enable input parity checking. CSIZE Character size mask. Values are CS5, CS6, CS7, or CS8. #define CSIZE 0000060 #define CS5 0000000 #define CS6 0000020 #define CS7 0000040 #define CS8 0000060 PARODD If set, then parity for input and output is odd; otherwise even parity is used. */ switch (parity) //設定同位,c_cflag和c_iflag有效 { case 'n': case 'N': //無校正 當然都不選 options.c_cflag &= ~PARENB; /* Clear parity enable */ options.c_iflag &= ~INPCK; /* Enable parity checking */ break; case 'o': //奇數同位 其中PARENB校正位有效;PARODD奇數同位 INPCK檢查校正 case 'O': options.c_cflag |= (PARODD | PARENB); /* 設定為奇效驗*/ options.c_iflag |= INPCK; /* Disnable parity checking */ break; case 'e': case 'E': //偶校正,奇數同位不選就是偶校正了 options.c_cflag |= PARENB; /* Enable parity */ options.c_cflag &= ~PARODD; /* 轉換為偶效驗*/ options.c_iflag |= INPCK; /* Disnable parity checking */ INPCK Enable input parity checking. break; default: fprintf(stderr,"Unsupported parity\n"); return (FALSE); } /* 設定停止位*/ switch (stopbits) //這是設定停止位元,影響的標誌是c_cflag { case 1: options.c_cflag &= ~CSTOPB; //不指明表示一位停止位 break; case 2: options.c_cflag |= CSTOPB; //指明CSTOPB表示兩位,只有兩種可能 break; default: fprintf(stderr,"Unsupported stop bits\n"); return (FALSE); } /* Set input parity option */ if (parity != 'n') //這是設定輸入是否進行校正 options.c_iflag |= INPCK; //這個地方是用來設定控制字元和逾時參數的,一般預設即可。稍微要注意的是c_cc數組的VSTART 和 VSTOP 元素被設定成DC1 和 DC3,代表ASCII 標準的XON和XOFF字元。所以如果在傳輸這兩個字元的時候就傳不過去,這時需要把軟體流量控制屏蔽 options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_cc[VTIME] = 150; // 15 seconds options.c_cc[VMIN] = 0; tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */ //重新整理和立刻寫進去 if (tcsetattr(fd,TCSANOW,&options) != 0) { perror("SetupSerial 3"); return (FALSE); } return (TRUE); } //串口設定架構到這裡就大概結束了,對於設定了資料位元校正位停止位和傳輸速率的連接埠已經可以傳輸大多數資訊。在實際中的情況往往是很多特例,比如, 在用write發送資料時沒有鍵入斷行符號,資訊就將發送不出去的情況,這主要是因為我們在輸出輸入時是按照 規範模式接受到斷行符號或者換行才發送,而很多情況我們是不需要斷行符號和換行的,這時,應當切換到行方式輸入,設定options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);不經處理直接發送。 又比如 在我們發送字元0x0d的時候,往往接受端得到的字元是0x0a 這是怎麼回事,原因是在串口設定中c_iflag和c_oflag中存在從NL-CR 和CR-NL的映射,也就是說,串口可以把斷行符號和換行看成一個字元,所以,此時我們應該屏蔽掉這些,用options.c_oflag &=~(INLCR|IGNCR|ICRNL|);和options.c_oflag &=~(ONLCR|OCRNL); 進行設定。 總之,串口的設定是很複雜也很麻煩的東西,具體情況要具體分析,找到相應的辦法,如果探索資料不能傳送,不妨耐點心在串口設定上找答案吧 言歸正傳,後面的東西就很簡單了,接下來是開啟串口: int OpenDev(char *Dev) { int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY這種方式看open函數 if (-1 == fd) { /*設定資料位元數*/ perror("Can't Open Serial Port"); return -1; } else return fd; } 然後是資料的接受和發送,把通用的主函數貼下來,很容易的。 int main(int argc, char **argv) { int fd; int nread; char buff[512]; char *dev ="/dev/ttyS0"; //linux下的連接埠就是通過開啟裝置檔案操作的 fd = OpenDev(dev); //開啟 if (fd>0) set_speed(fd,19200); //開啟後設定傳輸速率19200 else { printf("Can't Open Serial Port!\n"); exit(0); } if (set_Parity(fd,8,1,'N')== FALSE) //設定8,1,n 注意,這裡和上面要和下位機相符才可能通訊 { printf("Set Parity Error\n"); exit(1); } //一般讀的時候一般都用read ,寫的時候一般都用write,read要注意阻塞後程式停止不動,所以要用select 進行控制,注意tv每次迴圈都要設定;write 不用考慮阻塞,但要用迴圈寫方式保證一定寫完,其實讀最好也用迴圈讀方式保證一定能讀到所有東西並且能拼接在一起,然後在進行其他動作。最後while (1) 是串口通訊中常用的迴圈 就是一直執行,直到碰到break;這些東西挺煩瑣,不過其實也沒什麼。這裡就不詳細說了,下面是個最最簡單的。。 while(1) { while((nread = read(fd,buff,512))>0) { printf("\nLen %d\n",nread); buff[nread+1]='\0'; printf("\n%s",buff); } } //close(fd); //exit(0); } 完了,是不是不難,其實除了串口設定是新知識,,事實上linux都是檔案,串口是裝置檔案,設定好後,其他的東西就當成檔案進行操作吧。 |
|