2023年12月6日发(作者:)
1. 串口简介
串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是”数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。
Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux
下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的《Serial Programming Guide for POSIX Operating》。
2. 计算机串口的引脚说明
3. 序号 信号名称 符号 流向 功能
4. 2 发送数据 TXD DTE→DCE DTE发送串行数据
5. 3 接收数据 RXD DTE←DCE DTE接收串行数据
6. 4 请求发送 RTS DTE→DCE DTE请求 DCE 将线路切换到发送方式
7. 5 允许发送 CTS DTE←DCE DCE告诉 DTE 线路已接通可以发送数据
8. 6 数据设备就绪 DSR DTE←DCE DCE 准备好
9. 7 信号地 信号公共地
10. 8 载波检测 DCD DTE←DCE 表示 DCE 接收到远程载波
11. 20 数据终端就绪 DTR DTE→DCE DTE 准备好
22 振铃指示 RI DTE←DCE 表示 DCE 与线路接通,出现振铃
12.串口操作
o
串口操作需要的头文件
o
#include /*标准输入输出定义*/
o
#include /*标准函数库定义*/
o
#include /*Unix 标准函数定义*/
o
#include
o
#include
o
#include /*文件控制定义*/
o
#include /*PPSIX 终端控制定义*/
#include /*错误号定义*/
打开串口
在Linux下串口文件是位于/dev下的串口一为/dev/ttyS0; 串口二为/dev/ttyS1;打开串口是通过使用标准的文件打开函数操作:
o
int fd;
o
/*以读写方式打开串口*/
o o
o
o
o
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){
/* 不能打开串口一*/
perror(" 提示错误!");
}
o
o
o
o
o
o
o
o
设置串口
最基本的设置串口包括波特率设置,效验位和停止位设置。串口的设置主要是设置 struct termios 结构体的各成员值。
struct termio
{ unsigned short c_iflag; /* 输入模式标志 */
unsigned short c_oflag; /* 输出模式标志 */
unsigned short c_cflag; /* 控制模式标志*/
unsigned short c_lflag; /* local mode
flags */
unsigned char c_line; /* line
discipline */
unsigned char c_cc[NCC]; /* control
characters */
};
设置这个结构体很复杂,我这里就只说说常见的一些设置:
波特率设置
下面是修改波特率的代码:
struct termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200); /*设置为19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
设置波特率的例子函数:
/**
*@brief 设置串口通信速率
*@param fd 类型 int 打开串口的文件句柄
*@param speed 类型 int 串口速度
*@return void
*/
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400,
B1200, B300, B38400,
B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400,
1200, 300, 38400,
19200,
9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed){
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for ( i= 0; i < sizeof(speed_arr) /
sizeof(int); i++) {
if (speed == name_arr[i]) {
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt,
speed_arr[i]);
cfsetospeed(&Opt,
speed_arr[i]);
status = tcsetattr(fd1,
TCSANOW, &Opt);
if (status != 0) {
perror("tcsetattr
fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
效验位和停止位的设置:
无效验 8位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag = ~CS8;
奇效验(Odd) 7位 Option.c_cflag = ~PARENB;
Option.c_cflag &= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag = ~CS7;
偶效验(Even) 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag = ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag = ~CS7;
Space效验 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= &~CSIZE;
Option.c_cflag = CS8;
设置效验的函数:
/**
*@brief 设置串口数据位,停止位和效验位
*@param fd 类型 int 打开的串口文件句柄
*@param databits 类型 int 数据位 取值 为 7 或者8
*@param stopbits 类型 int 停止位 取值为 1 或者2
*@param parity 类型 int 效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int
parity)
{
struct termios options;
if ( tcgetattr( fd,&options) != 0) {
perror("SetupSerial 1");
return(FALSE);
}
options.c_cflag &= ~CSIZE;
switch (databits) /*设置数据位数*/
{
case 7:
options.c_cflag = CS7;
break;
case 8:
options.c_cflag = CS8;
break;
default:
fprintf(stderr,"Unsupported data
sizen"); return (FALSE);
}
switch (parity)
{
case ''n'':
case ''N'': options.c_cflag &= ~PARENB; /*
Clear parity enable */
options.c_iflag &= ~INPCK; /*
Enable parity checking */
break;
case ''o'':
case ''O'':
options.c_cflag = (PARODD
PARENB); /* 设置为奇效验*/
options.c_iflag = INPCK;
/* Disnable parity checking */
break;
case ''e'':
case ''E'':
options.c_cflag = PARENB; /*
Enable parity */
options.c_cflag &= ~PARODD; /* 转换为偶效验*/
options.c_iflag = INPCK; /*
Disnable parity checking */
break;
case ''S'':
case ''s'': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;break;
default:
fprintf(stderr,"Unsupported
parityn");
return (FALSE);
}
/* 设置停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag = CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop
bitsn");
return (FALSE);
} /* Set input parity option */
if (parity != ''n'')
options.c_iflag = INPCK;
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/
options.c_cc[VMIN] = 0; /* Update the options and do
it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}
需要注意的是:如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:
options.c_lflag &= ~(ICANON ECHO ECHOE ISIG);
/*Input*/
options.c_oflag &= ~OPOST; /*Output*/
读写串口
设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。
发送数据
char buffer[1024];
int Length;
int nByte;
nByte = write(fd, buffer ,Length);
读取串口数据
使用文件操作read函数读取,如果设置为原始模式(Raw
Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。
char buff[1024];
int Len;
int readByte = read(fd,buff,Len);
关闭串口
关闭串口就是关闭文件。 close(fd);
13.例子:下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件
14. /**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,
15. 但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。
16. **********************************************************************/
17. #define FALSE -1
18. #define TRUE 0
19. /*********************************************************************/
20. int OpenDev(char *Dev)
21. {
22. int fd = open( Dev, O_RDWR ); // O_NOCTTY
O_NDELAY
23. if (-1 == fd)
24. {
25. perror("Can''t Open Serial Port");
26. return -1;
27. }
28. else
29. return fd;
30. }
31. int main(int argc, char **argv){
32. int fd;
33. int nread;
34. char buff[512];
35. char *dev = "/dev/ttyS1"; //串口二
36. fd = OpenDev(dev);
37. set_speed(fd,19200);
38. if (set_Parity(fd,8,1,''N'') == FALSE) {
39. printf("Set Parity Errorn");
40. exit (0);
41. }
42. while (1) //循环读取数据
43. {
44. while((nread = read(fd, buff, 512))>0)
45. {
46. printf("nLen %dn",nread);
47. buff[nread+1] = '''';
48. printf( "n%s", buff); 49. }
50. }
51. //close(fd);
52. // exit (0);
}
53.串口配置参数详解(termios)
大多数系统都支持POSIX终端接口,POSIX终端通过一个termios结构来进行控制,该结构定义在termios.h文件中。termios结构如下:
54. struct termios
55. {
56. tcflag_t c_iflag; /* 输入选项标志 */
57. tcflag_t c_oflag; /* 输出选项标志 */
58. tcflag_t c_cflag; /* 控制选项标志 */
59. tcflag_t c_lflag; /* 本地选项标志 */
60. cc_t c_cc[NCCS]; /* 控制特性 */
};
c_iflag成员:
Flag Description
IGNBRK 忽略输入中的BREAK状态
如果设置了IGNBRK,将忽略BREAK。如果没有设置,但是设置了
BRKINT,那么BREAK将使得输入和输出队列被刷新,如果终端是一个前台进程组的控制终端,这个进程组中所有进程将收到BRKINT
SIGINT信号。如果既未设置IGNBRK也未设置BRKINT,BREAK将视为NUL同义字符,除非设置了PARMRK,这种情况下被视为序列377
IGNPAR 忽略桢错误和奇偶校验错误
如果没有设置IGNPAR,在有奇偶校验错误或者桢错误的字符前插PARMRK 入377。如果既没有设置IGNPAR也没有设置PARMRK,将所有奇偶校验错误或者桢错误的字符视为
INPCK 启用输入奇偶校验检测
ISTRIP 去掉第八位
INLCR 将输入的NL翻译为CR
IGNCR 忽略输入中的回车
ICRNL 将输入中的回车翻译为新行字符(除非设置了IGNCR)
IUCLC (不属于POSIX)将输入中的大写字母映射为小写字母
IXON 启用输出的XON/XOFF流控制
IXANY (不属于POSIX。1;XSI)允许任何字符来重新开始输出
IXOFF 启用输入的XON/XOFF流控制
IMAXBEL (不属于POSIX)当输入队列满时响铃。LINUX没有实现该位,总是将其视为已设置
c_oflag成员
Flag Description
OPOST 启用具体实现自行定义的输出
OLCUC (不属于POSIX)将输出中的小写字母映射为大写字母
ONLCR (XSI)将输出中的新行符映射为回车-换行
OCRNL 将输出中的回车映射为新行符
ONOCR 不在第0列输出回车
ONLRET 不输出回车
OFILL 发送填充字符作为延时
OFDEL
(不属于POSIX)填充字符是ASCII DEL(0177)。如果不设置填充字符则是ASCII NUL
NLDLY 新行延时掩码。取值为NL0和NL1
CRDLY 回车延时掩码。取值为CR0,CR1,CR2或CR3
水平跳格延时掩码。取值为TAB0,TAB1,TAB2,TAB3(或XTABS)。TABDLY 取值为TAB3,即XTABS,将扩展跳格为空格(每个跳格符填充8个空格)
BSDLY 回车延时掩码。取值为BS0或BS1.(从来没有被实现)
VTDLY 竖直跳格掩码。取值为VT0或VT1
FFDLY 进表延时掩码。取值为FF0或者FF1
c_cflag成员
Flag Description
CBAUD (不属于POSIX)波特率掩码(4+1位)
CBAUDEX (不属于POSIX)扩展的波特率掩码(1位),包含在CBAUD中
CSIZE 字符长度掩码。取值为CS5,CS6,CS7或CS8
CSTOPB 设置两个停止位
CREAD 打开接受者
PARENB 允许输出产生奇偶信息以及输入的奇偶校验
PARODD 输入和输出是奇校验
HUPCL 在最后一个进程关闭设备后,降低MODEM控制线(挂断)
CLOCAL 忽略MODEM控制线
LOBLK (不属于POSIX)从非当前SHELL层阻塞输出(用于sh1)
CIBAUD
(不属于POSIX)输入速度的掩码。CIBAUD各位的值与CBAUD各位相同,左移了IBSHIFT位
CRTSCTS (不属于POSIX)启用RTS/CTS(硬件)控制流 c_lflag成员
Flag
ISIG
Description
当接收到字符INTR,QUIT,SUSP或DSUSP时,产生相应的信号
(不属于POSIX;LINUX下不支持)如果同时设置了ICANON,终XCASE 端只有大写。输入被转换为小写,除了以前缀的字符。输出时,大写字符被前缀,小写字符被转换成大写
ECHO
ECHOE
回显输入字符
如果同时设置了ICANON,字符ERASE擦除前一个输入字符,WERASE擦除前一个词
ECHOK 如果同时设置了ICANON,字符KILL删除当前行
ECHONL 如果同时设置了ICANON,回显字符NL,即使没有设置ECHO
(不属于POSIX)如果同时设置了ECHO,除了TAB,NL,STARTECHOCTL 和STOP之外的ASCII控制信号被回显为x,这里X是比控制信号大0x40的ASCII码。例如字符0x08(BS)被回显为H
(不属于POSIX)如果同时设置了ICANON和IECHO,字符在删除ECHOPRT
的同时被打印
ECHOKE
(不属于POSIX)如果同时设置了ICANON,回显KILL时将删除一行中的每个字符,如同指定了ECHOE和ECHORPT一样
(不属于POSIX;LINUX不支持)输出被刷新。这个标志可以通过键入字符DISCARD来打开和关闭
禁止产生SIGINT,SIGQUIT和SIGSUSP信号时刷新输入和输出队列
(不属于POSIX;LINUX不支持)在读入一个字符时,输入队列中的所有字符被重新输出。(bash用他来处理typeahead)
DEFECHO (不属于POSIX)只在一个进程读的时候回显
FLUSHO
NOFLSH
TOSTOP 向试图写控制终端的后台进程组发送SIGTTOU信号
PENDIN
启用实现自定义的输入处理。这个标志必须与ICANON同时使用,IEXTEN 才能解释特殊字符EOL2,LNEXT,REPRINT和WERASE,IUCLC标志才有效
c_cc数组成员
略(这个表太长了,需要查询的话网上有很多).
61.常用设置
设置规范模式
规范模式是面向行的输入方式,输入字符被放入用于和用户交互可以编辑的缓冲区内,直接到读入回车或者换行符号时才结束。可以通过如下方式来设置:
option.c_lflag |= (ICANON | ECHO | ECHOE); 设置原始输入模式
原始输入模式是没有处理过的,当接收数据时,输入的字符在它们被接收后立即被传送,使用原始输入模式时候,一般可以选择取消ICANON,ECHO,ECHOE和ISIG选项。例如:
option.c_lflag &= ~(ICANON | ECHO | ECHOE);
设置输入奇偶选项
当激活c_cflag中的奇偶校验后,应该激活输入的奇偶校验。与之相关的标志有INPCK,IGNPAR,PARMRK和ISTRIP。一般是通过选择INPCK和ISTRIP激活检验和移除奇偶位。例如:
option.c_iflag |= (INPCK | ISTRIP);
设置软件控制流
软件控制流通过IXON,IXOFF和IXANY标志来设置.例如:
option.c_iflag |=(IXON | IXOFF | IXANY);
选择预处理输出
通过OPOST标志来设置预处理的输出。例如:
option.c_oflag |= OPOST;
选择原始数据输出
原始数据的输出通过设置c_oflag的OPOST标志。例如:
option.c_oflag &= ~OPOST;
设置软件流控制字符
软件流控制字符是通过c_cc数组中的VSTART和VSTOP来设置的,一般来说,它们应该被设置城DC1(021八进制)和DC3(023八进制),分别表示ASCII码的XON和XOFF字符。
设置读超时
c_cc 数组中的VMIN指定了最少读取的字符数,如果设置为0,那么VTIME
就指定了读取每个字符的等待时间。VTIME是以1/10秒为单位指定接收字符的超时时间的,如果VTIME设置为0,而端口没有用open或者 fcntl设置为NONBLOCK,那么read操作将会阻塞不确定的时间
62.网上找到的另一串口设置程序
设置波特率
63. int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200,
B300,
64. B38400, B19200, B9600, B4800, B2400, B1200,
B300, };
65. int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300,
66. 38400, 19200, 9600, 4800, 2400, 1200, 300, };
67. void set_speed(int fd, int speed)
68. { 69. int i;
70. int status;
71. struct termios Opt; //定义了这样一个结构
72.
73. tcgetattr(fd, &Opt); //用来得到机器原端口的默认设置
74. for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
75. {
76. if (speed == name_arr) //判断传进来是否相等
77. {
78. tcflush(fd, TCIOFLUSH); //刷新输入输出缓冲
79. cfsetispeed(&Opt, speed_arr);//这里分别设置
80. cfsetospeed(&Opt, speed_arr);
81. //这是立刻把bote
rates设置真正写到串口中去
82. status = tcsetattr(fd, TCSANOW, &Opt);
83. if (status != 0)
84. perror("tcsetattr fd1"); //设置错误
85. return;
86. }
87. tcflush(fd,TCIOFLUSH); //同上
88. }
}
设置数据位、校验位和停止位
int set_Parity(int fd,int databits,int stopbits,int parity)
{
struct termios options; //定义一个结构
if ( tcgetattr( fd,&options) != 0) //首先读取系统默认设置options中,必须
{
perror("SetupSerial 1");
return(FALSE);
}
options.c_cflag &= ~CSIZE; //这是设置c_cflag选项不按位数据位掩码
switch (databits){ /*设置数据位数*/
case 7:
options.c_cflag |= CS7; //设置c_cflag选项数据位为7位
break; case 8:
options.c_cflag |= CS8; //设置c_cflag选项数据位为8位
break;
default:
fprintf(stderr,"Unsupported data sizen"); //其他的都不支持
return (FALSE);
}
switch (parity){ //设置奇偶校验,c_cflag和c_iflag有效
case 'n':
case 'N': //无校验 当然都不选
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking
*/
break;
case 'o': //奇校验 其中PARENB校验位有效;PARODD奇校验
case 'O': options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
options.c_iflag |= INPCK; /* Disnable parity
checking */
break;
case 'e':
case 'E': //偶校验,奇校验不选就是偶校验了
options.c_cflag |= PARENB;/* Enable parity */
options.c_cflag &= ~PARODD;/* 转换为偶效验*/
options.c_iflag |= INPCK; /* Disnable parity
checking */
break;
default:
fprintf(stderr,"Unsupported parityn");
return (FALSE);
}
/* 设置停止位*/
switch (stopbits){ //这是设置停止位数,影响的标志是c_cflag
case 1:
options.c_cflag &= ~CSTOPB; //不指明表示一位停止位
break; case 2:
options.c_cflag |= CSTOPB; //指明CSTOPB表示两位,只有两种可能
break;
default:
fprintf(stderr,"Unsupported stop bitsn");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n') //这是设置输入是否进行校验
options.c_iflag |= INPCK;
// 这个地方是用来设置控制字符和超时参数的,一般默认即可。稍微要注意的是c_cc数组的VSTART 和 VSTOP 元素被设定成DC1 和 DC3,代表ASCII 标准的XON和XOFF字符。所以如果在传输这两个字符的时候就传不过去,这时需要把软件流控制屏蔽 options.c_iflag &= ~(IXON |
IXOFF | IXANY);
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH); //刷新和立刻写进去
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}


发布评论