串口简介
串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个 25 个脚的 DB25连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50英尺。
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与线路接通,出现振铃 |
串口操作
串口操作需要的头文件
#include <stdio.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> /*错误号定义*/ |
打开串口
在 Linux下串口文件是位于 /dev下的
串口一为 /dev/ttyS0
串口二为 /dev/ttyS1
打开串口是通过使用标准的文件打开函数操作:
int fd; /*以读写方式打开串口*/ fd =open("/dev/ttyS0", O_RDWR); if (-1 == fd){ /*不能打开串口一*/ perror("提示错误!"); } |
设置串口
最基本的设置串口包括波特率设置,效验位和停止位设置。
串口的设置主要是设置 struct termios结构体的各成员值。
struct termio { unsignedshort c_iflag;/*输入模式标志 */ unsignedshort c_oflag; /*输出模式标志 */ unsignedshort c_cflag; /*控制模式标志*/ unsignedshort c_lflag; /* local mode flags */ unsignedchar c_line; /* line discipline */ unsignedchar c_cc[NCC]; /* control characters */ }; |
设置这个结构体很复杂,我这里就只说说常见的一些设置:
波特率设置
下面是修改波特率的代码:
struct termios Opt; tcgetattr(fd, &Opt);/*获得当前设备模式,与终端相关的参数。fd=0标准输入*/ cfsetispeed(&Opt,B19200);/*设置结构termios输入波特率为19200Bps*/ cfsetospeed(&Opt,B19200); /*fd应该是文件描述的意思*/ tcsetattr(fd,TCANOW,&Opt);/*设置终端参数,TCANOW修改立即发生*/ |
设置波特率的例子函数:
/** *@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, }; voidset_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函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或输出缓存(用户程序已经写,但尚未发送)。queue参数应是下列三个常数之一: * TCIFLUSH刷清输入队列。 * TCOFLUSH刷清输出队列。 * TCIOFLUSH刷清输入、输出队列。 */ tcflush(fd, TCIOFLUSH);//设置前flush cfsetispeed(&Opt, speed_arr[i]); cfsetospeed(&Opt, speed_arr[i]); //通过tcsetattr函数把新的属性设置到串口上。 //tcsetattr(串口描述符,立即使用或者其他标示,指向termios的指针) status = tcsetattr(fd, TCSANOW, &Opt); if (status != 0) { perror("tcsetattr fd1"); return; } tcflush(fd,TCIOFLUSH); //设置后flush } } } |
效验位和停止位的设置:
无效验 | 8位 | Option.c_cflag &= ~PARENB; |
奇效验(Odd) | 7位 | Option.c_cflag |= ~PARENB; |
偶效验(Even) | 7位 | Option.c_cflag &= ~PARENB; |
Space效验 | 7位 | Option.c_cflag &= ~PARENB; |
设置效验的函数:
/** *@brief 设置串口数据位,停止位和效验位 *@param fd 类型 int 打开的串口文件句柄 *@param databits类型 int数据位 取值为 7或者8 *@param stopbits类型 int停止位 取值为 1或者2 *@param parity 类型 int 效验类型取值为N,E,O,,S */ intset_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; 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); |
下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件
/********************************************************************** *代码说明:使用串口二测试的,发送的数据是字符,但是没有发送字符串结束符号, *所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。 **********************************************************************/ #define FALSE -1 #define TRUE 0 /*********************************************************************/ intOpenDev(char *Dev) { //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; } intmain(int argc,char **argv) { int fd; int nread; char buff[512]; char *dev ="/dev/ttyS1";//串口二 fd = OpenDev(dev); set_speed(fd, 19200); if (set_Parity(fd, 8, 1,'N') == FALSE) { printf("Set Parity Error\n"); exit (0); } while (1)//循环读取数据 { while ((nread =read(fd, buff, 512))>0) { printf("\nLen %d\n", nread); buff[nread+1] ='\0'; printf("\n%s", buff); } } //close(fd); // exit (0);
|
1、虚拟机下使用串口的方法
使用vmwave,默认串口设备是没有添加的,通过vmwave将设备加入即可正常使用串口。虚拟机串口打开后,可能会占用windows下的串口。另外,虚拟机的串口收发比正常的速度的确要慢许多。
2、消除Linux串口收发的一些规则
Linux 串口收发有许多模式,如:
(1)接收返回模式:如果串口没有接收到数据,read()函数不返回。
(2)数据接收\n才返回接收的数据,否则read()函数返回0
(3)特殊字符解析问题,部分特殊字符接收/发送时,会被屏蔽或者转义。如发送0x0A接收变为0x0A 0x0A,0x0D被屏蔽等。
(4)接收反馈:如串口接收到数据,立即将该数据发送出去。
(上面是我遇到的一些问题,可能表述不很清楚,呵呵。如果用于收发txt文件,一般不大注意。)
3、解决问题的方法是,消除这些默认规则,关键是struct termios的参数影响。
struct termios {
tcflag_t c_iflag; /**//* 输入模式旗标 */
tcflag_t c_oflag; /**//* 输出模式旗标 */
tcflag_t c_cflag; /**//* 控制模式旗标 */
tcflag_t c_lflag; /**//* 区域模式旗标 */
cc_t c_line; /**//* 行控制 (line discipline) */
cc_t c_cc[NCCS]; /**//* 控制特性 */
};
由于研究不深,如果要消除所有上面的规则,我是如下处理的
struct termios options;
串口打开方式:
open ("dev/ttyS0" , O_RDWR|O_NOCTTY| O_NDELAY );
消除收发模式规则:
options.c_lflag = 0;
options.c_oflag = 0;
options.c_iflag = 0;
消除字符屏蔽规则:
options.c_cc[VINTR] = 0; /**//* Ctrl-c */
options.c_cc[VQUIT] = 0; /**//* Ctrl- */
options.c_cc[VERASE] = 0; /**//* del */
options.c_cc[VKILL] = 0; /**//* @ */
options.c_cc[VEOF] = 0; /**//* Ctrl-d */
options.c_cc[VTIME] = 1; /**//* */
options.c_cc[VMIN] = 0; /**//* */
options.c_cc[VSWTC] = 0; /**//* '' */
options.c_cc[VSTART] = 0; /**//* Ctrl-q */
options.c_cc[VSTOP] = 0; /**//* Ctrl-s */
options.c_cc[VSUSP] = 0; /**//* Ctrl-z */
options.c_cc[VEOL] = 0; /**//* '' */
options.c_cc[VREPRINT] = 0; /**//* Ctrl-r */
options.c_cc[VDISCARD] = 0; /**//* Ctrl-u */
options.c_cc[VWERASE] = 0; /**//* Ctrl-w */
options.c_cc[VLNEXT] = 0; /**//* Ctrl-v */
options.c_cc[VEOL2] = 0; /**//* '' */
以上设置,在其它参数串口设置前执行,如果你需要保留部分参数,请参阅http://blog.chinaunix.net/article.php?articleId=15964&blogId=60
在RedHat Feroda 4下编译通过
= = = = = = = = = = = 非阻塞read= = = = = = = = = = =
Q:在调用串口read(fd, buff, len);时,如果串口没有数据,会停在read处,请问有没有办法让这个read动作中止?
A:使用非阻塞方式select函数(I/O多工机制)或者open的时候加O_NONBLOCK参数。
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);关于这个函数的使用我会在下篇blog中整理。
= = = = = = = = = = = 串口收发源码= = = = = = = = = = =
一下代码已经经过我测试,没有问题。开发环境Redhat9,运行环境s3c2410
= = = = = = receive.c= = = = = =
#include <stdio.h> |
= = = = = send.c= = = = = =
#include <stdio.h> |
= = = = = =.makefile= = = = = =
|
下面是因工作需要而写的一个UART测试DEMO.源码如下:
/*
This Is A Demo To Test Uart For Linux.
Modify By SE7EN @2013-5-31
*/
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <strings.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#define BUFSIZE (512)
static int uart_fd;
struct termios options,oldtio,opt;
const char *pinputchr = "22222222 ";
int uart_attr_clear(int pathname)
{
int ret = -1;
ret = tcgetattr(pathname,&opt);
if(ret < 0)
{
perror("tcgetattr fail");
return -1;
}
opt.c_oflag &= ~OCRNL;
opt.c_lflag &= ~ECHO;
opt.c_cc[VINTR] = 0;
opt.c_cc[VERASE] = 0;
opt.c_cc[VQUIT] = 0;
opt.c_cc[VKILL] = 0;
opt.c_cc[VEOF] = 0;
opt.c_cc[VSTOP] = 0;
opt.c_cc[VSTART] = 0;
opt.c_cc[VSUSP] = 0;
opt.c_cc[VEOL] = 0;
opt.c_cc[VEOL2] = 0;
opt.c_cc[VREPRINT] = 0;
opt.c_cc[VWERASE] = 0;
opt.c_cc[VLNEXT] = 0;
tcgetattr(uart_fd,&opt);
cfmakeraw(&opt);
cfsetispeed(&opt,B115200);
cfsetospeed(&opt,B115200);
return 0;
}
int uart_baudrate_set(int speed)
{
int ret = -1;
switch(speed){
case 9600:
ret = cfsetispeed(&opt,B9600);
ret = cfsetospeed(&opt,B9600);
break;
case 19200:
ret = cfsetispeed(&opt,B19200);
ret = cfsetospeed(&opt,B19200);
break;
case 38400:
ret = cfsetispeed(&opt,B38400);
ret = cfsetospeed(&opt,B38400);
break;
case 57600:
ret = cfsetispeed(&opt,B57600);
ret = cfsetospeed(&opt,B57600);
break;
case 115200:
ret = cfsetispeed(&opt,B115200);
ret = cfsetospeed(&opt,B115200);
break;
default :
printf("Default Baudrate is 115200\n");
cfsetispeed(&opt,B115200);
cfsetospeed(&opt,B115200);
break;
}
return ret;
}
int uart_attr_set(int pathname)
{
int ret = -1;
opt.c_cflag |= (CLOCAL | CREAD);
ret = tcflush(pathname, TCIFLUSH);
ret = tcsetattr(pathname,TCSANOW,&opt);
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
opt.c_cflag &= ~CSTOPB;
opt.c_cflag &= ~PARENB;
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opt.c_oflag &= ~OPOST;
return ret;
}
int main(int argc, char* argv[])
{
int ret = -1;
int baudrate = 115200;
int ret_select = -1;
int count = 0;
fd_set set_input;
struct timeval timeout;
char *direction = NULL;
char *fname_uart = NULL;
char uart_buf[BUFSIZE] = {0};
char *psend = "send";
char *recieve = "recieve";
if (argc != 4) {
printf("Uage: ./test /dev/ttySn 115200 send/recieve\n");
return -1;
}
fname_uart = argv[1];
baudrate = atoi(argv[2]);
direction = argv[3];
uart_fd = open(fname_uart, O_RDWR|O_NOCTTY);
if (uart_fd < 0){
printf("Open %s Fail:",fname_uart);
perror("Open Fail");
return -1;
}
ret = uart_attr_clear(uart_fd);
if(ret < 0)
{
close(uart_fd);
return -1;
}
ret = uart_baudrate_set(baudrate);
if(ret < 0)
{
close(uart_fd);
perror("uart_baudrate_set fail");
return -1;
}
ret = uart_attr_set(uart_fd);
if(ret < 0)
{
close(uart_fd);
perror("uart_attr_set fail:");
return -1;
}
FD_ZERO(&set_input);
FD_SET(uart_fd,&set_input);
while(1)
{
ret = strcmp(psend,direction);
if(0 == ret)
{
printf("Send Data...\n");
ret = write(uart_fd,pinputchr,strlen(pinputchr));
if(ret>0)
printf("\nwrite success!\n");
else
printf("\nwrite fail!\n");
sleep(1);
}
else
{
printf("Rec Data...\n");
timeout.tv_sec = 1;
timeout.tv_usec = 0;
ret_select = select(uart_fd + 1,&set_input,NULL,NULL,&timeout);
if(ret_select < 0)
perror("select fail:");
else if(ret_select == 0)
{
printf("time out...\n");
}
else
{
if(FD_ISSET(uart_fd,&set_input))
{
ret = read(uart_fd,uart_buf,BUFSIZE);
printf("recieve %d data size = %d\n",count++,ret);
uart_buf[ret] = '\0';
printf("%s\n",uart_buf);
}
}
}
}
close(uart_fd);
return 0;
}