C语言实现BC28NB模组上报数据到电信云
目录
前言
一、串口层
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_Lenif( 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程序流程图:
C语言实现BC28NB模组上报数据到电信云相关推荐
- WiFi_combo模组功耗数据
Combo模组采用了先进的电源管理技术(默认 5 V 电压),可以在不同的 功耗模式之间切换,以下模式功耗依次降低: Active 模式:芯片射频处于正常工作状态.芯片可以接收.发送和监听信号. Mo ...
- 001-STM32+Air724UG(4G模组)基本控制篇(阿里云物联网平台)-使用MQTT接入阿里云物联网平台
前言 这节测试下使用STM32+Air724UG 和 Android 和 微信小程序 和 网页端 接入阿里云物联网平台 当前的各种物联网平台整体思想是提供一个网页端让用户去注册设备 这个设备就对应一个 ...
- 骐俊CAT1模组 - MQTT接入腾讯云平台篇
本次实验使用骐俊ML110S系列模组及开发底板,通过MQTT协议采用密钥的方式接入腾讯云平台,实现消息的发布及订阅,可分为接入注册及动态注册两种方式. 设备注册(接入注册) A.进入腾讯云平台注册 ...
- 4G模组EC20在使用电信物联网专用卡时无信号问题
手中有两个的EC20,在使用物联网专用卡测试时时发现一个问题,同一批次的物联网专用卡,在一块模组上能正常使用(下面简称A模组),另一块上就无信号(简称B模组). A模组: AT+CSQ+CSQ: 28 ...
- 【ODYSSEY-STM32MP157C】上报数据到阿里云 IoT 平台
我们在上一节<[ODYSSEY-STM32MP157C]驱动 UART 读取传感器数据> 已经成功读取到 PMS5003ST 传感器的数据,本节我们将学习如何将设备接入阿里云 IoT 平台 ...
- 机械臂关节模组制动相关(零差云控eBr)
目录 前言 一.电磁插销式抱闸 1.原理 2.特点 二.电磁摩擦片式制动器 1.原理 2.特点 三.零差云控电磁摩擦片式制动器(eBreaker) 1.简介 2.使用说明需要注意的事情 3.接线 总结 ...
- 数据“土豪”电信云告诉你,如何“玩转”数据生态
近日,T11 2017 暨 TalkingData 智能数据峰会在京举办.本届大会以"知机识变,有唐之盛"为主题,有来自不同行业的数据科学家.分析师.企业管理者参与,共同探讨大数据 ...
- 【数模matlab】数据建模:云模型
云模型 用于描述处理不确定性问题 基础概念 基本单位: "云"或者"云滴" 云的表达方式: 利用联合密度(x,µ)表示,µ是隶属度,µ->1则确定性越高 ...
- 公有云平台专题《中移云平台,NB-IoT模组对接中移OneNET平台》
目录 1.中移OneNET平台介绍 2.中移OneNET平台注册 3.OneNET平台创建产品 4.NB模组对接OneNET平台 5.NB模组上报数据.接收下行数据 1.中移OneNET平台介绍 On ...
最新文章
- 很实用的 “设为首页”与“加入收藏”代码
- 学校计算机数据采集处理系统,一种计算机数据采集处理分析系统的制作方法
- ssl2863-石子合并【dp练习】
- 英文教材《FPGA-Prototyping-By-Verilog-Examples》下载
- CSRF 攻击的对象
- React入门第一天(绑定事件,动态渲染,修改样式、传参)
- Python语言三种优点。
- CA数字证书包含哪些文件?如何查看SSL证书信息?
- 如何卸载Vmware Workstation虚拟机
- 屡不悔改,这60款APP被强制下架!
- java joda time_使用Joda-Time优雅的处理日期时间
- Java计算时间差、日期差总结
- 互联网日报 | 4月27日 星期二 | 美团回应被立案调查;滴滴开通老年人打车400热线;百度App月活跃用户数达5.58亿
- 利用Windows命令行解压zip压缩文件(不借助第三方软件)
- C++_求2个或3个正整数中的最大数,用带有默认参数的函数实现
- idea里把选中的变为大写或小写快捷键
- SiO2 酰肼修饰单分散二氧化硅微球/胺基修饰二氧化硅改性环氧树脂胶粘剂/硅烷修饰二氧化硅微球/3-氨丙基三乙氧基硅烷(APTS)修饰的二氧化硅
- 永中Office的魅力何在?
- Java集合框架完全解析
- json添加元素 vue_vue之packages.json添加注释的正确写法