Linux 串口编程分析
生活随笔
收集整理的這篇文章主要介紹了
Linux 串口编程分析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
這個話題,大家可能再熟悉不過了,網上資料很多,因為這是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都是文件,串口是設備文件,設置好后,其他的東西就當成文件進行操作吧。?
串口編程其實說白了, 是拿根串口線把電腦和所要控制的機器連接起來,然后在通過編程的方式對下位機進行發送指定的數據或進行控制,或進行傳輸,然后在接受下位機反饋回來的信息提示是否已經正確。是不是好俗!
串口是計算機上一種非常通用設備通信的協議,常用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都是文件,串口是設備文件,設置好后,其他的東西就當成文件進行操作吧。?
轉載于:https://www.cnblogs.com/spinsoft/archive/2012/05/08/2489928.html
總結
以上是生活随笔為你收集整理的Linux 串口编程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity BZip2压缩和解压,基于
- 下一篇: linux 下共享库创建及使用