STM32+ESP-01对接阿里飞燕平台
近年来,伴随着微电子技术和计算机信息技术的高速发展,物联网技术也就应运而生,万物互联成为可能。目前大部分消费类电子产品以及家电产品都已装载上了WIFI模块或者蓝牙模块,让它们都能连接入网,实现互联互通,远程控制、智能控制成为众多消费电子产品的卖点。因此WIFI模块的应用也随之成为嵌入式工程师必不可少的技能。
由于IPv4地址资源的紧缺,在局域网中的设备一般不能通过本地IP与外部网络直接建立连接,需要通过NAT网关进行IP映射,所以远程控制局域网中的物联网设备最好的解决方案就是通过外网服务器进行消息转发。目前各大互联网公司都推出了自己的物联网平台。比如阿里巴巴的飞燕平台,腾讯的微信硬件平台,机智云推出的机智云平台等等。
鉴于阿里巴巴集团的强大影响力,我们就以飞燕平台为例来讨论一下如何通过物联网平台进行WIFI设备到服务器再到手机终端的连接方案,开发我们的物联网产品。关于飞燕平台的细节信息请参考它的帮助文档https://help.aliyun.com/product/93051.html?spm=a2c4g.750001.list.264.36f87b13MRwU2W
由于采用硬件平台的不同相对应的对接程序也存在很大差异,对接前我们要先确定需要采用哪种硬件平台,例如今天我们的讨论是基于STM32MCU+ESP-01S WIFI模组的硬件配置,由于ESP-01S原始固件只支持TCP但不支持MQTT,所以业务逻辑控制以及MQTT协议要运行在MCU之上。连接框图如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9f67140258279ec0da3796cb63b988ab.png)
阿里飞燕平台是基于MQTT协议的,所以这里我们先来了解一下MQTT协议,根据网络协议分层如下图:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3afdcd8b900a7026b71fbd78d3e6448b.png)
MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它是基于 TCP/IP的协议簇的应用层协议,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议 。MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
关于MQTT协议的具体内容请移步https://www.cnblogs.com/dongkuo/p/11360419.html
这位博主进行了协议的详细介绍。
阿里飞燕平台为了方便用户使用,简化对接平台时的繁琐底层程序,其提供了各类开发语言的SDK,下载地址如下:
https://code.aliyun.com/linkkit/c-sdk/repository/archive.zip?spm=a2c4g.11186623.2.36.63fb4e1eEOB1mz&ref=v3.0.1
SDK的应用方法细节请参考帮助文档
https://help.aliyun.com/product/93051.html?spm=a2c4g.750001.list.264.36f87b13MRwU2W
下载完SDK后解压,双击打开config.bat文件,会弹出一个选择模块的配置界面如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/96b66ae9335837f82beb6ad1310c9ab6.png)
通过上下左右方向键移动光标,空格键选择和取消选择,我们选择以下选项
- PLATFORM_HAS_STDINT
- PLATFORM_HAS_DYNMEM
- FEATURE_MQTT_COMM_ENABLED
3.1 FEATURE_MQTT_DEFAULT_IMPL
3.2 FEATURE_MQTT_DIRECT - FEATURE_ATM_ENABLED
4.1 FEATURE_AT_TCP_ENABLED
由于不同模块AT指令的差异,我们这里不采用SDK自带的AT指令解析模块,所以在FEATURE_ATM_ENABLED下只选择FEATURE_AT_TCP_ENABLED选项。选择完毕后保存退出。然后双击打开extract.bat,便会在output文件夹中生成所需文件,将\output\eng下所有文件以及\output\examples\ mqtt_example_at.c(mqtt_example_at.c文件是MQTT客户端API的应用实例)拷贝至KEIL或者IAR中的STM32工程目录下,并添加进工程。而后我们需要修改\output\eng\wrappers\wrapper.c中的相关函数,来实现对接,需要修改的函数如下:
由于我们没有在MCU上运行OS所以其实以上函数并不需要,但SDK还是生成了上述函数实体,我们只需要将函数返回正确的值就可以,比如HAL_MutexCreate返回(void*)1,其他返回0。
以上函数是MCU与ESP-01S通过串口进行AT指令通信的主要实现函数。 - HAL_AT_CONN_Init
#define LINK_ID_MAX 5
typedef struct link_s
{
int fd;
} link_t;
static link_t g_link[LINK_ID_MAX];
int HAL_AT_CONN_Init(void)
{
int link;
memset(g_link, 0, sizeof(g_link));
for (link = 0; link < LINK_ID_MAX; link++)
{
g_link[link].fd = -1;
}
char cmd0[64] = {0};
char out0[64] = {0};
//选择模式1.Station 2.AP 3.AP+Station
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CWMODE=%d\r\n",3);
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",6000) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
//选择模式断开WIFI连接
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CWQAP\r\n");
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",6000) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
//选择模式连接WIFI,SSID:WIFISSID Password:WIFIPassword
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CWJAP=\"%s\",\"%s\"\r\n","WIFISSID","WIFIPassword");
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",6000) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
inited = true;
return 0;
}
- HAL_AT_CONN_Deinit
int HAL_AT_CONN_Deinit(void)
{
if (!inited)
{
return 0;
}
inited = false;
return 0;
}
- HAL_AT_CONN_Start
int HAL_AT_CONN_Start(at_conn_t *c)
{
char cmd0[128] = {0};
char out0[128] = {0};
//选择TCP连接模式为多连接
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CIPMUX=%d\r\n",1);
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",6000) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
int link_id;
for (link_id = 0; link_id < LINK_ID_MAX; link_id++)
{
if (g_link[link_id].fd >= 0)
{
continue;
}
else
{
g_link[link_id].fd = conn->fd;
break;
}
}
//建立TCP连接
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CIPSTART=%d,\"%s\",\"%s\",%d\r\n",link_id,"TCP",conn->addr,conn->r_port);
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",300) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
return (int)0;
}
- HAL_AT_CONN_Close
int HAL_AT_CONN_Close(int fd, int32_t remote_port)
{
int link_id;
link_id = fd_to_linkid(fd);
if(link_id < 0 || link_id >= LINK_ID_MAX)
{
at_conn_hal_debug("No connection found for fd (%d) in %s in %s", fd, __func__, __LINE__);
return -1;
}
char cmd0[64] = {0};
char out0[64] = {0};
//关闭TCP连接
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CIPCLOSE=%d\r\n",link_id);
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",6000) < 0)
{
//at_conn_hal_debug("%s %d failed", __func__, __LINE__);
//return -1;
}
g_link[link_id].fd = -1;
return 0;
}
- HAL_AT_CONN_Send
int HAL_AT_CONN_Send(int fd,
uint8_t *data,
uint32_t len,
char remote_ip[16],
int32_t remote_port,
int32_t timeout)
{
int link_id;
if (!data)
{
return -1;
}
link_id = fd_to_linkid(fd);
if (link_id < 0 || link_id >= LINK_ID_MAX)
{
at_conn_hal_debug("No connection found for fd (%d) in %s in %s", fd, __func__, __LINE__);
return -1;
}
char cmd0[64] = {0};
char out0[64] = {0};
//设置发送数据到连接link_id,数据长度为len
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CIPSEND=%d,%d\r\n",link_id,len);
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"\r\n","OK\r\n",300) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
//开始发送数据
if(at_send_data_wait_reply(data,len,out0,sizeof(out0),">","OK\r\n",6000) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
HAL_SleepMs(5000);
char Prefix[64] = {0};
HAL_Snprintf(Prefix, sizeof(Prefix), "+IPD,%d,",link_id);
//等待回复
if (at_yield(out0, sizeof(out0), Prefix, ":", AT_UART_TIMEOUT_MS) < 0)
{
return -1;
}
char mqtt_reply[256] = {0};
char c = NULL;
int num = atoi(out0);
for(int i=0;i < num;i++)
{
//抓取回复到mqtt_reply
if (at_getc(&c,timeout) != 0)
{
return -1;
}
mqtt_reply[i] = c;
}
struct at_conn_input param;
if (g_link[link_id].fd >= 0)
{
param.fd = g_link[link_id].fd;
param.data = mqtt_reply;
param.datalen = num;
param.remote_ip = NULL;
param.remote_port = 0;
//将接收到的数据上传给MQTT协议上层
if (IOT_ATM_Input(¶m) != 0)
{
at_conn_hal_debug(" %s socket %d get data len %d fail to post to at_conn, drop it\n",__func__, g_link[link_id].fd, len);
}
}
return (int)0; }
- HAL_AT_CONN_DomainToIp
int HAL_AT_CONN_DomainToIp(char *domain, char ip[16])
{
char cmd0[128] = {0};
char out0[128] = {0};
//根据域名获取IP地址
HAL_Snprintf(cmd0, sizeof(cmd0), "AT+CIPDOMAIN=\"%s\"\r\n",domain);
if(at_send_cmd_wait_reply(cmd0,sizeof(cmd0),out0,sizeof(out0),"+CIPDOMAIN:","\r\n",6000) < 0)
{
at_conn_hal_debug("%s %d failed", __func__, __LINE__);
return -1;
}
for(int i=0;i < 16;i++)
{
ip[i] = out0[i];
}
at_conn_hal_debug("IP->%s", ip);
return 0;
}
以上程序都是在测试阶段编写,比较随意,有不足之处希望批评指正。
设备端程序完善后我们可以在阿里飞燕平台的控制台上中的物联网平台上进行调试,首先我们修改自定义Topic中的get类为发布和订阅,因为在mqtt_example_at.c的调试例子中采用订阅发布这个Topic来实现。
最后我们就可以启动程序来进行飞燕平台的连接了。如果应用mqtt_example_at.c调试例子,我们需把mqtt_example_at.c中main函数修改一个名字如mqtt_example,以免与我们自己的main函数冲突,并在我们定义的main函数中调用mqtt_example。
后续还将讨论如何用微信小程序与阿里飞燕平台对接,实现微信->阿里飞燕平台->WIFI模组的整套连接方案。