當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > 資深程序員告訴你串口配置的詳細(xì)流程,不容錯(cuò)過
1,按照通信的基本方式分類,可以分為以下兩類:
1)并行通信
2)串行通信兩種。
并行通信是指利用多條數(shù)據(jù)傳輸線將一個(gè)資料的各位同時(shí)傳送。它的特點(diǎn)是傳輸速度
快,適用于短距離通信,但要求傳輸速度較高的應(yīng)用場(chǎng)合。
串行通信是指利用一條傳輸線將資料一位位地順序傳送。特點(diǎn)是通信線路簡單,利用
簡單的線纜就可實(shí)現(xiàn)通信,降低成本,適用于遠(yuǎn)距離通信,但傳輸速度慢的應(yīng)用場(chǎng)合。
那么接下來我們就來分析串口設(shè)置:
一般情況下,我們對(duì)串口中最基本的包括:波特率設(shè)置,校驗(yàn)位和停止位設(shè)置。然而串口的設(shè)置主要是設(shè)置struct termios結(jié)構(gòu)體的各成員值,如下所示:
struct termio
{
unsigned short c_iflag; /* 輸入模式標(biāo)志 */
unsigned short c_oflag; /* 輸出模式標(biāo)志 */
unsigned short c_cflag; /* 控制模式標(biāo)志*/
unsigned short c_lflag; /*本地模式標(biāo)志 */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCC]; /* control characters */
};
在這個(gè)結(jié)構(gòu)中最為重要的是c_cflag,通過對(duì)它的賦值,用戶可以設(shè)置波特率、字符大小、
數(shù)據(jù)位、停止位、奇偶校驗(yàn)位和硬件流控等。另外c_iflag 和c_cc 也是比較常用的標(biāo)志。
波特率:即每秒傳輸?shù)奈粩?shù),一般情況下有以下幾種:
B0 0波特率(放棄DTR)
B1800 1800波特率
B2400 2400波特率
B4800 4800波特率
B9600 9600波特率
B19200 19200波特率
B38400 38400波特率
B57600 57600波特率
B115200 115200波特率
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
/*保存測(cè)試現(xiàn)有串口參數(shù)設(shè)置,在這里如果串口號(hào)等出錯(cuò),會(huì)有相關(guān)的出錯(cuò)信息*/
if ( tcgetattr( fd,&oldtio) != 0)
{
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
/*步驟一,設(shè)置字符大小*/
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
/*設(shè)置停止位*/
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
/*設(shè)置奇偶校驗(yàn)位*/
switch( nEvent )
{
case 'O': //奇數(shù)
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E': //偶數(shù)
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N': //無奇偶校驗(yàn)位
newtio.c_cflag &= ~PARENB;
break;
}
/*設(shè)置波特率*/
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;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
/*設(shè)置停止位*/
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
/*設(shè)置等待時(shí)間和最小接收字符*/
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;
}
在配置完串口的相關(guān)屬性后,就可對(duì)串口進(jìn)行打開,讀寫操作了。其使用方式與文件操作一樣,區(qū)別在于串口是一個(gè)終端設(shè)備。
2,打開串口:
串口位于/dev中,可作為標(biāo)準(zhǔn)文件的形式打開,其中:
串口1 /dev/ttyS0
串口2 /dev/ttyS1
fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
Open函數(shù)中除普通參數(shù)外,另有兩個(gè)參數(shù)O_NOCTTY和O_NDELAY。
O_NOCTTY: 通知linix系統(tǒng),這個(gè)程序不會(huì)成為這個(gè)端口的控制終端。
O_NDELAY: 通知Linux系統(tǒng)不關(guān)心DCD信號(hào)線所處的狀態(tài)(端口的另一端是否激活或者停止)。
然后,恢復(fù)串口的狀態(tài)為阻塞狀態(tài),用于等待串口數(shù)據(jù)的讀入。用fcntl函數(shù):
fcntl(fd, F_SETFL, 0);
接著,測(cè)試打開的文件描述府是否引用一個(gè)終端設(shè)備,以進(jìn)一步確認(rèn)串口是否正確打開。
isatty(STDIN_FILENO);
串口的讀寫與普通文件一樣,使用read,write函數(shù)。
read(fd,buff,8);
write(fd,buff,8);
3,linux串口編程需要的頭文件
#include //標(biāo)準(zhǔn)輸入輸出定義
#include //標(biāo)準(zhǔn)函數(shù)庫定義
#include //Unix標(biāo)準(zhǔn)函數(shù)定義
#include
#include
#include //文件控制定義
#include //POSIX中斷控制定義
#include //錯(cuò)誤號(hào)定義
4,設(shè)置數(shù)據(jù)位、停止位和校驗(yàn)位
以下是幾個(gè)數(shù)據(jù)位、停止位和校驗(yàn)位的設(shè)置方法:(以下均為1位停止位)
8位數(shù)據(jù)位、無校驗(yàn)位:
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS8;
7位數(shù)據(jù)位、奇校驗(yàn):
Opt.c_cflag |= PARENB;
Opt.c_cflag |= PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
7位數(shù)據(jù)位、偶校驗(yàn):
Opt.c_cflag |= PARENB;
Opt.c_cflag &= ~PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
7位數(shù)據(jù)位、Space校驗(yàn):
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
5,某些設(shè)置項(xiàng)
在上面我們看到一些比較特殊的設(shè)置,下面簡述一下他們的作用。
c_cc數(shù)組的VSTART和VSTOP元素被設(shè)定成DC1和DC3,代表ASCII標(biāo)準(zhǔn)的XON和XOFF字符,如果在傳輸這兩個(gè)字符的時(shí)候就傳不過去,需要把軟件流控制屏蔽,即:
Opt.c_iflag &= ~ (IXON | IXOFF | IXANY);
有時(shí)候,在用write發(fā)送數(shù)據(jù)時(shí)沒有鍵入回車,信息就發(fā)送不出去,這主要是因?yàn)槲覀冊(cè)谳斎胼敵鰰r(shí)是按照規(guī)范模式接收到回車或換行才發(fā)送,而更多情況下我們是不必鍵入回車或換行的。此時(shí)應(yīng)轉(zhuǎn)換到行方式輸入,不經(jīng)處理直接發(fā)送,設(shè)置如下:
Opt.c_lflag &= ~ (ICANON | ECHO | ECHOE | ISIG);
還存在這樣的情況:發(fā)送字符0X0d的時(shí)候,往往接收端得到的字符是0X0a,原因是因?yàn)樵诖谠O(shè)置中c_iflag和c_oflag中存在從NL-CR和CR-NL的映射,即串口能把回車和換行當(dāng)成同一個(gè)字符,可以進(jìn)行如下設(shè)置屏蔽之:
Opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
Opt.c_oflag &= ~(ONLCR | OCRNL);
注意:控制符VTIME和VMIN之間有復(fù)雜的關(guān)系。VTIME定義要求等待的時(shí)間(百毫米,通常是unsigned char變量),而VMIN定義了要求等待的最小字節(jié)數(shù)(相比之下,read函數(shù)的第三個(gè)參數(shù)指定了要求讀的最大字節(jié)數(shù))。
如果VTIME=0,VMIN=要求等待讀取的最小字節(jié)數(shù),read必須在讀取了VMIN個(gè)字節(jié)的數(shù)據(jù)或者收到一個(gè)信號(hào)才會(huì)返回。
如果VTIME=時(shí)間量,VMIN=0,不管能否讀取到數(shù)據(jù),read也要等待VTIME的時(shí)間量。
如果VTIME=時(shí)間量,VMIN=要求等待讀取的最小字節(jié)數(shù),那么將從read讀取第一個(gè)字節(jié)的數(shù)據(jù)時(shí)開始計(jì)時(shí),并會(huì)在讀取到VMIN個(gè)字節(jié)或者VTIME時(shí)間后返回。
如果VTIME=0,VMIN=0,不管能否讀取到數(shù)據(jù),read都會(huì)立即返回
讀寫串口
發(fā)送數(shù)據(jù)方式如下,write函數(shù)將返回寫的位數(shù)或者當(dāng)錯(cuò)誤時(shí)為-1。
char buffer[1024];
int length;
int nByte;
nByte = write(fd, buffer, length);
讀取數(shù)據(jù)方式如下,原始數(shù)據(jù)模式下每個(gè)read函數(shù)將返回實(shí)際串口收到的字符數(shù),如果串口中沒有字符可用,回叫將會(huì)阻塞直到以下幾種情況:有字符進(jìn)入;一個(gè)間隔計(jì)時(shí)器失效;錯(cuò)誤發(fā)送。
在打開串口成功后,使用fcntl(fd, F_SETFL, FNDELAY)語句,可以使read函數(shù)立即返回而不阻塞。FNDELAY選項(xiàng)使read函數(shù)在串口無字符時(shí)立即返回且為0。
char buffer[1024];
int length;
int readByte;
readByte = read(fd, buffer, len);
注 意:設(shè)置為原始模式傳輸數(shù)據(jù)的話,read函數(shù)返回的字符數(shù)是實(shí)際串口收到的字符數(shù)。Linux下直接用read讀串口可能會(huì)造成堵塞,或者數(shù)據(jù)讀出錯(cuò) 誤,此時(shí)可使用tcntl或者select等函數(shù)實(shí)現(xiàn)異步讀取。用select先查詢com口,再用read去讀就可以避免上述錯(cuò)誤。
6,關(guān)閉串口
串口作為文件來處理,所以一般的關(guān)閉文件函數(shù)即可:
close(fd);