我是C和驱动程序编程的新手。目前,我正在使用Debian编程用户空间驱动程序以通过USB与RS232通信。在研究时,我遇到了以下一些代码。
tty.c_cflag &= ~PARENB; // No Parity
tty.c_cflag &= ~CSTOPB; // 1 Stop Bit
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; // 8 Bits
我理解这些行的后果,但是,如果每个控制标志常量(PARENB,CSTOPB等)与这些标志组合的长度相同,则这些操作才有意义。我似乎无法通过任何文档验证这一点(我迄今为止对C的主要不满之一,更难找到易于理解的文档。)来证实这一点。
我想确保我正确地理解程序,因为它是一种纯粹的归纳方法,我不确定为什么这些标志会被存储。有人可以验证这些发现,或指出我可能会忽略的东西吗?
实施例。
tty.c_cflag hypothetically is 4-bits long, each of the flags from the
previous code block corresponding to bits 3, 2, 1, 0. Then I believe the
following is how these are stored, if we were to say flags PARENB (3) and
CSTOPB (2) are high, and the other two flags are disabled.
tty.c_cflag = 1100
PARENB = 1000
CSTOPB = 0100
CSIZE = 0000
CS8 = 0000
答案 0 :(得分:6)
在C中,您可以找到的最佳文档是源代码本身,您可以在计算机上找到/usr/include/termios.h
(实际上分布在其中的一个或多个包含中) - 这是{ {3}}我根据你的答案,价值可能会根据你的Unix风格而改变。
在那里,您会发现您的tty
对象属于struct termios
类型,定义如下:
struct termios {
tcflag_t c_iflag; /* input flags */
tcflag_t c_oflag; /* output flags */
tcflag_t c_cflag; /* control flags */
tcflag_t c_lflag; /* local flags */
cc_t c_cc[NCCS]; /* control chars */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
所以c_cflag
的类型为tcflag_t
,由以下行定义:
typedef unsigned long tcflag_t;
预计unsigned long
为8个字节,即32位。
然后,您在示例中使用的所有标志的定义如下;使用8个字节的值:
#define PARENB 0x00001000 /* parity enable */
#define CSTOPB 0x00000400 /* send 2 stop bits */
#define CSIZE 0x00000300 /* character size mask */
#define CS8 0x00000300 /* 8 bits */
话虽如此,它的工作方式是c_cflag
用作位数组,这意味着每个位对于函数都很重要。这是一种常用的方法,因为位操作在处理能力方面“便宜”(您的CPU可以在一个周期内执行一些操作),而在内存空间中“便宜”,而不是使用32个布尔值的数组来存储值(一个大小为1字节的布尔类型来存储一个二进制值),你可以为每个字节存储8个二进制值。
另一个优点和优化是,因为您的CPU至少为32位,并且在2015年可能为64位,所以它可以在一个CPU周期中对32个值应用掩码。
位掩码的替代表示是创建如下所示的结构:
struct tcflag_t {
bool cignore;
uint8_t csize;
bool cstopb;
bool cread;
bool parenb;
bool hupcl;
bool clocal;
bool ccts_oflow;
bool crts_iflow;
bool cdtr_iflow;
bool ctdr_oflow;
bool ccar_oflow;
};
这将是12个字节。要改变它们,你必须做12次操作。
然后,您可以对字节执行的操作遵循布尔逻辑,该逻辑由真值表定义:
And(&
),Or(|
)和Not(~
)真值表:
| a | b | & | | a | b | | | | a | ~ |
| - | - | - | | - | - | - | | 0 | 1 |
| 0 | 0 | 0 | | 0 | 0 | 0 | | 1 | 0 |
| 0 | 1 | 0 | | 0 | 1 | 1 |
| 1 | 0 | 0 | | 1 | 0 | 1 |
| 1 | 1 | 1 | | 1 | 1 | 1 |
我们通常将和运算符昵称为“强制为零”,将或运算符称为“强制为1”,因为
除非两个值均为1
,否则和将生成0
,除非两个值均为0
,否则或将
结果为1
。
因此,如果我们考虑tty.c_cflag = 0x00000000
并且您想要启用奇偶校验:
tty.c_cflag |= PARENB;
然后tty.c_cflag
将包含0x00001000
,即0b1000000000000
然后你想设置7位大小:
tty.c_cflag |= CS7;
和tty.c_cflag
将包含0x00001200
,即0b1001000000000
现在,让我们回到您的问题:您的“等效”示例并不具有代表性,因为您正在考虑CSIZE
和CS8
不包含任何值。
让我们来看看你从例子中得到的代码:
tty.c_cflag &= ~PARENB; // No Parity
tty.c_cflag &= ~CSTOPB; // 1 Stop Bit
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; // 8 Bits
此处,tty.c_cflag
包含未知值:
0b????????????????????????????????
而且您知道您不需要奇偶校验,一个停止位和8位数据大小。所以,你在这里 否定“设置奇偶校验”值以将其关闭:
~PARENB == 0b0111111111111
然后使用 And 运算符,您将该位强制为零:
tty.c_cflag &= ~PARENB —→ 0b???????????????????0????????????
然后您对CSTOPB
执行相同的操作:
tty.c_cflag &= ~CSTOPB —→ 0b???????????????????0?0??????????
最后CSIZE
:
tty.c_cflag &= ~CSIZE —→ 0b???????????????????0?000????????
对于CSIZE
,目标是确保重置数据长度的两位值。
然后通过强制1
值:
tty.c_cflag |= CS8 —→ 0b???????????????????0?011????????
实际上,将CSIZE
重置为00
然后将CS8
设置为11
是没有用的,
直接执行tty.c_cflag |= CS8
会使其成为11
。但这是一种很好的做法
你想要从CS8
更改为CS7
,然后只设置两个位中的一个,即
另一个保持原值。
最后,当您打开串口时,库会检查这些值 配置端口,并对未强制使用的所有其他值使用默认值 你将能够使用你的串口。
我希望我的解释可以帮助您更好地了解标志设置的情况 在串口上,以及完全使用bitmasks。仅供参考,原则同样如此 用于许多其他事情,例如IPv4网络掩码,文件I / O等。
答案 1 :(得分:2)
宏的实际值取决于平台(例如,在Linux CSTOPB
上定义为0100
,而在某些BSD上定义为02000
)。这就是为什么你不应该对它们的确切值做出假设。
例如,CSIZE
和CS8
具有相同的值确实很常见,但在某些平台上它们可能没有,因此您首先使用CSIZE
掩码的补码进行AND (它将影响字符大小的所有位设置为零),然后在这些位的值中设置OR。如果你假设CS8
与掩码的模式相同,你可以省略第一步,但是代码会做错误的事情,并且在一个没有任何警告的情况下以一种非常模糊的方式在一个平台上这个假设没有成立。
这里PARENB
和CSTOPB
是单独的位标志(恰好是一个1位),可以使用按位OR |
进行设置,并通过按位AND运算{{1他们的补充&
。同时,包括~
在内的字符大小可以包含任意数量的1位,包括零 - 它们更像是存储在较大整数的特定位中的小整数。 CS8
是一个掩码,在所有表示字符大小的地方都有1位(CSIZE
,CS5
,CS6
,CS7
中的任意一个 - 这个掩码可用于精确提取字符大小(例如,测试CS8
),或在设置之前清除它(如if ((tty.c_flag & CSIZE) == CS8)
的情况)。