STM32上移植MQTT协议使用W5500以太网芯片连接阿里云

Phyllis ·
更新时间:2024-11-10
· 628 次阅读

STM32上移植MQTT协议使用W5500以太网芯片连接阿里云前言正题核心代码结论 前言

有机会做这次项目很开心,学到了很多东西,但刚开始的时候是真的从零开始,网上找的资料都大相径庭,很多代码都有问题,而且把别人做的模块一个个移植实在是很麻烦,为了不让后来需要做类似这个物联网项目的大家走歪路,我尽可能的把我这次做项目的核心事务说清楚,个人能力有限,有错的地方麻烦轻喷和指教,谢谢各位。

正题

首先先把要做什么搞清楚,顾名思义,本次项目内容即:
①把MQTT协议移植到stm32上;
②通过W5500以太网芯片,用网线连接至本地路由;
③由stm32本地网口连接到阿里云

使用到的工具:Keil5、pahoMqtt包、stm32f103RCT6、W5500、网线*1
调试工具:485通信usart口串口通信、串口调试助手、通信猫、阿里云、MQTTfx、MQTTBOX(当然如果不使用串口,单步Debug也是可以,我当时就这样,搞的我快崩溃了,最后还是弄了串口方便很多,也很简单)

学习资料:这边安利和推广一下B站上一个叫橙子的up主,他做的视频挺好的,很适合萌新上手,视频看看能明白很多东西;
友情链接:stm32 mqtt移植
如果有违权的地方麻烦友情提醒一下,方便我删除。

进入正题,MQTT协议,硬件部分不多说。
首先是下载Mqtt包,可以上github上下载,很多其他mqtt移植的博文都有链接,不过这里也贴一下;
友情链接:PahoMqtt-Github下载
下载后解压,把\paho.mqtt.embedded-c-master\MQTTPacket\src下的所有源文件导入到你的工程项目中,然后导入src所有的头文件路径,最后再把/sample下的transport.c、.h也导入;
这样首先第一步移植就成功了,很多人说transport.c里面有很多函数是Linux下编译的,所以用不了,那必然是用不了的2333,我们要做的事情就是重写transport.c和transport.h文件里的函数。主要有

transport_sendPacketBuffer //发送数据包到服务器
transport_getdata //从服务器获取数据
transport_open // 创建socket,绑定、连接等
transport_close // 关闭socket
这四个函数;
后续要Connect、Pub、Sub、Disconnect,都是基于这四个函数,以及一些MQTT_Package里头自带的一些不需要你写的函数;
最最重要的是搞清楚MQTT协议的工作方式,才能把功能实现,无论你是用一些常用的比如SIM800C、8266、Lora、W5500之类的通信途径,都是大同小异的;
工作方式:具体的几个名词不细讲。本地作为客户端连接到服务器,服务器作为Broker,你可以向Broker订阅话题,也可以向Broker发布话题,同样Broker作为服务器也可以对你Pub和Sub,宏观来看,是对等的;
首先你要有服务器的域名或者IP,只有域名就得做DNS解析,有IP直接IP连,端口号视你要连的服务器规定,阿里云就是固定的1883,通信猫18831之类的,在连接阿里云之前,我强烈建议先把连接通信猫作为实验调通了作为练手;
连接时,有以下几个参数:
data.clientID.cstring // 客户端ID
data.keepAliveInterval //心跳时长,所谓心跳就是等待时间,阿里云规定要//大于60,否则拒绝连接
data.cleansession// 清除位,默认1
data.username.cstring//用户名
data.password.cstring//密码
data.MQTTVersion //规定版本号,比如Viersion4,连接阿里云,透传
关于Qos不细说;
连接阿里云的具体过程:发出连接报文>收到CONNACK(云确认到你的连接的回执)>订阅>收到SUBACK,返回Qos>不断Read做轮询,该干什么干什么,比如Pub、心跳以及等待云端Pub;
阿里云对于C的JDK手册中说,规定Pub和Sub的报文格式为Json格式,因此要注意你在Pub和在Sub解析的时候都要注意Json格式,是否符合Json格式,网上有很多在线检测,请自行搜索;
很重要的一点,一般来说,自带的Socket库里头的read都是阻塞的,如果你需要同时(近似)Pub和Sub,一定要自己修改成非阻塞的,不难;

核心代码

下面贴上核心代码。

/** *@Todo:通过tcp发数据到服务器 */ int transport_sendPacketBuffer(unsigned char* buf, int buflen) { return send(SOCK_TCPS,buf,buflen); } /** *@Todo:阻塞方式接收tcp服务器发送之数据 */ int transport_getdata(unsigned char* buf, int count) { return recv(SOCK_TCPS,buf,count); } /** *@Todo:打开一个socket并连接到服务器 *@retval 小于表示fail */ int transport_open(void){ int32_t ret; //新建socket ret = socket(1, Sn_MR_TCP,2001, 0x00); if(ret != SOCK_TCPS){ printf("%d:Socket Error\r\n",SOCK_TCPS); while(1); }else{ printf("%d:Opened\r\n",SOCK_TCPS); } //连接TCP服务器 ret = connect(SOCK_TCPS,domain_ip,1883); if(ret != SOCK_OK){ printf("%d:Socket Connect Error\r\n",SOCK_TCPS); //while(1); }else{ printf("%d:Connected\r\n",SOCK_TCPS); } return 0; } /** *@Todo:关闭Socket */ int transport_close(void) { disconnect(SOCK_TCPS); close(SOCK_TCPS); return 0; } /***************************以上为基本四函数************************/ while(1){ timeh++; if(connect_flag == 1&&timeh==1000) { // Json格式,根据Ailiyun中定义变量进行数据发布,payload为负载 Ua = 56.7 -rand()%10+1; Ub = 34.8 -rand()%10+1; Uc = 69.2 -rand()%10+1; sprintf((char*)payload_out,"{\"params\":{\"nUa\":+%0.1f,\"Ub\":+%0.1f,\"Uc\":+%0.1f},\"method\":\"thing.event.property.post\"}",Ua,Ub,Uc); payload_out_len = strlen((char*)payload_out); topicString.cstring = topic_pub_set_info; //发布推送主题 len = MQTTSerialize_publish(buf, buflen, 0, req_qos, retained, msgid, topicString, payload_out, payload_out_len); rc = transport_sendPacketBuffer(buf, len); if(rc == len) // printf("send PUBLISH Successfully\r\n"); // 发出去和收回来的长度一样,代表发布ok } if(timeh>=1000) timeh = 0; switch(msgtypes) { case CONNECT: len = MQTTSerialize_connect(buf, buflen, &data); //发送连接信息 rc = transport_sendPacketBuffer(buf, len); //发送连接信息 if(rc == len){ // printf("send CONNECT Successfully\r\n"); } else{ msgtypes = CONNECT; continue; } break; case CONNACK: if(MQTTDeserialize_connack(&sessionPresent, &connack_rc,buf, buflen)) //收回执 { printf("MQTT is conncet OK!\r\n"); //收到回执,连接成功 connect_flag = 1; msgtypes = SUBSCRIBE; //订阅操作 } else { printf("Unable to connect, return code %d\r\n", connack_rc); //失败 } break; case SUBSCRIBE: topicString.cstring = topic_sub_set_info; len = MQTTSerialize_subscribe(buf, buflen, 0, msgid, 1, &topicString, &req_qos); rc = transport_sendPacketBuffer(buf, len); if(rc == len){ goto tt; } else { continue; } break; case SUBACK: rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos,buf, buflen); //订阅成功 QoS printf("granted qos is %d\r\n", granted_qos); if (granted_qos != 0) //goto exit; printf("Get qos"); break; case PUBLISH: rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,&payload_in, &payloadlen_in, (unsigned char*)buf, buflen); //收到服务器消息 printf("message arrived : %s\r\n", payload_in); json = cJSON_Parse((char *)payload_in); //解析数据包 if (!json) { printf("Error before: [%s]\r\n",cJSON_GetErrorPtr()); } else { json_id = cJSON_GetObjectItem(json , "id"); if(json_id->type == cJSON_String) { printf("id:%s\r\n", json_id->valuestring); } json_params = cJSON_GetObjectItem(json , "params"); if(json_params) { if(cJSON_GetObjectItem(json_params, "LEDSwitch")) { } if(cJSON_GetObjectItem(json_params, "p2")) { } } } cJSON_Delete(json); //释放空间 if(qos == 1) { printf("publish qos is 1,send publish ack.\r\n"); // memset(buf,0,buflen); len = MQTTSerialize_ack(buf,buflen,PUBACK,dup,msgid); //publish ack rc = transport_sendPacketBuffer(buf, len); if(rc == len) printf("send PUBACK Successfully\r\n"); else printf("send PUBACK failed\r\n"); } break; case PUBACK: printf("PUBACK!\r\n"); break; case PUBREC: printf("PUBREC!\r\n"); //just for qos2 USART2_sendbyte(12); break; case PUBREL: printf("PUBREL!\r\n"); //just for qos2 break; case PUBCOMP: printf("PUBCOMP!\r\n"); //just for qos2 break; case PINGREQ: len = MQTTSerialize_pingreq(buf, buflen); rc = transport_sendPacketBuffer(buf, len); break; case PINGRESP: printf("mqtt server Pong\r\n"); msgtypes = PINGREQ; break; default: break; } if(msgtypes!=SUBSCRIBE){ tt: rc=MQTTPacket_read(buf, buflen, transport_getdata); 轮询,根据收到的回执进行相应的处理 if(rc >0) //如果收到正确的,存在的回执,则进到相应状态 { msgtypes = rc; }else msgtypes=PINGREQ; } } exit: transport_close();

MQTT通信部分使用了以上的状态机,其实大部分代码网上都有,但是都不是完全正确,而且根据每个人情况不同,代码的理解也不同。
姑且算是原创吧(? 毕竟我也是自己搞的,如果违反了,请提醒我修改。

对域名的DNS解析,大家可以网上自己移植,我尝试过自己写,写了一天硬是没写出来,直接移植比较方便;

结论

写着写着感觉文字有点多…不太会写文章,大家有问题还是问吧,如果有看见会回答的,我个人认为这个项目大家没必要去花钱买资料看,网上可以白嫖的很多的,从0开始确实很艰辛…我也是花了几天时间去搞明白,最重要的是要理解,盲目的肝代码莫得用处(对自己说的…)


作者:乌冬Nymo



stm32 阿里云 芯片 mqtt 连接 以太网 阿里 w5500

需要 登录 后方可回复, 如果你还没有账号请 注册新账号