storysnailLinux串口编程笔记





作者

He YiJun – storysnail<at>hotmail.com

   

版权

转载请保留本声明!


本文档包含的原创代码根据General Public License,v3 发布
GPLv3
许可证的副本可以在这里获得:http://www.gnu.org/licenses/gpl.html

本文档根据GNU Free Documentation License 1.3发布
GFDL1.3
许可证的副本可以在这里获得:http://www.gnu.org/licenses/gfdl.html

文中所引用的软件版权详见各软件版权具体声明,文中所提及的所有商标均为各自商标所有人的财产。
作者在此向所有提供过帮助和支持的朋友表示感谢,此致!

更新

2015-04-12

修改笔误和版式

2014-11-10

修改版权

2009-09-09

整理成完整的记录

   






前言:

这半个月因肺部感染而不得不暂时终止那令人生厌的中石油巡检工作,闭门在家安静的
修养。整月的工钱自然是泡汤了,可却得来了极其珍贵的个人闲暇时光,让我能淋漓尽致的
做软件方面的研究,虽是粗茶淡饭,针剂苦药,但可静心埋头于书房,却比天堂还甜美!

恍惚已至月末,工作单位来了音讯,让我一下子从甜美的梦中惊醒,从哪里来,回哪里
去,这种如"主体思想"一样可怕的思维是我挥之不去的梦魇,无奈、不知所措、病弱的身体
却不由自主的向那发声的地方靠去!

好了,还是不再发牢骚了,只是个人觉得这种臃肿低效的国企能够存在,本身就是对“
国富论”绝佳的嘲讽,我只能用世界是多元的来啊Q一下了!

切入正题,这段时间做GSM/GPRSGPS的小东西,需要通过串口发送AT指令来控制,以前
调试一直在用串口助手和minicom之类的现成软件,可是一点都不爽,为什么不自己写个操作
串口的软件,就像在ARMstm32上一样!

这文章其实只是我的一个笔记,分为两篇,一篇是《storysnailWindows串口编程笔记》,
另一姊妹篇是《storysnailLinux串口编程笔记》,由于网上已经有非常多的类似文章,有些长篇
大论,有些短小精悍,连我自己都思考过是否有必要再写一篇,但在Ling的鼓动下还是写了!

本篇是Linux串口编程笔记,详细介绍了串口通信会用到的api函数,并提供了一个示例程序,
这个示例程序是在EEEPC701debian系统上编写测试的。

一:写串口程序用到的函数

1:Linuxwindows串口设备文件名对照



操作系统

串口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_RDONLYO_WRONLYO_RDWR标志位是互斥的,不可同时使用,但可与下列的标志位|运算组合。

O_CREAT

若欲打开的文件不存在则自动建立该文件。

O_EXCL

如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREATO_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。

O_NOCTTY

表明本程序不是该串口上的“控制终端”。即本程序不受Ctrl+cCtrl+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,并且设置errnoEAGAIN。在GNU CO_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>

intCom_Open(void)

{

intfd=-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信号线设置成低电位,这会告诉另一端的设备你的计算机状态。

举例:

intCom_Close(intfd)

{

if(fd<0)

return-1;

 

if(close(fd)==-1)

return-1;

 

printf("close uart\n\n");

 

return0;

}



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,...);

参数说明:

举例:

#defineBLOCK_IO0

#defineNONBLOCK_IO1

 

intCom_SetIOBlockFlag(intfd,intvalue)

{

intoldflags;

 

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

 

returnfcntl(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 返回输入队列中的字节数

 

4IO控制命令用于获取和设置MODEM握手,如RTSCTSDTRDSRRICD等,不过有些嵌入式设备的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状态位

intfd;

intstatus;

ioctl(fd,TIOCMGET,&status);

//设置MODEM状态位,DTR信号线设成掉线状态。

intfd;

intstatus;

ioctl(fd,TIOCMGET,&status);

status&=~TIOCM_DTR;

ioctl(fd,TIOCMSET,&status);

 

//得到串口输入队列中的字节数

intfd;

intbytes;

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

举例:

structtermiosoptions;

speed_tInSpeed,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.hbits/termios.h头文件中,由一个termios结构体和12个函数构成,这里只介绍了部分内容,更多内容可参考本文结尾提到的书籍!

 

1termios结构

 

 

typedefunsignedcharcc_t;

typedefunsignedintspeed_t;

typedefunsignedinttcflag_t;

 

#defineNCCS32

structtermios

{

tcflag_tc_iflag;/*输入方式标志 */

tcflag_tc_oflag;/*输出方式标志 */

tcflag_tc_cflag;/*控制方式标志 */

tcflag_tc_lflag;/*局部方式标志 */

cc_tc_line;/* line discipline */

cc_tc_cc[NCCS];/*控制字符数组 */

speed_tc_ispeed;/*输入波特率 */

speed_tc_ospeed;/*输出波特率 */

};



2termios结构体的c_cflag成员

termios结构体的c_cflag描述基本的串口硬件控制,像波特率、是否激活奇偶校验检查、校验方式、一个字节的数据位个数、停止位个数和硬件流控制等。所有的都是位操作,需要使用位运算的与或非组合来设置或者清除相关标志。

 

#defineCSIZE0000060//一个字节的数据位个数

#defineCS50000000//一个字节的数据位个数为5

#defineCS60000020//一个字节的数据位个数为6

#defineCS70000040//一个字节的数据位个数为7

#defineCS80000060//一个字节的数据位个数为8

#defineCSTOPB0000100//停止位个数2,否则为1 不理解的参见下面的代码

#defineCREAD0000200//启用接收器

#definePARENB0000400//激活奇偶校验检查

#definePARODD0001000//校验方式为奇效验,否则为偶效验

#defineHUPCL0002000//最后一次关闭时挂断,即将DTR信号线设置成低电位

#defineCLOCAL0004000//忽略状态线,没有流控制

#defineCRTSCTS020000000000//启用硬件流控制,注意软件流控是在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;





3termios结构体的c_lflag成员

termios结构体的c_lflag用来设置串口驱动与用户之间的界面,例如打开还是关闭回显,是否显示ERASE字符,使用加工方式输入还是使用非加工方式输入。由于我只需要得到原始数据,所以只需要将termios结构体的c_lflag0即可。

 

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);

注意:当发送数据给调制解调器或其它计算机时,如果它们对发送的数据启用回显,则发送端千万不要启用回显,那样会导致两个连接的串口之间生成信息反馈循环。

 

4termios结构体的c_oflag成员

termios结构体的c_oflag用来控制串口的数据输出例如转换换行符CR/LF等,或选择使用加工方式输出还是使用非加工方式输出。和上面一样,我只需要得到原始数据,所以只需要将termios结构体的c_oflag0即可。

 

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;



5termios结构体的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);



6termios结构体的c_cc成员

termios结构体的c_cc里面包括了控制字符的定义和超时参数。

c_cc数组在加工方式和非加工方式下的作用不同,作为c_cc数组索引的宏常量也分为两种情况使用,

并且索引值在两种加工方式下有部分重叠,所以一定要区分对待。

 

#defineVINTR0

#defineVQUIT1

#defineVERASE2

#defineVKILL3

#defineVEOF4

#defineVTIME5

#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的另一个功能就是设置超时参数,MINTIME存储在c_cc数组中,通过VMINVTIME引用. .MINTIME只在非加工方式下才有实际意义,它们分别用来控制等待多少字节的输入数据到达以及等待多长时间,单位为0.1秒。超时设置在下列两种情况下会失效:

  • 在加工方式:使用O_NUNBLOCK参数调用openfcntl函数,使IO变为非阻塞

  • 对于非加工输入方式,典型的设置如下:

options.c_cc[VMIN]=1;

options.c_cc[VTIME]=0;

下面是MINTIME的功能组合表:


MIN = 0

MIN > 0

TIME = 0

read立即返回[0,nbytes]

当队列中有大于MIN的字节时,read返回[MIN,nbytes]否则read会一直阻塞

TIME > 0

TIME没溢出时,read返回[MIN,nbytes]



TIME溢出时,read返回[1,MIN]



这的TIMEread被阻塞的时间

TIME没溢出时,read返回[MIN,nbytes]



TIME溢出时,read返回[1,MIN]

这个TIME是队列里接收到的字节间的时间,所以调用者可能会被一直阻塞



 

 

 

 

 

 

 

 





三:示例程序

上面介绍了大部分的串口通信会用到的函数和数据结构,Linux上写串口通讯程序时可以选择

采用多进程,当然也可以使用Pthread的多线程,不过我的示例程序并没有使用这些,和windows

上的示例程序类似,我还是认为这样可以更清晰的展现如何操作串口。

 

/**************************************************************************************************

* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *

main.c

 

Develop Team : ls

Programmer : He YiJun (storysnail<at>gmail.com)

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"

 

#defineCMD_MAX_LEN255

 

/**************************************************************************************************

函数名称: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<at>gmail.com)

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<at>gmail.com)

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_IONONBLOCK_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:比特率 3006001200240048009600192003840057600115200

databit:一个字节的数据位个数 5678

stopbit:停止位个数12

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:比特率 3006001200240048009600192003840057600115200

出口参数:成功返回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的文章