1.串口简介
串行通信接口(简称“串口”)是计算机一种常用的接口,因其连接线少、通讯简单的特点而得到广泛使用。常用的串口是 RS-232-C接口(又称EIA RS-232-C),它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准,全称为“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。该标准规定采用一个25个脚的DB25 连接器,对连接器的每个引脚的信号内容加以规定,并且还对各种信号的电平加以规定。传输距离在码元畸变小于4%的情况下,传输电缆长度应为50英尺(即15.24米)。
Linux 操作系统从一开始就对串行口提供了很好的支持,下表为计算机串口的引脚说明。
序号 | 信号名称 | 符号 | 流向 | 功能 |
2 | 发送数据 | TXD | DTE→DCE | DTE发送串行数据 |
3 | 接收数据 | RXD | DTE←DCE | DTE接收串行数据 |
4 | 请求发送 | RTS | DTE→DCE | DTE请求DCE将线路切换到发送方式 |
5 | 允许发送 | CTS | DTE←DCE | DCE告诉DTE线路已接通可以发送数据 |
6 | 数据设备准备好 | DSR | DTE←DCE | DCE准备好 |
7 | 信号地 |
|
| 信号公共地 |
8 | 载波检测 | DCD | DTE←DCE | 表示DCE接收到远程载波 |
20 | 数据终端准备好 | DTR | DTE→DCE | DTE准备好 |
22 | 振铃指示 | RI | DTE←DCE | 表示DCE与线路接通,出现振铃 |
2.串口操作
(1)串口头文件:以下是串口操作需要的头文件。
#include <stdio.h> /*标准输入输出*/
#include <string.h> /*标准字符串处理*/
#include <stdlib.h> /*标准函数库*/
#include <unistd.h> /*Unix标准函数*/
#include <sys/types.h> /*数据类型定义,比如一些XXX_t的类型*/
#include <sys/stat.h> /*返回值结构*/
#include <fcntl.h> /*文件控制*/
#include <termios.h> /*PPSIX终端控制*/
#include <errno.h> /*错误号*/
(2)串口打开:Linux串口文件位于/dev目录下,串口0为/dev/ttyS0,串口1为/dev/ttyS1,打开串口需要使用标准的文件打开函数open。
/*串口打开*/
int open_dev(char *Dev)
{
int fd = 0;
fd = open(Dev, O_RDWR|O_NOCTTY| O_NDELAY);
if(-1 == fd)
{
perror("open COM error");
return -1;
}
return fd;
}
(3)串口设置:最基本的串口设置包括波特率、校验位和停止位,主要是设置结构体struct termios各成员的值,以下是结构体struct termios组成。
struct termio
{
tcflag_t c_iflag; /*输入模式标志,unsigned short*/
tcflag_t c_oflag; /*输出模式标志,unsigned short*/
tcflag_t c_cflag; /*控制模式标志,unsigned short*/
tcflag_t c_lflag; /*本地模式标志,unsigned short*/
cc_t c_line; /*线路规程,unsigned char*/
cc_t c_cc[NCCS]; /*控制字符,unsigned char*/
speed_t c_ispeed; /*输入波特率,unsigned int*/
speed_t c_ospeed; /*输出波特率,unsigned int*/
};
波特率设置。
/*波特率设置*/
struct termios Opt = {0};
tcgetattr(fd, &Opt); /*获得当前设备模式与终端相关的参数,fd=0标准输入*/
cfsetispeed(&Opt, B19200); /*设置结构termios输入波特率为19200Bps*/
cfsetospeed(&Opt, B19200); /*设置结构termios输出波特率为19200Bps*/
tcsetattr(fd, TCANOW, &Opt); /*设置终端参数,TCANOW修改立即发生*/
/*设置串口通信速率*/
int set_speed(int fd, int speed)
{
int index = 0;
int status = 0;
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,};
struct termios Opt = {0};
tcgetattr(fd, &Opt);
for(index = 0; index < sizeof(speed_arr)/sizeof(int); index++)
{
if(speed == name_arr[index])
{
/*tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或输出缓
存(用户程序已经写,但尚未发送)。queue参数应是下列三个常数之一:TCIFLUSH刷清输入
队列,TCOFLUSH刷清输出队列,TCIOFLUSH刷清输入输出队列。*/
/*设置前flush*/
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[index]);
cfsetospeed(&Opt, speed_arr[index]);
/*tcsetattr(串口描述符, 立即使用或者其他标示, 指向termios的指针),通过
tcsetattr函数把新的属性设置到串口上。*/
status = tcsetattr(fd, TCSANOW, &Opt);
if(0 != status)
{
perror("tcsetattr COM error");
return -1;
}
/*设置后flush*/
tcflush(fd, TCIOFLUSH);
return 0;
}
}
return 0;
}
校验位和停止位设置。
/*校验位和停止位设置*/
struct termios Opt = {0};
/*无校验,8位*/
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= ~CS8;
/*奇校验(Odd),7位*/
Opt.c_cflag |= ~PARENB;
Opt.c_cflag &= ~PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= ~CS7;
/*偶校验(Even),7位*/
Opt.c_cflag &= ~PARENB;
Opt.c_cflag |= ~PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= ~CS7;
/*Space校验,7位*/
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= &~CSIZE;
Opt.c_cflag |= CS8;
/*设置数据位、停止位和校验位*/
int set_parity(int fd, int databits, int stopbits, int parity)
{
struct termios Opt = {0};
if(0 != tcgetattr(fd, &Opt))
{
perror("tcgetattr COM error");
return -1;
}
/*设置数据位,取值为7或8*/
Opt.c_cflag &= ~CSIZE;
switch(databits)
{
case 7:
Opt.c_cflag |= CS7;
break;
case 8:
Opt.c_cflag |= CS8;
break;
default:
fprintf(stderr, "Unsupported data size\n");
return -1;
break;
}
/*设置停止位,取值为1或2*/
switch(stopbits)
{
case 1:
Opt.c_cflag &= ~CSTOPB;
break;
case 2:
Opt.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return -1;
break;
}
/*设置校验位,取值为E,N,O,S*/
switch(parity)
{
case 'e':
case 'E':
{
Opt.c_cflag |= PARENB; /*Enable parity*/
Opt.c_cflag &= ~PARODD; /*转换为偶效验*/
Opt.c_iflag |= INPCK; /*Disnable parity checking*/
}break;
case 'n':
case 'N':
{
Opt.c_cflag &= ~PARENB; /*Clear parity enable*/
Opt.c_iflag &= ~INPCK; /*Enable parity checking*/
}break;
case 'o':
case 'O':
{
Opt.c_cflag |= (PARODD | PARENB); /*设置为奇效验*/
Opt.c_iflag |= INPCK; /*Disnable parity checking*/
}break;
case 's': /*as no parity*/
case 'S': /*as no parity*/
{
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
}break;
default:
fprintf(stderr, "Unsupported parity\n");
return -1;
break;
}
/*设置结构体输入校验位*/
if('n' != parity)
{
Opt.c_iflag |= INPCK;
}
tcflush(fd, TCIFLUSH);
Opt.c_cc[VTIME] = 150; /*设置超时15秒*/
Opt.c_cc[VMIN] = 0; /*更新结构体并立即执行*/
if(0 != tcsetattr(fd, TCSANOW, &Opt))
{
perror("tcsetattr COM error");
return -1;
}
return 0;
}
如果不是开发终端之类的,串口仅用于传输数据,不需要处理数据,可将串口设置为原始模式(Raw Mode)进行通讯。
/*原始模式设置*/
struct termios Opt = {0};
Opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
Opt.c_oflag &= ~OPOST; /*Output*/
(4)串口读写:读写串口需要使用标准的文件读写函数read和write。
(5)串口关闭:关闭串口需要使用标准的文件关闭函数close。
3.demo
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
int fd = 0;
int nread = 0;
char buff[512] = {0};
char *dev = "/dev/ttyS0"; /*串口0*/
fd = open_dev(dev);
set_speed(fd, 19200);
set_parity(fd, 8, 1, 'N');
while(1)
{
nread = read(fd, buff, 512);
buff[nread+1] = '\0';
printf("fd:%d, nread:%d, buff:%s\n", fd, nread, buff);
}
close(fd);
return 0;
}
4.其他
(1)虚拟机下使用串口的方法:VMware默认串口设备是未添加的,通过VMware将设备加入即可正常使用串口。虚拟机串口打开后,可能会占用Windows下的串口。此外,虚拟机的串口收发速度比正常速度要慢很多。
(2)消除Linux串口收发的一些规则
Linux串口收发有许多模式,比如:
①接收返回模式,如果串口没有接收到数据,read()函数不返回;
②数据接收\n才返回接收的数据,否则read()函数返回0;
③特殊字符解析问题,部分特殊字符接收或发送时会被屏蔽或者转义;
④接收反馈,串口接收到数据后会立即将该数据发送出去。
解决方法是:将串口参数初始化,可消除默认规则,如果需要保留部分参数,参考如下。
struct termios Opt;
/*消除收发模式规则*/
Opt.c_lflag = 0;
Opt.c_oflag = 0;
Opt.c_iflag = 0;
/*消除字符屏蔽规则*/
Opt.c_cc[VINTR] = 0; /* Ctrl-c*/
Opt.c_cc[VQUIT] = 0; /* Ctrl-\ */
Opt.c_cc[VERASE] = 0; /* del */
Opt.c_cc[VKILL] = 0; /* @ */
Opt.c_cc[VEOF] = 4; /* Ctrl-d */
Opt.c_cc[VTIME] = 5; /* inter-character timer, timeout VTIME*0.1 */
Opt.c_cc[VMIN] = 0; /* blocking read until VMIN character arrives */
Opt.c_cc[VSWTC] = 0; /* '\0' */
Opt.c_cc[VSTART] = 0; /* Ctrl-q */
Opt.c_cc[VSTOP] = 0; /* Ctrl-s */
Opt.c_cc[VSUSP] = 0; /* Ctrl-z */
Opt.c_cc[VEOL] = 0; /* '\0' */
Opt.c_cc[VREPRINT] = 0; /* Ctrl-r */
Opt.c_cc[VDISCARD] = 0; /* Ctrl-u */
Opt.c_cc[VWERASE] = 0; /* Ctrl-w */
Opt.c_cc[VLNEXT] = 0; /* Ctrl-v */
Opt.c_cc[VEOL2] = 0; /* '\0' */