原创 串口相关的一点

2011-6-14 21:39 1503 10 8 分类: MCU/ 嵌入式
一台机子发送长度一定的字符串,我怎样才能完整的接受到呢?read和write好像是没有
缓冲区的,两次read中间可能就把一些数据丢了。而且我一次发送73个字符,而read却一

只能接受20多个,等循环到下一次read时,一些数据就错过了吗?
有没有办法建立一个缓冲区呢,让发送的数据统统写入缓冲内?
-------------------
会,有硬件缓冲区,你发送的数据将一直保留在缓冲区里,直到缓冲区满掉才被新的数据冲走,否则一直留在那里可供读取,读取掉的数据也移出缓冲区(其实没有真正意义上的移出,只是读取指针再指向那些数据)
在win下可以使用中断来实行数据的即时读取,就是缓冲区一收到新的数据可以触发中断服务程序将收到的新数据立即读取掉,但linux下似乎无法这样做(可能知道方法),只能用不断循环读取的方法来保证读取到所有的数据
======================================================================
等待来自多个信号来源的输入

这一段很短. 它只能被拿来当成写程序时的提示, 故范例程序也很简短. 但这个范例不只能用在序列埠上, 还可以用在被当成文档来使用的装置上.

select 呼叫及伴随它所引发的巨集共用 fd_set. fd_set 则是一个位元阵列, 而其中每一个位元代表一个有效的文档叙述结构. select 呼叫接受一个有效的文档叙述结构并传回 fd_set 位元阵列, 而该位元阵列中若有某一个位元为 1, 就表示相对映的文档叙述结构的文档发生了输入, 输出或有例外事件. 而这些巨集提供了所有处理 fd_set 的功能. 亦可参考手册 select(2).

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

main()
{
   int    fd1, fd2;  /* 输入源 1 及 2 */
   fd_set readfs;    /* 文档叙述结构设定 */
   int    maxfd;     /* 最大可用的文档叙述结构 */
   int    loop=1;    /* 回圈在 TRUE 时成立 */ 

   /* 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;  /* 测试最大位元输入 (fd) */

   /* 输入回圈 */
   while (loop) {
     FD_SET(fd1, &readfs);  /* 测试输入源 1 */
     FD_SET(fd2, &readfs);  /* 测试输入源 2 */
     /* block until input becomes available */
     select(maxfd, &readfs, NULL, NULL, NULL);
     if (FD_ISSET(fd1))         /* 如果输入源 1 有信号 */
       handle_input_from_source1();
     if (FD_ISSET(fd2))         /* 如果输入源 2 有信号 */
       handle_input_from_source2();
   }

}   

这个范例程序在等待输入信号出现前, 不能确定它会停顿下来. 如果你需要在输入时加入逾时功能, 只需把 select 呼叫换成:

int res;
struct timeval Timeout;

/* 设定输入回圈的逾时值 */
Timeout.tv_usec = 0;  /* 毫秒 */
Timeout.tv_sec  = 1;  /* 秒 */
res = select(maxfd, &readfs, NULL, NULL, &Timeout);
if (res==0)
/* 文档叙述结构数在 input = 0 时, 会发生输入逾时. */ 

这个程序会在 1 秒钟后逾时. 如果超过时间, select 会传回 0, 但是应该留意 Timeout 的时间递减是由 select 所等待输入信号的时间为基准. 如果逾时的值是 0, select 会马上结束返回.

==================================================================

 设置termios的标志位
-------------------------

这里是对你在使用你自己打开的串行设备时设置termios标志的一些提示(即与你使
用已存在的控制tty相反)

3.6.2.1 c_iflag
...............

你也许希望将*所有*‘c_iflag’的标志位设成0,除非你希望使用软件流控制(ick),
在这种情况下你设置‘IXON’和‘IXOFF’。(译者注:有三个标志控制流控制:
IXON,IXOFF ,和IXANY,如果IXON被设置,那么tty输入队列的软件流控制
被设置。当程序无法跟上输入队列的速度,tty传输一个STOP字符,而当输入队
列差不多空时发送START字符。如果IXON被设置,那么tty输出队列的软件流控
制被设置。当tty所连接的设备跟上输出速度,tty将阻塞程序对tty的写操作。如果
IXANY被设置,那么一旦tty从设备收到任何字符,被暂定的输出将继续 - 译自SCO
Unix 网上文档http://uw7doc.sco.com/SDK_sysprog/CTOC-TermDevCntl.html,“TTY flow
control”章节,第五,六段)

3.6.2.2 c_oflag
...............

大部分‘c_oflag’的标志位是为了能使对于慢终端的输出可以正常工作所做的这
样或那样的黑客行为,由此,一些较新的系统认为几乎所有这些标志位已经过
时从而摈弃了它们(特别是所有血淋淋(gory)的输出排列对齐(output-padding)选项)。
如同‘c_iflag’,将它设置成全0对于大部分应用程序来说是合理的。

3.6.2.3 c_cflag
...............

当设置字符的大小时,记住首先使用‘CSIZE’屏蔽,比如设置8位字符,需要:

         attr.c_cflag &= ~CSIZE;
         attr.c_cflag |= CS8;

在‘c_cflag’里的其它你有可能需要设置为*真*的标志包括‘CREAD’和
‘HUPCL’。

如果你需要产生偶校验,那么设置‘PARENB’并清除‘PARODD’;如果你
需要产生奇校验,那么同时设置‘PARENB’和‘PARODD’。如果你根本不
想设置校验,那么确认清除‘PARENB’。

清除‘CSTOPB’ ,除非你真需要产生两个停止位。

设置硬件流控制的标志可能也能在‘c_cflag’中找到,但它们不是被标准化的(
这是一个遗憾)

3.6.2.4 c_lflag
...............

大部分应用程序可能需要关闭‘ICANON’(标准状态,即基于行的,并进行输
入处理),‘ECHO’和‘ISIG’。

‘IEXTEN’是个更复杂的问题。如果你把它关闭,具体实现允许你作一些非
标准的事情(比如在‘c_cc’中定义增加的控制字符)从而可能导致不可预料的接
果,但是在一些系统上,你可能需要保持‘IEXTEN’标志为真以得到一些有用
的特征,比如硬件流控制。

3.6.2.5 c_cc
............

这是一个包括输入中带有特殊含义字符的数组。这些字符被命名为‘VINTR’,
‘VSTOP’等等;这些名字是这个数组的索引。

(这些字符中的两个其实根本不是字符,而是当‘ICANON’被关闭时对于
‘read()’函数行为的控制;它们是‘VMIN’和‘VTIME’。)

这些索引名字经常被提及的方式会让人以为它们是实在的变量,比如“设置
VMIN为1” 其实意味着“设置c_cc[VMIN]为1”。这种简写是有用的并且只是
偶尔引起误会。

‘c_cc’的很多变量位置只有当其它标志被设定时才会用到。

只有‘ICANON’被设置,才用到以下变量:
     ‘VEOF’,‘VEOL’,‘VERASE’,‘VKILL’(如果定义了而且
     ‘IEXTEN’被设定,那么‘VEOL2’,‘VSTATUS’和‘VWERASE’
        也用到)

只有‘ISIG’被设置,才用到以下变量:
     ‘VINTR’,‘VQUIT’,‘VSUSP’(如果定义了而且‘IEXTEN’被设定,
       那么‘VDSUSP’也用到)

只有‘IXON’或‘IXOFF’被设置,才用到以下变量:
     ‘VSTOP’,‘VSTART’

只有‘ICANON’被取消,才用到以下变量:
     ‘VMIN’,‘VTIME

不同系统实现会定义增加的‘c_cc’变量。谨慎的做法是在设定你希望使用的值
以前,使用‘_POSIX_VDISABLE’初始化这些变量(常量‘NCCS’提供这个数
组的大小)

VMIN’和‘VTIME’(根据不同的实现方法,它们有可能和‘VEOF’和‘VEOL’
分享相同两个变量)具有以下含义。‘VTIME’的值(如果为0)总是被解释为以十
分之一秒为单位的计时器)(译者注:VTIME变量是一个字节长,所以1表示0.1秒,
最大为255,表示25.5秒)

`c_cc[VMIN] > 0, c_cc[VTIME] > 0'
     只要输入已经有VMIN字节长,或者输入至少有一个字符而在读取最后一个字
     符之前VTIME已经过期,或者被信号中断,‘read()’将返回。

`c_cc[VMIN] > 0, c_cc[VTIME] == 0'
     只要输入已经有VMIN字节长,或者被信号中断,‘read()’将返回。否则,将
     无限等待下去。

`c_cc[VMIN] == 0, c_cc[VTIME] > 0'
     只要有输入‘read()’就返回;如果VTIME过期却没有数据,它会返回没有读
     到字符。(这和调制解调器挂断时的文件结束标志有一点冲突;使用1作为VMIN
     调用‘alarm()’或‘select()’函数并给定超时参数可以避免这个问题。)

`c_cc[VMIN] == 0, c_cc[VTIME] == 0'
      ‘read()’总是立刻返回;如果没有数据则返回没有读到字符。(与上面的问题
        相同)

====================================================================

顺便说一下termios中的c_cc域中VTIMEVMIN的一些提示。
假设终端处于raw模式,进程发出了read的系统调用,并等待结果。
如果VTIMEVMIN均大于0.
VTIME表示读取字符间隔的时间,但仅在接收到第一个字符之后才作用。在VTIME超时之前,tty设备的读缓冲区已经有了VMIN个字符,那么read立刻返回。若已经接收到1个字符,VTIME超时,那么read至少能返回一个字符。如果连第一个字符都没有收到,那么进程阻塞。
如果VMIN>0,VTIME=0。
如果读到了VMIN个字符,那么立刻返回。否则进程被阻塞。
如果VMIN=0,VTIME>0。
当收到一个字符或者超时时,read返回。返回值可能为1或者0。
如果VMIN=0,VTIME=0。
那么返回超过read调用所指示的字节数的缓冲区内所有可用字符。如果缓冲区没有字符,那么返回0。

可见如果应用程序想真正收到指定的字节数目还是选择最后一种情况比较好。

另外一点,当调用write写串口时,驱动程序会从用户提供的缓冲区一个字符一个的取然后copy到tty设备驱动的发送缓冲区,当发送缓冲区充满之后或者用户的缓冲区字符拿完之后才去真正的写串口。
所以write时最好把count参数最好设置为1


==================================================================

 

文章评论0条评论)

登录后参与讨论
我要评论
0
10
关闭 站长推荐上一条 /2 下一条