串口在嵌入式的调试阶段可以说是必备的工具,当然现在出来的JTAG之类的工具也还可以,但对于想降低开发成本的人来说,串口无疑说是一种很好的解决方法。不仅由于它的普遍与低廉,还由于它的协议简单,通信方便适用于本地设备之间的通信等。
串口(又名:串行接口)按电气标准及协议来分包括RS-232-C、RS-422、RS485等。RS-232-C也称标准串口,最常用的一种串行通讯接口。它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。传统的RS-232-C接口标准有22根线,采用标准25芯D型插头座(DB25),后来使用简化为9芯D型插座(DB9),现在应用中25芯插头座已很少采用,常用的是DB9的接口 。 RS-232采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信。
在Linux下的串口编程相对于无Linux操作系统下的编程要繁琐些。在无Linux操作系统下的编程只需要对相关寄存器进行一些设置就可以了,而基于Linux操作系统的串口编程其设置要通过一个结构体实现,在这个结构体里面含有很多东西,要一一的弄清楚还是有一定的难度的,不过好在有一些是已经默认设置好了的,只要对其中部分进行设置修改就可以定制自己的串口通信方式。下面就是如何在Linux操作系统下如何设置串口。
实验环境:虚拟机:Vmware Workstation 7.1.4 build-385536; host OS version: Windows 7 Ultimate, 32-bit 6.1.7601, Service Pack 1; guest OS version: Debian 5 Linux debian 2.6.26-2-686; gcc version 4.3.2 (Debian 4.3.2-1.1) ;可以通过以下命令查看Linux OS 信息: cat /proc/version,在我机子上的信息如下:Linux version 2.6.26-2-686 (Debian 2.6.26-26) (dannf@debian.org) (gcc version 4.1.3 20080704 (prerelease) (Debian 4.1.2-25)。
串口结构体:struct termios。该结构体的定义为:
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
该结构体的定义及每个成员变量的具体含义都在/usr/include/bits/termios.h有具体的说明,可以查看该文件以了解其具体内容。由于所用的Linux版本不同,上面结构体的内容可能有所不同,以Linux OS中/usr/include/bits/termios.h中的定义为准。
必须要设置的是:串口的速率、数据位、校验位、停止位、读最小等待时间、字符最小读数量,然后就是一些其它的设置,比如:流控制、大小写转换、回显等。常用的设置为:速率:115200或9600;数据位:8位;无校验位;无停止位;最小等待时间:按实时性确定;最小字符读数量:按读串口数据重要性确定。也常将流控制、大小写转换等去掉,有时可以不用设置回显,要想了解每个成员的具体含义这里有说明:http://www.kernel.org/doc/man-pages/online/pages/man3/termios.3.html。对于串口读要注意是否要采用非阻塞模式,是否将串口设置成为一个控制终端,对于要求每次都能读到数据的进程而言,采用阻塞模式显然要好些,而对于类似查询方式进行读的进程来说可以采用非阻塞模式,如此则可以减小程序在读串口时的等待时间,这种情况可以采用信号量、消息队列、共享内存等方式进行进程间通信,告知要读串口的进程什么时候去读串口,去读多少数据量。当然在做练习时,可以只做一个串口读程序或串口写程序即可。练习中以只读与非控制终端方式打开串口,要想设置成非阻塞模式可以再加入O_NONBLOCK控制位,也可以通过调用函数:int fcntl(int fd, int cmd, .../* ary */) 对已经打开的文件进行设置成相应的工作方式。
在做练习时,由于台式机有两个串口,但我们得要一根串口连接线将这两个串口连接起来,而对于笔记本电脑,则需要将USB或其它的接口转换为串口,将USB转换为串口要常见些,现在已经有这样的硬件设备了,在网上购买即可使用,不过还得要相应的驱动程序,到网上下载驱动程序就可以了,用得最多的USB转串口的是PL-2303(Windows 7 / Windows Vasta)(用XP系统去下载相关的驱动就行了)如此一来那就得需要将两个USB接口转换为串口,还要有一根串口转接线。由于我们使用的开发环境为:Windows 下的虚拟机+ Linux OS 所以有一个很方便的解决方案:使用虚拟串口工具+虚拟机的虚拟串口功能,文章后面有相关软件的下载地址,可以去那里下载相关的软件。相关的设置在此就不用多说了。会用虚拟机的应该知道怎么做。这样相关的基础知识与平台的搭建就基本上完成了,接下来就是实际的编程了,下面的程序实现的是读串口,串口的设置相对简单了些,要想写成一个通用的串口设置函数,后面有相应的介绍。
测试程序如下:该程序从/dev/ttyS1(虚拟机中的串口)读数据,当读到最后一个字符为回车或空格或接收到字符数大于BUFSIZ(大概是:4096)时,就在最后加入一个结束符:’\0’,然后将收到的数据输出到标准输出上(屏幕上),如果没有收到数据就一直等待数据的到来(类似阻塞模式)。
/* ** Copyright (c) 2011 SWUST XinXiGongChengZiZhong0905 ** All rights reserved ** ** Filename : serial.c ** Summary : Read data from serial port(/dev/ttyS1) ** ** Version : Debug ** Author : cdy <yyphtwx@gmail.com> ** Edit time : 2011-12-20 ** Finisht time : 2011-12-20 */ #include <signal.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <termio.h> int main (void) { int fd = -1; int read_len = 0; char recv_buf[BUFSIZ]; struct termios opt; memset(recv_buf, '0', BUFSIZ); fd = open("/dev/ttyS1", O_RDONLY | O_NOCTTY); if (fd < 0) { perror("open fd failed"); exit(0); } else printf("open fd successful!\n"); tcflush(fd, TCIOFLUSH); if (tcgetattr(fd, &opt) != 0) { perror("get options of serial file Description failed"); exit(0); } else printf("get options of serial file Description successful!\n"); /* Settings: no flow_control;databit: 8;stopbit: 1;no parity */ opt.c_cflag &= ~CRTSCTS; /* To set the flow_control bit in c_cflag */ opt.c_cflag &= ~CSIZE; /* mask the character size bits */ opt.c_cflag |= CS8; /* To set the data bits in c_cflag */ opt.c_cflag &= ~CSTOPB; /* setting the stop bits in c_cflag */ opt.c_iflag &= ~INPCK; /* To set the parity bit in c_cflag */ opt.c_cflag &= ~PARENB; /* To set the I/O speed */ cfsetispeed(&opt, B115200); cfsetospeed(&opt, B115200); /* Other settings at here */ opt.c_cflag |= CLOCAL; /* Ignore modem control lines */ opt.c_cflag |= CREAD; /* Enable receiver */ opt.c_cc[VTIME] = 0; /* Waitting 0 seconds */ opt.c_cc[VMIN] = 0; /* Reading 0 character at least */ opt.c_lflag &= ~ICANON; /* Enable canonical mode */ //opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); tcflush(fd, TCIOFLUSH); if(tcsetattr(fd, TCSANOW, &opt) != 0) { perror("set serial port fialed"); exit(0); } else printf("set serial port successful!\n"); tcflush(fd, TCIOFLUSH); while (1) { while ( (read_len = read(fd, recv_buf, BUFSIZ)) > 0) { if ( (recv_buf[read_len - 1] == '\n')\ || (recv_buf[read_len - 1] == ' ')\ || (read_len > BUFSIZ) ) { recv_buf[read_len] = '\0'; printf("Read data length : %d\t", read_len); printf("Read data : %s\n\n", recv_buf); } } // end of while(read...) } // end of while(1) if (close(fd) < 0) { perror("close fd failed"); exit(0); } else printf("close fd successful!\n"); return(1); }
当然也可以将上面程序里面设置串口的步骤写成一个具有一定通用性的函数,程序代码如下:
/************************************************************************ ** Name : UART_SET ** Function : to set the serial ports ** Entry parameters : int fd -- file Description reffered to the serial port ** int speed -- the speed of serial port will be used ** int flow_control -- the flow control:0 not,1 hardware control,2 software control ** int databit -- data bits : 5/6/7/8 ** int stopbit -- stop bits : 1/2 ** int/char parity -- parity bits : N/E/O/S ** Export parameters : succcessful return 0, failed return 1 ************************************************************************/ int UART_SET(int fd, int speed, int flow_control, int databit, int stopbit, char parity) { int i = 0; int speed_arry[] = {1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400}; int speed_arry_nu[] = {B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400}; if (tcgetattr(fd, &opt) != 0) { perror("get options of fd fialed!"); return(0); } else printf("tcgetattr successful!\n"); /* seting the serial port speed */ for (i = 0; i < sizeof(speed_arry) / sizeof(int); i++) { if (speed == speed_arry[i]) { cfsetispeed(&opt, speed_arry_nu[i]); cfsetospeed(&opt, speed_arry_nu[i]); } /* Set the speed now */ if(tcsetattr(fd, TCSANOW, &opt) != 0) { perror("set speed fialed"); exit(1); } } /* To set the flow_control bit in c_cflag */ switch(flow_control) { case 2: /* using software flow control */ opt.c_iflag |= (IXON | IXOFF | IXANY); break; case 1: /* using hardware flow control */ opt.c_cflag |= CRTSCTS; break; default: /* don't use flow control */ opt.c_cflag &= ~CRTSCTS; break; } /* To set the data bits in c_cflag */ opt.c_cflag &= ~CSIZE; switch (databit) { case 7: opt.c_cflag |= CS7; break; case 6: opt.c_cflag |= CS6; break; case 5: opt.c_cflag |= CS5; break; default: opt.c_cflag |= CS8; break; } /* setting the stop bits in c_cflag */ if (2 == stopbit) { opt.c_cflag |= CSTOPB; } else /* one stopbits */ { opt.c_cflag &= ~CSTOPB; } /* To set the parity bit in c_cflag */ switch (parity) { case 'n': case 'N': opt.c_iflag &= ~INPCK; opt.c_cflag &= ~PARENB; break; case 'e': case 'E': opt.c_iflag |= INPCK; opt.c_cflag |= PARENB; opt.c_cflag &= ~PARODD; break; case 'o': case 'O': opt.c_iflag |= INPCK; opt.c_cflag |= (PARENB | PARODD); break; case 'S': case 's': /*as no parity*/ opt.c_cflag &= ~PARENB; opt.c_cflag &= ~CSTOPB; break; default: /* Disable parity falg */ opt.c_iflag &= ~INPCK; opt.c_cflag &= ~PARENB; break; } /* Other settings at here */ opt.c_cflag |= CLOCAL; /* Ignore modem control lines */ opt.c_cflag |= CREAD; /* Enable receiver */ opt.c_cc[VTIME] = 0; /* Waitting 0 seconds */ opt.c_cc[VMIN] = 0; /* Reading 0 character at least */ opt.c_lflag &= ~ICANON; /* Enable canonical mode */ tcflush(fd, TCIOFLUSH); if(tcsetattr(fd, TCSANOW, &opt) != 0) { perror("set UART failed"); return(1); } tcflush(fd, TCIOFLUSH); return(0); }
说明 :在UART_SET函数里面有一个struct termios opt的结构体,该结构体已经被定义为全局变量,可以UART_SET函数里面是直接使用,当然也可以将该结构体作为参数传递给UART_SET,其运行效果与前面的相同。
相关软件下载:
1. 串口助手:http://download.csdn.net/detail/csdn_copyleft/3943033
2. 虚拟串口:http://115.com/file/clyg1bzy
3. PL-2303驱动:http://download.csdn.net/detail/csdn_copyleft/3523004
参考文章:
1. http://blog.csdn.net/tigerjb/article/details/6179291
2. http://www.ibm.com/developerworks/cn/linux/l-serials/index.html
3. http://www.kernel.org/doc/man-pages/online/pages/man3/termios.3.html