操作串口需要包含的头文件:
#include <stdio.h> /*标准输入输出定义*/
#include <errno.h> /*错误号定义*/
#include <sys/stat.h>
#include <fcntl.h> /*文件控制定义*/
#include <termios.h> /*POSIX 终端控制定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <sys/types.h>
#include <unistd.h> /*UNIX 标准函数定义*/
我们都知道,在Linux下,除了网络设备,其余的都是文件的形式。串口设备也一样在/dev下。
所以我们可以通过open系统调用函数来访问它。
C 语言
#define serial_device "/dev/ttyS1"
//打开串口
int open_port(void)
{
int fd; //串口的标识符
fd=open(serial_device,O_RDWR | O_NOCTTY | O_NDELAY);
if(fd == -1)
{
//不能打开串口
perror("open_port: Unable to open /dev/ttyS0 -");
return(fd);
}
else
{
fcntl(fd, F_SETFL, 0);
printf("open ttys1 .....\n");
return(fd);
}
}
//O_NOCTTY用来告诉Linux这个程序不会成为“控制终端”,不说明这个标志的话,任何输入都会影响你的程序。
//O_NDELAY用来告诉Linux这个程序不关心DCD信号,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
C++
DALBase.h
class CDALBase
{
public:
CDALBase();
CDALBase(string strPath);
virtual ~CDALBase(void);
virtual int open(void);
virtual int open(string strPath);
virtual int close(void);
virtual int read(unsigned char *pBuf, unsigned int iLen);
virtual int write(string str);
virtual int write(unsigned char *pBuf, unsigned int iLen);
virtual int getFd(void) const;
virtual string getPath(void) const;
protected:
virtual void setFd(int iFd);
virtual void setPath(string strPath);
private:
int m_iFd;
string m_strPath;
};
DALBase.cpp
CDALBase::CDALBase()
{
}
CDALBase::CDALBase(string strPath):m_strPath(strPath)
{
}
CDALBase::~CDALBase()
{
this->close();
}
int CDALBase::open(void)
{
this->m_iFd = ::open(this->m_strPath.c_str(), O_RDWR | O_NOCTTY);
if(this->m_iFd <= 0)
{
cout << "CDALBase:" << this->m_strPath << "open fail!" << endl;
}
return this->m_iFd;
}
int CDALBase::open(string strPath)
{
this->m_strPath = strPath;
return this->open();
}
int CDALBase::close(void)
{
if(m_iFd > 0)
{
return ::close(this->m_iFd);
}
return m_iFd;
}
int CDALBase::read(unsigned char *pBuf, unsigned int iLen)
{
return ::read(this->m_iFd, pBuf, iLen);
}
int CDALBase::write(string str)
{
return ::write(this->m_iFd, str.c_str(), str.length());
}
int CDALBase::write(unsigned char *pBuf, unsigned int iLen)
{
return ::write(this->m_iFd, pBuf, iLen);
}
int CDALBase::getFd(void) const
{
return this->m_iFd;
}
string CDALBase::getPath(void) const
{
return this->m_strPath;
}
void CDALBase::setFd(int iFd)
{
this->m_iFd = iFd;
}
void CDALBase::setPath(string strPath)
{
this->m_strPath = strPath;
}
Serial.h
class CSerialPort: public CDALBase
{
public:
CSerialPort(string strPath = "/dev/ttyS1");
~CSerialPort();
int setParm(int baudrate = 115200, int databits = 8, int stopbits = 1, char parity = 'N');
int getBaudRate(void);
int getDatabits(void);
int getStopbits(void);
char getParity(void);
private:
int m_iBaudRate;
int m_iDatabits;
int m_iStopbits;
char m_cParity;
};
DALReceive.cpp
void *DALReceiveThread(void *arg)
{
······
int nFdSerialPort; // 串口描述符
CSerialPort serialPort;
g_pDALSerialPort = &serialPort;
serialPort.open();
······
与普通文件一样,使用read,write函数。
C 语言
/**
*串口发送数据函数
*fd:串口描述符
*data:待发送数据
*datalen:数据长度
*/
int serial_write(int fd ,char *data, int datalen)
{
int len=0;
//获取实际传输数据的长度
len=write(fd,data,datalen);
printf("send data OK! datalen=%d\n",len);
return len;
}
/**
*串口接收数据
*要求启动后,在pc端发送ascii文件
*/
int serial_read(int fd,char buff[],int datalen)
{
int nread=0;
printf("Ready for receiving data...");
nread=read(fd,buff,datalen);
if(nread>0)
{
printf("readlength=%d\n",nread);
buff[nread]='\0';
printf("%s\n",buff);
}
return nread;
}
C++
如第二部分代码
最基本的设置串口包括波特率设置校验位和停止位设置。这由通信双方协定。
很多系统都支持POSIX终端(串口)接口.程序可以利用这个接口来改变终端的参数,比如,波特率,字符大小等等.要使用这个端口的话,你必须将<termios.h>头文件包含到你的程序中。这个头文件中定义了终端控制结构体和POSIX控制函数。
最重要的就是这个结构体:
struct termios
{
tcflag_t c_iflag; //输入模式标志,控制终端输入方式
tcflag_t c_oflag; //输出模式标志,控制终端输出方式
tcflag_t c_cflag; //控制模式标志,指定终端硬件控制信息
tcflag_t c_lflag; //本地模式标志,控制终端编辑功能
cc_t c_cc[NCCS]; //控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等。
};
其中我们更关注的是c_cflag控制选项。其中包含了波特率、数据位、校验位、停止位的设置。
它可以支持很多常量名称其中设置数据传输率为相应的数据传输率前要加上“B”。
c_cflag成员不能直接对其初始化,而要将其通过与、或操作使用其中的某些选项。
设置串口属性主要是配置termios结构体中的各个变量,大致流程如下:
使用函数tcgetattr保存原串口属性
struct termios newtio,oldtio;
tcgetattr(fd,&oldtio);
通过位掩码的方式激活本地连接和接收使能选项:CLOCAL和CREAD
newtio.c_cflag | = CLOCAL | CREAD;
使用函数cfsetispeed和cfsetospeed设置数据传输率
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
通过位掩码设置字符大小和数据位
newtio.c_cflag &= ~CSIZE;//字符长度,设置数据位之前一定要屏掉这个位
newtio.c_cflag |= CS8;
设置奇偶校验位需要用到两个termios中的成员:c_cflag和c_iflag。首先要激活c_cflag中的校验位使能标志PARENB和是否进行奇偶校验,同时还要激活c_iflag中的奇偶校验使能。
//设置奇校验:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
//设置偶校验:
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
激活c_cflag中的CSTOPB设置停止位。若停止位为1,则清除CSTOPB;若停止位为0,则激活CSTOPB
newtio.c_cflag &= ~CSTOPB;
数据流控制指是使用何种方法来标志数据传输的开始和结束。可以选择不使用数据流控制、使用硬件进行流控制和使用软件进行流控制
// 不使用数据流控制
opt.c_cflag &= ~CRTSCTS
// 硬件流控制
opt.c_cflag |= CRTSCTS
// 软件流控制
opt.c_cflag | = IXON|IXOFF|IXANY
设置最少字符和等待时间。在对接收字符和等待时间没有特别要求的情况下,可以将其设置为0
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
调用函数”tcflush(fd,queue_selector)”来处理要写入引用的对象,queue_selector可能的取值有以下几种。
TCIFLUSH:刷新收到的数据但是不读
TCOFLUSH:刷新写入的数据但是不传送
TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送
激活配置。在完成配置后,需要激活配置使其生效。使用tcsetattr()函数
int tcsetattr(int filedes,int opt,const struct termios *termptr);
串口作为文件来处理,所以一般的关闭文件函数即可:
close(fd);
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int set_serial(int fd,int nSpeed,int nBits,char nEvent,int nStop)
{
struct termios newttys1,oldttys1;
/*保存原有串口配置*/
if(tcgetattr(fd,&oldttys1)!=0)
{
perror("Setupserial 1");
return -1;
}
bzero(&newttys1,sizeof(newttys1));
newttys1.c_cflag|=(CLOCAL|CREAD ); /*CREAD 开启串行数据接收,保证程序可以从串口中读取数据
;CLOCAL打开本地连接模式,保证程序不占用串口*/
newttys1.c_cflag &=~CSIZE;//字符长度,设置数据位之前一定要屏掉这个位
/*数据位选择*/
switch(nBits)
{
case 7:
newttys1.c_cflag |=CS7;
break;
case 8:
newttys1.c_cflag |=CS8;
break;
}
/*设置奇偶校验位*/
switch( nEvent )
{
case '0': /*奇校验*/
newttys1.c_cflag |= PARENB;/*开启奇偶校验*/
newttys1.c_iflag |= (INPCK | ISTRIP);/*INPCK打开输入奇偶校验;ISTRIP去除字符的第八个比特 */
newttys1.c_cflag |= PARODD;/*启用奇校验(默认为偶校验)*/
break;
case 'E':/*偶校验*/
newttys1.c_cflag |= PARENB; /*开启奇偶校验 */
newttys1.c_iflag |= ( INPCK | ISTRIP);/*打开输入奇偶校验并去除字符第八个比特*/
newttys1.c_cflag &= ~PARODD;/*启用偶校验*/
break;
case 'N': /*无奇偶校验*/
newttys1.c_cflag &= ~PARENB;
break;
}
/*设置波特率*/
switch( nSpeed )
{
case 2400:
cfsetispeed(&newttys1, B2400);
cfsetospeed(&newttys1, B2400);
break;
case 4800:
cfsetispeed(&newttys1, B4800);
cfsetospeed(&newttys1, B4800);
break;
case 9600:
cfsetispeed(&newttys1, B9600);
cfsetospeed(&newttys1, B9600);
break;
case 115200:
cfsetispeed(&newttys1, B115200);
cfsetospeed(&newttys1, B115200);
break;
default:
cfsetispeed(&newttys1, B9600);
cfsetospeed(&newttys1, B9600);
break;
}
/*设置停止位*/
if( nStop == 1)/*设置停止位;若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOPB*/
{
newttys1.c_cflag &= ~CSTOPB;/*默认为一位停止位; */
}
else if( nStop == 2)
{
newttys1.c_cflag |= CSTOPB;/*CSTOPB表示送两位停止位*/
}
/*--------------------其他配置-----------------------*/
/*设置本地模式为原始模式*/
Opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/*
*ICANON:允许规范模式进行输入处理
*ECHO:允许输入字符的本地回显
*ECHOE:在接收EPASE时执行Backspace,Space,Backspace组合
*ISIG:允许信号
*/
/*设置输出模式为原始输出*/
Opt.c_oflag &= ~OPOST; //OPOST:若设置则按定义的输出处理,否则所有c_oflag失效
/*发送字符0X0d的时候,往往接收端得到的字符是0X0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之*/
Opt.c_oflag &= ~(ONLCR | OCRNL);
Opt.c_iflag &= ~(ICRNL | INLCR);
Opt.c_cflag &= ~CRTSCTS;// 不使用数据流控制
Opt.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /*c_cc数组的VSTART和VSTOP元素被设定成DC1和DC3,代表ASCII标准的XON和XOFF字符,如果在传输这两个字符的时候就传不过去,需要把软件流控制屏蔽*/
/*设置最少字符和等待时间,对于接收字符和等待时间没有特别的要求时*/
newttys1.c_cc[VTIME] = 0;/*非规范模式读取时的超时时间;*/
newttys1.c_cc[VMIN] = 0; /*非规范模式读取时的最小字符数*/
tcflush(fd ,TCIFLUSH);/*tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */
/*激活配置使其生效*/
if((tcsetattr( fd, TCSANOW,&newttys1))!=0)
{
perror("com set error");
return -1;
}
return 0;
}
表1 c_iflag参数表
键 值 | 说 明 |
---|---|
IGNBRK | 忽略BREAK键输入 |
BRKINT | 如果设置了IGNBRK,BREAK键输入将被忽略 |
IGNPAR | 忽略奇偶校验错误 |
PARMRK | 标识奇偶校验错误 |
INPCK | 允许输入奇偶校验 |
ISTRIP | 去除字符的第8个比特 |
INLCR | 将输入的NL(换行)转换成CR(回车) |
IGNCR | 忽略输入的回车 |
ICRNL | 将输入的回车转化成换行(如果IGNCR未设置的情况下) |
IUCLC | 将输入的大写字符转换成小写字符(非POSIX) |
IXON | 允许输出时对XON/XOFF流进行控制 |
IXANY | 输入任何字符将重启停止的输出 |
IXOFF | 允许输入时对XON/XOFF流进行控制 |
IMAXBEL | 当输入队列满的时候开始响铃 |
表2 c_oflag参数
键 值 | 说 明 |
---|---|
OPOST | 处理后输出 |
OLCUC | 将输出的小写字符转换成大写字符(非POSIX) |
ONLCR | 将输出的NL(换行)转换成CR(回车)及NL(换行) |
OCRNL | 将输出的CR(回车)转换成NL(换行) |
ONOCR | 第一行不输出回车符 |
ONLRET | 不输出回车符 |
OFILL | 发送填充字符以延迟终端输出 |
OFDEL | 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NUL |
NLDLY | 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s) |
CRDLY | 回车延迟,取值范围为:CR0、CR1、CR2和 CR3 |
TABDLY | 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3 |
BSDLY | 空格输出延迟,可以取BS0或BS1 |
VTDLY | 垂直制表符输出延迟,可以取VT0或VT1 |
FFDLY | 换页延迟,可以取FF0或FF1 |
表3 c_cflag参数
键 值 | 说 明 |
---|---|
CBAUD | 波特率(4+1位)(非POSIX) |
CBAUDEX | 附加波特率(1位)(非POSIX) |
CSIZE | 字符长度,取值范围为CS5、CS6、CS7或CS8 |
CSTOPB | 设置两个停止位 |
CREAD | 使用接收器 |
PARENB | 使用奇偶校验 |
PARODD | 对输入使用奇偶校验,对输出使用偶校验 |
HUPCL | 关闭设备时挂起 |
CLOCAL | 忽略调制解调器线路状态 |
CRTSCTS | 使用RTS/CTS流控制 |
表4 c_lflag参数
键 值 | 说 明 |
---|---|
ISIG | 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号 |
ICANON | 使用标准输入模式 |
XCASE | 在ICANON和XCASE同时设置的情况下,终端只使用大写 |
ECHO | 显示输入字符 |
ECHOE | 如果ICANON同时设置,ERASE将删除输入的字符 |
ECHOK | 如果ICANON同时设置,KILL将删除当前行 |
ECHONL | 如果ICANON同时设置,即使ECHO没有设置依然显示换行符 |
ECHOPRT | 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX) |
TOSTOP | 向后台输出发送SIGTTOU信号 |
表5 c_cc支持的控制字符
宏 | 说 明 |
---|---|
VINTR | Interrupt字符 |
VQUIT | Quit字符 |
VERASE | Erase字符 |
VKILL | Kill字符 |
VEOF | End-of-file字符 |
VMIN | 非规范模式读取时的最小字符数 |
VEOL | 附加的End-of-file字符 |
VTIME | 非规范模式读取时的超时时间 |
VSTOP | Stop字符 |
VSTART | Start字符 |
VSUSP | Suspend字符 |