Skip to content

Porting MQTT protocol to STM32 and W5500, Ali Cloud

STM32上移植MQTT协议使用W5500以太网芯片连接阿
The MQTT protocol is transplanted on the STM32 and the W5500 Ethernet chip is used to connect to Alibaba Cloud

WIZnet

Published August 30, 2022

Original author: 乌冬NymoOriginal source (new tab)

Porting  MQTT protocol to STM32 and W5500, Ali Cloud

Components

Hardware components Software Apps and online services

WIZnet parts

Project description

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;
阿里云规定Pub和Sub的报文格式为Json格式,因此在Pub和在Sub解析的时候都要注意Json格式是否符合,网上有很多在线检测
很重要的一点,自带的Socket库里头的read都是阻塞的,如果你需要同时(近似)Pub和Sub,需修改成非阻塞的;

核心代码
下面贴上核心代码。
————————————————
版权声明:本文为CSDN博主「乌冬Nymo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41655062/article/details/105181398

/**
*@Todo: Send data to the server via tcp
*/
int transport_sendPacketBuffer(unsigned char* buf, int buflen)
{
  return send(SOCK_TCPS,buf,buflen);
}

/**
 *@Todo: Receiving data sent by tcp server in blocking mode
*/
int transport_getdata(unsigned char* buf, int count)
{
  return recv(SOCK_TCPS,buf,count);
}

/**
 *@Todo: Open a socket and connect to the server
 *@retval less than means fail
*/
int transport_open(void){
	int32_t ret;
	//New 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);
  }
	 //Connect to TCP server
	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: Close Socket
*/
int transport_close(void)
{
  disconnect(SOCK_TCPS);
  close(SOCK_TCPS);
  return 0;
}
/***************************The above are the basic four functions***************** *******/


	while(1){
		timeh++;
			if(connect_flag == 1&&timeh==1000)
		  {  // Json format, data is released according to variables defined in Ailiyun, payload is the 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;		//Publish push topic
				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"); // The length of the sent out and the received back are the same, which means the release is ok
		  }
			if(timeh>=1000)
				timeh = 0;
			switch(msgtypes)
		{

			case CONNECT:	len = MQTTSerialize_connect(buf, buflen, &data); 						//Send connection information
							rc = transport_sendPacketBuffer(buf, len);		//Send connection information
							if(rc == len){															//
								printf("send CONNECT Successfully\r\n");
							}
							else{
								msgtypes =  CONNECT; 
								continue;
							}
							break;

			case CONNACK:   if(MQTTDeserialize_connack(&sessionPresent, &connack_rc,buf, buflen))	//Retract the execution
							{
								printf("MQTT is conncet OK!\r\n");									//Receipt of receipt, connection is successful
								connect_flag = 1;
								msgtypes = SUBSCRIBE;													//Subscription operation
							}
							else
							{
								printf("Unable to connect, return code %d\r\n", connack_rc);		//failure
							}
							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);	//Subscription is successful 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);	//Receive server message
							printf("message arrived : %s\r\n", payload_in);
							json = cJSON_Parse((char *)payload_in);			//Analyze the data packet
							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);   //Free up space
							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);       	       Polling, corresponding processing according to the received receipt
			    if(rc >0)													//If you receive the correct, existing receipt, then enter the corresponding state
				  {
				  	msgtypes = rc;
				  }else msgtypes=PINGREQ;
		  }

	}
	exit:
	transport_close();

MQTT通信部分使用了以上的状态机,其实大部分代码网上都有,但是都不是完全正确,而且根据每个人情况不同,代码的理解也不同。

结论
写着写着感觉文字有点多…不太会写文章,大家有问题还是问吧,如果有看见会回答的,我个人认为这个项目大家没必要去花钱买资料看,网上可以白嫖的很多的,从0开始确实很艰辛…我也是花了几天时间去搞明白,最重要的是要理解,盲目的肝代码莫得用处(对自己说的…)
————————————————
版权声明:本文为CSDN博主「乌冬Nymo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41655062/article/details/105181398

Comments

Similar projects you might like

Comments