串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是RS-232-C接口(又称EIA RS-232-C)它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个25个脚的DB25连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于4%的情况下,传输电缆长度应为50英尺。
在linux文件中,所有的设备文件一般都位于/dev下,其中串口一、串口二分别对应的设备名依次为“/dev/ttyS0”、“/dev/ttyS1”,可以查看在/dev下的文件以确认。在linux下面对于串口的读写就可以通过简单的read、write函数来完成,所不同的是只是需要对串口的其他参数另坐配置。
1. 串口编程需要用到的头文件#include <stdio.h> // 标准输入输出定义 #include <stdlib.h> #include <fcntl.h> // 文件控制定义,主要完成串口通信中对文件的读写操作 #include <unistd.h> // linux标准函数定义 #include <sys/ioctl.h> #include <termios.h> // POSIX终端控制定义 #include <sys/time.h> #include <sys/types.h>
2. 串口终端函数2.1 打开串口设备int fd; char *device = "/dev/tts/0"; // 设备路径,初始使用UART0
for(t=1;t<argc;t++) // 获取程序入口时输入的参数 { if(!strcmp(argv[t],"-d") && (argc > (t+1))) { device = argv[t+1]; } } if(!strcmp(device,"/dev/tts/1")) // 不允许使用UART1,因为它已和PC相连。 { printf("can not use /dev/tts/1\n"); return -1; } fd = open(device, O_RDWR); // 打开设备 if (fd < 0) // 设备打开失败 { printf("open device error\n"); return -1; }
2.2 设置串口最基本的串口设置包括波特率设置,校验位和停止位设置。实际上串口的设置只要是设置Struct termios()结构体中各成员的值。
struct termio { unsigned short c_iflag; /* 输入模式标志 */ unsigned short c_oflag; /* 输出模式标志 */ unsigned short c_cflag; /* 控制模式标志*/ unsigned short c_lflag; /* local mode flags */ unsigned char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control characters */ };
2.2.1 波特率设置struct termios Opt; tcgetattr(fd, &Opt); // 得到当前串口的参数 cfsetispeed(&Opt,B19200); /*设置为19200Bps*/ cfsetospeed(&Opt,B19200); tcsetattr(fd,TCANOW,&Opt); // 激活新配置
设置波特率的例子函数:
/** *@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, }; 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(fd1, TCSANOW, &Opt); if (status != 0) { perror("tcsetattr fd1"); return; } tcflush(fd,TCIOFLUSH); } } }
2.2.2 校验位和停止位设置/** *@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 */ break; case 'o': case 'O': options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/ options.c_iflag |= INPCK; // INPCK:奇偶校验使能 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': // Space 校验 case 's': /*as no parity*/ options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_iflag |= INPCK; break; default: fprintf(stderr,"Unsupported parity\n"); return (FALSE); }
// 设置停止位 switch (stopbits) { case 1: options.c_cflag &= ~CSTOPB; // 1个停止位 break; case 2: options.c_cflag |= CSTOPB; // 2个停止位 break; default: fprintf(stderr,"Unsupported stop bits\n"); return (FALSE); }
// 处理未接收的字符 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); }
2.2.3 模式设置需要注意的是,如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通信。
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始数据输入 newtio.c_oflag &= ~(OPOST); // 原始数据输出
2.2.4 串口配置实例void init_ttyS(int fd) { struct termios newtio;
bzero(&newtio, sizeof(newtio)); // 得到当前串口的参数 tcgetattr(fd, &newtio);
// 将输入波特率设为19200 // 将输出波特率设为19200 cfsetispeed(&newtio, B19200); cfsetospeed(&newtio, B19200);
// 使能接收并使能本地状态 newtio.c_cflag |= (CLOCAL | CREAD);
// 无校验 8位数据位1位停止位 newtio.c_cflag &= ~PARENB; newtio.c_cflag &= ~CSTOPB; newtio.c_cflag &= ~CSIZE;
// 8个数据位 newtio.c_cflag |= CS8;
// 原始数据输入 newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); newtio.c_oflag &= ~(OPOST);
// 设置等待时间和最小接收字符数 newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0;
// 处理未接收的字符 tcflush(fd, TCIFLUSH);
// 激活新配置 tcsetattr(fd,TCSANOW,&newtio); }
|
2.1 读串口读取串口数据使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。
char buff[1024]; int Len; int readByte = read(fd,buff,Len);
可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。
void SERIAL_RX(void) { // read(fd, RXBUF , RX_len); #if 1 int ret,n,pos,retval; fd_set rfds; struct timeval tv ; pos = 0;//指向接收缓冲
for(n = 0; n < RX_len; n++) { RXBUF = 0xFF; }
FD_ZERO(&rfds);// 清空串口接收端口集 FD_SET(fd,&rfds);// 设置串口接收端口集 tv.tv_sec = 2; tv.tv_usec = 0;
while(FD_ISSET(fd,& rfds)) // 检测串口是否有读写动作 { // 每次循环都要清空,否则不会检测到有变化 FD_ZERO(&rfds);// 清空串口接收端口集 FD_SET(fd,&rfds);// 设置串口接收端口集
retval = select(fd+1,&rfds,NULL,NULL,&tv); if(retval == -1) { perror("select()"); break; } else if(retval) { //判断是否还有数据 //sleep(2); ret = read(fd, RXBUF, RX_len); pos += ret; //printf("ret = %d \n",ret); if((RXBUF[pos-2] == '\r') & (RXBUF[pos-1] == '\n')) // 确实接收到了数据,并打印出来 { FD_ZERO(&rfds); FD_SET(fd,&rfds); retval = select(fd+1,&rfds,NULL,NULL,&tv); if(!retval)//no datas { break; } } } else { break; } } }
Linux下直接用read读串口可能会造成堵塞,或数据读出错误。然而用select先查询com口,再用read去读就可以避免,并且当com口延时时,程序可以退出,这样就不至于由于com口堵塞,程序就死了。
Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明):
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
先说明两个结构体:
l struct [b]fd_set[/b]
可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如:
? FD_ZERO(fd_set *set):清除一个文件描述符集;
? FD_SET(int fd, fd_set *set):将一个文件描述符加入文件描述符集中;
? FD_CLR(int fd, fd_set *set):将一个文件描述符从文件描述符集中清除;
? FD_ISSET(int fd, fd_set *set): 检查集合中指定的文件描述符是否可以读写。
一会儿举例说明。
l struct timeval
是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
struct timeval{ long tv_sec; long tv_unsec; }
具体解释select的参数:
l int maxfdp
是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
l [b]fd_set *readfds[/b]
是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
l [b]fd_set *writefds[/b]
是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
l [b]fd_set *errorfds[/b]
同上面两个参数的意图,用来监视文件错误异常。
l struct timeval* timeout
是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
l 返回值:
负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。
一般来说,在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关的文件描述符后,使用FD_CLR来清除描述符集。
2.2 写串口读写串口设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。发送数据:
char buffer[1024]; int Length; int nByte; nByte = write(fd, buffer ,Length);
2.3 关闭串口close(fd);
3. 具体应用实例#include <stdio.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include<time.h> #include <termios.h> #include <sys/select.h> #include <sys/types.h> //#include "COMM.h"
#define ACK_SUCCESS 0x00 // 操作成功 #define ACK_FAIL 0x01 // 操作失败 #define ACK_FULL 0x04 // Full #define ACK_NOUSER 0x05 // 无此用户 #define ACK_USER_EXIST 0x07 // 用户已存在 #define ACK_TIMEOUT 0x08 // 采集超时 #define ACK_COUNT 0x3 //发生错误时候,重试次数
extern unsigned char TXBUF[9900]; extern unsigned char RXBUF[9900]; extern unsigned char rev_ok; extern unsigned int TX_len; extern unsigned int RX_len; extern unsigned char one_onecontrast(unsigned char user_number_h,unsigned char user_number_l); extern unsigned char one_morecontrast(void); extern unsigned char Get_UserNumber_Right(void); extern unsigned char set_addmode(unsigned char yn_repeat); extern unsigned char add_Fingerprint(unsigned char time_number,unsigned char user_number_h, unsigned char user_number_l,unsigned char user_right); extern unsigned char del_alluser(void); extern unsigned char del_oneuser(unsigned char user_number_h,unsigned char user_number_l); extern unsigned char read_usernumber(void);
int fd;
unsigned char USER_NUMBER_H; // 用户号高8位 unsigned char USER_NUMBER_L; // 用户号低8位 unsigned char USER_RIGHT; // 用户权限
unsigned char FEATURE_BUFFER[512]; // 要下传的指纹特征值数据 unsigned char FEATURE_LEN; // 要下传的指纹特征值的长度
unsigned char CharToHex(unsigned char ch); unsigned char select_one_onecontrast(void); void select_Get_Usernumber_right(void);
/********************************************************************************************* * name: SERIAL_TX * func: 发生数据到指纹模块串口 * para: none * ret: none * modify: * comment: *********************************************************************************************/ int SERIAL_TX(void) { int ret;
ret = write(fd, TXBUF, TX_len); // 试图从串口发送数 据 if(ret == -1) // 确实接收到了数据,并打印出来 { // *(rcv_buf+ret)='\0'; printf(" Write device error!\n"); return -1; // ret = 0; }
return 0; }
/********************************************************************************************* * name: SERIAL_RX * func: 从指纹模块串口接收数据 * para: none * ret: none * modify: * comment: *********************************************************************************************/ void SERIAL_RX(void) { // read(fd, RXBUF , RX_len); #if 1 int ret,n,pos,retval; fd_set rfds; struct timeval tv ; pos = 0;//指向接收缓冲 tv.tv_sec = 2; tv.tv_usec = 0; for(n = 0; n < RX_len; n++) { RXBUF = 0xFF; }
//while(FD_ISSET(fd,&uart_r)||FD_ISSET(fd,&uart_w)); // 检测串口是否有读写动作 while(1) // 检测串口是否有读写动作 { FD_ZERO(&rfds);// 清空串口接收端口集 FD_SET(fd,&rfds);// 设置串口接收端口集 retval = select(fd+1,&rfds,NULL,NULL,&tv); if(retval == -1) { perror("select()"); break; } else if(retval) { //判断是否还有数据 //sleep(2); ret = read(fd, RXBUF, RX_len); pos += ret; //printf("ret = %d \n",ret); if((RXBUF[pos-2] == '\r') & (RXBUF[pos-1] == '\n')) // 确实接收到了数据,并打印出来 { FD_ZERO(&rfds); FD_SET(fd,&rfds); retval = select(fd+1,&rfds,NULL,NULL,&tv); if(!retval)//no datas { break; } } } else { break; } } }
void init_ttyS(int fd) { struct termios newtio;
bzero(&newtio, sizeof(newtio)); tcgetattr(fd, &newtio); // 得到当前串口的参数 cfsetispeed(&newtio, B19200); // 将输入波特率设为19200 cfsetospeed(&newtio, B19200); // 将输出波特率设为19200 newtio.c_cflag |= (CLOCAL | CREAD); // 使能接收并使能本地状态 newtio.c_cflag &= ~PARENB; // 无校验 8位数据位1位停止位 newtio.c_cflag &= ~CSTOPB; newtio.c_cflag &= ~CSIZE; newtio.c_cflag |= CS8; newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始数据输入 newtio.c_oflag &= ~(OPOST);
newtio.c_cc[VTIME] = 0; // 设置等待时间和最小接收字符数 newtio.c_cc[VMIN] = 0;
tcflush(fd, TCIFLUSH); // 处理未接收的字符 tcsetattr(fd,TCSANOW,&newtio); // 激活新配置 }
unsigned char set_addmode(unsigned char yn_repeat) { unsigned char check, i; TXBUF[0] = 0xF5; TXBUF[1] = 0x2D; TXBUF[2] = 0x00; TXBUF[3] = yn_repeat; TXBUF[4] = 0x00; TXBUF[5] = 0x00; check = TXBUF[1]; for (i = 2; i < 6; i++) { check ^= TXBUF[i]; } TXBUF[6] = check; TXBUF[7] = 0xF5;
rev_ok = 1; TX_len = 8; RX_len = 8;
SERIAL_TX(); sleep(delaytime); SERIAL_RX(); rev_ok = RXBUF[4];
return (rev_ok); }
int main(int argc, char ** argv) { int t,ret; char mode;
char *device = "/dev/tts/0"; // 设备路径,初始使用UART0
for(t=1;t<argc;t++) // 获取程序入口时输入的参数 { if(!strcmp(argv[t],"-d") && (argc > (t+1))) { device = argv[t+1]; } } if(!strcmp(device,"/dev/tts/1")) // 不允许使用UART1,因为它已和PC相连。 { printf("can not use /dev/tts/1\n"); return -1; } fd = open(device, O_RDWR); // 打开设备
if (fd < 0) // 设备打开失败 { printf("open device error\n"); return -1; }
init_ttyS(fd); // 初始化设备
while(1) { set_addmode(1); // 设置指纹添加模式,禁止重复 } close(fd); // 关闭打开的设备 return 0; // 正常返回 }
|
|