1792|2

282

帖子

7

TA的资源

一粒金砂(高级)

楼主
 

【智能水杯托】05-连接阿里云同步时间和数据 [复制链接]

 

上一贴介绍给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应用实现云端设置,这个作品就完成了。争取下一贴把这些事情全部搞定。

 

 

 

最新回复

看起来实现的差不多了呀   详情 回复 发表于 2022-10-13 09:05
点赞 关注
 
 

回复
举报

282

帖子

2

TA的资源

一粒金砂(高级)

沙发
 

看起来实现的差不多了呀

点评

将阿里云数据同步给TouchGFX就OK了  详情 回复 发表于 2022-10-13 09:31
 
 
 

回复

282

帖子

7

TA的资源

一粒金砂(高级)

板凳
 
hehung 发表于 2022-10-13 09:05 看起来实现的差不多了呀

将阿里云数据同步给TouchGFX就OK了

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表