MT7688学习笔记(6)——OpenWrt下串口编程

一、头文件

操作串口需要包含的头文件:

#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下。


image

所以我们可以通过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结构体中的各个变量,大致流程如下:

4.1 保存原串口属性

使用函数tcgetattr保存原串口属性

struct termios newtio,oldtio; 
tcgetattr(fd,&oldtio);

4.2 激活本地连接和接收使能

通过位掩码的方式激活本地连接和接收使能选项:CLOCAL和CREAD

newtio.c_cflag | = CLOCAL | CREAD;

4.3 设置波特率

使用函数cfsetispeed和cfsetospeed设置数据传输率

cfsetispeed(&newtio,B115200); 
cfsetospeed(&newtio,B115200);

4.4 设置数据位

通过位掩码设置字符大小和数据位

newtio.c_cflag &= ~CSIZE;//字符长度,设置数据位之前一定要屏掉这个位

newtio.c_cflag |= CS8;

4.5 设置奇偶校验位

设置奇偶校验位需要用到两个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); 

4.6 设置停止位

激活c_cflag中的CSTOPB设置停止位。若停止位为1,则清除CSTOPB;若停止位为0,则激活CSTOPB

newtio.c_cflag &= ~CSTOPB;

4.7 设置数据流控制

数据流控制指是使用何种方法来标志数据传输的开始和结束。可以选择不使用数据流控制、使用硬件进行流控制和使用软件进行流控制

// 不使用数据流控制
opt.c_cflag &= ~CRTSCTS
// 硬件流控制
opt.c_cflag |= CRTSCTS
// 软件流控制
opt.c_cflag | = IXON|IXOFF|IXANY

4.8 设置最少字符和等待时间

设置最少字符和等待时间。在对接收字符和等待时间没有特别要求的情况下,可以将其设置为0

newtio.c_cc[VTIME] = 0; 
newtio.c_cc[VMIN] = 0;

4.9 清空终端未完成的输入/输出请求及数据

调用函数”tcflush(fd,queue_selector)”来处理要写入引用的对象,queue_selector可能的取值有以下几种。
TCIFLUSH:刷新收到的数据但是不读
TCOFLUSH:刷新写入的数据但是不传送
TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送

4.10 激活配置

激活配置。在完成配置后,需要激活配置使其生效。使用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字符

• 由 Leung 写于 2018 年 10 月 9 日

• 参考:C——Linux下的串口编程
    Linux 下串口编程之四 编程实现
    linux下串口通讯参数设置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 串口操作 串口操作需要的头文件 #include /*标准输入输出定义*/ #include /*标准函数库定...
    旅行家John阅读 1,196评论 0 3
  • 本文转自:http://www.cnblogs.com/jason-lu/articles/3173988.htm...
    不合格码农阅读 2,866评论 0 0
  • 姓名:莫益彰 学号:16030140019 【嵌牛导读】:串口通信是指外设和计算机间,通过数据信号线 、地线、控制...
    换个名字消消毒阅读 1,483评论 1 5
  • 第一章 初见 多年之前的一场孽缘,在多年之后终是埋下了仇恨的种子 冰府,江南地区的数一...
    苏诺lo阅读 290评论 0 0
  • 入坑雷神系列漫画大概是看了雷神3之后,而当我越看越深入时,发现里面的人物关系越发复杂(看的第一本应该是Thor v...
    Alian1810阅读 317评论 0 0