您的位置:首页 > 其它

Serial Programming HOWTO

2007-03-14 12:00 441 查看
SerialProgrammingHOWTO

[b]Linux[/b][b]串口编程[/b]中英文简体对照beta版

翻译:CarolLi
原作:GaryFrerkinggary@frerking.org
PeterBaumann


ThisdocumentdescribeshowtoprogramcommunicationswithdevicesoveraserialportonaLinuxbox.
本文档记述了如何在Linux设备上通过串口进行通信的程序开发
中文简体版以英文1.01版为原文,同时参考了繁体中文的串口编程HOWTO

1.Introduction简介
ThisistheLinuxSerialProgrammingHOWTO.Allabouthowtoprogramcommunicationswithotherdevices/computersoveraseriallineunderLinux.Differenttechniquesareexplained:CanonicalI/O(onlycompletelinesaretransmitted/received),asyncronousI/O,andwaitingforinputfrommultiplesources.
ThisisthefirstupdatetotheinitialreleaseoftheLinuxSerialProgrammingHOWTO.TheprimarypurposeofthisupdateistochangetheauthorinformationandconvertthedocumenttoDocBookformat.Intermsoftechnicalcontent,verylittleifanythinghaschangedatthistime.Sweepingchangestothetechnicalcontentaren'tgoingtohappenovernight,butI'llworkonitasmuchastimeallows.
Ifyou'vebeenwaitinginthewingsforsomeonetotakeoverthisHOWTO,you'vegottenyourwish.Pleasesendmeanyandallfeedbackyouhave,it'dbeverymuchappreciated.
Allexamplesweretestedusingai386LinuxKernel2.0.29.
本文是为Linux串口程序编写的HOWTO.主要讨论如何在Linux环境下,编写串口与其它计算机设备进行通讯的程序。文中所谈到的技术包括:标准的I/O(只具备传送/接收线的),异步I/O,以及等待来自多信号源输入的程序。
本文是初始的linuxserialprogramminghowto的第一个升级版。主要升级了一些作者信息,把文件转换为DocBook格式。就技术内容而言,几乎没什么大的改变。大规模的技术内容的改变是不可能一夜之间发生的,如果时间允许,我会尽量做一些工作。
如果你正在一边等着有谁来接管这份HOWTO,那你的心愿达成了。我会感谢你发来的任何反馈信息。
所有的示例都在i386LinuxKernel2.0.29下测试通过。

1.1.CopyrightInformation版权信息
Thisdocumentiscopyrighted(c)1997PeterBaumann,(c)2001GaryFrerkingandisdistributedunderthetermsoftheLinuxDocumentationProject(LDP)license,statedbelow.
Unlessotherwisestated,LinuxHOWTOdocumentsarecopyrightedbytheirrespectiveauthors.LinuxHOWTOdocumentsmaybereproducedanddistributedinwholeorinpart,inanymediumphysicalorelectronic,aslongasthiscopyrightnoticeisretainedonallcopies.Commercialredistributionisallowedandencouraged;however,theauthorwouldliketobenotifiedofanysuchdistributions.
LinuxSerial-Programming-HOWTO的版權(C)1997归PeterBaumann所有,(C)2001归GaryFrerking所有,并且以LDPlisence(附后)发布。
除非另做申明,LinuxHOWTO文件的版权归各自的作者所有。LinuxHOWTO文件可以完整或部份的以實物或電子版形式复制或者发布,只要能在所有的拷贝中保留版權申明即可。我们鼓励允许商業的发布,不過,如果以此形式发布的话,请告知作者。

Alltranslations,derivativeworks,oraggregateworksincorporatinganyLinuxHOWTOdocumentsmustbecoveredunderthiscopyrightnotice.Thatis,youmaynotproduceaderivativeworkfromaHOWTOandimposeadditionalrestrictionsonitsdistribution.Exceptionstotheserulesmaybegrantedundercertainconditions;pleasecontacttheLinuxHOWTOcoordinatorattheaddressgivenbelow.
Inshort,wewishtopromotedisseminationofthisinformationthroughasmanychannelsaspossible.However,wedowishtoretaincopyrightontheHOWTOdocuments,andwouldliketobenotifiedofanyplanstoredistributetheHOWTOs.
Ifyouhaveanyquestions,pleasecontact<linux-howto@metalab.unc.edu>
所有的翻譯,以及其他派生的工作,或整合合併任何LinuxHOWTO文件都必須在此版權申明的規範下进行.也就是说,你不可以在从HOWTO所衍生的工作中,散佈的文件上附加額外的限制條款。除了這些規則,都可获得某種條件的授與;請见后面的地址来聯絡LinuxHOWTO協調員。
簡而言之,我們希望儘可能得透過各種渠道来促進這份资料的流通,不過,我们強烈希望將此版權宣告置於HOWTO的文件上,并且希望任何想重新发佈HOWTO的人可以通知我們一下。
如果你有問題,請通过email
linux-howto@metalab.unc.edu进行联系
.

1.2.Disclaimer申明
Noliabilityforthecontentsofthisdocumentscanbeaccepted.Usetheconcepts,examplesandothercontentatyourownrisk.Asthisisaneweditionofthisdocument,theremaybeerrorsandinaccuracies,thatmayofcoursebedamagingtoyoursystem.Proceedwithcaution,andalthoughthisishighlyunlikely,theauthor(s)donottakeanyresponsibilityforthat.
Allcopyrightsareheldbytheirbytheirrespectiveowners,unlessspecificallynotedotherwise.Useofaterminthisdocumentshouldnotberegardedasaffectingthevalidityofanytrademarkorservicemark.
Namingofparticularproductsorbrandsshouldnotbeseenasendorsements.
Youarestronglyrecommendedtotakeabackupofyoursystembeforemajorinstallationandbackupsatregularintervals.
使用本文的概念,例子及其他内容的风险由您自己承担,我们对此造成的后果不负责任。由于这是份新的文档,可能存在着错误或误差,而有可能导致对您的系统的损害。请小心操作,虽然这是几乎不可能发生的,作者不对此承担任何责任。

除非特别标注,所有的版权归其各自的作者。使用此文档不可标住任何商标或服务标记。
特定的产品或品牌的命名不可被理解为是认可的。(晕,这一段应该请律师来翻)。

强烈推荐您在重大的安装前备份系统,并且做到定期备份。
1.3.NewVersions版本更新
Aspreviouslymentioned,notmuchisnewintermsoftechnicalcontentyet.
如前面所提到的,此版本在技术内容上较前一版本并没有什么大的更新。
1.4.Credits感谢
TheoriginalauthorthankedMr.Strudthoff,MichaelCarter,PeterWaltenberg,AntoninoIanella,GregHankins,DavePfaltzgraff,SeanLincolne,MichaelWiedmann,andAdreyBonar.
原作者感谢Strudthoff,MichaelCarter,PeterWaltenberg,AntoninoIanella,GregHankins,DavePfaltzgraff,SeanLincolne,MichaelWiedmann,andAdreyBonar诸位先生。
1.5.Feedback意见反馈
Feedbackismostcertainlywelcomeforthisdocument.Withoutyoursubmissionsandinput,thisdocumentwouldn'texist.Pleasesendyouradditions,commentsandcriticismstothefollowingemailaddress:<gary@frerking.org>.
非常欢迎对此文档提出反馈意见。有任何补充,评论,批评请发信到:gary@frerking.org
2.Gettingstarted入门
2.1.Debugging调试
ThebestwaytodebugyourcodeistosetupanotherLinuxbox,andconnectthetwocomputersviaanull-modemcable.Useminiterm(availablefromtheLDPprogrammersguide(ftp://sunsite.unc.edu/pub/Linux/docs/LDP/programmers-guide/lpg-0.4.tar.gzintheexamplesdirectory)totransmitcharacterstoyourLinuxbox.Minitermcanbecompiledveryeasilyandwilltransmitallkeyboardinputrawovertheserialport.Onlythedefinestatement#defineMODEMDEVICE"/dev/ttyS0"hastobechecked.SetittottyS0forCOM1,ttyS1forCOM2,etc..Itisessentialfortesting,thatallcharactersaretransmittedraw(withoutoutputprocessing)overtheline.Totestyourconnection,startminitermonbothcomputersandjusttypeaway.Thecharactersinputononecomputershouldappearontheothercomputerandviceversa.Theinputwillnotbeechoedtotheattachedscreen.
调试代码最好的方法,是另外建立一台Linux主机(Linuxbox),采用非调制解调器的串口线(null-modem)连接两台机器。还可以使用minicom(可以从LDP编程指南上获得:ftp://sunsite.unc.edu/pub/Linux/docs/LDP/programmers-guide/lpg-0.4.tar.gz里的examples目录)来传输字符到你的Linux主机。Miniterm很容易编译而且会直接把键盘的输入不做处理(raw方式)地传到串口。只需要把定义申明#defineMODEMDIVICE“/dev/ttyS0”改一下。COM1口就设置成ttyS0,COM2口就是ttyS1,以此类推…测试是必需的,所有的字符直接通过缆线传输,不进行任何输出处理。为了测试你的连接,在你的两台机器上开启minicom,然后随意输入一些字符。从一台电脑中输入的字符应该能显示在另一台设备上,反之亦然。输入的字符不会回显(echo)在本地的屏幕上.
Tomakeanull-modemcableyouhavetocrosstheTxD(transmit)andRxD(receive)lines.Foradescriptionofacableseesect.7oftheSerial-HOWTO.

自制非调制解调器的串口连接线(null-modemcable)时,你需要将一端的传送端(TxD)与另一端的接收端(RxD)连接,一端的接收端与另外一端的传送端连接,详情见Serial-HOWTO第七节。
Itisalsopossibletoperformthistestingwithonlyonecomputer,ifyouhavetwounusedserialports.Youcanthenruntwominitermsofftwovirtualconsoles.Ifyoufreeaserialportbydisconnectingthemouse,remembertoredirect/dev/mouseifitexists.Ifyouuseamultiportserialcard,besuretoconfigureitcorrectly.IhadmineconfiguredwrongandeverythingworkedfineaslongasIwastestingonlyonmycomputer.WhenIconnectedtoanothercomputer,theportstartedloosingcharacters.Executingtwoprogramsononecomputerjustisn'tfullyasynchronous.
如果你的电脑有两个空闲的串口端口的话,那么只要使用一台机器就可以做这些试验了,你可以在两个虚拟控制台上各运行一个miniterm,分别用来发送和接收结果。如果你使用串口鼠标,记得在试验前将/dev/mouse重定向。如果你使用多端口的串口卡(multiportserialcard),一定要确保此设备配置正确,我的电脑就曾因为配置错误,而出现这样的问题:当我在自己的机器上测试的时候一切正常,而连接到其他电脑上的时候,端口开始丢失数据。注意,在一台机器上运行两个串口应用程序,并不是完全异步的。
2.2.PortSettings端口设置
Thedevices/dev/ttyS*areintendedtohookupterminalstoyourLinuxbox,andareconfiguredforthisuseafterstartup.Thishastobekeptinmindwhenprogrammingcommunicationwitharawdevice.E.g.theportsareconfiguredtoechocharacterssentfromthedevicebacktoit,whichnormallyhastobechangedfordatatransmission.
[align=left]设备/dev/ttyS*会被当作连接到你的Linux机器的终端设备,而且在系统启动后就已经配置好了。這一点是在你寫raw设备的串口通信程式時必需牢記的.举例来说,這個串口被設定為回显(echo)所有自此设备送出的字符,通常在做数据传输时,需要改變這種工作模式。[/align]
Allparameterscanbeeasilyconfiguredfromwithinaprogram.Theconfigurationisstoredinastructurestructtermios,whichisdefinedin<asm/termbits.h>:

#defineNCCS19

structtermios{

tcflag_tc_iflag;/*inputmodeflags*/

tcflag_tc_oflag;/*outputmodeflags*/

tcflag_tc_cflag;/*controlmodeflags*/

tcflag_tc_lflag;/*localmodeflags*/

cc_tc_line;/*linediscipline*/

cc_tc_cc[NCCS];/*controlcharacters*/

}

所有的参数都可以在程序中轻松配置。配置保存在结构体structtermios中,这个结构是在<asm/termbits.h>中定义的

#defineNCCS19

structtermios{

tcflag_tc_iflag;/*输入模式标志*/

tcflag_tc_oflag;/*输出模式标志*/

tcflag_tc_cflag;/*控制模式标志*/

tcflag_tc_lflag;/*本地模式标志*/

cc_tc_line;/*行控制linediscipline*/

cc_tc_cc[NCCS];/*控制字符controlcharacters*/

}

Thisfilealsoincludesallflagdefinitions.Theinputmodeflagsinc_iflaghandleallinputprocessing,whichmeansthatthecharacterssentfromthedevicecanbeprocessedbeforetheyarereadwithread.Similarlyc_oflaghandlestheoutputprocessing.c_cflagcontainsthesettingsfortheport,asthebaudrate,bitspercharacter,stopbits,etc..Thelocalmodeflagsstoredinc_lflagdetermineifcharactersareechoed,signalsaresenttoyourprogram,etc..Finallythearrayc_ccdefinesthecontrolcharactersforendoffile,stop,etc..Defaultvaluesforthecontrolcharactersaredefinedin<asm/termios.h>.Theflagsaredescribedinthemanualpagetermios(3).Thestructuretermioscontainsthec_line(linediscipline)element,whichisnotusedinPOSIXcompliantsystems.
这个文件也包括了所有标志(flag)的定义。c_iflag中的输入模式标志,进行所有的输入处理,也就是说从其他设备传来的字符,在被read函数读入之前,会先按照标志进行预处理。同样的,c_oflag定义了所有的输出处理。c_cflag包括了对端口的设置,比如波特率,停止符号等等。c_lflag包括了,决定了字符是否回显,信号是否发送到你的程序,等等所有的本地工作方式。c_cc定义了所有的控制符号,例如文件结束符和停止符等等,所有的这些参数的默认值都定义在<asm/termios.h>中,man手册页termios(3)中有这些参数的具体描述。termio结构体中还包括在不能在POSIX系统中使用的c_line参数(行控制)
2.3.InputConceptsforSerialDevices[b]串口设备的输入概念[/b]
Herethreedifferentinputconceptswillbepresented.Theappropriateconcepthastobechosenfortheintendedapplication.Wheneverpossible,donotloopreadingsinglecharacterstogetacompletestring.WhenIdidthis,Ilostcharacters,whereasareadforthewholestringdidnotshowanyerrors.
这里将介绍串行设备三种不同的输入方式,你需要为你的程序选择合适的工作方式。任何可能的情况下,不要采用循环读取单字符的方式来获取一个字符串。我以前这样做的时候,就丢失了字符,而读取整个字符串的read方法,则没有这种错误。
2.3.1.CanonicalInputProcessing标准输入模式
Thisisthenormalprocessingmodeforterminals,butcanalsobeusefulforcommunicatingwithotherdlinputisprocessedinunitsoflines,whichmeansthatareadwillonlyreturnafulllineofinput.AlineisbydefaultterminatedbyaNL(ASCIILF),anendoffile,oranendoflinecharacter.ACR(theDOS/Windowsdefaultend-of-line)willnotterminatealinewiththedefaultsettings.
Canonicalinputprocessingcanalsohandletheerase,deleteword,andreprintcharacters,translateCRtoNL,etc..

这是终端设备的标准处理模式,在与其它dl的以行为单位的输入通讯中也很有用。这种方式中,
read
会传回一整行完整的输入.一行的结束,默认是以
NL
(ASCII值
LF
),文件结束符,或是一个行结束字符。默认设置中,
CR
(DOS/Windows里的默认行结束符)并不是行结束标志。
标准的输入处理还可以处理清除,删除字,重画字符,转换
CR
NL
等等功能。

Non-CanonicalInputProcessingwillhandleafixedamountofcharactersperread,andallowsforacharactertimer.Thismodeshouldbeusedifyourapplicationwillalwaysreadafixednumberofcharacters,oriftheconnecteddevicesendsburstsofcharacters.

非标准输入处理可以用于需要每次读取固定数量字符的情况下,并允许使用字符接收时间的定时器。这种模式可以用在每次读取固定长度字符串的程序中,或者所连接的设备会突然送出大量字符的情况下。

Thetwomodesdescribedabovecanbeusedinsynchronousandasynchronousmode.Synchronousisthedefault,whereareadstatementwillblock,untilthereadissatisfied.Inasynchronousmodethereadstatementwillreturnimmediatlyandsendasignaltothecallingprogramuponcompletion.Thissignalcanbereceivedbyasignalhandler.
前面敘述的兩種模式都可以用在同步與异步的傳輸模式。默认是在同步的模式下工作的,也就是在尚未讀完数据之前,
read
的狀態會被阻塞(block)。而在异步模式下,
read
的狀態會立即返回並送出一个信号到所调用的程式直到完成工作。這個信号可以由信号处理程式handler來接收。
2.3.4.WaitingforInputfromMultipleSources等待来自多信号源的输入
Thisisnotadifferentinputmode,butmightbeuseful,ifyouarehandlingmultipledevices.InmyapplicationIwashandlinginputoveraTCP/IPsocketandinputoveraserialconnectionfromanothercomputerquasi-simultaneously.Theprogramexamplegivenbelowwillwaitforinputfromtwodifferentinputsources.Ifinputfromonesourcebecomesavailable,itwillbeprocessed,andtheprogramwillthenwaitfornewinput.
Theapproachpresentedbelowseemsrathercomplex,butitisimportanttokeepinmindthatLinuxisamulti-processingoperatingsystem.TheselectsystemcallwillnotloadtheCPUwhilewaitingforinput,whereasloopinguntilinputbecomesavailablewouldslowdownotherprocessesexecutingatthesametime.

[align=left]本节介绍的不是另一个输入模式,不过如果你要处理来自多个设备的数据的话,可能会很有用。在我的应用程序中,我需要同时通过一个TCP/IPsocket和一个串口来处理其它计算机传来的输入。下面给出的示例程序将等待来自两个不同输入源的输入。如果其中一个信号源出现,程序就会进行相应处理,同时程序会继续等待新的输入。[/align]
后面提出的方法看起来相当覆杂,但请记住Linux是一个多进程的操作系统。系统调用select并不会在等待输入信号时增加CPU的负担,而如果使用轮询方式来等待输入信号的话,则将拖慢其它正在执行的进程。

3.ProgramExamples示例程序

Allexampleshavebeenderivedfromminiterm.c.Thetypeaheadbufferislimited
to255characters,justlikethemaximumstringlengthforcanonicalinputprocessing
(<linux/limits.h>or<posix1_lim.h>).

Seethecommentsinthecodeforexplanationoftheuseofthedifferentinputmodes.
Ihopethatthecodeisunderstandable.Theexampleforcanonicalinputiscommented
best,theotherexamplesarecommentedonlywheretheydifferfromtheexamplefor
canonicalinputtoemphasizethedifferences.

Thedescriptionsarenotcomplete,butyouareencouragedtoexperimentwiththe
examplestoderivethebestsolutionforyourapplication.

Don'tforgettogivetheappropriateserialportstherightpermissions(e.g.:
chmoda+rw/dev/ttyS1)!

所有的示例来自于miniterm.c.Thetypeahead缓存器限制在255字节的大小,这与标
准输入(canonicalinput)进程的字符串最大长度相同(<linux/limits.h>或<posix1_lim.h>).

代码中的注释解释了不同输入模式的使用以希望这些代码能够易于理解。标准输入程序的示
例做了最详细的注解,其它的示例则只是在不同于标准输入示例的地方做了强调。

叙述不是很完整,但可以激励你对这范例做实验,以延生出合于你所需应用程序的最佳解.

不要忘记赋予串口正确的权限(也就是:chmoda+rw/dev/ttyS1)!

3.1.CanonicalInputProcessing标准输入模式

CODE
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<termios.h>
#include<stdio.h>

/*baudratesettingsaredefinedin<asm/termbits.h>,whichisincludedby<termios.h>*/
//波特率的设置定义在<asm/termbits.h>.包含在<termios.h>里
#defineBAUDRATEB38400

/*changethisdefinitionforthecorrectport*/
//定义您所需要的串口号
#defineMODEMDEVICE"/dev/ttyS1"

#define_POSIX_SOURCE1/*POSIXcompliantsourcePOSIX系统兼容*/

#defineFALSE0
#defineTRUE1

volatileintSTOP=FALSE;

main(){
intfd,c,res;
structtermiosoldtio,newtio;
charbuf[255];

/*Openmodemdeviceforreadingandwritingandnotascontrolling
ttybecausewedon'twanttogetkillediflinenoisesendsCTRL-C.
开启设备用于读写,但是不要以控制tty的模式,因为我们并不希望在发送Ctrl-C
后结束此进程
*/

fd=open(MODEMDEVICE,O_RDWR|O_NOCTTY);
if(fd<0){perror(MODEMDEVICE);exit(-1);}
tcgetattr(fd,&oldtio);/*savecurrentserialportsettings*/
//储存当前的串口设置
bzero(&newtio,sizeof(newtio));/*clearstructfornewportsettings*/
//清空新的串口设置结构体
/*
BAUDRATE:Setbpsrate.Youcouldalsousecfsetispeedandcfsetospeed.
CRTSCTS:outputhardwareflowcontrol(onlyusedifthecablehasall
ecessarylines.Seesect.7ofSerial-HOWTO)
CS8:8n1(8bit,noparity,1stopbit)
CLOCAL:localconnection,nomodemcontol
CREAD:enablereceivingcharacters
BAUDRATE:设置串口的传输速率bps,也可以使用cfsetispeed和cfsetospeed来设置
CRTSCTS:输出硬件流控(只能在具完整线路的缆线下工作,参考Serial-HOWTO第七节)
CS8:8n1(每一帧8比特数据,无奇偶校验位,1比特停止位)
CLOCAL:本地连接,无调制解调器控制
CREAD:允许接收数据
*/
newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;

/*
IGNPAR:ignorebyteswithparityerrors
ICRNL:mapCRtoNL(otherwiseaCRinputontheothercomputerwillnot
terminateinput)otherwisemakedeviceraw(nootherinputprocessing)
IGNPAR:忽略奇偶校验出错的字节
ICRNL:把CR映像成NL(否则从其它机器传来的CR无法终止输入)或者就把设备设
为raw状态(没有额外的输入处理)
*/
newtio.c_iflag=IGNPAR|ICRNL;

/*
Rawoutput.Raw模式输出
*/
newtio.c_oflag=0;

/*
ICANON:enablecanonicalinput
disableallechofunctionality,anddon'tsendsignalstocallingprogram
ICANON:启动标准输出,关闭所有回显echo功能,不向程序发送信号
*/
newtio.c_lflag=ICANON;

/*
initializeallcontrolcharacters
defaultvaluescanbefoundin/usr/include/termios.h,and
aregiveninthecomments,butwedon'tneedthemhere
初始化所有的控制字符,默认值可以在/usr/include/termios.h找到,
并且做了注解,不过这里我们并不需要考虑这些
*/
newtio.c_cc[VINTR]=0;/*Ctrl-c*/
newtio.c_cc[VQUIT]=0;/*Ctrl-/*/
newtio.c_cc[VERASE]=0;/*del*/
newtio.c_cc[VKILL]=0;/*@*/
newtio.c_cc[VEOF]=4;/*Ctrl-d*/
newtio.c_cc[VTIME]=0;/*inter-charactertimerunused*/
/*不使用字符间的计时器*/
newtio.c_cc[VMIN]=1;/*blockingreaduntil1characterarrives*/
/*阻塞,直到读取到一个字符*/
newtio.c_cc[VSWTC]=0;/*'/0'*/
newtio.c_cc[VSTART]=0;/*Ctrl-q*/
newtio.c_cc[VSTOP]=0;/*Ctrl-s*/
newtio.c_cc[VSUSP]=0;/*Ctrl-z*/
newtio.c_cc[VEOL]=0;/*'/0'*/
newtio.c_cc[VREPRINT]=0;/*Ctrl-r*/
newtio.c_cc[VDISCARD]=0;/*Ctrl-u*/
newtio.c_cc[VWERASE]=0;/*Ctrl-w*/
newtio.c_cc[VLNEXT]=0;/*Ctrl-v*/
newtio.c_cc[VEOL2]=0;/*'/0'*/

/*
nowcleanthemodemlineandactivatethesettingsfortheport
清空数据线,启动新的串口设置
*/
tcflush(fd,TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

/*
terminalsettingsdone,nowhandleinput
Inthisexample,inputtinga'z'atthebeginningofalinewill
exittheprogram.
终端设置完成,现在就可以处理数据了
在本程序中,在一行的开始输入一个'z'会终止该程序
*/
while(STOP==FALSE){/*loopuntilwehaveaterminatingcondition*/
//循环直到满足终止条件
/*readblocksprogramexecutionuntilalineterminatingcharacteris
input,evenifmorethan255charsareinput.Ifthenumber
ofcharactersreadissmallerthanthenumberofcharsavailable,
subsequentreadswillreturntheremainingchars.reswillbeset
totheactualnumberofcharactersactuallyread
即使输入超过255个字节,读取的程序段还是会一直等到行结束符出现才会停止。
如果读到的字符少于应刚获得的字符数,则剩下的字符串会在下一次读取时读到。
res用来获得每次真正读到的字节数
*/
res=read(fd,buf,255);
buf[res]=0;/*setendofstring,sowecanprintf*/
//设置字符串结束符,从而可以顺利使用printf
printf(":%s:%d/n",buf,res);
if(buf[0]=='z')STOP=TRUE;
}
/*restoretheoldportsettings恢复旧的串口设置*/
tcsetattr(fd,TCSANOW,&oldtio);
}

3.2.Non-CanonicalInputProcessing非标准输入模式

Innon-canonicalinputprocessingmode,inputisnotassembledintolinesandinput
processing(erase,kill,delete,etc.)doesnotoccur.Twoparameterscontrolthe
behaviorofthismode:c_cc[VTIME]setsthecharactertimer,andc_cc[VMIN]sets
theminimumnumberofcharacterstoreceivebeforesatisfyingtheread.

IfMIN>0andTIME=0,MINsetsthenumberofcharacterstoreceivebefore
thereadissatisfied.AsTIMEiszero,thetimerisnotused.

IfMIN=0andTIME>0,TIMEservesasatimeoutvalue.Thereadwillbe
satisfiedifasinglecharacterisread,orTIMEisexceeded(t=TIME*0.1s).
IfTIMEisexceeded,nocharacterwillbereturned.

IfMIN>0andTIME>0,TIMEservesasaninter-charactertimer.Theread
willbesatisfiedifMINcharactersarereceived,orthetimebetweentwocharacters
exceedsTIME.Thetimerisrestartedeverytimeacharacterisreceivedandonly
becomesactiveafterthefirstcharacterhasbeenreceived.

IfMIN=0andTIME=0,readwillbesatisfiedimmediately.Thenumberof
characterscurrentlyavailable,orthenumberofcharactersrequestedwillbereturned.
AccordingtoAntonino(seecontributions),youcouldissueafcntl(fd,F_SETFL,FNDELAY);
beforereadingtogetthesameresult.

Bymodifyingnewtio.c_cc[VTIME]andnewtio.c_cc[VMIN]allmodesdescribedabovecanbetested.

在非标准输入模式中,输入的数据并不组合成行,也不会进行erase,kill,delete等输
入处理。我们只是用两个参数来控制这种模式的输入行为:c_cc[VTIME]设定字符输入间
隔时间的计时器,而c_cc[VMIN]设置满足读取函数的最少字节数。

MIN>0,TIME=0:读取函数在读到了MIN值的字符数后返回。

MIN=0,TIME>0:TIME决定了超时值,读取函数在读到一个字节的字符,或者等待读
取时间超过TIME(t=TIME*0.1s)以后返回,也就是说,即使没有从串口中读到数
据,读取函数也会在TIME时间后返回。

MIN>0,TIME>0:读取函数会在收到了MIN字节的数据后,或者超过TIME时间没收
到数据后返回。此计时器会在每次收到字符的时候重新计时,也只会在收到第一个字节后才
启动。

MIN=0,TIME=0:读取函数会立即返回。实际读取到的字符数,或者要读到的字符
数,会作为返回值返回。根据Antonino(参考conditions),可以使用fcntl(fd,F_SETFL,
FNDELAY),在读取前获得同样的结果。

改变了nettio.c_cc[VTIME]和newtio.c_cc[VMIN],就可以测试以上的设置了。

CODE
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<termios.h>
#include<stdio.h>
#defineBAUDRATEB38400
#defineMODEMDEVICE"/dev/ttyS1"

#define_POSIX_SOURCE1/*POSIXcompliantsource*/

#defineFALSE0
#defineTRUE1

volatileintSTOP=FALSE;

main(){
intfd,c,res;
structtermiosoldtio,newtio;
charbuf[255];
fd=open(MODEMDEVICE,O_RDWR|O_NOCTTY);
if(fd<0){perror(MODEMDEVICE);exit(-1);}

tcgetattr(fd,&oldtio);/*savecurrentportsettings*/
bzero(&newtio,sizeof(newtio));
newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;
newtio.c_iflag=IGNPAR;
newtio.c_oflag=0;

/*setinputmode(non-canonical,noecho,...)*/
//设置输入模式为非标准输入
newtio.c_lflag=0;
newtio.c_cc[VTIME]=0;/*inter-charactertimerunused*/
//不是用字符间隔计时器
newtio.c_cc[VMIN]=5;/*blockingreaduntil5charsreceived*/
//收到5个字符数以后,read函数才返回

tcflush(fd,TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

while(STOP==FALSE){/*loopforinput*/
res=read(fd,buf,255);/*returnsafter5charshavebeeninput*/
buf[res]=0;/*sowecanprintf...*/
printf(":%s:%d/n",buf,res);
if(buf[0]=='z')STOP=TRUE;
}
tcsetattr(fd,TCSANOW,&oldtio);

3.3.AsynchronousInput异步输入模式

CODE
#include<termios.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/signal.h>
#include<sys/types.h>

#defineBAUDRATEB38400
#defineMODEMDEVICE"/dev/ttyS1"

#define_POSIX_SOURCE1/*POSIXcompliantsource*/
#defineFALSE0
#defineTRUE1

volatileintSTOP=FALSE;
voidsignal_handler_IO(intstatus);/*definitionofsignalhandler*/
//定义信号处理程序
intwait_flag=TRUE;/*TRUEwhilenosignalreceived*/
//TRUE代表没有受到信号,正在等待中

main(){
intfd,c,res;
structtermiosoldtio,newtio;
structsigactionsaio;
/*definitionofsignalaction*/
//定义信号处理的结构

charbuf[255];

/*openthedevicetobenon-blocking(readwillreturnimmediatly)*/
//是用非阻塞模式打开设备read函数立刻返回,不会阻塞
fd=open(MODEMDEVICE,O_RDWR|O_NOCTTY|O_NONBLOCK);
if(fd<0){perror(MODEMDEVICE);exit(-1);}

/*installthesignalhandlerbeforemakingthedeviceasynchronous*/
//在进行设备异步传输前,安装信号处理程序
saio.sa_handler=signal_handler_IO;
saio.sa_mask=0;
saio.sa_flags=0;
saio.sa_restorer=NULL;
sigaction(SIGIO,&saio,NULL);

/*allowtheprocesstoreceiveSIGIO*/
//允许进程接收SIGIO信号
fcntl(fd,F_SETOWN,getpid());

/*Makethefiledescriptorasynchronous(themanualpagesaysonly
O_APPENDandO_NONBLOCK,willworkwithF_SETFL...)*/
//设置串口的文件描述符为异步,man上说,只有O_APPEND和O_NONBLOCK才能使用F_SETFL
fcntl(fd,F_SETFL,FASYNC);
tcgetattr(fd,&oldtio);/*savecurrentportsettings*/

/*setnewportsettingsforcanonicalinputprocessing*/
//设置新的串口为标准输入模式
newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;
newtio.c_iflag=IGNPAR|ICRNL;
newtio.c_oflag=0;
newtio.c_lflag=ICANON;
newtio.c_cc[VMIN]=1;
newtio.c_cc[VTIME]=0;
tcflush(fd,TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

/*loopwhilewaitingforinput.normallywewoulddosomething
usefulhere循环等待输入,通常我们会在这里做些其它的事情*/
while(STOP==FALSE){
printf("./n");usleep(100000);

/*afterreceivingSIGIO,wait_flag=FALSE,inputisavailableandcanberead*/
//在收到SIGIO信号后,wait_flag=FALSE,表示有输入进来,可以读取了
if(wait_flag==FALSE){
res=read(fd,buf,255);
buf[res]=0;
printf(":%s:%d/n",buf,res);
if(res==1)STOP=TRUE;/*stoploopifonlyaCRwasinput*/
wait_flag=TRUE;/*waitfornewinput等待新的输入*/
}
}

/*restoreoldportsettings*/
tcsetattr(fd,TCSANOW,&oldtio);
}

/***************************************************************************
*signalhandler.setswait_flagtoFALSE,toindicateaboveloopthat*
*charactershavebeenreceived.*
***************************************************************************/

//信号处理函数,设置wait_flag为FALSE,以告知上面的循环函数串口收到字符了
voidsignal_handler_IO(intstatus){
printf("receivedSIGIOsignal./n");
wait_flag=FALSE;
}

3.4.WaitingforInputfromMultipleSources等待来自多个源的输入

Thissectioniskepttoaminimum.Itisjustintendedtobeahint,andtherefore
theexamplecodeiskeptshort.Thiswillnotonlyworkwithserialports,butwith
anysetoffiledescriptors.

Theselectcallandaccompanyingmacrosuseafd_set.Thisisabitarray,which
hasabitentryforeveryvalidfiledescriptornumber.selectwillacceptafd_set
withthebitssetfortherelevantfiledescriptorsandreturnsafd_set,inwhich
thebitsforthefiledescriptorsaresetwhereinput,output,oranexception
occurred.Allhandlingoffd_setisdonewiththeprovidedmacros.Seealsothe
manualpageselect(2).

这一部分的内容很少,只是作为一个提示,因此这段代码也很简短。而且这部分内容不仅适
用于串口编程,而且适用于任意的一组文件描述符。

select调用及其相应的宏,使用fd_set.这是一个比特数组,其中每一个比特代表了一个
有效的文件描述符号。select调用接收一个有效的文件描述符结构,并返回fd_set比特
数组,如果此比特数组中有某一个位设为1,就表示对应的文件描述符发生了输入,输出或
者有例外事件。所有fg_set的处理都由宏提供了,具体参考manselect2。

CODE
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

main()
{
intfd1,fd2;/*inputsources1and2输入源1和2*/
fd_setreadfs;/*filedescriptorset*/
intmaxfd;/*maximumfiledesciptorused用到的文件描述符的最大值*/
intloop=1;/*loopwhileTRUE循环标志*/

/*open_input_sourceopensadevice,setstheportcorrectly,and
returnsafiledescriptor*/
//open_input_source函数打开一个设备,正确设置端口,并返回文件描述符
fd1=open_input_source("/dev/ttyS1");/*COM2*/
if(fd1<0)exit(0);
fd2=open_input_source("/dev/ttyS2");/*COM3*/
if(fd2<0)exit(0);
maxfd=MAX(fd1,fd2)+1;/*maximumbitentry(fd)totest*/

/*loopforinput*/
while(loop){
FD_SET(fd1,&readfs);/*settestingforsource1*/
FD_SET(fd2,&readfs);/*settestingforsource2*/
/*blockuntilinputbecomesavailable阻塞直到有输入进来*/
select(maxfd,&readfs,NULL,NULL,NULL);
if(FD_ISSET(fd1))/*inputfromsource1available源1有输入*/
handle_input_from_source1();
if(FD_ISSET(fd2))/*inputfromsource2available源2有输入*/
handle_input_from_source2();
}
}

Thegivenexampleblocksindefinitely,untilinputfromoneofthesourcesbecomesavailable.Ifyouneedtotimeoutoninput,justreplacetheselectcallby:

这个例子会导致未知的阻塞,知道其中一个源有数据输入。如果你需要为输入设置一个超时值,就用下面的select替代:

CODE
intres;
structtimevalTimeout;

/*settimeoutvaluewithininputloop在输入循环中设置超时值*/
Timeout.tv_usec=0;/*milliseconds设置毫秒数*/
Timeout.tv_sec=1;/*seconds设置秒数*/
res=select(maxfd,&readfs,NULL,NULL,&Timeout);
if(res==0)
/*numberoffiledescriptorswithinput=0,timeoutoccurred.所有的文件描述符都没有得到输入,超时退出返回0*/

Thisexamplewilltimeoutafter1second.Ifatimeoutoccurs,selectwillreturn0,butbewarethatTimeoutisdecrementedbythetimeactuallywaitedforinputbyselect.Ifthetimeoutvalueiszero,selectwillreturnimmediatly.

这个例子会在1秒以后超时退出,如果发生超时,select返回0,请注意Timeout是根据select实际等待输入的时间递减的,如果把timeout设为0,select函数会立刻退出。

2.3.3.AsynchronousInput异步输入模式2.3.2.Non-CanonicalInputProcessing非标准输入模式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: