我可以用几条微不足道的标签来定义:“石油炼化产业工人”、“程序匠人”、“成人大学学历”、“一个还算顾家的男人”、“会弹吉他和画画的父亲”、“他”。没错,你没看错,“他”也是我的标签,在熙熙攘攘的人群中,我就是那个“他”,默默无闻却自得其乐的耕耘在自己的一方天地里!
分类: C/C++
2015-04-11 15:57:55
storysnail的Linux串口编程笔记
作者 |
He
YiJun – storysnail |
||||||||
团队 |
ls |
||||||||
版权 |
转载请保留本声明!
本文档包含的原创代码根据General
Public License,v3 发布
本文档根据GNU
Free Documentation License 1.3发布
文中所引用的软件版权详见各软件版权具体声明,文中所提及的所有商标均为各自商标所有人的财产。 |
||||||||
更新 |
|
前言:
这半个月因肺部感染而不得不暂时终止那令人生厌的中石油巡检工作,闭门在家安静的
修养。整月的工钱自然是泡汤了,可却得来了极其珍贵的个人闲暇时光,让我能淋漓尽致的
做软件方面的研究,虽是粗茶淡饭,针剂苦药,但可静心埋头于书房,却比天堂还甜美!
恍惚已至月末,工作单位来了音讯,让我一下子从甜美的梦中惊醒,从哪里来,回哪里
去,这种如"主体思想"一样可怕的思维是我挥之不去的梦魇,无奈、不知所措、病弱的身体
却不由自主的向那发声的地方靠去!
好了,还是不再发牢骚了,只是个人觉得这种臃肿低效的国企能够存在,本身就是对“
国富论”绝佳的嘲讽,我只能用世界是多元的来啊Q一下了!
切入正题,这段时间做GSM/GPRS和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返回[1,MIN]
这的TIME是read被阻塞的时间 |
TIME没溢出时,read返回[MIN,nbytes]
TIME溢出时,read返回[1,MIN] 这个TIME是队列里接收到的字节间的时间,所以调用者可能会被一直阻塞 |
三:示例程序
上面介绍了大部分的串口通信会用到的函数和数据结构,Linux上写串口通讯程序时可以选择
采用多进程,当然也可以使用Pthread的多线程,不过我的示例程序并没有使用这些,和windows
上的示例程序类似,我还是认为这样可以更清晰的展现如何操作串口。
/**************************************************************************************************
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
main.c
Develop Team : ls
Programmer
: He YiJun (storysnail
Program comments : Ling Ying
License : GPLv3
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 : ls
Programmer
: He YiJun (storysnail
Program comments : Ling Ying
License : GPLv3
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 : ls
Programmer
: He YiJun (storysnail
Program comments : Ling Ying
License : GPLv3
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 : ls
# Programmer : He YiJun (storysnail<at>gmail.com)
# Program comments : Ling Ying
# License : GPLv3
# 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的文章