上一贴介绍给TouchGFX界面添加硬件支持。实现基本能用的实物。这一贴介绍开发板通过ESP8266模块连接阿里云,并获取网络时间,同步水杯状态,设置参数等操作。
我用的ESP8266模块是安信可出品的ESP-12S,如下图。
图1、WiFi模块
由于购买比较早,里面的固件都不支持MQTT协议,所以第一件事是先升级WiFi固件。下面链接是安信可官方页面,所有需要用到的软件包都能在这个页面找到。
https://docs.ai-thinker.com/esp8266
具体的升级教程网上很多,不再细说,此处列举一个。
https://blog.csdn.net/jdhuzb/article/details/119678009
开发板和WiFi模块硬件连接,我采用的UART1口,对应板上CN3的PMOD#2和PMOD#3引脚,如下图所示。
图2、串口接口
CN3接口可以直接提供3.3V电源,用4根线引出到WiFi模块即可,连接好的实验板如下图。
图3、连接好的实验板
硬件连接好后,开始设计软件。先用STM32CubeMX配置串口,如下图。由于要接收不定长的字符串帧数据,采用串口空闲中断+串口DMA中断实现,所以需要设置DMA通道。
图4、配置串口
串口的基本配置STM32CubeMX都会自动生成好,比较重要的是需要自己编写空闲中断回调函数,这个官方HAL库没有提供。如下代码是我编写的UART1和UART2透传的回调函数,基本实现DMA全自动收发,只占用CPU非常少的资源。
代码1、串口透传
/* USER CODE BEGIN 0 */
void HAL_UART_IdleCallback(UART_HandleTypeDef*huart)
{
uint8_t len=0;
if(huart->Instance==USART1)
{
HAL_UART_DMAStop(&huart1);//中止DMA
len = UART1_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//已经接收了多少个字节 = 总共要接收的字节数 - NDTR
if(len > 0) //如果确实接收到了数据
{
HAL_UART_Transmit_DMA(&huart2, UART1_RX_BUF, len); //将RxBuffer中的数据通过DMA发送到串口
}
__HAL_UART_CLEAR_OREFLAG(&huart1);//清除ORE标记,防止在关闭DMA期间有数据进来,造成ORE错误
HAL_UART_Receive_DMA(&huart1,UART1_RX_BUF,UART1_RX_BUF_SIZE);
}
len=0;
if(huart->Instance==USART2)
{
HAL_UART_DMAStop(&huart2);//中止DMA
len = UART2_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);//已经接收了多少个字节 = 总共要接收的字节数 - NDTR
if(len > 0) //如果确实接收到了数据
{
HAL_UART_Transmit_DMA(&huart1, UART2_RX_BUF, len); //将RxBuffer中的数据通过DMA发送到串口
}
__HAL_UART_CLEAR_OREFLAG(&huart2);//清除ORE标记,防止在关闭DMA期间有数据进来,造成ORE错误
HAL_UART_Receive_DMA(&huart2,UART2_RX_BUF,UART2_RX_BUF_SIZE);
}
}
/* USER CODE END 0 */
其中要注意两点:其一,DMA缓冲buffer要足够长,至少要大于阿里云传回的最大报文长度。其二,开启下次中断前要清除ORE标记,防止在中断处理关闭DMA期间有数据进来,造成ORE错误。
接下来是在阿里云平台建立产品和设备,这个网上教程太多,这里不再细说,发个如下链接供参考。
https://blog.csdn.net/u014421313/article/details/125412417
下图是我在阿里云上创建的设备,带了3个属性,分别是:水杯状态,水杯在位置超时时间,水杯离开位置超时时间。
图5、阿里云上创建的设备
接下来是编写单片机的连网程序。网上各种例程较多,可能是考虑各种异常情况,大部分弄得都比较复杂,看起来头大。我这则是采用最简单粗暴的方式连接阿里云,代码结构也非常简单,适合新手练习使用。我先是在头文件《esp_mqtt.h》里面把联网信息都定义好,考虑这个只是演示项目,不需要灵活配置,就没有拆分成灵活的。如下代码是头文件。
代码2、头文件
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __ESP_MQTT_H
#define __ESP_MQTT_H
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_huart huart1
#define AT_ATE0 "ATE0\r\n" // 关闭回显
#define AT_CMD "AT\r\n" // 测试指令
#define AT_RST "AT+RST\r\n" //复位esp8266
#define AT_CWMODE "AT+CWMODE=1\r\n" //Station模式
#define AT_CIPSNTPCFG "AT+CIPSNTPCFG=1,8,\"ntp1.aliyun.com\"\r\n" //开启SNTP服务器
#define AT_CWJAP "AT+CWJAP=\"asdf\",\"1234567890\"\r\n" //连接WIFI
#define AT_MQTTUSERCFG "AT+MQTTUSERCFG=0,1,\"NULL\",\"dev_001&a1sNYGnke9A\",\"28B62147116DB6C77D436A81F12964F817CDE0\",0,0,\"\"\r\n" //配置 MQTT 用户属性
#define AT_MQTTCLIENTID "AT+MQTTCLIENTID=0,\"12345|securemode=3\\,signmethod=hmacsha1|\"\r\n" //配置 MQTT 客户端
#define AT_MQTTCONN "AT+MQTTCONN=0,\"a1sNYGnke9A.iot-as-mqtt.cn-shanghai.aliyuncs.com\",1883,1\r\n" //连接/查询 MQTT Broker
//订阅消息
#define AT_MQTTSUB "AT+MQTTSUB=0,\"/sys/a1sNYGnke9A/dev_001/thing/service/property/set\",0\r\n" //订阅消息
//属性上报
#define AT_MQTTPUB "AT+MQTTPUB=0,\"/sys/a1sNYGnke9A/dev_001/thing/event/property/post\",\"{\\\"params\\\":{\\\"%s\\\":%d}}\",0,0\r\n" //
#define AT_TIME "AT+CIPSNTPTIME?\r\n" // 获取网络时间
#define RCV_BUFFER_SIZE 300
#define SEND_BUFFER_SIZE 300
void ESP_ConnectMqtt(void);
void ESP_Pub(uint8_t *p_params,uint32_t u_vale);
int8_t ESP_CalBack(uint8_t *pData,uint32_t len);
void ESP_GetTime(void);
#ifdef __cplusplus
}
#endif
#endif /* __ESP_MQTT_H */
然后在《esp_mqtt.c》里面,直接用串口一帧帧发送,每帧之间留一点延时,等阿里云返回数据。如下代码是《esp_mqtt.c》整个文件。
代码3、C文件
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "esp_mqtt.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
uint8_t RCV_BUFFER[RCV_BUFFER_SIZE]; // 接收缓存
char SEND_BUFFER[RCV_BUFFER_SIZE]; //发送缓存
A_CLOCK a_clock;
CUP_PARAM cup_param;
//连接阿里云,由于WiFi模块已经存上路由器信息,此处省略连接路由步骤
void ESP_ConnectMqtt(void)
{
HAL_UART_Transmit_DMA(&ESP_huart, "\0\0", 2); //解决首次发送只发出前两个字节问题
HAL_Delay(50);
HAL_UART_Transmit_DMA(&ESP_huart, AT_RST, strlen(AT_RST));
HAL_Delay(2000);
HAL_UART_Transmit_DMA(&ESP_huart, AT_ATE0, strlen(AT_ATE0));
HAL_Delay(500);
HAL_UART_Transmit_DMA(&ESP_huart, AT_CWMODE, strlen(AT_CWMODE));
HAL_Delay(500);
HAL_UART_Transmit_DMA(&ESP_huart, AT_CIPSNTPCFG, strlen(AT_CIPSNTPCFG));
HAL_Delay(500);
HAL_UART_Transmit_DMA(&ESP_huart, AT_MQTTUSERCFG, strlen(AT_MQTTUSERCFG));
HAL_Delay(500);
HAL_UART_Transmit_DMA(&ESP_huart, AT_MQTTCLIENTID, strlen(AT_MQTTCLIENTID));
HAL_Delay(500);
HAL_UART_Transmit_DMA(&ESP_huart, AT_MQTTCONN, strlen(AT_MQTTCONN));
HAL_Delay(500);
HAL_UART_Transmit_DMA(&ESP_huart, AT_MQTTSUB, strlen(AT_MQTTSUB));
HAL_Delay(500);
}
//发布消息
void ESP_Pub(uint8_t *p_params,uint32_t u_vale)
{
sprintf(SEND_BUFFER, AT_MQTTPUB, p_params,u_vale);
HAL_UART_Transmit_DMA(&ESP_huart, (const uint8_t *)SEND_BUFFER, strlen(SEND_BUFFER));
HAL_Delay(500);
}
//数据接收,解析
int8_t ESP_CalBack(uint8_t *pData,uint32_t len)
{
cJSON *item;
char *str;
char str1[3];
memcpy(RCV_BUFFER,pData,len);
////
str = (char *)RCV_BUFFER;
str = strstr(str, "+CIPSNTPTIME:");
if (str != NULL)
{
str = strchr(str, ':');
if (str != NULL)
{
str1[0] = str[12];
str1[1] = str[13];
str1[2] = 0;
a_clock.a_hour = atoi(str1);
str1[0] = str[15];
str1[1] = str[16];
str1[2] = 0;
a_clock.a_min = atoi(str1);
str1[0] = str[18];
str1[1] = str[19];
str1[2] = 0;
a_clock.a_sec = atoi(str1);
printf("time:%d:%d:%d\r\n",a_clock.a_hour,a_clock.a_min,a_clock.a_sec);
return 1;
}
}
/////
str = (char *)RCV_BUFFER;
str = strstr(str, "params");
if (str != NULL)
{
str = strchr(str, '{');
if (str != NULL)
{
cJSON *str_json = cJSON_Parse(str); //创建JSON解析对象,返回JSON格式是否正确
if(str_json == NULL)
{
printf("error:%s;\r\n",cJSON_GetErrorPtr());
cJSON_Delete(str_json);//释放内存
return -1;
}
else
{
// printf("str:%s;\r\n",str);
item = cJSON_GetObjectItem(str_json, "CS");
if(item!=NULL)
{
cup_param.c_state = item->valueint;
printf("CUP state:%d\r\n",cup_param.c_state);
}
item = cJSON_GetObjectItem(str_json, "CIT");
if(item!=NULL)
{
cup_param.c_cit = item->valueint;
printf("CUP in timeout:%d\r\n",cup_param.c_cit);
}
item = cJSON_GetObjectItem(str_json, "CLT");
if(item!=NULL)
{
cup_param.c_clt = item->valueint;
printf("CUP left timeout:%d\r\n",cup_param.c_clt);
}
cJSON_Delete(str_json);//释放内存
}
return 1;
}
}
return -1;
}
//获取网络时间
void ESP_GetTime(void)
{
HAL_UART_Transmit_DMA(&ESP_huart, AT_TIME, strlen(AT_TIME));
HAL_Delay(50);
}
这里重点说一下数据解析回调函数。这个里面分两部分,第一部分是将接收的时间字符串里面的时分秒三个参数提取出来,用于发给模拟时钟校时。这个比较简单,找准字符串位置,直接用atoi()函数将字符串转为int数字就行了。第二部分是对收到阿里云的设置报文进行解析。网上大部分例程对于这种参数少,内容简单的json字符串,都是用字符串函数一点点提取,我觉这样比较麻烦,所以此处我直接调用了cJSON库进行解析,有种牛刀宰鸡的爽快!最终将结果都从调试串口打印出来。
经过反复调试,终于实现了阿里云数据上报和订阅,具体展示如下视频。
视频1、阿里云连接展示
以上,实现了开发板数据和阿里云的对接。后面再把更新的时间数据,订阅接收的设置数据同步给TouchGFX界面。最后再做一个web应用实现云端设置,这个作品就完成了。争取下一贴把这些事情全部搞定。
|