对串口的操作一般分为四步:打开串口、设置串口、读写串口和关闭串口。Linux下,可以通过设备文件访问串口只需要open()相应的设备文件即可。
串口的设备文件名:
1.直接连接的串口,串口的设备文件为/dev/ttyS0、/dev/ttyS1等等,S0对应串口1,以此类推。
2.通过USB转串口连接,设备文件为/dev/ttyUSB0
$dmesg|grep ttyS* 命令可以显示系统串口拔插信息。
需要注意的是,普通用户不能打开设备文件。可以提升shell的权限$sudo -s,这样就使shell拥有了超级权限。$sudo su username 可以退回普通权限。
打开串口:
{
int fd = open( Dev, O_RDWR );
if (-1 == fd)
{
perror("Can't Open Serial Port");
return -1;
}
else
return fd;
}
设置串口:设置串口的关键在于termios结构体的设置。termios的详细文档在这个链接。
termios函数族提供了一个常规的终端接口,用于控制非同步通信端口。这个结构体包含了至少下列成员:
tcflag_t c_iflag; /* 输入模式 */
tcflag_t c_oflag; /* 输出模式 */
tcflag_t c_cflag; /* 控制模式 */
tcflag_t c_lflag; /* 本地模式 */
cc_t c_cc[NCCS]; /* 控制字符 */
设置串口通信速率:
/* 设置串口通信速率
fd 类型 int 打开串口的文件句柄
speed 类型 int 串口速度
*/
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(fd, TCSANOW, &Opt);
if (status != 0)
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
设置串口停止位、数据位、校验位:
/*
设置串口数据位,停止位和效验位
fd 类型 int 打开的串口文件句柄
databits 类型 int 数据位 取值 为 7 或者8
stopbits 类型 int 停止位 取值为 1 或者2
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 size\n");
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 parity\n");
return (FALSE);
}
/* 设置停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}
读写串口:
while(1)
{
while((nread = read(fd,buff,512))>0)
{
<span style="white-space:pre"> </span>for(i=0;i<nread;i++)
{
printf("%3x",buff[i]);
}
<span style="white-space:pre"> </span>printf("\n");
<span style="white-space:pre"> </span>write(fd,buff,nread);
}
}
关闭串口:
close(fd)
在编程过程中出现的问题:
1.多串口同时读写操作。
多串口读写需要在打开设备文件后设置为非阻塞型读写。
fd = OpenDev(dev);
fcntl(fd,F_SETFL,FNDELAY); //非阻塞
2.发送0x00~0xff全部十六进制数。
(1)收到05 06 07 08 09 0a
修改终端I/O的输入处理模式为非规范输入方式。规范方式输入处理中,终端输入以行为单位进行处理,0x0a即'/n',相当于按下了enter键,本行才输出。
Opt.c_lflag &= ~(ICANON); //非规范方式输入
(2)收到05 06 07 08 09 0a 0b 0c 0a 0e ...
0x05之前的数据都相当于特殊的控制字符,所以要去除。
Opt.c_lflag &= ~(ICANON|ISIG); //非规范方式输入、去除ISIG
(3)收到01 02 03 ... 0a 0b 0c 0a 0e 0f...
window下的回车编码为“0x0d 0x0a”,而Linux下的回车编码为“0x0a“。所以要取消回车与新行符的映射。
Opt.c_iflag &= ~(INLCR|ICRNL|IGNCR);//取消映射 关闭软件流控制
Opt.c_oflag &= ~(ONLCR|OCRNL); //取消映射
(4)0x11、0x13收不到
0x11和0x13是软件流控制符。
Opt.c_iflag &= ~(INLCR|ICRNL|IGNCR|IXON|IXOFF);//取消映射 关闭软件流控制
(5)从0x80开始往后,数据printf为ffffff80 。定义字符是应定义为无符号型。
// char buff[512];
unsigned char buff[512];
(6)完整接收0x00到0xff。
3.串口read一次最多读8个字节。
与串口的缓存有关,一般一次最多缓存8个字节。
最后附上完整的双串口读写程序:
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix标准函数定义*/
#include <sys/types.h> /**/
#include <sys/stat.h> /**/
#include <fcntl.h> /*文件控制定义*/
#include <termios.h> /*PPSIX终端控制定义*/
#include <errno.h> /*错误号定义*/
/***@brief 设置串口通信速率
*@param fd 类型 int 打开串口的文件句柄
*@param speed 类型 int 串口速度
*@return void*/
#define FALSE -1
#define TRUE 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, };
void set_speed(int fd, int speed)
{
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
Opt.c_lflag &= ~(ICANON|ISIG); //非规范方式输入
Opt.c_iflag &= ~(INLCR|ICRNL|IGNCR);
Opt.c_oflag &= ~(ONLCR|OCRNL); //取消映射
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(fd, TCSANOW, &Opt);
if (status != 0)
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
/**
*@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 size\n");
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 parity\n");
return (FALSE);
}
/* 设置停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
/* options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag &= ~(INLCR | ICRNL | IGNCR);
options.c_oflag &= ~(ONLCR | OCRNL);
*/
return (TRUE);
}
/**
*@breif 打开串口
*/
int OpenDev(char *Dev)
{
int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY
if (-1 == fd)
{ /*设置数据位数*/
perror("Can't Open Serial Port");
return -1;
}
else
return fd;
}
/**
*@breif main()
*/
int main(int argc, char **argv)
{
int fd_1,fd_2;
int i;
int nread_1,nread_2;
char buff_1[512],buff_2[512];
char *dev_1="/dev/ttyS0";
char *dev_2="/dev/ttyUSB0";
fd_1 = OpenDev(dev_1);
fd_2 = OpenDev(dev_2);
fcntl(fd_1,F_SETFL,FNDELAY);
fcntl(fd_2,F_SETFL,FNDELAY); //设置为非阻塞型
if (fd_1>0)
set_speed(fd_1,19200);
else
{
printf("Can't Open Serial Port 1!\n");
exit(0);
}
if (set_Parity(fd_1,8,1,'N')== FALSE)
{
printf("Set Parity 1 Error\n");
exit(1);
}
if (fd_2>0)
set_speed(fd_2,19200);
else
{
printf("Can't Open Serial Port 2!\n");
exit(0);
}
if (set_Parity(fd_2,8,1,'N')== FALSE)
{
printf("Set Parity 2 Error\n");
exit(1);
}
while(1)
{
while((nread_1 = read(fd_1,buff_1,512))>0)
{
/*printf("\nLen %d\n",nread);
buff[nread+1]='\0';
printf("\n%s",buff);*/
printf("Serial_Port_1:%d\n",nread_1);
for(i=0;i<nread_1;i++)
{
// printf("%d\n",nread);
// printf("%d\n",i);
printf("%3x",buff_1[i]);
}
printf("\n");
write(fd_1,buff_1,nread_1);
}
while((nread_2 = read(fd_2,buff_2,512))>0)
{
printf("Serial_Port_2:%d\n",nread_2);
for(i=0;i<nread_2;i++)
{
printf("%3x",buff_2[i]);
}
printf("\n");
write(fd_2,buff_2,nread_2);
}
}
//close(fd);
//exit(0);
}
参考文章: