嵌入式Linux下串口编程详解含源码
Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。
串 行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换 接口技术标准"该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。
Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的 《Serial Programming Guide for POSIX Operating Systems》
计算机串口的引脚说明
| 序号 | 信号名称 | 符号 | 流向 | 功能 |
| 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 与线路接通,出现振铃 |
|
串口操作需要的头文件
#include <stdio.h> /*标准输入输出定义*/ |
|
在 Linux 下串口文件是位于 /dev 下的
串口一 为 /dev/ttyS0
串口二 为 /dev/ttyS1
打开串口是通过使用标准的文件打开函数操作:
int fd; |
|
最基本的设置串口包括波特率设置,效验位和停止位设置。
串口的设置主要是设置 struct termios 结构体的各成员值。
struct termio |
设置这个结构体很复杂,我这里就只说说常见的一些设置:
波特率设置
下面是修改波特率的代码:
struct termios Opt; |
设置波特率的例子函数:
/** |
效验位和停止位的设置:
| 无效验 | 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; |
设置效验的函数:
/** |
需要注意的是:
如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/ |
|
设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。
- 发送数据
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); |
|
下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件
/**********************************************************************代码说明:使用串口二测试的,发送的数据是字符, |
- Serial Programming Guide for POSIX Operating Systems
- Linux 的源代码
- 代码下载: 代码
以上文章转自IBM Linux技术资料库。
下面给出了串口配置的完整的函数。通常,为了函数的通用性,通常将常用的选项都在函数中列出,这样可以大大方便以后用户的调试使用。该设置函数如下所示:
intset_opt(intfd,intnSpeed,intnBits,charnEvent,intnStop)
{
structtermios newtio,oldtio;
/*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
if( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return-1;
}
bzero( &newtio,sizeof( newtio ) );
/*步骤一,设置字符大小*/
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
/*设置停止位*/
switch( nBits )
{
case7:
newtio.c_cflag |= CS7;
break;
case8:
newtio.c_cflag |= CS8;
break;
}
/*设置奇偶校验位*/
switch( nEvent )
{
case'O'://奇数
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case'E'://偶数
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case'N'://无奇偶校验位
newtio.c_cflag &= ~PARENB;
break;
}
/*设置波特率*/
switch( nSpeed )
{
case2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
/*设置停止位*/
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
elseif( nStop == 2 )
newtio.c_cflag |= CSTOPB;
/*设置等待时间和最小接收字符*/
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
/*处理未接收字符*/
tcflush(fd,TCIFLUSH);
/*激活新配置*/
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return-1;
}
printf("set done!\n");
return0;
}
下面给出了一个完整的打开串口的函数,同样写考虑到了各种不同的情况。程序如下所示:
/*打开串口函数*/
intopen_port(intfd,intcomport)
{
char*dev[]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2"};
longvdisable;
if(comport==1)//串口 1
{ fd =open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
if(-1 == fd){
perror("Can't Open Serial Port");
return(-1);
}
}
elseif(comport==2)//串口 2
{ fd =open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY);
if(-1 == fd){
perror("Can't Open Serial Port");
return(-1);
}
}
elseif(comport==3)//串口 3
{
fd =open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY);
if(-1 == fd){
perror("Can't Open Serial Port");
return(-1);
}
}
/*恢复串口为阻塞状态*/
if(fcntl(fd, F_SETFL, 0)<0)
printf("fcntl failed!\n");
else
printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
/*测试是否为终端设备*/
if(isatty(STDIN_FILENO)==0)
printf("standard input is not a terminal device\n");
else
printf("isatty success!\n");
printf("fd-open=%d\n",fd);
returnfd;
}











