概要 STM32F103c8t6+ESP8266(esp-01s)+MQTT固件 连接阿里云 里面用到了对串口不定长的数据的DMA+中断的接受方式。不了解的可以看这个篇文章STM32F407的串口接收不定长数据两种方式HAL库
一、MQTT固件 对于该项目我们利用了MQTT固件,这个固件可以让我们更加简单的利用MQTT进行数据传输,利用该固件我们不需要对MQTT进行封装,直接用。 我们可以去安信可的官网下载MQTT的固件(安信可官网固件下载 )在安信可的官网上下载的固件还需要下载下载进esp8266的工具。如果这个有对应的下载工具。也有mqtt的固件 链接:https://pan.baidu.com/s/1gbizlkm997HnCW5H3B7n3A 提取码:8ex1 我尝试了一下1471这个固件号的是可以用的,其他的好像型号不对flash大小不够,有专业的可以给我讲解一下,谢谢。
下载的方式是利用串口,可以用wifi的转接板或者别的串口工具。我这边用的时转接板。 插上之后选择esp8266下载工具 进入之后根据下图进行操作 MQTT固件连接阿里云对比AT固件连接云平台来说是更加简单,我们只需要掌握MQTT固件的AT指令就行。 我这里将列出几个关键的指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 AT+MQTTUSERCFG=0 ,1 ,"" ,"用户名" ,"密码" ,0 ,0 ,"" AT+MQTTCLIENTID=0 ,"ix25oHiHCSl.stm32|securemode=2\,signmethod=hmacsha156\,timestamp=1686921535251|" AT+MQTTCONN=0 ,"iot-06z00b28nanp9ew.mqtt.iothub.aliyuncs.com" ,1883 ,1 AT+MQTTPUB=0 ,"/sys/ix25oHiHCSl/stm32/thing/event/property/post" ,"{\"params\":{\"temperature\":89\,\"humi\":0\}\,\"version\":\"1.0.0\"}" ,0 ,0 +MQTTSUBRECV:0 ,"/sys/ix25oHiHCSl/stm32/thing/event/property/post" ,75 ,{"params" :{"temperature" :16.300000 ,"Humidity" :38.600000 },"version" :"1.0.0" }
二、阿里云账号注册 注册自己的账号之后,进入
然后添加设备这个mqtt连接参数十分重要,是stm32连接的关键
添加物模型数据 这里添加自己的想要的数据,这个很关键。添加成功之后可以在这里看到 刚开始的数值为0或者为空
三、stm32f103的配置 1、选择对应芯片,这里使用的是stm32f103c8t6,配置一些下载、时钟 使用系统帮我们配置的时钟
2、初始化串口,串口1作为调试打印的串口,串口2作为esp8266的通信串口。串口1不需要开启中断,串口2需要开启中断,并且该项目使用空闲中断+接收的DMA的方式。 开启usart2的中端 开启接收的DMA方式 3、cubemx的配置结束,需要加功能的自己添加,这里只是一个最简单的工程。
三、esp8266的接收和发送 对应一些基础知识和基本的使用,c语言知识我这里就不会做更多的介绍了。另外我们四利用cubemx创建的工程,我们最好按照他的格式去写代码。
1、printf 添加printf打印的支持,方便我们的调试,在usart.h文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #if 1 #pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; void _sys_exit(int x){ x = x; } int fputc (int ch, FILE *f) { while ((USART1->SR & 0X40 ) == 0 ); USART1->DR = (uint8_t ) ch; return ch; } #endif
2、串口2的DMA接收中断 这里也是稍微提及一下,如果想要更深入的了解,请看我写的另一篇 STM32F407的串口接收不定长数据两种方式HAL库 。 在这个阶段,我们需要定义一个数组,这个数组用来接收wifi给我们发送的数据, 大家可以我一样起同一个名字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void USART2_IRQHandler (void ) { HAL_UART_IRQHandler(&huart2); if (__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET) { HAL_UART_DMAStop(&huart2); esp_cnt = ESPBUFF_MAX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx); HAL_UART_Transmit(&huart1,esp_buff,esp_cnt,1000 ); HAL_UART_Receive_DMA(&huart2,esp_buff,ESPBUFF_MAX_SIZE); __HAL_UART_CLEAR_IDLEFLAG(&huart2); } }
3、esp8266的函数方法 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 void ESP8266_SendString (uint8_t *str,uint8_t len) { uint8_t i=0 ; for (i=0 ;i<len;i++) { USART2->DR = *str; str++; HAL_Delay(1 ); } } uint8_t ESP8266_SendCmd (uint8_t *cmd,uint8_t *res) { uint8_t num = 200 ; ESP8266_Clear(); ESP8266_SendString(cmd,strlen ((const char *)cmd)); while (num--) { if (strstr ((const char *)esp_buff,(const char *)res)!=NULL ) { ESP8266_Clear(); return 0 ; } HAL_Delay(10 ); } return 1 ; }
初始化 我们是在这里开启dma的接收的,还有空闲中断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define WIFI_NAME "m_phone" #define WIFI_PASS "123456678" void ESP8266_Init (void ) { HAL_UART_Receive_DMA(&huart2,esp_buff,ESPBUFF_MAX_SIZE); __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE); while (ESP8266_SendCmd("AT+RST\r\n" , "ready" )) while (ESP8266_SendCmd("AT\r\n" ,"OK" )){} while (ESP8266_SendCmd("AT+CWMODE=1\r\n" ,"OK" )){} while (ESP8266_SendCmd("AT+CWJAP=\"" WIFI_NAME"\",\"" WIFI_PASS"\"\r\n" ,"OK" )){} printf ("success" ); }
清空esp8266的数组
1 2 3 4 5 void ESP8266_Clear () { memset (esp_buff,0 ,sizeof (esp_buff)); esp_cnt = 0 ; }
四、连接阿里云 1、通过上面的操作我们可以连接wifi热点,后面我们将连接阿里云。在第二点中我们创建了一个设备,里面提供了连接用到的MQTT参数。 根据这个更改对应的信息,用我提供的是连接不上的,我更改了。
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 #define ALI_USERNAME "stm32&ix25oHiHCSl" #define ALICLIENTLD "ix25oHiHCSl.stm32|securemode=2\\,signmethod=hmasha256\\,timestamp=1688993186406|" #define ALI_PASSWD "08d7d8eb8bf44a452813fe04194cdb3b2d6b5ec58accfd115878efb403d0144a9" #define ALI_MQTT_HOSTURL "iot-06z00b14nanp9ew.mqtt.iothub.aliyuncs.com" #define ALI_PORT "1883" #define ALI_TOPIC_SET "/sys/ix25aHqHCSl/stm32/thing/service/property/set" #define ALI_TOPIC_POST "/sys/ix25aHqHCSl/stm32/thing/event/property/post" void Ali_Yun_Init (void ) { while (ESP8266_SendCmd("AT+MQTTUSERCFG=0,1,\"NULL\",\"" ALI_USERNAME"\",\"" ALI_PASSWD"\",0,0,\"\"\r\n" ,"OK" )){} HAL_Delay(10 ); while (ESP8266_SendCmd("AT+MQTTCLIENTID=0,\"" ALICLIENTLD"\"\r\n" ,"OK" )){} while (ESP8266_SendCmd("AT+MQTTCONN=0,\"" ALI_MQTT_HOSTURL"\",1883,1\r\n" ,"OK" )){} Ali_Yun_Topic(); } void Ali_Yun_Topic (void ) { while (ESP8266_SendCmd("AT+MQTTSUB=0,\"" ALI_TOPIC_SET"\",0\r\n" ,"OK" )){} while (ESP8266_SendCmd("AT+MQTTSUB=0,\"" ALI_TOPIC_POST"\",0\r\n" ,"OK" )){} }
在上面的代码中,订阅和发布的主题都是不一样的,要根据自己的设备进行更改。
五、数据上报和数据解析 对于发送和阿里云下发的数据都是一个json格式 发送的格式: AT+MQTTPUB=0,”/sys/ix25oHiHCSl/stm32/thing/event/property/post”,”{"params":{"temperature":1,"Humidity":1},"version":"1.0.0"}”,0,0 接收数据的格式: +MQTTSUBRECV:0,”/sys/ix25oHiHCSl/stm32/thing/service/property/set”,121,{“method”:”thing.service.property.set”,”id”:”1469885784”,”params”:{“Humidity”:45.3,”temperature”:25.5},”version”:”1.0.0”} 所以我们主要是对数据进行组装和解析 我们这里用到了cJSON,这是一个开源的项目,大家可以自己在网上找一下,可以在网盘中下载,就一个cJSON.h和一个cJSON.c两个文件。
1、发送数据 我们是模拟了两个数据,temp_value、humi_value,大家可以把自己想要的数据上传
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 void Ali_Yun_Send (void ) { uint8_t msg_buf[1024 ]; uint8_t params_buf[1024 ]; uint8_t data_value_buf[24 ]; uint16_t move_num = 0 ; cJSON *send_cjson = NULL ; char *str = NULL ; int i=0 ; printf ("str = %p\r\n" ,&str); cJSON *params_cjson = NULL ; memset (msg_buf,0 ,sizeof (msg_buf)); memset (params_buf,0 ,sizeof (params_buf)); memset (data_value_buf,0 ,sizeof (data_value_buf)); send_cjson = cJSON_CreateObject(); params_cjson = cJSON_CreateObject(); printf ("cjson发送数据 temp_value = %f\r\n" ,temp_value); printf ("cjson发送数据 humi_value = %f\r\n" ,humi_value); cJSON_AddNumberToObject(params_cjson,"temperature" ,temp_value++); cJSON_AddNumberToObject(params_cjson,"Humidity" ,humi_value++); cJSON_AddItemToObject(send_cjson, "params" , params_cjson); cJSON_AddItemToObject(send_cjson,"version" ,cJSON_CreateString("1.0.0" )); str = cJSON_PrintUnformatted(send_cjson); printf ("json格式 = %s\r\n" ,str); for (i=0 ;*str!='\0' ;i++) { params_buf[i] = *str; if (*(str+1 )=='"' ||*(str+1 )==',' ) { params_buf[++i] = '\\' ; } str++; move_num++; } str = str - move_num; printf ("params_buf = %s\r\n" ,params_buf); sprintf ((char *)msg_buf,"AT+MQTTPUB=0,\"" ALI_TOPIC_POST"\",\"%s\",0,0\r\n" ,params_buf); printf ("开始发送数据:%s\r\n" ,msg_buf); ESP8266_SendCmd(msg_buf,"OK" ); ESP8266_Clear(); cJSON_Delete(send_cjson); if (str!=NULL ){ free (str); str = NULL ; printf ("释放str空间成功\r\n" ); } }
上面的代码是利用cjson的对象,组装成一个json格式,但是我们在用at指令发送的时候是要有 ‘\‘这个字符的,所以我们将数据进行了第二次的处理。这里注意要对cjson和str的内存进行释放。不然会出很多问题 。
2、数据的解析 这里也是对温度和湿度进行解析,需要解析别的也可以自己添加,主要是对cjson的函数使用。如果有必要我后面出一个对cjson的使用文章,主要是cjson的内存释放问题,比较麻烦,我被这个搞了好久。
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 uint8_t cjson_err_num = 0 ; void Ali_Yun_GetRCV (void ) { cJSON *cjson = NULL ; int num; char topic_buff[256 ]; char recv_buffer[ESPBUFF_MAX_SIZE]; char *ptr_recv = strstr ((const char *)esp_buff,"+MQTTSUBRECV" ); if (ptr_recv!=NULL ) { memset (topic_buff,0 ,sizeof (topic_buff)); sscanf ((char *)esp_buff,"+MQTTSUBRECV:0,%[^,],%d,%s" ,topic_buff,&num,recv_buffer); if (strstr (topic_buff,ALI_TOPIC_SET)) { printf ("========================数据解析开始===========================\r\n" ); printf ("接收数据成功,开始解析 %s\r\n" ,recv_buffer); cjson = cJSON_Parse(recv_buffer); if (cjson==NULL ) { printf ("cjson 解析错误\r\n" ); cjson_err_num++; if (cjson_err_num>3 ){ ESP8266_Clear(); cjson_err_num = 0 ; } printf ("========================数据解析失败===========================\r\n" ); } else { cJSON *json_data = NULL ; json_data = cJSON_GetObjectItem(cjson,"params" ); cjson_err_num = 0 ; if (json_data==NULL ){ printf ("cjson 没有数据\r\n" ); return ; }else { printf ("cjson 内存大小为 = %d\r\n" ,sizeof (cjson)); if (cJSON_GetObjectItem(json_data,"temperature" )!=NULL ) { temp_value = cJSON_GetObjectItem(json_data,"temperature" )->valuedouble; printf ("csjon解析成功 temp_value = %f\r\n" ,temp_value); } if (cJSON_GetObjectItem(json_data,"Humidity" )!=NULL ) { humi_value = cJSON_GetObjectItem(json_data,"Humidity" )->valuedouble; printf ("csjon解析成功 Humidity = %f\r\n" ,humi_value); } } ESP8266_Clear(); cJSON_Delete(cjson); printf ("========================数据解析成功===========================\r\n" ); } } } }
该方法是利用了sscanf对每一个部分进行分割,取到json格式的部分,交给cjson解析,主要还是注意内存泄漏的问题。
六、主函数 因为c8t6资源本来就少,所以我们这里并没有用到定时器,利用标志位大概取一个时间循环发送数据,对于判断接收标志位的判断也是可以丢到while循环,因为我们对espbuff的清理都是在处理了数组之后再去清空的,所以一般情况是不会造成数据没有接收到的情况。这个发送数据的时间,大家可以利用定时器,我这边就不加了。
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 int main (void ) { uint16_t time = 0 ; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); ESP8266_Init(); Ali_Yun_Init(); while (1 ) { time++; if (time>1000 ) { Ali_Yun_Send(); time = 0 ; } Ali_Yun_GetRCV(); HAL_Delay(5 ); } }
小结 总的来说这个项目还是比较简单,适合新手对串口通信进一步了解。 第一次写这种文章,请多多指教吧!谢谢!源码下载