1. 介绍

随着物联网的全面普及,作为终端的单片机也需要联网,本文讲述的是一种低成本的物联网方案,硬件使用GPRS模块和STM32单片机,网络基于MQTT报文协议,相比于WIFI局域网,GPRS(最新LTE)具有更大的适用性。同时MQTT协议具有使用方便,资源消耗少和成本低的优点,非常适合物联网设备使用。

硬件:STM32F103RBT6  SIM800C

软件:stm32_hal  Keil MDK V5  pahoMQTT

pahoMQTT 选择Embedded C版本,主要考虑单片机平台的资源有限,嵌入式版本不使用动态内存分配,基本与平台无关,使用起来相对比较困难。

选择Embedded C/C++版本,github下载地址

2. 拷贝需要移植的文件

pahoMQTT实现的文件,在目录 paho.mqtt.embedded-c-master\MQTTPacket\src 下(10个C文件及头文件)

接口和主函数调文件  在目录 paho.mqtt.embedded-c-master\MQTTPacket\samples下(只需要2个C文件和对应头文件)

移植后的工程目录如下所示

3.修改接口和主测试文件

以上文件都找齐后,移植只需要修改transport.c下的几个接口函数,paho例程如下:

/*原文件使用的Socket接口作为例子,包含
transport_sendPacketBuffer  透传一串字符
transport_getdata           接收一串字符
transport_open              打开和连接MQTT
transport_close             关闭连接 (可省略为空)移植时,只需要重写以上上个函数
*/
static int mysock = INVALID_SOCKET;int transport_sendPacketBuffer(int sock, unsigned char* buf, int buflen)
{int rc = 0;rc = write(sock, buf, buflen);return rc;
}int transport_getdata(unsigned char* buf, int count)
{int rc = recv(mysock, buf, count, 0);//printf("received %d bytes count %d\n", rc, (int)count);return rc;
}
/*不懂,移植时同上*/
int transport_getdatanb(void *sck, unsigned char* buf, int count)
{int sock = *((int *)sck);  /* sck: pointer to whatever the system may use to identify the transport *//* this call will return after the timeout set on initialization if no bytes;in your system you will use whatever you use to get whichever outstandingbytes your socket equivalent has ready to be extracted right now, if any,or return immediately */int rc = recv(sock, buf, count, 0);   if (rc == -1) {/* check error conditions from your system here, and return -1 */return 0;}return rc;
}/**
return >=0 for a socket descriptor, <0 for an error code
@todo Basically moved from the sample without changes, should accomodate same usage for 'sock' for clarity,
removing indirections
*/
int transport_open(char* addr, int port)
{
int* sock = &mysock;int type = SOCK_STREAM;struct sockaddr_in address;
#if defined(AF_INET6)struct sockaddr_in6 address6;
#endifint rc = -1;
#if defined(WIN32)short family;
#elsesa_family_t family = AF_INET;
#endifstruct addrinfo *result = NULL;struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL};static struct timeval tv;*sock = -1;if (addr[0] == '[')++addr;if ((rc = getaddrinfo(addr, NULL, &hints, &result)) == 0){struct addrinfo* res = result;/* prefer ip4 addresses */while (res){if (res->ai_family == AF_INET){result = res;break;}res = res->ai_next;}#if defined(AF_INET6)if (result->ai_family == AF_INET6){address6.sin6_port = htons(port);address6.sin6_family = family = AF_INET6;address6.sin6_addr = ((struct sockaddr_in6*)(result->ai_addr))->sin6_addr;}else
#endifif (result->ai_family == AF_INET){address.sin_port = htons(port);address.sin_family = family = AF_INET;address.sin_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr;}elserc = -1;freeaddrinfo(result);}if (rc == 0){*sock = socket(family, type, 0);if (*sock != -1){
#if defined(NOSIGPIPE)int opt = 1;if (setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&opt, sizeof(opt)) != 0)Log(TRACE_MIN, -1, "Could not set SO_NOSIGPIPE for socket %d", *sock);
#endifif (family == AF_INET)rc = connect(*sock, (struct sockaddr*)&address, sizeof(address));#if defined(AF_INET6)elserc = connect(*sock, (struct sockaddr*)&address6, sizeof(address6));#endif}}if (mysock == INVALID_SOCKET)return rc;tv.tv_sec = 1;  /* 1 second Timeout */tv.tv_usec = 0;  setsockopt(mysock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval));return mysock;
}int transport_close(int sock)
{
int rc;rc = shutdown(sock, SHUT_WR);rc = recv(sock, NULL, (size_t)0, 0);rc = close(sock);return rc;
}

网络连接使用的是SIM800C模块,使用AT指令实现网络数据的透传功能

一般启动步骤包含:上电->注册网络-> 附着GPRS->设置单链接->设置透传->激活网络->连接MQTT服务器 ,SIM800C初始化函数如下

........................一般函数略过......................../*-------------------------------------------------*/
/*函数名:800C连接物联网云服务器                   */
/*参  数:data:数据                               */
/*返回值:无                                       */
/*-------------------------------------------------*/
char SIM800C_Connect_IoTServer(char *addr,int port)
{char i;                         //定义一个变量,用于for循环/*********SIM800C初始化**********/SIM800C_GPIO_Init();            //控制800C的IO初始化if(SIM800C_Power())             //控制800C开机或重启,如果返回1,说明开机重启失败,准备重启{            return 1;                   //返回1}/*********SIM800C注册网络**********/u1_printf("请等待注册上网络... ...\r\n");        //串口输出信息if(SIM800C_CREG(30))                             //等待注册上网络,超时单位1s,超时时间30s{                            u1_printf("注册网络超时,准备重启\r\n");     //串口输出信息return 2;                                    //返回2}else {u1_printf("注册上网络\r\n");               //串口输出信息}/*********SIM800C查询信号**********/if(SIM800C_CSQ(60))                              //查询信号强度,超时单位100ms,超时时间6s{                             u1_printf("查询信号强度超时,准备重启\r\n"); //串口输出信息return 3;                                    //返回3    }/*********SIM800C附着GPRS**********/u1_printf("请等待附着上GPRS... ...\r\n");        //串口输出信息if(SIM800C_CGATT(30)){                           //查询附着GPRS,超时单位1s,超时时间30su1_printf("查询附着GPRS超时,准备重启\r\n"); //串口输出信息return 4;                                    //返回4   }else u1_printf("附着上GPRS\r\n");               //串口输出信息/*********SIM800C设置单链接**********/u1_printf("请等待设置单链接... ...\r\n");        //串口输出信息if(SIM800C_SendCmd("AT+CIPMUX=0",60)){           //设置单链接模式,超时单位100ms,超时时间6s   u1_printf("设置单链接失败,准备重启\r\n");   //串口输出信息return 5;                                    //返回5  }else u1_printf("设置单链接模式成功\r\n");       //串口输出信息/*********SIM800C设置透传**********/u1_printf("请等待设置透传... ...\r\n");         //串口输出信息if(SIM800C_SendCmd("AT+CIPMODE=1",60)){         //设置透传模式,超时单位100ms,超时时间6s        u1_printf("设置透传失败,准备重启\r\n");    //串口输出信息return 6;                                   //返回6}else u1_printf("设置透传成功\r\n");            //串口输出信息   /*********SIM800C激活网络**********/if(SIM800C_ActivateNetwork()){                   //准备激活网络u1_printf("激活网络失败,准备重启\r\n");     //串口输出信息return 7;                                    //返回7}Delay_Ms(500);                                   //适当延时for(i=0;i<3;i++)                                 //重试3次                                   {u1_printf("请等待连接上服务器... ...\r\n");  //串口输出信息if(SIM800C_TCPConnect(addr,port,10)){        //同服务器建立TCP连接,超时单位1s,超时时间10s u1_printf("连接失败,准备再次连接\r\n"); //串口输出信息Delay_Ms(500);                           //适当延时if(SIM800C_TCPClose(10)){                //准备再次连接前要先发送关闭指令,超时单位1s,超时时间10s u1_printf("连接异常,准备重启\r\n"); //串口输出信息Delay_Ms(500);                       //适当延时return 8;                            //返回7}}else return 0;                              //正确,返回0}u1_printf("连接不上服务器,准备重启\r\n");       //串口输出信息return 9;
}

修改后的transport.c的程序如下

#include "transport.h"
#include "sim800c.h"
#include "usart.h"
#include "Array.h"/**********传输发送缓冲数据包*************/
int transport_sendPacketBuffer(unsigned char* buf, int buflen)
{int rc = 0;HAL_StatusTypeDef sta;sta = HAL_UART_Transmit(&huart2, buf, buflen, 1000);if(sta == 0)rc = buflen;return rc;
}/*计算两次的间隔时间,单位 ms*/
uint32_t Diff_Tim(uint32_t pastim)
{uint32_t ntim;ntim = HAL_GetTick();if(ntim > pastim)return (ntim - pastim);elsereturn (0xFFFFFFFF - pastim) + ntim;
}/**********接收数据*************/
int transport_getdata(unsigned char* buf, int count)
{int rc = 0;uint32_t tim = HAL_GetTick();       //获得当前时间while(rc != count)                  /*添加 1秒超时判断  如果超过1秒,跳出*/{rc = getQueueBuff(&u2_rx,(int8_t *)buf,count);if(Diff_Tim(tim) > 500){break;}osDelay(5);}return rc;
}
int transport_getdatanb(unsigned char* buf, int count)
{int rc = 0;rc = getQueueBuff(&u2_rx,(int8_t *)buf,count);return rc;
}/**********连接服务器*************/
int transport_open(char* addr, int port)
{return SIM800C_Connect_IoTServer(addr,port);
}/**********断开服务器*************/
int transport_close(void)
{SIM800C_TCPClose(1);       printf("exit。。。\r\n");
}/**********清空循环队列*************/
void RecClear(void)
{initQueue(&u2_rx);
}

4.主程序测试(pahoMQTT例程)

int main(int argc, char *argv[])
{MQTTPacket_connectData data = MQTTPacket_connectData_initializer;int rc = 0;int mysock = 0;unsigned char buf[200];int buflen = sizeof(buf);int msgid = 1;MQTTString topicString = MQTTString_initializer;int req_qos = 0;char* payload = "mypayload";int payloadlen = strlen(payload);int len = 0;char *host = "m2m.eclipse.org";int port = 1883;stop_init();if (argc > 1)host = argv[1];if (argc > 2)port = atoi(argv[2]);mysock = transport_open(host, port);if(mysock < 0)return mysock;printf("Sending to hostname %s port %d\n", host, port);data.clientID.cstring = "me";data.keepAliveInterval = 20;data.cleansession = 1;data.username.cstring = "testuser";data.password.cstring = "testpassword";len = MQTTSerialize_connect(buf, buflen, &data);rc = transport_sendPacketBuffer(mysock, buf, len);/* wait for connack */if (MQTTPacket_read(buf, buflen, transport_getdata) == CONNACK){unsigned char sessionPresent, connack_rc;if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) != 1 || connack_rc != 0){printf("Unable to connect, return code %d\n", connack_rc);goto exit;}}elsegoto exit;/* subscribe */topicString.cstring = "substopic";len = MQTTSerialize_subscribe(buf, buflen, 0, msgid, 1, &topicString, &req_qos);rc = transport_sendPacketBuffer(mysock, buf, len);if (MQTTPacket_read(buf, buflen, transport_getdata) == SUBACK)     /* wait for suback */{unsigned short submsgid;int subcount;int granted_qos;rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos, buf, buflen);if (granted_qos != 0){printf("granted qos != 0, %d\n", granted_qos);goto exit;}}elsegoto exit;/* loop getting msgs on subscribed topic */topicString.cstring = "pubtopic";while (!toStop){/* transport_getdata() has a built-in 1 second timeout,your mileage will vary */if (MQTTPacket_read(buf, buflen, transport_getdata) == PUBLISH){unsigned char dup;int qos;unsigned char retained;unsigned short msgid;int payloadlen_in;unsigned char* payload_in;int rc;MQTTString receivedTopic;/*接收到消息  */rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,&payload_in, &payloadlen_in, buf, buflen);printf("message arrived %.*s\n", payloadlen_in, payload_in);}printf("publishing reading\n");/*发布消息*/len = MQTTSerialize_publish(buf, buflen, 0, 0, 0, 0, topicString, (unsigned char*)payload, payloadlen);rc = transport_sendPacketBuffer(mysock, buf, len);}printf("disconnecting\n");len = MQTTSerialize_disconnect(buf, buflen);rc = transport_sendPacketBuffer(mysock, buf, len);exit:transport_close(mysock);return 0;
}

5. 结束

pahoMQTT embedded 移植相对简单,MQTT embedded提供的发布和订阅功能函数一般由两部分组成,先序列化在发送一串数据,使用起来困难一点点,根据MQTT的通信协议和机制,还是能够容易理解。主要难点在于SIM800C的驱动稳定可靠,需要完整的驱动程序朋友,欢迎留言。

pahoMQTT+SIM800C+STM32 移植和使用相关推荐

  1. STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085和串口只发送数据不能接收数据问题

    STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085的问题讨论:http://www.rt-thr ...

  2. STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085和不能接收数据问题讨论

    STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085的问题讨论:http://www.rt-thr ...

  3. contiki STM32移植

    1.contiki简介 [2016年6月更新 强烈建议各位使用arm-none-eabi-gcc工具链,推荐在Instant Contiki中开发Contiki应用,不推荐使用IAR] "C ...

  4. micropython STM32移植笔记(一)

    micropython STM32移植笔记(一) 首先,我是一个搞硬件的,说白了是做电路的,呵呵... 一直想学一些软件,只会搞硬件的工程师是没有灵魂的,画了7年的PCB板卡了,但是每次调试硬件都要求 ...

  5. STM32移植uC/OSIII

    坚持就是胜利 一.UCOS简介 01 简介 02 uC/OS-III的任务 03 其他介绍 二.操作过程 01 题目要求 02 配置CubeMX 03 下载代码 04 移植代码 05 效果展示 三.实 ...

  6. stm32 移植java_把Lua移植到stm32上,效果不错! (amoBBS 阿莫电子论坛)

    因为我们产品的需要满足不同行业需求,所以一直在寻找一个脚本语言,以便灵活配置. 前段时间还自己花时间去实现一个C语言解释器,看了一堆编译原理的东西,以及虚拟机等,头都搞大了,把基本功能实现了,但总是不 ...

  7. 来了!STM32移植LuatOS,潘多拉示例全新教程

    进击的五月,继上期<使用Air724UG制作简易贪吃蛇>教程之后,@打盹的消防车 又为大家带来基于STM32的潘多拉LuatOS移植全新教程: 为什么使用潘多拉作为教程呢? STM32不能 ...

  8. STM32移植BME680传感器输出IAQ(室内空气质量)

    STM32移植BME680传感器输出IAQ(室内空气质量) 1.准备材料 软件: stm32cubemx .IAR for arm.jlink(或者stlink)驱动(已安装请忽略).任意的串口调试助 ...

  9. STM32移植LVGL(LittleVGL)

    STM32移植LVGL(LittleVGL) 一.什么是LVGL https://lvgl.io/ 这是LVGL的官网, http://lvgl.100ask.org/8.2/intro/index. ...

最新文章

  1. 02-VTK编译安装
  2. Objective-C学习笔记(十九)——对象方法和类方法的相互调用
  3. 脊回归(Ridge Regression) 岭回归
  4. 在deepin系统中制作桌面快捷方式
  5. View 4桌面虚拟化炙手可热 但未熟透
  6. Light-Head R-CNN相关资料
  7. Java库可以软件著作权,(最新整理)软件著作权-源代码范本
  8. 无线网状网、Zigbee、RFID三种技术分析
  9. 可以创建专业的客户端/服务器视频会议应用程序的音频和视频控件LEADTOOLS Video Conferencing SDK...
  10. 数据生态mysql_数据生态:MySQL复制技术与生产实践
  11. 深度探索C++对象模型
  12. oracle安装最后一步完成了就消失了_Oracle安装过程中遇到的一些问题及解决方案...
  13. python turtle库下载_win10+python3.8安装turtle库
  14. 读书笔记-Java完全自学手册
  15. js创建节点删除节点实例
  16. 哈佛专家推荐5种最健康食物
  17. 关于两个Myeclipse共存的问题,同时激活的问题
  18. 公会晋升计划第 2 季来啦!
  19. 苏州微软面试--机器学习自然语言处理
  20. ubuntu系统外连接两个显示器设置

热门文章

  1. OpenCV--模糊图像操作
  2. java鼠标监听事件方法_java 鼠标事件监听ActionListener
  3. 使用nutch搭建类似百度/谷歌的搜索引擎
  4. 十年游戏老兵,如何开发出一款受索尼青睐的VR游戏?
  5. COMSOL异常关闭补救方法
  6. C++学习必备网站推荐收藏
  7. 移动端js判断是app还是微信还是浏览器
  8. BCI 常见问题解答
  9. 《RHCSA/RHCE8 红帽 Linux认证学习教程》 即将出版
  10. ifind设置自选股票池