storysnail的Linux串口编程笔记
Author : He YiJun (storysnail<at>gmail.com QQ:363559089) Develop Team : 7fane Team (http://www.7fane.com,测试网站,到2013年11月底) Editor : Yang QiuXi Art Designer : He YiJun License : 7fane Team License 1.0 Last Update : 2013-03-26
这半个月因肺部感染而不得不暂时终止那令人生厌的中石油巡检工作,闭门在家安静的
修养。整月的工钱自然是泡汤了,可却得来了极其珍贵的个人闲暇时光,让我能淋漓尽致的
做软件方面的研究,虽是粗茶淡饭,针剂苦药,但可静心埋头于书房,却比天堂还甜美!
恍惚已至月末,工作单位来了音讯,让我一下子从甜美的梦中惊醒,从哪里来,回哪里
去,这种如"主体思想"一样可怕的思维是我挥之不去的梦魇,无奈、不知所措、病弱的身体
却不由自主的向那发声的地方靠去!
好了,还是不再发牢骚了,只是个人觉得这种臃肿低效的国企能够存在,本身就是对“
国富论”绝佳的嘲讽,我只能用世界是多元的来啊Q一下了!
切入正题,这段时间做GSM/GPRG和GPS的小东西,需要通过串口发送AT指令来控制,以前
调试一直在用串口助手和minicom之类的现成软件,可是一点都不爽,为什么不自己写个操作
串口的软件,就像在ARM和stm32上一样!
这文章其实只是我的一个笔记,分为两篇,一篇是《storysnail的Windows串口编程笔记》,
另一篇是《storysnail的Linux串口编程笔记》,由于网上已经有非常多的类似文章,有些长篇
大论,有些短小精悍,连我自己都思考过是否有必要再写一篇,但在Ling的鼓动下还是写了!
本篇是Linux串口编程笔记,详细介绍了串口通信会用到的api函数,并提供了一个示例程序,
这个示例程序是在EEEPC701的debian系统上编写测试的。
一:写串口程序用到的函数
1:Linux与windows串口设备文件名对照
操作系统 串口1 串口2 USB/RS-232转换器 Windows COM1 COM2 COMX(我的系统上X=4) Linux /dev/ttyS0 /dev/ttyS1 /dev/ttyUSB0
2:写串口程序用到的函数
串行通讯函数定义在termios.h头文件中,所以需要包含该文件。下面是要介绍的函数列表
open 打开串口 close 关闭串口 read 接收数据 write 发送数据 fcntl 设置IO为阻塞或非阻塞 ioctl 实现POSIX.1 GTI控制界面所有函数功能 tcgetattr 读取串口设备的当前属性,保存在termios_p所指向的结构中 tcsetattr 设置串口设备的当前属性 cfgetospeed 返回输出波特率 cfgetispeed 返回输入波特率 cfsetispeed 设定输入波特率 cfsetospeed 设定输出波特率
2.1
open
用途:打开串口
原型:int open( const char * pathname,int flags);
参数说明:
pathname: 指向欲打开的文件路径字符串
flags 所能使用的标志位:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件。
O_RDONLY、O_WRONLY、O_RDWR标志位是互斥的,不可同时使用,但可与下列的
标志位|运算组合。
O_CREAT 若欲打开的文件不存在则自动建立该文件。
O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,
否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为
符号连接,则会打开文件失败。
O_NOCTTY 表明本程序不是该串口上的“控制终端”。即本程序不受Ctrl+c、Ctrl+z这类
组合键产生的信号影响。
O_TRUNC 若文件存在并且以可写的方式打开时,此标志位会令文件长度清为0,
而原来存于该文件的 资料也会消失。
O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK 非阻塞模式打开。在打开很多串行端口设备时,open函数有时候会阻塞很长一段
时间.例如当打开一个调制解调器的端口就会阻塞直到DCD信号线有信号电压为止,
如果串口的另一端没有连接任何设备,那么DCD信号线上就不会有信号电压,这会
导致open函数一直阻塞在那里等待DCD信号,导致程序失去响应。使用该选项
程序会忽略DCD信号线上的信号。所以为了无阻塞地打开一个文件但不影响正常的
阻塞IO,必须先用O_NONBLOCK选项调用open函数,然后使用fcntl切换到非阻塞IO
状态。
O_NDELAY 其实和O_NONBLOCK基本相同,所产生的结果都是使I/O变成非阻塞模式,唯一的
一点差别是O_NDELAY会让函数马上返回0,而O_NONBLOCK在读不到数据时会返回-1,
并且设置errno为EAGAIN。在GNU C中O_NDELAY只是为了与BSD的程序兼容,实际上
在fcntl.h中是使用O_NONBLOCK作为宏定义,所以建议现在使用O_NONBLOCK.
#define O_NDELAY O_NONBLOCK
O_SYNC 以同步的方式打开文件。
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。
O_DIRECTORY如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。
特别说明:
关于FNDELAY
还有一个FNDELAY,实际上在fcntl.h中是使用O_NDELAY作为宏定义.
#define FNDELAY O_NDELAY
举例:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <termios.h>
int Com_Open(void) { int fd = -1; fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK); if(fd == -1) { perror("open_port: Unable to open /dev/ttyUSB0"); } if(Com_SetIOBlockFlag(fd,BLOCK_IO) == -1) printf("IO set error\n"); return (fd); }
2.2
close
用途:关闭串口
原型:int close( int fd);
参数说明:
fd: 文件描述符,关闭串口后计算机会将DTR信号线设置成低电位,这会告诉另一端的设备你的计算机状态。
举例:
int Com_Close(int fd) { if (fd < 0) return -1; if (close(fd) == -1) return -1; printf("close uart\n\n"); return 0; }
2.3
read
用途:接收数据
原型:ssize_t read(int fd, void *buf, ssize_t nbyte);
参数说明:
fd: 文件描述符
buffer:读取缓冲区
number:要读多少个字节,不能大于buf指向的缓冲区大小
举例:
n = read(fd,buf,sizeof(buf));
2.4
write
用途:发送数据
原型:ssize_t write (int fd,const void * buf,size_t count);
参数说明:
fd: 文件描述符
buffer:写入缓冲区
count:要写多少个字节
write函数也会返回发送数据的字节数或者在发生错误的时候返回-1。
通常,发送数据最常见的错误就是EIO,当调制解调器或者数据链路将Data Carrier Detect(DCD)
信号线弄掉了,就会发生这个错误。而且,直至关闭端口这个情况会一直持续。
举例:
n = write(fd, "AT\r", 3); if (n < 0) perror("write() of 4 bytes failed!\n", stderr);
2.5
fcntl
用途:设置IO为阻塞或非阻塞
原型:int fcntl(int fd,int cmd,...);
参数说明:略
举例:
#define BLOCK_IO 0
#define NONBLOCK_IO 1
int Com_SetIOBlockFlag(int fd,int value) { int oldflags; if (fd == -1) return -1; oldflags = fcntl(fd,F_GETFL,0); if(oldflags == -1) { printf("get IO flags error\n"); return -1; } if(value == BLOCK_IO) oldflags &= ~O_NONBLOCK; //设置成阻塞IO else oldflags |= O_NONBLOCK; //设置成非阻塞IO return fcntl(fd,F_GETFL,oldflags); }
2.6
ioctl
用途:实现POSIX.1 GTI控制界面所有函数功能,配置串口不仅仅可以使用上面说的方法,
在Linux环境下,还可以使用ioctl系统调用来实现,写过驱动程序的人都知道,
ioctl是个大口袋,任何IO操作都可以交给它。
原型:int ioctl(int fd, int request, ...);
参数说明:
fd: 串口设备文件的文件描述符。
request: 参数在asm-generic/ioctl.h头文件中定义
串口设置
TCGETS 读取当前的串口属性
同功能函数:tcgetattr
TCSETS 设置串口属性并立即生效
同功能函数:tcsetattr(fd, TCSANOW, &options)
TCSETSF 设置串口属性,等到输入输出队列都清空了再生效
同功能函数:tcsetattr(fd, TCSAFLUSH, &options)
TCSETSW 设置串口属性,等到输入输出队列都清空或传输完成了再生效
同功能函数:tcsetattr(fd, TCSADRAIN, &options)
TCSBRK 在指定时间后发送break
同功能函数:tcsendbreak
TCXONC 控制软件流控制
同功能函数:tcflow
TCFLSH 丢弃输入输出队列中尚未传送或读取的数据!
同功能函数:tcflush
注意:tcflush这个函数的命名就是个灾难,因为"flush"在linux中用于
描述“等待直至所有输入输出全部传送完毕”,例如:fflush
FIONREAD 返回输入队列中的字节数
这4个IO控制命令用于获取和设置MODEM握手,如RTS、CTS、DTR、DSR、RI、CD等,不过有些嵌入式设备的uart
并不包括完整的MODEM控制信号线
TIOCMGET 获取MODEM状态位
TIOCMSET 设置MODEM状态位
TIOCMBIC 清除指示MODEM的位
TIOCMBIS 设置指示MODEM的位
获得串口控制信号
TIOCM_LE DSR (data set ready/line enable)
TIOCM_DTR DTR (data terminal ready)
TIOCM_RTS RTS (request to send)
TIOCM_ST Secondary TXD (transmit)
TIOCM_SR Secondary RXD (receive)
TIOCM_CTS CTS (clear to send)
TIOCM_CAR DCD (data carrier detect)
TIOCM_CD Synonym for TIOCM_CAR
TIOCM_RNG RNG (ring)
TIOCM_RI Synonym for TIOCM_RNG
TIOCM_DSR DSR (data set ready)
举例:
//获取MODEM状态位 int fd; int status; ioctl(fd, TIOCMGET, &status); //设置MODEM状态位,将DTR信号线设成掉线状态。 int fd; int status; ioctl(fd, TIOCMGET, &status); status &= ~TIOCM_DTR; ioctl(fd, TIOCMSET, &status); //得到串口输入队列中的字节数 int fd; int bytes; ioctl(fd, FIONREAD, &bytes);
2.7
tcgetattr()
用途:读取串口设备的当前属性,保存在termios_p所指向的结构中
原型:int tcgetattr (int fd, struct termios *termios_p);
参数说明:
fd: 串口设备描述符
termios_p: termios结构体指针
2.8
tcsetattr()
用途:设置串口设备的当前属性
原型:int tcsetattr (int fd, int action,const struct termios *termios_p);
参数说明:
fd: 串口设备描述符
action: TCSANOW 立即做出改变
TCSADRAIN 等到输入输出队列都清空了再生效
TCSAFLUSH 等到输入输出队列都清空或传输完成了再生效
termios_p: termios结构体指针
2.9
cfgetospeed()
用途:返回输出波特率
原型:speed_t cfgetospeed (const struct termios *termios_p);
参数说明:
termios_p: termios结构体指针
2.10
cfgetispeed()
用途:返回输入波特率
原型:speed_t cfgetispeed (const struct termios *termios_p);
参数说明:
termios_p: termios结构体指针
2.11
cfsetispeed()
用途:设定输入波特率
原型:int cfsetispeed (struct termios *termios_p, speed_t speed);
termios_p: termios结构体指针
speed: 见cfsetispeed()函数
2.12
cfsetospeed()
用途:设定输出波特率
原型:int cfsetospeed (struct termios *termios_p, speed_t speed);
termios_p: termios结构体指针
speed: 详见bits/termios.h头文件,这里只列出几个常用值
#define B1200 0000011
#define B1800 0000012
#define B2400 0000013
#define B4800 0000014
#define B9600 0000015
#define B19200 0000016
#define B38400 0000017
#define B57600 0010001
#define B115200 0010002
举例:
struct termios options; speed_t InSpeed,OutSpeed; tcgetattr(fd,&options); InSpeed = cfgetispeed(&options); OutSpeed = cfgetospeed(&options); //设置输入和输出波特率为115200 cfsetispeed(&options,B115200); cfsetospeed(&options,B115200); tcsetattr(fd,TCSANOW,&options);
二:配置串口 -- POSIX.1 GTI控制界面
POSIX.1 GTI定义在termios.h和bits/termios.h头文件中,由一个termios结构体和12个函数构成,这里只介绍
了部分内容,更多内容可参考本文结尾提到的书籍!
1:termios结构
typedef unsigned char cc_t; typedef unsigned int speed_t; typedef unsigned int tcflag_t; #define NCCS 32 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]; /* 控制字符数组 */ speed_t c_ispeed; /* 输入波特率 */ speed_t c_ospeed; /* 输出波特率 */ };
2:termios结构体的c_cflag成员
termios结构体的c_cflag描述基本的串口硬件控制,像波特率、是否激活奇偶校验检查、
校验方式、一个字节的数据位个数、停止位个数和硬件流控制等。所有的都是位操作,需要使
用位运算的与或非组合来设置或者清除相关标志。
#define CSIZE 0000060 //一个字节的数据位个数 #define CS5 0000000 //一个字节的数据位个数为5 #define CS6 0000020 //一个字节的数据位个数为6 #define CS7 0000040 //一个字节的数据位个数为7 #define CS8 0000060 //一个字节的数据位个数为8 #define CSTOPB 0000100 //停止位个数2,否则为1 不理解的参见下面的代码 #define CREAD 0000200 //启用接收器 #define PARENB 0000400 //激活奇偶校验检查 #define PARODD 0001000 //校验方式为奇效验,否则为偶效验 #define HUPCL 0002000 //最后一次关闭时挂断,即将DTR信号线设置成低电位 #define CLOCAL 0004000 //忽略状态线,没有流控制 #define CRTSCTS 020000000000 //启用硬件流控制,注意软件流控是在c_iflag中设置的
设置一个字节的数据位个数 options.c_flag &= ~CSIZE; /* Mask the character size bits */ options.c_flag |= CS8; /* Select 8 data bits */ 设置奇偶校验 无奇偶校验 (8N1) options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; 偶校验(7E1) options.c_cflag |= PARENB options.c_cflag &= ~PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7; 奇校验(7O1) options.c_cflag |= PARENB options.c_cflag |= PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7; Mark校验,通过2位停止位来模拟(7M1) options.c_cflag &= ~PARENB options.c_cflag |= CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7; Space校验 ,这与无奇偶校验相同(7S1) options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; 设置硬件流控制 启用硬件流控制 options.c_cflag |= CRTSCTS; /* Also called CRTSCTS 停用硬件流控制 options.c_cflag &= ~CRTSCTS;
3:termios结构体的c_lflag成员
termios结构体的c_lflag用来设置串口驱动与用户之间的界面,例如打开还是关闭回显,
是否显示ERASE字符,使用加工方式输入还是使用非加工方式输入。由于我只需要得到原始数据,
所以只需要将termios结构体的c_lflag置0即可。
ISIG 启用 SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT信号
ICANON 启用加工方式输入,否则使用非加工方式输入
XCASE 启用加工大小写
ECHO 启用回显
ECHOE 用BS-SP-BS回显擦写字符(ERASE)
ECHOK 回显KILL字符
ECHONL 回显NL字符
NOFLSH 禁止中断、退出或终止后清除输出队列
IEXTEN 启用实现定义的功能
ECHOCTL 回显控制字符,例如 ^和 ~?
ECHOKE 通过擦去屏幕上的字符回显KILL
FLUSHO 输出被清除
PENDIN 重新打印悬挂的输出
TOSTOP 为后台输出发送SIGTTOU信号
设置非加工方式输入 在非加工方式下,原始输入根本不会被处理。输入字符只是被原封不动的接收。 options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 设置加工方式输入 options.c_lflag |= (ICANON | ECHO | ECHOE);
注意:当发送数据给调制解调器或其它计算机时,如果它们对发送的数据启用回显,
则发送端千万不要启用回显,那样会导致两个连接的串口之间生成信息反馈循环。
4:termios结构体的c_oflag成员
termios结构体的c_oflag用来控制串口的数据输出例如转换换行符CR/LF等,或选择
使用加工方式输出还是使用非加工方式输出。和上面一样,我只需要得到原始数据,所以
只需要将termios结构体的c_oflag置0即可。
OPOST 启用加工方式输出,否则使用非加工方式输出
OLCUC 转换输出时的小写字符为大写字符
ONLCR 将换行字符(NL)转换成回车换行字符(CR-NL)
OCRNL 将回车字符(CR)转换成换行字符(NL)
NOCR No CR output at column 0
ONLRET NL performs CR function
OFILL Use fill characters for delay
OFDEL Fill character is DEL
NLDLY Mask for delay time needed between lines
NL0 No delay for NLs
NL1 Delay further output after newline for 100 milliseconds
CRDLY Mask for delay time needed to return carriage to left column
CR0 No delay for CRs
CR1 Delay after CRs depending on current column position
CR2 Delay 100 milliseconds after sending CRs
CR3 Delay 150 milliseconds after sending CRs
TABDLY Mask for delay time needed after TABs
TAB0 No delay for TABs
TAB1 Delay after TABs depending on current column position
TAB2 Delay 100 milliseconds after sending TABs
TAB3 Expand TAB characters to spaces
BSDLY Mask for delay time needed after BSs
BS0 No delay for BSs
BS1 Delay 50 milliseconds after sending BSs
VTDLY Mask for delay time needed after VTs
VT0 No delay for VTs
VT1 Delay 2 seconds after sending VTs
FFDLY Mask for delay time needed after FFs
FF0 No delay for FFs
FF1 Delay 2 seconds after sending FFs
设置成使用非加工方式输出,当然c_oflag中其它选项都会失效 options.c_oflag &= ~OPOST;
5:termios结构体的c_iflag成员
termios结构体的c_iflag用来控制串口的数据输入,例如剥离输入字符为8位,使奇偶效验生效等
c_iflag成员可以使用的常量
常量 描述
INPCK 启用奇偶效验检查
IGNPAR 忽略奇偶效验错误
PARMRK 标识奇偶效验出错的数据
ISTRIP 剥去输入字符至7位
IXON 启用输出软件流控制
IXOFF 启用输入软件流控制
IXANY 启用任何输入字符回复暂停的输出
IGNBRK 忽略输入行的终止条件
BRKINT 当输入行中监测到终止条件时发送SIGINT信号
INLCR 将换行字符(NL)转换成回车字符(CR)
IGNCR 忽略CR
ICRNL 将回车字符(CR)转换成换行字符(NL)
IUCLC 将大写字符转换成小写字符
IMAXBEL 输入队列满时响铃
启用奇偶效验检查并从接收字符串中脱去奇偶校验位: options.c_iflag |= (INPCK | ISTRIP); 启用IGNPAR会忽略奇偶效验错误并给数据放行。如果你使用8N1模式,可以考虑开启该功能。 当启用INPCK后,启用PARMRK会标识奇偶效验出错的数据。设该数据为'X',如果启用IGNPAR,那么一个NUL('\0')字符会被 加入到发生奇偶校验错误的字符前面,即'\0''X'。 否则,DEL('\377',注意这是8进制)和NUL('\0')字符会和出错的字符一起送出,即'\377''\0''X'。 启用软件流控制 options.c_iflag |= (IXON | IXOFF | IXANY); 禁用软件流控制 options.c_iflag &= ~(IXON | IXOFF | IXANY);
6:termios结构体的c_cc成员
termios结构体的c_cc里面包括了控制字符的定义和超时参数。
c_cc数组在加工方式和非加工方式下的作用不同,作为c_cc数组索引的宏常量也分为两种情况使用,
并且索引值在两种加工方式下有部分重叠,所以一定要区分对待。
#define VINTR 0
#define VQUIT 1
#define VERASE 2
#define VKILL 3
#define VEOF 4
#define VTIME 5
#define VMIN 6
#define VSWTC 7
#define VSTART 8
#define VSTOP 9
#define VSUSP 10
#define VEOL 11
#define VREPRINT 12
#define VDISCARD 13
#define VWERASE 14
#define VLNEXT 15
#define VEOL2 16
c_cc的另一个功能就是设置超时参数,MIN和TIME存储在c_cc数组中,通过VMIN和VTIME引用.
MIN和TIME只在非加工方式下才有实际意义,它们分别用来控制等待多少字节的输入数据到达
以及等待多长时间,单位为0.1秒。
超时设置在下列两种情况下会失效:
在加工方式
使用O_NUNBLOCK参数调用open和fcntl函数,使IO变为非阻塞
对于非加工输入方式,典型的设置如下: options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 0;
下面是MIN和TIME的功能组合表:
MIN = 0 MIN > 0 TIME = 0 read立即返回[0,nbytes] 当队列中有大于MIN的字节时,read返回[MIN,nbytes] 否则read会一直阻塞 TIME > 0 TIME没溢出时,read返回[MIN,nbytes] TIME没溢出时,read返回[MIN,nbytes] TIME溢出时,read返回[1,MIN] TIME溢出时,read返回[1,MIN] 这的TIME是read被阻塞的时间 这个TIME是队列里接收到的字节间的时间, 所以调用者可能会被一直阻塞
三:示例程序
上面介绍了大部分的串口通信会用到的函数和数据结构,Linux上写串口通讯程序时可以选择
采用多进程,当然也可以使用Pthread的多线程,不过我的示例程序并没有使用这些,和windows
上的示例程序类似,我还是认为这样可以更清晰的展现如何操作串口。
/************************************************************************************************** * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * main.c Develop Team : 7fane Team Team Leader : He YiJun (storysnail<at>gmail.com QQ:363559089) Main Programmer : He YiJun Program comments : Ling Ying License : 7fane Team License 1.0 Last Update : 2013-03-25 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 功能说明: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 更 新: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 已知问题: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * **************************************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "uart-linux.h" #define CMD_MAX_LEN 255 /************************************************************************************************** 函数名称:ME_Init() 函数功能:设备初始化,其实这个函数只是聋子的耳朵--摆设 函数说明:无 入口参数:fd:串口设备文件描述符 出口参数:成功返回0,失败返回-1 调用实例:无 **************************************************************************************************/ int ME_Init(int fd) { unsigned char ReadBuffer[COM_MAX_BUFFER+1]; unsigned char WriteBuffer[COM_MAX_BUFFER+1]; ssize_t rCount = 0; ssize_t wCount = 0; while (1) { sleep(1); memset(ReadBuffer,'\0',COM_MAX_BUFFER+1); rCount = Com_Read(fd,ReadBuffer,COM_MAX_BUFFER); if(rCount > 0) { printf("Read com: %s\n",ReadBuffer); printf("Read com char num: %d\n",(int)rCount); if((strstr((char *)ReadBuffer,"AT") != NULL) && (strstr((char *)ReadBuffer,"OK") != NULL)) { break; } } sleep(1); memset(WriteBuffer,'\0',COM_MAX_BUFFER+1); WriteBuffer[0] = 'A'; WriteBuffer[1] = 'T'; WriteBuffer[2] = 0x0d; WriteBuffer[3] = '\0'; wCount = Com_Write(fd,WriteBuffer,strlen((char *)WriteBuffer)); if(wCount > 0) { printf("Wrote com: %s\n",WriteBuffer); printf("Wrote com char num: %d\n",(int)wCount); } } return 0; } /************************************************************************************************** 函数名称: main() 函数功能:main() 函数说明:main() 入口参数:无 出口参数:0 调用实例:无 **************************************************************************************************/ int main() { unsigned char ReadBuffer[COM_MAX_BUFFER+1]; unsigned char WriteBuffer[COM_MAX_BUFFER+1]; char cmd[CMD_MAX_LEN+1]; ssize_t rCount = 0; ssize_t wCount = 0; int fd = -1; if((fd = Com_Open()) == -1) { return 0; } if(Com_Setup(fd,115200, 8, 1, 0, 0) == -1) { Com_Close(fd); return 0; } if(ME_Init(fd) == -1) { Com_Close(fd); return 0; } while (1) { memset(cmd,'\0',CMD_MAX_LEN+1); printf ("\nEnter Command: "); if (!fgets (cmd, CMD_MAX_LEN,stdin)) { perror ("fget error"); exit(1); } /* Get rid of the new line at the end */ /* Messages use 8-bit characters */ cmd[strlen(cmd)-1] = '\0'; if (strcmp (cmd, "$Quit") == 0) break; if (strncmp (cmd, "block",sizeof("block")) == 0) { if(Com_SetIOBlockFlag(fd,BLOCK_IO) != -1) { printf("Set IO block flags success!\n"); } else { printf("Set IO block flags error!\n"); } } if (strncmp (cmd, "nonblock",sizeof("nonblock")) == 0) { if(Com_SetIOBlockFlag(fd,NONBLOCK_IO) != -1) { printf("Set IO no block flags success!\n"); } else { printf("Set IO no block flags error!\n"); } } if (strncmp (cmd, "read",sizeof("read")) == 0) { memset(ReadBuffer,'\0',COM_MAX_BUFFER+1); rCount = Com_Read(fd,ReadBuffer,COM_MAX_BUFFER); if(rCount > 0) { printf("ReadBuffer: %s\n",ReadBuffer); printf("Read com char num: %d\n",(int)rCount); } } if (strncmp (cmd, "write",sizeof("write")) == 0) { memset(WriteBuffer,'\0',COM_MAX_BUFFER); WriteBuffer[0] = 'A'; WriteBuffer[1] = 'T'; WriteBuffer[2] = 0x0d; WriteBuffer[3] = '\0'; printf("WriteBuffer: %s\n",WriteBuffer); wCount = Com_Write(fd,WriteBuffer,strlen((char *)WriteBuffer)); sleep(1); memset(ReadBuffer,'\0',COM_MAX_BUFFER+1); rCount = Com_Read(fd,ReadBuffer,COM_MAX_BUFFER); if(rCount > 0) { printf("ReadBuffer: %s\n",ReadBuffer); printf("Read com char num: %d\n",(int)rCount); } } } Com_Close(fd); return 0; } /************************************************************************************************** * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * uart-linux.h Develop Team : 7fane Team Team Leader : He YiJun (storysnail<at>gmail.com QQ:363559089) Main Programmer : He YiJun Program comments : Ling Ying License : 7fane Team License 1.0 Last Update : 2013-03-25 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 功能说明: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 更 新: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 已知问题: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * **************************************************************************************************/ #ifndef __UART_LINUX_H__ #define __UART_LINUX_H__ #define UART_MIN(A,B) ((A) < (B) ? (A):(B)) #define COM_MAX_BUFFER 512 //串口数据缓存的最大字节数 #define BLOCK_IO 0 #define NONBLOCK_IO 1 extern int Com_Open(void); extern int Com_Close(int fd); extern int Com_SetIOBlockFlag(int fd,int value); extern int Com_GetInQueByteCount(int fd,int *ByteCount); extern int Com_Setup(int fd,unsigned int baud, int databit, int stopbit, int parity, int flow); extern int Com_ChangeBaudrate(int fd, unsigned int baud); extern ssize_t Com_Read(int fd, unsigned char *ReadBuffer, ssize_t ReadSize); extern ssize_t Com_Write(int fd, unsigned char *WriteBuffer, ssize_t WriteSize); #endif /************************************************************************************************** * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * uart-linux.c Develop Team : 7fane Team Team Leader : He YiJun (storysnail<at>gmail.com QQ:363559089) Main Programmer : He YiJun Program comments : Ling Ying License : 7fane Team License 1.0 Last Update : 2013-03-25 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 功能说明: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 更 新: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 已知问题: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * **************************************************************************************************/ #include <termios.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/select.h> #include <stdio.h> #include <string.h> #include "uart-linux.h" /************************************************************************************************** 函数名称:Com_Open() 函数功能:打开串口 函数说明:无 入口参数:无 出口参数:成功返回串口设备文件描述符,失败返回-1 调用实例:无 **************************************************************************************************/ int Com_Open(void) { int fd = -1; fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK); if(fd == -1) { perror("open_port: Unable to open /dev/ttyUSB0"); } if(Com_SetIOBlockFlag(fd,BLOCK_IO) == -1) printf("IO set error\n"); return (fd); } /************************************************************************************************** 函数名称:Com_Close() 函数功能:关闭串口 函数说明:无 入口参数:fd:串口设备文件描述符 出口参数:无 调用实例:无 **************************************************************************************************/ int Com_Close(int fd) { if (fd < 0) return -1; if (close(fd) == -1) return -1; printf("close uart\n\n"); return 0; } /************************************************************************************************** 函数名称:Com_SetIOBlockFlag() 函数功能:设置IO为阻塞或非阻塞 函数说明:无 入口参数:fd:串口设备文件描述符 value:BLOCK_IO或NONBLOCK_IO 出口参数:失败返回-1,否则返回其它值 调用实例:无 **************************************************************************************************/ int Com_SetIOBlockFlag(int fd,int value) { int oldflags; if (fd == -1) return -1; oldflags = fcntl(fd,F_GETFL,0); if(oldflags == -1) { printf("get IO flags error\n"); return -1; } if(value == BLOCK_IO) oldflags &= ~O_NONBLOCK; //设置成阻塞IO else oldflags |= O_NONBLOCK; //设置成非阻塞IO return fcntl(fd,F_GETFL,oldflags); } /************************************************************************************************** 函数名称:Com_GetInBufSize() 函数功能:得到串口输入队列中的字节数 函数说明:无 入口参数:fd:串口设备文件描述符 InBufSize:串口输入队列中的字节数会保存在该指针所指向的内存 出口参数:失败返回-1,否则返回0 调用实例:无 **************************************************************************************************/ int Com_GetInQueByteCount(int fd,int *ByteCount) { int bytes = 0; if (fd == -1) return -1; if(ioctl(fd, FIONREAD, &bytes) != -1) { *ByteCount = bytes; return 0; } return -1; } /************************************************************************************************** 函数名称:Com_Setup() 函数功能:串口设定函数 函数说明:无 入口参数:fd:串口设备文件描述符 baud:比特率 300、600、1200、2400、4800、9600、19200、38400、57600、115200 databit:一个字节的数据位个数 5、6、7、8 stopbit:停止位个数1、2 parity:奇偶校验 0:无奇偶效验 1:奇效验 2:偶效验 flow:硬件流控制 0:无流控、 1:软件流控 2:硬件流控 出口参数:失败返回-1,否则返回0 调用实例:无 **************************************************************************************************/ int Com_Setup(int fd,unsigned int baud, int databit, int stopbit, int parity, int flow) { struct termios options; if (fd == -1) return -1; if(tcgetattr(fd, &options) == -1) return -1; switch (baud) { //取得比特率 case 300: options.c_cflag = B300; break; case 600: options.c_cflag = B600; break; case 1200: options.c_cflag = B1200; break; case 2400: options.c_cflag = B2400; break; case 4800: options.c_cflag = B4800; break; case 9600: options.c_cflag = B9600; break; case 19200: options.c_cflag = B19200; break; case 38400: options.c_cflag = B38400; break; case 57600: options.c_cflag = B57600; break; case 115200: options.c_cflag = B115200; break; default: options.c_cflag = B19200; break; } switch (databit) { //取得一个字节的数据位个数 case 5: options.c_cflag &= ~CSIZE; options.c_cflag |= CS5; break; case 6: options.c_cflag &= ~CSIZE; options.c_cflag |= CS6; break; case 7: options.c_cflag &= ~CSIZE; options.c_cflag |= CS7; break; case 8: options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; break; default: options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; break; } switch (parity) { //取得奇偶校验 case 0: options.c_cflag &= ~PARENB; // 无奇偶效验 options.c_iflag &= ~(INPCK | ISTRIP); // 禁用输入奇偶效验 options.c_iflag |= IGNPAR; // 忽略奇偶效验错误 break; case 1: options.c_cflag |= (PARENB | PARODD); // 启用奇偶效验且设置为奇效验 options.c_iflag |= (INPCK | ISTRIP); // 启用奇偶效验检查并从接收字符串中脱去奇偶校验位 options.c_iflag &= ~IGNPAR; // 不忽略奇偶效验错误 break; case 2: options.c_cflag |= PARENB; // 启用奇偶效验 options.c_cflag &= ~PARODD; // 设置为偶效验 options.c_iflag |= (INPCK | ISTRIP); // 启用奇偶效验检查并从接收字符串中脱去奇偶校验位 options.c_iflag &= ~IGNPAR; // 不忽略奇偶效验错误 break; default: options.c_cflag &= ~PARENB; // 无奇偶效验 options.c_iflag &= ~(INPCK | ISTRIP); // 禁用输入奇偶效验 options.c_iflag |= IGNPAR; // 忽略奇偶效验错误 break; } switch (stopbit) { //取得停止位个数 case 1: options.c_cflag &= ~CSTOPB; // 一个停止位 break; case 2: options.c_cflag |= CSTOPB; // 2个停止位 break; default: options.c_cflag &= ~CSTOPB; // 默认一个停止位 break; } switch (flow) { //取得流控制 case 0: options.c_cflag &= ~CRTSCTS; // 停用硬件流控制 options.c_iflag &= ~(IXON | IXOFF | IXANY); // 停用软件流控制 options.c_cflag |= CLOCAL; // 不使用流控制 case 1: options.c_cflag &= ~CRTSCTS; // 停用硬件流控制 options.c_cflag &= ~CLOCAL; // 使用流控制 options.c_iflag |= (IXON | IXOFF | IXANY); // 使用软件流控制 break; case 2: options.c_cflag &= ~CLOCAL; // 使用流控制 options.c_iflag &= ~(IXON | IXOFF | IXANY); // 停用软件流控制 options.c_cflag |= CRTSCTS; // 使用硬件流控制 break; default: options.c_cflag &= ~CRTSCTS; // 停用硬件流控制 options.c_iflag &= ~(IXON | IXOFF | IXANY); // 停用软件流控制 options.c_cflag |= CLOCAL; // 不使用流控制 break; } options.c_cflag |= CREAD; // 启用接收器 options.c_iflag |= IGNBRK; // 忽略输入行的终止条件 options.c_oflag = 0; // 非加工方式输出 options.c_lflag = 0; // 非加工方式 //options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //options.c_oflag &= ~OPOST; //如果串口输入队列没有数据,程序将在read调用处阻塞 options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 0; if(tcsetattr(fd, TCSANOW, &options) == -1) // 保存配置并立刻生效 return -1; //清空串口输入输出队列 tcflush(fd, TCOFLUSH); tcflush(fd, TCIFLUSH); return 0; } /************************************************************************************************** 函数名称:Com_ChangeBaudrate() 函数功能:设定串口波特率 函数说明:无 入口参数:fd:串口设备文件描述符 baud:比特率 300、600、1200、2400、4800、9600、19200、38400、57600、115200 出口参数:成功返回0,失败返回-1 调用实例:无 **************************************************************************************************/ int Com_ChangeBaudrate(int fd, unsigned int baud) { struct termios options; struct termios old_options; unsigned int baudrate = B19200; if (fd == -1) return -1; if(tcgetattr(fd, &old_options) == -1) return -1; if(tcgetattr(fd, &options) == -1) return -1; switch (baud) { case 300: baudrate = B300; break; case 600: baudrate = B600; break; case 1200: baudrate = B1200; break; case 2400: baudrate = B2400; break; case 4800: baudrate = B4800; break; case 9600: baudrate = B9600; break; case 19200: baudrate = B19200; break; case 38400: baudrate = B38400; break; case 57600: baudrate = B57600; break; case 115200: baudrate = B115200; break; default: baudrate = B19200; break; } if(cfsetispeed(&options, baudrate) == -1) return -1; if(cfsetospeed(&options, baudrate) == -1) { tcsetattr(fd, TCSANOW, &old_options); return -1; } while(tcdrain(fd) == -1);//tcdrain(fd);保证输出队列中的所有数据都被传送 //清空串口输入输出队列 tcflush(fd, TCOFLUSH); tcflush(fd, TCIFLUSH); if(tcsetattr(fd, TCSANOW, &options) == -1) { tcsetattr(fd, TCSANOW, &old_options); return -1; } return 0; } /************************************************************************************************** 函数名称:Com_Read() 函数功能:接收数据 函数说明:无 入口参数:fd:串口设备文件描述符 ReadBuffer:将数据写入ReadBuffer所指向的缓存区,并返回实际读到的字节数 ReadSize:欲读取的字节数 出口参数:成功返回实际读到的字节数,失败返回-1 调用实例:无 **************************************************************************************************/ ssize_t Com_Read(int fd, unsigned char *ReadBuffer, ssize_t ReadSize) { ssize_t rCount = 0; //实际读到的字节数 ssize_t dwBytesRead = 0; int InQueByteCount = 0; if (fd < 0) { perror("file description is valid"); return -1; } if (ReadBuffer == NULL) { perror("read buf is NULL"); return -1; } if(ReadSize > COM_MAX_BUFFER) dwBytesRead = COM_MAX_BUFFER; else dwBytesRead = ReadSize; memset(ReadBuffer, '\0', dwBytesRead); if(Com_GetInQueByteCount(fd,&InQueByteCount) != -1) { printf("Uart Queue have %d bytes\n",InQueByteCount); dwBytesRead=UART_MIN(dwBytesRead,InQueByteCount); } if(!dwBytesRead) return -1; rCount = read(fd, ReadBuffer, dwBytesRead); if (rCount < 0) { perror("read error\n"); return -1; } return rCount; } /************************************************************************************************** 函数名称:Com_Write() 函数功能:发送数据 函数说明:无 入口参数:fd:串口设备文件描述符 WriteBuffer:将WriteBuffer所指向的缓冲区中的数据写入串口 WriteSize:欲写入的字节数 出口参数:成功返回实际写入的字节数,失败返回-1 调用实例:无 **************************************************************************************************/ ssize_t Com_Write(int fd, unsigned char *WriteBuffer, ssize_t WriteSize) { ssize_t wCount = 0; //实际写入的字节数 ssize_t dwBytesWrite=WriteSize; if (fd < 0) { perror("file description is valid"); return -1; } if((dwBytesWrite > COM_MAX_BUFFER) || (!dwBytesWrite)) return -1; wCount = write(fd, WriteBuffer, dwBytesWrite); if (wCount < 0) { perror("write errot\n"); return -1; } while(tcdrain(fd) == -1); return wCount; }
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * # makefile # # Develop Team : 7fane Team # Team Leader : He YiJun (storysnail<at>gmail.com QQ:363559089) # Main Programmer : He YiJun # Program comments : Ling Ying # License : 7fane Team License 1.0 # Last Update : 2013-03-25 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * all:hara-CtlPanel-linux CC=gcc CFLAGS = -Wall -ggdb STD99 = -std=c99 OBJFILES = main.o uart-linux.o hara-CtlPanel-linux:$(OBJFILES) $(CC) $(CFLAGS) $(STD99) $(OBJFILES) -o hara-CtlPanel-linux main.o:uart-linux.h $(CC) $(CFLAGS) -c main.c -o main.o uart.o:uart-linux.h $(CC) $(CFLAGS) -c uart-linux.c -o uart-linux.o clean: rm -f *.o *~ hara-CtlPanel-linux
本文主要参考了
W.Richard stevens所著的《Advanced Programming in the UNIX Environment》
宋宝华编著的《linux设备驱动开发详解》
同时还参考了http://www.easysw.com/~mike/serial/serial.html的文章
7fane Team 协议 1.0 中文版
当您阅读、理解并愿意遵守以下条款时,您就拥有了获取、使用、复制、分发或通过通信
网络传播7fane Team作品的权利。
1. 7fane Team的作品可以在非商业用途下免费使用。
2. 如果软件提供了源代码,那么你可以更改源代码或软件接口以适应你的应用。
3. 在未获得7fane Team的授权之前,你不可以在商业用途下使用7fane Team的作品,
也不可以将7fane Team的作品用于营利为目的的活动。关于获得许可,请发送Email
到storysnail@gmail.com以获取更多信息。
4. 你不得租赁,再许可,出售,转让,抵押7fane Team的作品和服务。
5. 你不得删除或修改7fane Team作品的版权信息和相关的链接,例如网址信息或“关于窗口”
中的所有信息,除非您已经获得7fane Team的书面授权。
6. 你不可以通过修改7fane Team的作品来获得衍生作品,更不可以重新分配这些衍生作品。
7. 如果您不能遵守本协议,您的许可将被终止,您必须停止使用并删除7fane Team作品及其
副本,并且不可以再继续获取、使用、复制、分发或通过通信网络传播7fane Team作品。
8. 7fane Team拥有并保留修改本协议的权利和在本协议修改后不另行通知的权利。修改后
的新协议将适用于新的许可用户。
9. 7fane Team作品是作为不提供任何明确的或隐含的赔偿或担保的形式提供的。
10.7fane Team对于使用其作品而生成的任何信息不承担任何责任,也不会对传播或使用
这些信息承担责任。
11. 用户出于自愿而使用本7fane Team作品,您必须了解使用的风险,我们不承诺对用户提供
任何形式的技术支持、使用担保,所以你必须承担全部风险。
关于 "7fane Team作品" 的定义:
"7fane Team作品" 包括文本、图像、音频视频、软件等由7fane Team创作的一切。
版权所有 (c)2001-2012,7fane Team 保留所有权利。
<script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>