硬件 STM32F103C8T6
ESP-01S
软件
SYS->Debug->Serial Wire
RCC->HSE->Crystal/Ceramic Resonator
PC13->GPIO_Out
USART1->Mode->Asynchronous ,参数默认
USART2>Mode->Asynchronous ,参数默认
NVIC->USART2 global interrupt->Enabled
串口1与上位机tongxin
串口2与服务器通信
服务器 本次使用的MQTT服务器,有钱的朋友们也可以自行去阿里云购买一个自己的云服务器。
EMQX 。EMQX Cloud 是 EMQ 公司推出的一款面向物联网领域的 MQTT 消息中间件产品。作为全球首个全托管的 MQTT 5.0 公有云服务,EMQX Cloud 提供了一站式运维代管、独有隔离环境的 MQTT 消息服务。在万物互联的时代,EMQX Cloud 可以帮助您快速构建面向物联网领域的行业应用,轻松实现物联网数据的采集、传输、计算和持久化。
此网站提供了一个免费在线MQTT 5服务器
Broker
broker-cn.emqx.io
TCP端口
1883
Websocket端口
8083
TCP/TLS端口
8883
Websocket/TLS端口
8084
CA证书文件
broker.emqx.io-ca.crt
代码编写 程序中主要的文件有
esp8266.c esp8266.h
MqttKit.c MqttKit.h
onenet.c onenet.h
cJSON.c cJSON.h
将上述文件分别添加到Inc和Src中,可能会遇到以下情况
这时只要重新加载一下CMakeLists.txt
esp8266 模块使用的是安心可得ESP-01S
ESP-01S 是由安信可科技开发的 Wi-Fi 模块,该模块核心处理器ESP8266 在较小尺寸封装中集成了业界领先的 Tensilica L106 超低功耗 32 位微型MCU,带有16位精简模式,主频支持 80 MHz 和 160 MHz,支持 RTOS,集成Wi-Fi MAC/ BB/RF/PA/LNA。ESP-01S Wi-Fi 模块支持标准的 IEEE802.11 b/g/n 协议,完整的TCP/IP 协议栈。用户可以使用该模块为现有的设备添加联网功能,也可以构建独立的网络控制器。
esp8266.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #ifndef __esp_H #define __esp_H #include "main.h" #define REV_OK 0 #define REV_WAIT 1 typedef _Bool uint1;typedef unsigned char uint8;typedef char int8;typedef unsigned short uint16;typedef short int16;typedef unsigned int uint32;typedef int int32;typedef unsigned int size_t ;void Usart_SendString (UART_HandleTypeDef *USARTx, unsigned char *str, unsigned short len) ;void UsartPrintf (UART_HandleTypeDef *huart, char *fmt,...) ;void ESP8266_Init (void ) ;void ESP8266_Clear (void ) ; void ESP8266_SendData (unsigned char *data, unsigned short len) ;unsigned char *ESP8266_GetIPD (unsigned short timeOut) ;#endif
下面介绍以下几个重要功能函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void Usart_SendString (UART_HandleTypeDef *USARTx, unsigned char *str, unsigned short len) { uint16_t j; for (j=0 ;j<len;j++) { while ((USART2->SR&0X40 )==0 ); USART2->DR=*str++; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void UsartPrintf (UART_HandleTypeDef *huart, char *fmt,...) { int printf_TXBUFFERSIZE=1026 ; char buffer[printf_TXBUFFERSIZE]; int i=0 ; va_list arg_ptr; va_start(arg_ptr,fmt); vsnprintf(buffer,printf_TXBUFFERSIZE+1 ,fmt,arg_ptr); while ((i<printf_TXBUFFERSIZE)&&buffer[i]) { HAL_UART_Transmit(huart,(uint8_t *)&buffer[i++],1 ,0xFFFF ); } va_end(arg_ptr); }
上述两个函数在usart.c中都没有定义,需要自己定义串口数据发送和格式化打印函数。
esp8266初始化函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void ESP8266_Init (void ) { __HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE); ESP8266_Clear(); UsartPrintf(&huart1, "> 0. AT - 测试MCU-8266通讯\r\n" ); while (ESP8266_SendCmd("AT\r\n" , "OK" )) HAL_Delay(500 ); UsartPrintf(&huart1, "> 1. AT+RST - 软复位8266\r\n" ); ESP8266_SendCmd("AT+RST\r\n" , "" ); HAL_Delay(500 ); ESP8266_SendCmd("AT+CIPCLOSE\r\n" , "" ); HAL_Delay(500 ); UsartPrintf(&huart1, "> 2. AT+CWMODE=1,1 - 设置8266工作模式为STA\r\n" ); while (ESP8266_SendCmd("AT+CWMODE=1\r\n" , "OK" )) HAL_Delay(500 ); UsartPrintf(&huart1, "> 3. AT+CWDHCP=1,1 - 使能STA模式下DHCP\r\n" ); while (ESP8266_SendCmd("AT+CWDHCP=1,1\r\n" , "OK" )) HAL_Delay(500 ); UsartPrintf(&huart1, "> 4. AT+CWJAP - 连接WIFI\r\n" ); while (ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP" )) HAL_Delay(500 ); UsartPrintf(&huart1, "> 5. AT+CIPSTART - 连接服务器\r\n" ); while (ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT" )) HAL_Delay(500 ); ESP8266_INIT_OK = 1 ; UsartPrintf(&huart1, "> 6. ESP8266 Init OK - ESP8266初始化成功\r\n" ); }
通过串口1实时打印esp8266的状态,串口2通过esp8266发送指令连接wifi、连接服务器等
ESP8266_WIFI_INFO、ESP8266_ONENET_INFO两个宏定义为
1 2 3 4 5 6 7 8 9 #define WIFI_SSID "******" #define WIFI_PSWD "******" #define SERVER_HOST "broker.emqx.io" #define SERVER_PORT "1883" #define ESP8266_WIFI_INFO "AT+CWJAP=\"" WIFI_SSID "\",\"" WIFI_PSWD "\"\r\n" #define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"" SERVER_HOST "\"," SERVER_PORT "\r\n"
在移植过程中只需修改上述宏即可
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include "main.h" #include "usart.h" #include "gpio.h" #include "esp8266.h" #include "onenet.h" const char *devSubTopic[] = {"/mysmarthome/sub" };const char devPubTopic[] = "/mysmarthome/pub" ;uint8_t ESP8266_INIT_OK = 0 ;void SystemClock_Config (void ) ;int main (void ) { unsigned short timeCount = 0 ; unsigned char *dataPtr = NULL ; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); UsartPrintf(&huart1, "Hardware init OK\r\n" ); ESP8266_Init(); while (OneNet_DevLink()) HAL_Delay(500 ); OneNet_Subscribe(devSubTopic, 1 ); while (1 ) { if (++timeCount >= 200 ) { UsartPrintf(&huart1, "OneNet_Publish\r\n" ); OneNet_Publish(devPubTopic, "MQTT Publish Test" ); timeCount = 0 ; ESP8266_Clear(); } dataPtr = ESP8266_GetIPD(3 ); if (dataPtr != NULL ) OneNet_RevPro(dataPtr); HAL_Delay(10 ); } }
主函数中,以下这几处都跟stm32与服务器实现发布与订阅有重要作用
1 2 3 4 OneNet_Subscribe(devSubTopic, 1 ); OneNet_Publish(devPubTopic, "MQTT Publish Test" ); dataPtr = ESP8266_GetIPD(3 ); OneNet_RevPro(dataPtr);
下面是发布和订阅的话题名称,可自行修改
1 2 3 4 const char *devSubTopic[] = {"/mysmarthome/sub" };const char devPubTopic[] = "/mysmarthome/pub" ;
对发布内容来说,修改msg的值即可确定stm32发布给服务器的内容
1 void OneNet_Publish (const char *topic, const char *msg)
而stm32订阅服务器的数据(即服务器发布给stm32的数据)需要在OneNet_RevPro函数中对数据进行json处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 void OneNet_RevPro (unsigned char *cmd) { MQTT_PACKET_STRUCTURE mqttPacket = {NULL , 0 , 0 , 0 }; char *req_payload = NULL ; char *cmdid_topic = NULL ; unsigned short topic_len = 0 ; unsigned short req_len = 0 ; unsigned char type = 0 ; unsigned char qos = 0 ; static unsigned short pkt_id = 0 ; short result = 0 ; char *dataPtr = NULL ; char numBuf[10 ]; int num = 0 ; cJSON *json , *json_value; type = MQTT_UnPacketRecv(cmd); switch (type) { case MQTT_PKT_CMD: result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len); if (result == 0 ) { UsartPrintf(&huart1, "cmdid: %s, req: %s, req_len: %d\r\n" , cmdid_topic, req_payload, req_len); MQTT_DeleteBuffer(&mqttPacket); } break ; case MQTT_PKT_PUBLISH: result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id); if (result == 0 ) { UsartPrintf(&huart1, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n" , cmdid_topic, topic_len, req_payload, req_len); json = cJSON_Parse(req_payload); if (!json)UsartPrintf(&huart1, "Error before: [%s]\n" , cJSON_GetErrorPtr()); else { json_value = cJSON_GetObjectItem(json, "LED" ); if (json_value->valueint) HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); else HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); } cJSON_Delete(json); } break ; case MQTT_PKT_PUBACK: if (MQTT_UnPacketPublishAck(cmd) == 0 ) UsartPrintf(&huart1, "Tips: MQTT Publish Send OK\r\n" ); break ; default : result = -1 ; break ; } ESP8266_Clear(); if (result == -1 ) return ; if (strstr ((char *)req_payload, "OPEN" )) { UsartPrintf(&huart1, "Blue_Led_ON\r\n" ); } else if (strstr ((char *)req_payload, "CLOED" )) { UsartPrintf(&huart1, "Blue_Led_OFF\r\n" ); } if (type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH) { MQTT_FreeBuffer(cmdid_topic); MQTT_FreeBuffer(req_payload); } }
移植过程中只需要修改下面这部分内容即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 case MQTT_PKT_PUBLISH: result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id); if (result == 0 ) { UsartPrintf(&huart1, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n" , cmdid_topic, topic_len, req_payload, req_len); json = cJSON_Parse(req_payload); if (!json)UsartPrintf(&huart1, "Error before: [%s]\n" , cJSON_GetErrorPtr()); else { json_value = cJSON_GetObjectItem(json, "LED" ); if (json_value->valueint) HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); else HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); } cJSON_Delete(json); } break ;
发布数据时要以json格式进行发送eg:{"msg": "hello"}
上述中当我们发布{"LED":1},LED等就为高电平,{"LED":0},LED等就为低电平
烧录程序后,连接串口,打开自己的手机热点(必须用2.4G的wifi不能用5G的),然后出现以下情况及表示连接服务器成功。
EMQ服务器的使用 首先登录官网EMQX ,找到学习下的MQTT WebSocket客户端
进去之后,点击适用
创建新的连接,自定义Name,Host和Port修改成自己的服务器名即可,然后Connect
STM32发布、服务器订阅 New Subscription->订阅stm32的发布话题/mysmarthome/pub
此时就会看到stm32向服务器发来的消息MQTT Publish Test,实现
STM32订阅、服务器发布 将Payload选为JSON格式发布,topic为/mysmarthome/sub,发布内容为{"LED":1}和{"LED":0},实现控制stm32的LED亮灭。
注意!!!,在新建发布与订阅话题中,有一项为QoS,此选项要与STM32中一致
分别在onenet.c中的OneNet_Subscribe和OneNet_Publish函数中
MQTT客户端的使用 上面中使用是在线发布与订阅,下面使用MQTT软件实现发布订阅
下载地址MQTT.fx
打开之后点击小齿轮新建连接
然后Apply,Connect之后就可以发布和订阅话题了
订阅话题
发布话题
移植 下面总结下移植时需要修改的参数
参数
说明
WIFI_SSID
WIFI的名称 必须用2.4G的wifi不能用5G的,且不能用中文、空格
WIFI_PSWD
WIFI密码
SERVER_HOST
MQTT服务器域名或IP
SERVER_PORT
MQTT服务器端口(一般为1883不用改)
*devSubTopic[]
订阅话题
devPubTopic[]
发布话题
OneNet_Publish(const char topic, const char ** msg**)
发布内容
cJSON_GetObjectItem(json, “LED” );
对订阅的命令做出判断