Linux串口编程详解
Linux串口編程詳解(阻塞模式、非阻塞模式、select函數(shù))
之前一直覺得串口編程很簡(jiǎn)單,這兩天仔細(xì)研究后發(fā)現(xiàn)串口里的各種參數(shù)還挺復(fù)雜,稍不注意就容易出錯(cuò),這里總結(jié)一下網(wǎng)上的各種文章及自己的理解與實(shí)踐。
open 函數(shù)
功能描述:用于打開或創(chuàng)建文件,成功則返回文件描述符,否則返回-1,open返回的文件描述符一定是最小的未被使用的描述符
#include<fcntl.h>
int open(const char * pathname,int flags);
int open(const char * pathname,int flags,mode_t mode);
參數(shù)flags:一些文件模式選擇,有如下幾個(gè)參數(shù)可以設(shè)置
O_RDONLY? ? ? ?只讀模式
O_WRONLY? ? ? 只寫模式
O_RDWR? ? ? ? ? 讀寫模式
上面三個(gè)參數(shù)在設(shè)置的時(shí)候必須選擇其中一個(gè)!下面的參數(shù)是可選的
O_APPEND? ? ? ? ? ?每次寫操作都寫入文件的末尾
O_CREAT? ? ? ? ? ? ? 如果指定文件不存在,則創(chuàng)建這個(gè)文件
O_EXCL? ? ? ? ? ? ? ??如果要?jiǎng)?chuàng)建的文件已存在,則返回 -1,并且修改 errno 的值
O_TRUNC? ? ? ? ? ? ?如果文件存在,并且以只寫/讀寫方式打開,則清空文件全部?jī)?nèi)容
O_NOCTTY? ? ? ? ? ?如果路徑名指向終端設(shè)備,不要把這個(gè)設(shè)備用作控制終端
O_NONBLOCK? ? ?如果路徑名指向 FIFO/塊文件/字符文件,則把文件的打開和后繼 I/O設(shè)置為非阻塞模式
O_NDELAY? ? ? ? ? ?表示用于設(shè)置非阻塞方式。同時(shí)通知Linux系統(tǒng),這個(gè)程序不關(guān)心DCD信號(hào)線所處的狀態(tài)(端口的另一端是否激活或者停止)。如果用戶沒有指定這個(gè)標(biāo)志,則進(jìn)程將會(huì)一直處在睡眠狀態(tài),直到DCD信號(hào)線被激活。
下面三個(gè)常量同樣是選用的,他們用于同步輸入輸出
O_DSYNC? ? ? ?等待物理 I/O 結(jié)束后再 write。在不影響讀取新寫入的數(shù)據(jù)的前提下,不等待文件屬性更新
O_RSYNC? ? ? ?read等待所有寫入同一區(qū)域的寫操作完成后再進(jìn)行
O_SYNC? ? ? ? ? 等待物理 I/O 結(jié)束后再 write,包括更新文件屬性的 I/O
對(duì)于串口的打開操作,必須使用O_NOCTTY參數(shù),它表示打開的是一個(gè)終端設(shè)備,程序不會(huì)成為該端口的控制終端。如果不使用此標(biāo)志,任務(wù)的一個(gè)輸入(比如鍵盤終止信號(hào)等)都會(huì)影響進(jìn)程。
open函數(shù)第三個(gè)參數(shù)mode為可選參數(shù),表示打開文件權(quán)限
fcntl 函數(shù)
功能描述:根據(jù)文件描述詞來操作文件的特性,返回-1代表出錯(cuò)
#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);
fcntl()函數(shù)主要有5種功能:
復(fù)制一個(gè)現(xiàn)有的描述符(cmd=F_DUPFD/F_DUPFD_CLOEXEC)
獲得/設(shè)置文件描述符標(biāo)記(cmd=F_GETFD/F_SETFD)
獲得/設(shè)置文件狀態(tài)標(biāo)記(cmd=F_GETFL/F_SETFL) #常用
獲得/設(shè)置異步I/O所有權(quán)(cmd=F_GETOWN/F_SETOWN)
獲得/設(shè)置記錄鎖(cmd=F_GETLK/F_SETLK/F_SETLKW) #常用
F_SETFL :設(shè)置文件狀態(tài)標(biāo)志。
其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT,? O_EXCL, O_NOCTTY 和 O_TRUNC不受影響,
能更改的標(biāo)志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。
此函數(shù)功能強(qiáng)大,在此不做詳細(xì)敘述!
串口參數(shù)初始化
串口參數(shù)由結(jié)構(gòu)體termio設(shè)置:
struct termio
{
unsigned short c_iflag; /* 輸入模式標(biāo)志 /
unsigned short c_oflag; / 輸出模式標(biāo)志 /
unsigned short c_cflag; / 控制模式標(biāo)志*/
unsigned short c_lflag; /* local mode flags /
unsigned char c_line; / line discipline /
unsigned char c_cc[NCC]; / control characters */
};
具體參見:http://kevinlad.me/2017/11/25/Termios%E7%BB%93%E6%9E%84%E4%BD%93/
這里列出常見配置:
int set_port_attr(int fd,int baudrate, int databit, const char *stopbit, char parity, int vtime,int vmin )
{
struct termios opt;
tcgetattr(fd, &opt); //獲取初始設(shè)置
}
static void set_baudrate(struct termios *opt, unsigned int baudrate)
{
cfsetispeed(opt, baudrate);
cfsetospeed(opt, baudrate);
}
static void set_stopbit(struct termios *opt, const char stopbit)
{
if (0 == strcmp (stopbit, “1”)) {
opt->c_cflag &= ~CSTOPB; / 1位停止位t /
}else if (0 == strcmp (stopbit, “1.5”)) {
opt->c_cflag &= ~CSTOPB; / 1.5位停止位 /
}else if (0 == strcmp (stopbit, “2”)) {
opt->c_cflag |= CSTOPB; / 2 位停止位 /
}else {
opt->c_cflag &= ~CSTOPB; / 1 位停止位 */
}
}
static void set_data_bit(struct termios *opt, unsigned int databit)
{
opt->c_cflag &= ~CSIZE;
switch (databit) {
case 8:
opt->c_cflag |= CS8;
break;
case 7:
opt->c_cflag |= CS7;
break;
case 6:
opt->c_cflag |= CS6;
break;
case 5:
opt->c_cflag |= CS5;
break;
default:
opt->c_cflag |= CS8;
break;
}
}
static void set_parity(struct termios opt, char parity)
{
switch (parity) {
case ‘N’: / 無校驗(yàn) /
case ‘n’:
opt->c_cflag &= ~PARENB;
break;
case ‘E’: / 偶校驗(yàn) /
case ‘e’:
opt->c_cflag |= PARENB;
opt->c_cflag &= ~PARODD;
break;
case ‘O’: / 奇校驗(yàn) */
case ‘o’:
opt->c_cflag |= PARENB;
opt->c_cflag |= ~PARODD;
break;
case ‘S’:
case ‘s’:
opt->c_cflag &= ~PARENB; /*清除校驗(yàn)位 disable pairty checking Space校驗(yàn) /
opt->c_cflag &= ~CSTOPB;
opt->c_iflag |= INPCK;
break;
default: / 其它選擇為無校驗(yàn) */
opt->c_cflag &= ~PARENB;
break;
}
}
阻塞式讀寫配置
打開時(shí)使用:
fd = open(USAR1, O_RDWR | O_NOCTTY );//阻塞式讀寫
打開后使用fcntl函數(shù)修改:
fcntl(fd, F_SETFL, 0); //設(shè)為阻塞
阻塞式讀寫可設(shè)置以下兩參數(shù):
opt.c_cc[VMIN] = vmin; ? //設(shè)置非規(guī)范模式下的超時(shí)時(shí)長和最小字符數(shù):阻塞模式起作用
opt.c_cc[VTIME] = vtime; //VTIME與VMIN配合使用,是指限定的傳輸或等待的最長時(shí)間
若 VMIN = 0 ,VTIME = 0? ,函數(shù)read未讀到任何參數(shù)也立即返回,相當(dāng)于非阻塞模式;
若 VMIN = 0,? ?VTIME > 0? ,函數(shù)read讀取到數(shù)據(jù)立即返回,若無數(shù)據(jù)則等待VTIME時(shí)間返回;
若 VMIN >?0,? ?VTIME =?0? ,函數(shù)read()只有在讀取到VMIN個(gè)字節(jié)的數(shù)據(jù)或者收到一個(gè)信號(hào)的時(shí)候才返回;
若 VMIN >?0,? ?VTIME > 0? ,從read讀取第一個(gè)字節(jié)的數(shù)據(jù)時(shí)開始計(jì)時(shí),并會(huì)在讀取到VMIN個(gè)字節(jié)或者VTIME時(shí)間后返回。
非阻塞式讀寫配置
打開時(shí)使用
open(USAR1, O_RDWR | O_NOCTTY | O_NDELAY );//非阻塞式讀寫
open(USAR1, O_RDWR | O_NOCTTY | O_NONBLOCK);//非阻塞式讀寫
open(USAR1, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);//非阻塞式讀寫
打開后使用fcntl修改設(shè)置
fcntl(socket_descriptor, F_SETFL, flags | O_NONBLOCK); //設(shè)為非阻塞
O_NONBLOCK和O_NDELAY都是設(shè)置為非阻塞模式,但是它們有些差別,O_NDELAY會(huì)使I/O函式馬上返回0,但是又衍生出一個(gè)問題,因?yàn)樽x取到檔案結(jié)尾時(shí)所回傳的也是0,這樣無法得知是哪中情況;而O_NONBLOCK它在讀取不到數(shù)據(jù)時(shí)會(huì)回傳-1,并且設(shè)置errno為EAGAIN。
select函數(shù)讀寫
int select(int nfds, fd_set *rdfds, fd_set *wtfds, fd_set *exfds, struct timeval *timeout)
ndfs:select監(jiān)視的文件句柄,一般設(shè)為要監(jiān)視的文件中的最大文件號(hào)加一;
rdfds:select監(jiān)視的可讀文件句柄集合,當(dāng)rdfds映象的文件句柄狀態(tài)變成可讀時(shí)系統(tǒng)告訴select函數(shù)返回。這個(gè)集合中有一個(gè)文件可讀,select就會(huì)返回一個(gè)大于0的值,表示有文件可讀,如果沒有可讀的文件,則根據(jù)timeout參數(shù)再判斷是否超時(shí),若超出timeout的時(shí)間,select返回0,若發(fā)生錯(cuò)誤返回負(fù)值,可以傳入NULL值,表示不關(guān)心任何文件的讀變化;
wtfds: select監(jiān)視的可寫文件句柄集合,當(dāng)wtfds映象的文件句柄狀態(tài)變成可寫時(shí)系統(tǒng)告訴select函數(shù)返回,如果這個(gè)集合中有一個(gè)文件可寫,select就會(huì)返回一個(gè)大于0的值,表示有文件可寫,如果沒有可寫的文件,則根據(jù)timeout參數(shù)再判斷是否超時(shí),若超出timeout的時(shí)間,select返回0,若發(fā)生錯(cuò)誤返回負(fù)值,可以傳入NULL值,表示不關(guān)心任何文件的寫變化;
exfds:select監(jiān)視的異常文件句柄集合,當(dāng)exfds映象的文件句柄上有特殊情況發(fā)生時(shí)系統(tǒng)會(huì)告訴select函數(shù)返回;?
timeout:select的超時(shí)結(jié)束時(shí)間,設(shè)為0則為阻塞模式,設(shè)為大于0的值時(shí)若所監(jiān)視的句柄無狀態(tài)變化則等待timeout時(shí)間后返回0;
配置函數(shù):
FD_ZERO(fd_set *fdset):清空fdset與所有文件句柄的聯(lián)系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯(lián)系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯(lián)系。
FD_ISSET(int fd, fdset *fdset):檢查fdset聯(lián)系的文件句柄fd是否可讀寫,>0表示可讀寫。
select函數(shù)非常強(qiáng)大,它能同時(shí)監(jiān)測(cè)多個(gè)對(duì)象,只要在注冊(cè)的對(duì)象集中有一個(gè)或多個(gè)對(duì)象被激活就會(huì)有反應(yīng),所以利用select函數(shù)能在一個(gè)線程中處理多個(gè)等待式操作,這里以多串口阻塞讀取為例:
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#define USAR1 “/dev/ttyS1”
#define USAR2 “/dev/ttyS2”
#define USAR3 “/dev/ttyS3”
#define USAR4 “/dev/ttyS4”
char buf[255] = {0};
int set_port_attr (int fd,int baudrate, int databit, const char *stopbit, char parity, int vtime,int vmin );
static void set_baudrate (struct termios *opt, unsigned int baudrate);
static void set_data_bit (struct termios *opt, unsigned int databit);
static void set_stopbit (struct termios *opt, const char *stopbit);
static void set_parity (struct termios *opt, char parity);
void main()
{
int fd1,fd2,fd3,fd4;
int ret;
int rxlen = 0;
fd_set rfds;
struct timeval tv;
int retval;
}
此段程序?yàn)橥瑫r(shí)監(jiān)控4路串口接收狀態(tài),將接受的內(nèi)容直接原路返回,串口采用的是阻塞讀取模式,select函數(shù)也采用阻塞式讀取模式。
————————————————
版權(quán)聲明:本文為CSDN博主「發(fā)呆健將」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/m0_38096844/article/details/90716182
總結(jié)
以上是生活随笔為你收集整理的Linux串口编程详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 失败记录两则
- 下一篇: BAD SYSTEM CONFIG IN