Linux下读取RFID卡号(C串口编程)
由于项目需要用到RFID、GPRS、摄像头等模块所以便看了一下,整理了一下学习思路,本篇先是整理一下串口读取RFID卡号的程序思路,后面还会更其他的
RFID模块:
本次采用的是125K的RFID读卡器和标签,很容易理解的,其实就是一张卡片里面存了一串数字(这个问题有点像你问一个艺术家洛必达法则是啥咦洛必达是啥),然后有个读卡器,当你把卡片放到读卡器上时,读卡器会将卡里面存的卡号读取出来,然后放到串口发送缓冲区,等待我们去读取,那么问题就是怎么读取。
串口读写:
大家都知道。linux下面一切皆文件,设备也不例外,上面提到的串口就是个设备文件,linux设备文件一般存放在“/dev/”下,当你ls的时候会发现一大堆什么ttyS0、sda、video....现在笔记本串口设备文件一般都是ttyUSBx(x=0,1,2...)。既然是文件,那就能打开喽,不过它不是被“右键->打开”,而是被“系统调用open()”。当然不只是把它打开就完了,操作串口有一系列的系统调用。说到系统调用,其实就是系统底层给在上层编写程序的你提供的一些系统级函数。
一些需要的头文件:
- #include <unistd.h> /*linux系统调用*/
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <fcntl.h> /*文件控制*/
- #include <sys/stat.h> /*文件状态*/
- #include <sys/types.h> /*定义系统类型,像size_t等*/
- #include <errno.h> /*出错码*/
- #include <termios.h> /*终端参数*/
1.打开串口
这里把open()这个系统调用封装成一个com_open()函数,可以方便判断是否打开成功并打印错误信息。
参数*DEV是文件路径(上面提到的/dev/ttyUSBx),第二个参数告诉它是以什么方式打开,常见的有:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
注:上面的三个不能同时出现,即不能这样写O_RDONLY | O_RDWR,像下面这些是可选的:
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 阻塞模式(详见配置串口处)
open()返回值是int型fd文件描述符,对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符(0~255,不过有些是系统已经占用的),这个文件描述符用来告诉我们要对哪个文件进行操作。
- int com_open(const char *DEV)
- {
- int fd = -1;
- open(DEV, O_RDWR);
- if(fd == -1)
- {
- perror("open error");
- exit(0);
- }
- return fd;
- }
2.配置串口
设置串口属性(类似于约定好双方通信协议),即上面提到的配置串口,其实主要就是设置termios.h中的termios结构体参数:
- typedef struct com_attr /*我自己定义的串口属性结构*/
- {
- unsigned int baudrate; /*波特率*/
- unsigned char databits; /*数据位*/
- unsigned char stopbits; /*停止位*/
- unsigned char parity; /*校验位*/
- }com_attr;
- struct termios /*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[NCC]; //control characters
- }
可以看到两个参数,第一个文件描述符,告诉它你想在个文件操作,第二个是我定义的串口属性结构体:
注:由于项目需要,可能有些不必要的参数我就没有去设置和解释,详细可以google一下配置串口属性结构体详细介绍!
- int set_com_attr(int fd, com_attr *attr)
- {
- struct termios opt;
- memset(&opt, 0, sizeof(struct termios));
- tcgetattr(fd, &opt);
- cfmakeraw(&opt);
- /*******************波特率********************/
- printf("set baudrate %d\n", attr->baudrate);
- switch (attr->baudrate)
- {
- case 50:
- cfsetispeed(&opt, B50);
- cfsetospeed(&opt, B50);
- break;
- case 75:
- cfsetispeed(&opt, B75);
- cfsetospeed(&opt, B75);
- break;
- case 110:
- cfsetispeed(&opt, B110);
- cfsetospeed(&opt, B110);
- break;
- case 134:
- cfsetispeed(&opt, B134);
- cfsetospeed(&opt, B134);
- break;
- case 150:
- cfsetispeed(&opt, B150);
- cfsetospeed(&opt, B150);
- break;
- case 200:
- cfsetispeed(&opt, B200);
- cfsetospeed(&opt, B200);
- break;
- case 300:
- cfsetispeed(&opt, B300);
- cfsetospeed(&opt, B300);
- break;
- case 600:
- cfsetispeed(&opt, B600);
- cfsetospeed(&opt, B600);
- break;
- case 1200:
- cfsetispeed(&opt, B1200);
- cfsetospeed(&opt, B1200);
- break;
- case 1800:
- cfsetispeed(&opt, B1800);
- cfsetospeed(&opt, B1800);
- break;
- case 2400:
- cfsetispeed(&opt, B2400);
- cfsetospeed(&opt, B2400);
- break;
- case 4800:
- cfsetispeed(&opt, B4800);
- cfsetospeed(&opt, B4800);
- break;
- case 9600:
- cfsetispeed(&opt, B9600);
- cfsetospeed(&opt, B9600);
- break;
- case 19200:
- cfsetispeed(&opt, B19200);
- cfsetospeed(&opt, B19200);
- break;
- case 38400:
- cfsetispeed(&opt, B38400);
- cfsetospeed(&opt, B38400);
- break;
- case 57600:
- cfsetispeed(&opt, B57600);
- cfsetospeed(&opt, B57600);
- break;
- case 115200:
- cfsetispeed(&opt, B115200);
- cfsetospeed(&opt, B115200);
- break;
- case 230400:
- cfsetispeed(&opt, B230400);
- cfsetospeed(&opt, B230400);
- break;
- case 460800:
- cfsetispeed(&opt, B460800);
- cfsetospeed(&opt, B460800);
- break;
- case 500000:
- cfsetispeed(&opt, B500000);
- cfsetospeed(&opt, B500000);
- break;
- case 576000:
- cfsetispeed(&opt, B576000);
- cfsetospeed(&opt, B576000);
- break;
- case 921600:
- cfsetispeed(&opt, B921600);
- cfsetospeed(&opt, B921600);
- break;
- case 1000000:
- cfsetispeed(&opt, B1000000);
- cfsetospeed(&opt, B1000000);
- break;
- case 1152000:
- cfsetispeed(&opt, B1152000);
- cfsetospeed(&opt, B1152000);
- break;
- case 1500000:
- cfsetispeed(&opt, B1500000);
- cfsetospeed(&opt, B1500000);
- break;
- case 2000000:
- cfsetispeed(&opt, B2000000);
- cfsetospeed(&opt, B2000000);
- break;
- case 2500000:
- cfsetispeed(&opt, B2500000);
- cfsetospeed(&opt, B2500000);
- break;
- case 3000000:
- cfsetispeed(&opt, B3000000);
- cfsetospeed(&opt, B3000000);
- break;
- case 3500000:
- cfsetispeed(&opt, B3500000);
- cfsetospeed(&opt, B3500000);
- break;
- case 4000000:
- cfsetispeed(&opt, B4000000);
- cfsetospeed(&opt, B4000000);
- break;
- default:
- printf("unsupported baudrate %d\n", attr->baudrate);
- return FALSE;
- break;
- }
- /************************校验位************************/
- switch (attr->parity)
- {
- case COMM_NOPARITY:
- opt.c_cflag &= ~PARENB;
- opt.c_iflag &= ~INPCK;
- break;
- case COMM_ODDPARITY:
- opt.c_cflag |= PARENB;
- opt.c_cflag |= PARODD;
- opt.c_iflag |= INPCK;
- break;
- case COMM_EVENPARITY:
- opt.c_cflag |= PARENB;
- opt.c_cflag &= ~PARODD;
- opt.c_iflag |= INPCK;
- default:
- printf("unsupported parity %d\n", attr->parity);
- return FALSE;
- break;
- }
- opt.c_cflag &= ~CSIZE; /*无论设置多少校验位都需要的*/
- /*******************数据位*****************/
- switch (attr->databits)
- {
- case 5:
- opt.c_cflag |= CS5;
- break;
- case 6:
- opt.c_cflag |= CS6;
- break;
- case 7:
- opt.c_cflag |= CS7;
- break;
- case 8:
- opt.c_cflag |= CS8;
- break;
- default:
- printf("unsupported data bits %d\n", attr->databits);
- return FALSE;
- break;
- }
- opt.c_cflag &= ~CSTOPB;
- /*******************停止位***************/
- switch (attr->stopbits)
- {
- case COMM_ONESTOPBIT:
- opt.c_cflag &= ~CSTOPB;
- break;
- case COMM_TWOSTOPBITS:
- opt.c_cflag |= CSTOPB;
- break;
- default:
- printf("unsupported stop bits %d\n", attr->stopbits);
- return FALSE;
- break;
- }
- /*等待时间,阻塞模式下设置的*/
- //opt.c_cc[VTIME] = 0; /*设置超时时间*/
- //opt.c_cc[VMIN] = 1;
- opt.c_iflag &= ~(ICRNL | INLCR);
- opt.c_iflag &= ~(IXON | IXOFF | IXANY);/*关闭软件流控(一般都是关闭软硬流控,我也不知道为啥)*/
- tcflush(fd, TCIOFLUSH); //刷清缓冲区
- if (tcsetattr(fd, TCSANOW, &opt) < 0)
- {
- printf("tcsetattr faild\n");
- return FALSE;
- }
- return TRUE;
- }
当以阻塞模式打开时也可以通过修改结构体termios来改变位非阻塞模式或者通过函数fcntl()函数:
阻塞:fcntl(fd, F_SETFL, 0)
对于read,阻塞指当串口输入缓冲区没有数据的时候,read函数将会阻塞在这里,直到串口输入缓冲区中有数据可读取时read读到了需要的字节数之后,返回值为读到的字节数;对于write,指当串口输出缓冲区满或剩下的空间小于将要写入的字节数,write函数将阻塞在这里,一直到串口输出缓冲区中剩下的空间大于等于将要写入的字节数,执行写入操作,返回写入的字节数。
注:控制符VTIME定义要等待的时间t(百毫秒),VMIN定义了要等待的最小字节数n,以下几种情况:
VTIME=0,VMIN=n,read必须在读取了VMIN个字节的数据或者收到一个信号才会返回。
VTIME=t,VMIN=0,不管能否读取到数据,read也要等待VTIME的时间量。
VTIME=t,VMIN=n,那么将从read读取第一个字节的数据时开始计时,并会在读取到VMIN个字节或者VTIME时间后返回。
VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。
非阻塞的定义:fcntl(fd, F_SETFL,FNDELAY)
当串口输入缓冲区没有数据的时候,read函数立即返回,返回值为0。
- void get_com_attr(int fd)
- {
- struct termios opt;
- if(fd < 0)
- {
- printf("get_com_attr error");
- exit(0);
- }
- memset(&opt, 0, sizeof(struct termios));
- tcgetattr(fd, &opt);
- cfmakeraw(&opt);
- }
有必要说一下int tcsetattr(int fd, int optional_actions, const struct termios *termios_p)
int tcgetattr(int fd, struct termios *termios_p)
tcgetattr函数用于获取与终端相关的参数。参数fd为终端的文件描述符,结果保存在termios结构体中。
tcsetattr函数用于设置终端的相关参数。参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数;
optional_actions可以取如下的值:
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
3.读取串口
这里也是将read()系统调用封装成com_read(),当我们设置好通讯协议了(串口属性),就可以对串口进行读写了。
参数一fd就不用说了,第二个参数read_buff从名字看出就是要把数据读到这个缓冲区中,第三个参数是你想要读多少字节,注意是”你想要“,而返回值则是读到的真正字节数,当你读到末尾(假如缓冲区有10个字节,而你想要读20个)或者出现异常中断了读操作,就会出现返回值ret(return) != nbytes。
- int com_read(int fd, unsigned char *read_buff, unsigned int nbytes)
- {
- int ret;
- if(fd < 0)
- {
- printf("com_read error");
- exit(0);
- }
- ret = read(fd, read_buff, nbytes);
- return ret;
- }
4.写入串口
道理和写差不多
- int com_write(int fd, BYTE *write_buff, DWORD nbytes)
- {
- int ret;
- if(fd < 0)
- {
- printf("com_write error");
- exit(0);
- }
- ret = write(fd, write_buff, nbytes);
- return ret;
- }
5.关闭串口
记得每次操作完串口要关闭串口(当然了,当你操作多个文件时可别操作错了文件描述符,那就gg了)
- void com_close(int fd)
- {
- if(fd < 0)
- {
- printf("com_close error");
- exit(0);
- }
- close(fd);
- }
好了,万事具备,下面就可以插上设备刷卡读卡号啦(注意看清你的设备ttyUSBx中的x是多少啊),具体读卡号函数就看大家的具体需求啦。
由于博主的项目需求是要将卡号变成一个字符串然后再填充到另一个字符串,然后再巴拉巴拉,可是这个卡号读出来是一串16进制数据,所以想了半天决定用类型转换(不过听说可以用fprintf)。