C语言实现BC28NB模组上报数据到电信云

本文详细介绍了如何使用C语言实现BC28模组接入阿里云的流程,包括串口层、指令操作层、BC28指令集层和NB应用层的封装。在串口层,重点讲解了串口的打开、配置、关闭及数据发送和接收函数。指令操作层实现了AT指令的发送与解析。BC28指令集层封装了与BC28模组相关的AT指令。NB应用层则提供了网络注册、IP设置、数据上报等功能。通过层层封装,简化了与BC28模组的交互,提高了代码的可移植性。
摘要由CSDN通过智能技术生成

目录

前言

一、串口层

1.comport_open()

2.comport_conf()

3.comport_close()

4.int comport_send()

二、指令操作层

1.send_atcmd():

2.at_fetch():

三、BC28指令集层 

四、NB应用层

1.find_nbiot_dev():

 2.check_nbiot_attach():

3.set_nbiot_register():

 4.nbiot_report_cloud():

总结


前言

        在用C语言实现bc28上报数据之前,需要了解bc28NB模组上报阿里云的流程。具体可参考之前的博客:BC28上报数据到电信云平台

        为了具有良好的移植性,我将整个项目封装了4层:

        1.串口层:这一层封装了端口的打开、初始化、关闭、发送数据和接收数据函数。

        2.指令操作层:这一层封装了两个函数,一个是AT指令的发送与回调,另一个是字符串切割获取有用信息。

        3.BC28指令集层:这一层封装了移远BC28相关的有用的AT指令。

        4.NB应用层:这一层封装了nb模组附着网络、查询硬件信息、设置IOT平台ip和端口、上报数据函数。

        通过这种封装,整个项目一层调用一层,使用者最后只需要调用最外层的nbiot层函数即可。在开始编写程序时,首先需要学习一些基本的串口通信属性设置:


一、串口层

        串口层的主要功能就是实现串口的打开、关闭、初始化、发送和接收信息操作。串口的这些操作都是通过对串口文件描述符的open、close、read、write实现的,这一部分与socket操作类似。而不同的也是最麻烦的就是串口属性的初始化操作,需要学习termios结构体、以及各种串口属性设置函数大家可以参考其他博主的介绍:termios结构体及相关函数介绍

        为了方便对串口属性的操作,于是我自己定义了一个结构体,用来保存和设置串口属性。相关操作会在下面的串口初始化中阐明。

typedef struct comport_s
{
    int             fd;       //串口文件描述符
    int             baudrate; //波特率
    int             databits; //数据位
    int             mSend_Len;//单次最大发送长度
    char            parity;   //奇偶校验位
    int             stopbits; //停止位
    char            tty_name[SERLALNAME];//串口名称:"ttyUSB0"
    char            modelname[SERLALNAME];//型号:“BC28”
    struct termios  oldtermios; //串口原始属性
}comport_t;

1.comport_open()

        这里有一个重要的点就是串口读取(read)数据的阻塞与非阻塞模式,有以下两种方式来控制:

        1、open打开串口时,默认是阻塞模式,如果想要设置非阻塞模式那么参数就要加上O_NDELAY/O_NONBLOCK,二者的区别是:在read时,如果读不到数据,O_NDELAY会返回0,由于正常读取到文件末尾时,也会返回0,这样就无法区分是否是遗产隔离,所以就引入了O_NONBLOCK,在读不到数据时,返回-1,并且设置errno为EAGAIN,而读到结尾处,正常返回0。

        2、打开串口后,用fcntl()函数进行设置。

        fcntl( fd,  F_SETFL,  O_NONBLOCK ); //设为非阻塞

        fcntl( fd,  F_SETFL,  0 ); //设为阻塞,阻塞情况下,可以采用 c_cc[VTIME] 与 c_cc[VMIN] 进行限定。

        本次串口编程采用阻塞模式,利用I/O多路复用select实现程序的软中断。

int comport_open(comport_t *comport,char *devname)
{
    int              rv=-1; //return value返回值
    
    /*  参数合法性检测  */
    if( !comport || !devname)
    {
        printf("%s,Invalid parameter\n",__func__);
        return -1;
    }

    /*  
    *  O_NOCTTY:不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。
    *  O_NDELAY:这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
    *  O_NDELAY/O_NONBLOCK:以非阻塞模式打开
    */
    comport->fd = open(devname,O_RDWR | O_NOCTTY);
    if( comport->fd < 0 )
    {
        printf("%s,Open %s failed:%s\n",__func__,comport->tty_name,strerror(errno));
        return -1;
    }

    /*  检查串口是否处于阻塞态  */
    if( (rv = fcntl(comport->fd,F_SETFL,0)) < 0 )
    {
        printf("%s,Fcntl check faile.\n",__func__);
        return -2;
    }

    /*  是否为终端设备  */
    if( 0 == isatty(comport->fd) )  
    {
        printf("%s:[%d] is not a Terminal equipment.\n",comport->tty_name,comport->fd);
        return -3;
    }

    printf("Open %s successfully.\n",comport->tty_name);

    return 0;
}

2.comport_conf()

int comport_conf(comport_t *comport)
{
    char                baudrate[BAUDRATE]={0};
    struct termios      newtermios;

    if( !comport )
    {
        printf("invalid parameter.\n");
        return -1;
    }

    memset(&newtermios,0,sizeof(struct termios));
    memset(&(comport->oldtermios),0,sizeof(struct termios));
    
    if( tcgetattr(comport->fd, &(comport->oldtermios)) )
    {
        printf("%s,get termios to oldtermios failure:%s\n",__func__,strerror(errno));
        return -2;
    }

    if(tcgetattr(comport->fd,&newtermios))
    {
        printf("%s,Get termios to newtermios failure:%s\n",__func__,strerror(errno));
        return -3;
    }

    /* 修改控制模式,保证不会占用串口 */
    newtermios.c_cflag |= CLOCAL;

    /* 启动接收器,能够从串口中读取输入数据 */
    newtermios.c_cflag |= CREAD;
    
    /*  CSIZE字符大小掩码,将与设置databits相关的标致位置零 */
    newtermios.c_cflag &= ~CSIZE;

    newtermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /*  
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志,ERASE字符删除前一个所输入的字符,WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符,生成一个相应的信号
     */

    newtermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /*  
     *  BRKINT:BREAK将会丢弃输入和输出队列中的数据(flush),并且如果终端为前台进程组的控制终端,则BREAK将会产生一个SIGINT信号发送到这个前台进程组
     *  ICRNL: 将输入中的CR转换为NL
     *  INPCK: 允许奇偶校验
     *  ISTRIP: 剥离第8个bits
     *  IXON: 允许输出端的XON/XOF流控
     */
    
    /*  OPOST: 表示处理后输出,按照原始数据输出 */ 
    newtermios.c_oflag &= ~(OPOST);

    if( comport->baudrate )
    {
        sprintf(baudrate,"B%d",comport->baudrate);

        /* 设置输入输出波特率 */
        cfsetispeed(&newtermios,(speed_t)baudrate);
        cfsetospeed(&newtermios,(speed_t)baudrate);
    }
    else
    {
        cfsetispeed(&newtermios,B9600);
        cfsetospeed(&newtermios,B9600);
    }

    /* 设置数据位 */
    switch( comport->databits )
    {
        case '6':
            newtermios.c_cflag |= CS6;
            break;

        case '7':
            newtermios.c_cflag |= CS7;
            break;

        case '8':
            newtermios.c_cflag |= CS8;
            break;

        default:
            newtermios.c_cflag |= CS8;
            break;
    }

    /*  设置奇偶校验方式  */
    switch(comport->parity)
    {
        /*  无校验  */
        case 'n':
        case 'N':
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_iflag &= ~INPCK;
            break;

        /*  偶校验 */
        case 'e':
        case 'E':
            newtermios.c_cflag |= PARENB;
            newtermios.c_cflag &= ~PARODD;
            newtermios.c_iflag |= INPCK;
            break;

        /*  奇校验 */
        case 'o':
        case 'O':
            newtermios.c_cflag |= PARENB;
            newtermios.c_cflag |= PARODD;
            newtermios.c_iflag |= INPCK;
            break;

        /*  设置为空格  */
        case 's':
        case 'S':
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_cflag &= ~CSTOPB;
            break;

        /*  默认无校验 */
        default:
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_iflag &= ~INPCK;
            break;
    }

    /*  设置停止位  */
    switch( comport->stopbits )
    {
        case '1':
            newtermios.c_cflag &= ~CSTOPB;
            break;

        case '2':
            newtermios.c_cflag |= CSTOPB;
            break;

        default:
            newtermios.c_cflag &= ~CSTOPB;
            break;
    }

    /* 设置超时   */
    newtermios.c_cc[VTIME] = 0;  //最长等待时间
    newtermios.c_cc[VMIN] = 0;  //最小接收字符 

    comport->mSend_Len = 128;  //若命令长度大于mSend_Len,则每次最多发送为mSend_Len

    if( tcflush(comport->fd,TCIFLUSH) )
    {
        printf("%s,Failed to clear the cache:%s\n",__func__,strerror(errno));
        return -4;
    }
    
    if( tcsetattr(comport->fd,TCSANOW,&newtermios) )
    {
        printf("%s,tcsetcomport failure:%s\n",__func__,strerror(errno));
        return -5;
    }

    printf("Comport Conf  Successfully......\n");
    
    return 0;
}

3.comport_close()

int comport_close(comport_t *comport)
{
    if(tcflush(comport->fd,TCIOFLUSH))  //清零用于串口通信的缓冲区
    {
        printf("%s,Tcflush faile:%s\n",__func__,strerror(errno));
        return -1;
    }

    /*  将串口设置为原有属性 */
    if(tcsetattr(comport->fd,TCSANOW,&(comport->oldtermios)))
    {
        printf("%s,Set old options failed:%s\n",__func__,strerror(errno));
        return -2;
    }

    close(comport->fd);

    return 0;
}

4.int comport_send()

int comport_send(comport_t *comport,char *sbuf,int sbuf_len)
{
    char            *ptr = NULL;
    char            *end = NULL;
    int             rv;

    if( !comport || !sbuf || sbuf_len <= 0 )
    {
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }

    if( comport->fd < 0 )
    {
        printf("Serail not connected.\n");
        return -2;
    }

    if( comport->mSend_Len < sbuf_len )
    {
        ptr = sbuf;
        end = sbuf + sbuf_len;
        
        do
        {
            if( comport->mSend_Len < (end - ptr) )
            {
                rv = write(comport->fd,ptr,comport->mSend_Len);
                if( rv <= 0 || rv != comport->mSend_Len )
                {
                    printf("Write to com port[%d] failed:%s\n",comport->fd,strerror(errno));
                    return -3;
                }
                
                ptr += comport->mSend_Len;
            }
            else
            {
                rv = write(comport->fd,ptr,(end - ptr));
                if( rv <= 0 || rv != (end - ptr) )
                {
                    printf("Write to com port[%d] failed:%s\n",comport->fd,strerror(errno));
                    return -4;
                }


                ptr += (end - ptr);
            }
        }while(end > ptr);
    }
    else
    {
        printf("write:%s\b",sbuf);
        rv = write(comport->fd,sbuf,sbuf_len);
        if( rv <= 0 || rv != sbuf_len )
        {
            printf("Write to com port[[%d] failed:%s\n",comport->fd,strerror(errno));
            return -5;
        }
    }

    printf("comport_secd Successfully\n");

    return rv;
}

二、指令操作层

        这一层主要实现了AT指令的发送和接收、对接收的字符串关键字进行切割。

1.send_atcmd():

        在实现AT指令发送的函数前,首先需要了两个重要的串口收发数据的特性:

        1、发送给串口的指令要以"\r\n"结尾表示发送完毕。

        2、我们使用read接收串口发送的信息时,实际是从串口芯片缓存中读取数据,因此一次能读取多少数据取决于缓存大小,通常在信息稍大的时候我们需要多次从缓存中读取消息。

        所以在读取串口消息的时候采用I/O多路复用,将read放在死循环中,通过select的超时设置(串口收发数据会有延迟大概200ms-2000ms)来实现程序中断。

int send_atcmd(comport_t *comport,char *atcmd,char *buf,int buf_size,int timeout)
{
    int                 rv = -1;
    fd_set              rfds;
    struct timeval      time_out;
    struct timeval      *time;
    char                *ptr;
    int                 bytes = 0;

    if(!comport || !atcmd || !buf || buf_size <= 0)
    {
        printf("%s,invalid parameter...\n",__func__);
        return -1;
    }

    rv = comport_send(comport,atcmd,strlen(atcmd));
    if( rv < 0 )
    {
        printf("comport_send is failed:%s...\n",strerror(errno));
        return -2;
    }

    memset(buf,0,buf_size);

    FD_ZERO(&rfds);
    FD_SET(comport->fd,&rfds);

    time_out.tv_sec = 0;
    time_out.tv_usec = timeout * 1000;

    ptr = buf;

    while(1)
    {
        rv = select(comport->fd + 1, &rfds, NULL, NULL, &time_out);
        if( rv < 0 )
        {
            printf("select open failed:%s.\n ",strerror(errno));
            break;
        }
        else if( 0 == rv )
        {
            rv = -4;
            break;
        }
        else
        {
            rv = read( comport->fd, ptr, buf_size - bytes );
            if( rv < 0 )
            {
                printf("%s,read is failure.\n",__func__);
                return -5;
            }
            ptr += rv;
            bytes += rv;

            if( strstr(buf,"OK\r\n") )
            {
                rv = 0;
                break;
            }

            if( strstr(buf,"ERROR\r\n") )
            {
                rv = -6;
            }
        }
    }

    return rv;
}

2.at_fetch():

        这个函数的主要功能就是用给出的首尾字符来对字符串进行切割,保存有用信息。

int at_fetch(char *atreply, char *start_key, char *end_key, char *rbuf, int size)
{
    char                    *rbuf_start;
    char                    *rbuf_end;
    int                     rv =-1;

    if( !atreply || !start_key || size < 0 )
    {
        printf("%s,invalid parameter...\n",__func__);
        return -1;
    }

    memset(rbuf,0,size);

    rbuf_start = strstr(atreply,start_key);
    if( rbuf_start == NULL )
    {
        printf("strstr start_key :%s failure\n", start_key);
        return -2;
    }

    rbuf_start += strlen(start_key);

    if( end_key != NULL )
    {
        rbuf_end = strstr(rbuf_start,end_key);
        if( rbuf_end == NULL )
        {
            printf("strstr end_key %s failure\n", rbuf_end);
            return -3;
        }
    }

    if( rbuf != NULL && ((rbuf_end-rbuf_start) <= size) )
    {
        strncpy(rbuf, rbuf_start, rbuf_end-rbuf_start);
    }

    return 0;
}

三、BC28指令集层 

        这一层主要封装了各种移远BC28的相关AT指令。自己定义了一个nbiot_hwinfo_t的结构体类型,用来保存硬件信息。

typedef struct nbiot_hwinfo_s {
    char     manufacturer[MAX_NFRMTN_T];//制造商
    char     modules_name[MAX_NFRMTN_T];//模块名
    char     num[MAX_NFRMTN_T];//产品序列号
    char     imei[MAX_NFRMTN_T];
    char     cimi[MAX_NFRMTN_T];
    char     version[MAX_NFRMTN_T];
}nbiot_hwinfo_t;

int atcmd_at(comport_t *comport);  //查询是否连接成功
int atcmd_csq(comport_t *comport);  //查询信号强度
int atcmd_cereg(comport_t *comport); //查询网络注册状态
int atcmd_cgatt(comport_t *comport); //查询网络是否被激活(附着上基站
int atcmd_cgatt1(comport_t *comport); //触发网络连接
int atcmd_cgpaddr(comport_t *comport); //查询模块的IP地址
int atcmd_ncdp(comport_t *comport , char *ip , char *port); //设置IOT平台的ip和端口
int atcmd_cgsn(comport_t *comport , char *imei , int size); //查询设备唯一标识码
int atcmd_cimi(comport_t *comport , char *cimi , int size); //查询国际移动用户标识码
int atcmd_cgmm(comport_t *comport , char *nmae , int size); //请求厂商模型标识
int atcmd_cgmr(comport_t *comport , char *version , int size); //请求厂商软件版本
int atcmd_ati(comport_t *comport , nbiot_hwinfo_t *ctx); //显示产品ID
int atcmd_qlwuladtaex(comport_t *comport, int temp); //上报数据
int atcmd_set_echo(comport_t *comport,int flag); //设置回显

我以指令AT为例,来展示AT的函数:

        向串口发送“AT\r\n”后,串口返回的消息保存到wbuf中,使用strstr()函数对“OK”进行字符串匹配。

int atcmd_at(comport_t *comport)
{
    char                wbuf[INFORMATION];
    int                 rv;

    if( !comport )
    {
        printf("open atcmd_at:“at”failure...\n");
        return -1;
    }

    memset(wbuf , 0 , sizeof(wbuf));
    memset(rbuf , 0 , sizeof(rbuf));

    rv = send_atcmd(comport,"AT\r\n",wbuf,sizeof(wbuf),TIME_OUT);
    if( rv < 0 )
    {
        printf("send_atcmd_AT failure\n");
        return -2;
    }

    if( !strstr(wbuf,"OK") )
    {
        printf("NBiot is not connection...\n");
        return -3;
    }

    printf("AT:%s\n",wbuf);

    return 0;
}

四、NB应用层

1.find_nbiot_dev():

        这个函数的功能就是查询NB模组对应的串口名称,原理就是使用函数opendir()打开“/dev”文件夹,readdir()遍历所有文件名,找到文件名开头为“ttyUSB”的文件,挨个打开发送“AT+CGMM”查看对应的型号进行字符串匹配。

int find_nbiot_dev( comport_t *comport)//查找BC28对应的串口
{
    DIR             *pDir = NULL;
    struct dirent   *pEnt = NULL;
    int             i = 0;
    int             rv = 0;
    char            buf[BUF_MAX];
    char            devname[BUF_MAX];

    memset(buf, 0, sizeof(buf));
    memset(devname, 0, sizeof(devname));

    pDir = opendir(PATH);
    if( NULL == pDir )
    {
        printf("open dir failure");
        return -1;
    }

    while( pEnt = readdir(pDir) )
    {
        if( strstr(pEnt->d_name,"ttyUSB") )
        {
            snprintf(devname, sizeof(devname), "%s/%s", PATH, pEnt->d_name);
            if( comport_open( comport, devname ) != 0 )
            {
                continue;
            }
            printf("open is %s\n",devname);

            if( comport_conf(comport) != 0 )
            {
                printf("config failure\n");
                continue;
            }

            rv = send_atcmd(comport, AT_MODEL, buf, sizeof(buf), TIME_OUT);
            if( rv < 0 )
            {
                comport_close(comport);
                continue;
            }
            else
            {
                if( strstr(buf,comport->modelname) )
                {
                    strncpy(comport->tty_name,devname,sizeof(comport->tty_name));
                                       
                    closedir(pDir);
                    
                    return 0;
                }
            }

            comport_close(comport);
        }
    }
    closedir(pDir);

    return -2;
}

 2.check_nbiot_attach():

        这个函数是用来检查NB模块的网络附着情况。

int check_nbiot_attach(comport_t *comport, nbiot_hwinfo_t *nbiot)
{
	int                 rv; 

    rv = atcmd_at(comport);
    if( rv < 0 ) 
    {   
        printf("nbiot_at is failure  rv  :%d\n",rv);
        return -1; 
    }
	
	rv = nbiot_module(comport, nbiot);
    if( rv < 0 ) 
    {   
        printf("nbiot_module is failed..rv :%d\n",rv);
        return -2; 
    }

    rv = atcmd_csq(comport);
    if( rv < 0 ) 
    {   
        printf("nbiot_csq is failure rv  :%d\n",rv);
        return -3; 
    }

    rv = atcmd_cgatt1(comport);
    if( rv < 0 ) 
    {   
        printf("nbiot_cgatt1? is failure  rv :%d\n" , rv);
        return -4; 
    }
    
    rv = atcmd_cereg(comport);
    if( rv < 0 ) 
    {   
        printf("nbiot_cereg is failure rv :%d\n" , rv);
        return -5;    
    }
	
	rv = atcmd_cgatt(comport);
    if( rv < 0 )
    {
        printf(" nbiot_cgatt is  failure  rv :%d\n", rv);
        return -6;
    }
	
    rv = atcmd_cgpaddr(comport);
    if( rv < 0 )
    {
        printf(" nbiot_cgpaddr id failure  rv :%d\n");
        return -7;
    }

    return 0;
}

3.set_nbiot_register():

/*  设置注册信息(ip,端口)  */
int set_nbiot_register( comport_t *comport , char *ip , char *port)
{
    int                 rv;

    rv = atcmd_ncdp(comport, ip, port);
    if( rv < 0 )
    {
        printf("atcmd ncdp is failure ...\n");
        return -1;
    }

    return 0;
}

 4.nbiot_report_cloud():

/*  数据上报  */
int nbiot_report_cloud(comport_t *comport, int temp)
{
    int                 rv;

    rv = atcmd_qlwuladtaex(comport , temp);
    if( rv < 0 )
    {
        printf("atcmd_qlwuladteax is failure rv :%d...\n",rv);
        return -1;
    }

    return 0;
}

总结

        main程序流程图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值