概述
本文介绍在机智云物联网云平台上实现基于STM32的WiFi通信功能,即通过WiFi发送指令,使STM32能够完成特定的操作。具体介绍如何通过WiFi控制LED灯的亮灭,更多复杂功能可自行拓展。
这里将使用一个WiFi模块来连接单片机,在WiFi模块中烧写固件,同时在单片机中移植所需的代码,最终实现所需的功能。
硬件准备
- 任意一种STM32单片机
- 一个WiFi模块:推荐使用ESP-07S、ESP-12S或者ESP-12F,这是机智云平台开发文档中推荐的安信可科技开发的WiFi模块。由于这三种WiFi模块在网上售卖的都是无引脚的,所以需要在同一家网店额外购买引脚并自行焊接。当然了,机智云平台的开发文档中还说明了其他厂商WiFi模块的使用,这里不作详细介绍,只介绍安信可ESP8266系列。按理来说除了ESP-07S、ESP-12S或者ESP-12F,其他WiFi模块也可以,但本人没有试过,如果使用其他安信可ESP8266系列的WiFi模块,注意其Flash大小,只有8Mbit、16Mbit和32Mbit大小的才行,常见的ESP-01S应该是8Mbit的。
软件准备
- keil5 MDK
- 机智云产品调试APP,用于在手机上发送指令来控制STM32(在“开发者中心”-“下载中心”-“机智云产品调试APP”,扫码下载到手机上)
详细步骤
一、创建产品
- 搜索“机智云”,进入官网。
- 在官网点击右上角“开发者中心”,注册,登录。
- 点击创建智能产品。
- 根据需要选择方案。这里举例使用“照明”中的“自定义方案”,点击图中的“灯”。
- 填写产品名称,数据传输方式改为“定长”,点击“创建”。
- 点击“去编辑”以创建数据点。
- 编辑相关信息,由于只需控制LED灯的亮灭,一共两种状态,故选择数据类型为“布尔值”,届时,STM32将收到LED灯的开或者关信息。如需其他功能,可选择枚举(如需要控制灯变为红、绿、蓝等颜色)、数值(如需要给某个变量赋值)、拓展(自定义数据类型)。读写类型的“可写”意味着可以在手机APP上通过WiFi改变变量,“只读”意味着只能在手机APP上显示变量,不能更改。
二、烧录固件
- 说明:将机智云提供的GAgent固件烧录到WiFi模块中,目的是使WiFi模块能够连接WiFi并接收到网络指令。
- 下载烧录工具:在“开发者中心”-“文档中心”-左侧“机智云平台概述”-“设备接入”-“GAgent通讯模组使用教程”-“ESP8266串口烧写说明”,下载模组资料,如图:
- 下载完成后打开得到如下文件,其中,“规格说明书”为三种WiFi模块的说明文档;“模组固件”为要烧写进WiFi模块的固件,提供给Flash为32Mbit的WiFi模块的;“模组日志解码映射文件”另作他用,暂不需要;“烧录工具”即为烧录所需工具。
- 如所用WiFi模块不是SPI Flash为32Mbit的,则需另行下载模组固件,在“开发者中心”-“下载中心”,一般进入“下载中心”即为GAgent固件下载页面,下载如图所示固件包:
- 在之前打开的“ESP8266串口烧写说明”中即有烧写步骤,可按其中步骤烧写,这里不再另行说明,需要注意的是,如果使用的不是SPI Flash为32Mbit的WiFi模块,需要更改下图中的选项;另外,WiFi模组内部电路有的引脚已经默认上拉,不需要连接,可在“规格说明书”中查看,不确定的也可以直接按步骤说明全部连接。
三、移植代码
(1)文件获取
- 说明:STM32是通过串口和WiFi模块进行通信的,为了STM32能够接收到WiFi模块传递的指令,需要移植所需代码。
- 在“开发者中心”,进入所创建的智能产品中,选择“独立MCU方案”,点击左侧的“MCU开发”,选择硬件平台为“其他平台”,这是为了能将生成的代码包移植到任意一种硬件平台上。填入“Product Secret”,在左上角有显示。然后点击生成“代码包”。生成完毕后点击下载。
- 下载完成后打开得到如下图所示文件:最下面的PDF文档为移植步骤说明,由于其中的步骤并非很详细完整,在这里另外说明移植步骤。
(2)文件转移
- 接下来,开始移植代码。首先,将上图文件中的Gizwits和Utils文件复制粘贴到STM32工程文件中,如下图所示:
- 打开keil5软件,将这两个文件夹添加为STM32工程文件的两个组,如下图中的组Gizwits和组Utils:
- 之后,将这两个组的文件路径添加到头文件路径中,如下图所示:
- 打开上述第3点所示的文件夹中的User文件,有一个main.c文件,打开,复制如下6行代码到STM32的main.c文件中:
- 复制粘贴时按照对应位置放置,如下图所示:
(3)完善代码
①实现串口驱动
- MCU方案需要用户实现一个串口,用于设备MCU与WIFI模组之间的数据通信,同时,需要实现串口接收中断服务函数接口,该接口调用gizPutData()函数实现串口数据的接收并且写入协议层数据缓冲区。所以现在需要配置一个串口并编写其中断服务函数,这里使用串口USART1。
#include "stm32f10x.h" // Device header #include "gizwits_product.h" void Serial1_Init(void) { RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA |RCC_APB2Periph_USART1,ENABLE ); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; GPIO_Init(GPIOA,&GPIO_InitStructure); USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate=9600; USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx ; USART_InitStructure.USART_Parity=USART_Parity_No; USART_InitStructure.USART_StopBits=USART_StopBits_1; USART_InitStructure.USART_WordLength=USART_WordLength_8b; USART_Init(USART1 ,&USART_InitStructure); USART_ITConfig (USART1 ,USART_IT_RXNE ,ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_Init(&NVIC_InitStructure); USART_Cmd (USART1,ENABLE ); } void USART1_IRQHandler(void) //USART1中断服务函数 { uint8_t value = 0; if(USART_GetITStatus (USART1,USART_IT_RXNE )==SET) { value=USART_ReceiveData (USART1); gizPutData(&value, 1); //用于实现串口数据的接收并且写入协议层数据缓冲区 USART_ClearITPendingBit (USART1,USART_IT_RXNE ); } }
在上述代码中,要包含头文件#include "gizwits_product.h",因为下面的串口中断服务函数用到的函数gizPutData()在该文件中声明。
-
下面是这个C文件对应的头文件:
#ifndef _SERIAL1_H #define _SERIAL1_H void Serial1_Init(void); #endif
- 完善串口写函数uartWrite(),用于发送数据报文到WiFi模组,该函数位于gizwits_product.c文件中,在该文件中找到该函数,添加图中框出的四行代码进去。
- 同时,在该文件中包含外设头文件#include "stm32f10x.h",因为所添加的几行代码涉及STM32外设USART。
②实现定时器
- 协议层使用到了一个系统时间,该事件单位为毫秒,所以要求用户实现一个毫秒定时器,并且实现中断服务函数,该函数调用gizTimerMs()实现协议层系统时间的维护。所以现在要配置一个定时器及其中断服务函数,这里用到定时器TIM4。
#include "stm32f10x.h" // Device header #include "gizwits_product.h" void TIM4_Init(void) { RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM4 ,ENABLE ); TIM_InternalClockConfig (TIM4); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ; TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1 ; TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInitStructure.TIM_Period=72-1; TIM_TimeBaseInitStructure.TIM_Prescaler=1000-1; TIM_TimeBaseInit (TIM4,&TIM_TimeBaseInitStructure); //每1ms进入一次中断 TIM_ClearFlag (TIM4,TIM_FLAG_Update ); TIM_Cmd (TIM4,ENABLE ); TIM_ITConfig (TIM4,TIM_IT_Update ,ENABLE ); NVIC_PriorityGroupConfig (NVIC_PriorityGroup_2 ); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn ; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; NVIC_Init(&NVIC_InitStructure); } void TIM4_IRQHandler(void) //定时器TIM4中断服务函数 { if(TIM_GetITStatus(TIM4, TIM_IT_Update)==1) { gizTimerMs(); //实现协议层系统时间的维护 TIM_ClearITPendingBit(TIM4, TIM_IT_Update); } }
其中,头文件包含#include "gizwits_product.h",因为中断函数中使用到的gizTimerMs()是在文件gizwits_product.c中定义的。但是这个函数没有在gizwits_product.h文件中声明,所以需要在该文件中声明一下该函数:
- 该C文件的头文件为:
#ifndef _TIM4_H #define _TIM4_H void TIM4_Init(void); #endif
③实现芯片复位函数
- 根据串口协议文档规定,模组可以发送命令复位设备MCU,所以用户需要实现mcuRestart()接口完成设备的复位。
- 在gizwits_product.c文件中找到void mucRestart(void)函数,将下面两行代码移植到其函数体内:
__set_FAULTMASK(1); NVIC_SystemReset();
具体如图:
④实现串口打印驱动
- 如果用户需要打印日志调试信息,要求用户实现printf函数。协议层将用GIZWITS_LOG宏替代printf,进行相关信息的打印。如果用户不使用日志调试,那么需要将协议层相关日志打印部分的代码屏蔽掉方可运行。所以现在需要配置一个串口并实现printf重定向,这里使用串口USART3。
#include "stm32f10x.h" // Device header #include <stdio.h> void Serial3_Init(void) { RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB ,ENABLE ); RCC_APB1PeriphClockCmd (RCC_APB1Periph_USART3,ENABLE ); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; GPIO_Init(GPIOB,&GPIO_InitStructure); //配置发送引脚PB10 GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11; GPIO_Init(GPIOB,&GPIO_InitStructure); //配置接收引脚PB11 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx ; USART_InitStructure.USART_BaudRate=9600; USART_InitStructure.USART_WordLength=USART_WordLength_8b ; USART_InitStructure.USART_Parity=USART_Parity_No ; USART_InitStructure.USART_StopBits=USART_StopBits_1 ; USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None ; USART_Init (USART3,&USART_InitStructure); USART_Cmd (USART3,ENABLE ); } void Serial_SendByte3(uint8_t Byte) //通过USART3发送一个字节 { USART_SendData (USART3 ,Byte); while (USART_GetFlagStatus (USART3 ,USART_FLAG_TXE )==RESET ); } int fputc(int ch,FILE *f) //printf重定向,需要#include <stdio.h>,作用是打印到串口 { Serial_SendByte3(ch); return ch; }
这里的printf重定向的方法需要使用精简c库,点击“魔术棒”按钮(Options for Target),勾选Use McroLIB选项。
- 该C文件的头文件为:
#ifndef _SERIAL3_H #define _SERIAL3_H #include <stdio.h> void Serial3_Init(void); #endif
⑤实现配置入网
-
模组支持SoftAp和AirLink两种方式配置入网,相应接口为gizwitsSetMode(),官方指导书建议使用按键方式选择配网方式,这里不详细介绍按键选择方式,只用最简单的单一配网选择方式。
/*配置入网*/ gizwitsSetMode(WIFI_SOFTAP_MODE); //使用SoftAP方式配置入网 // gizwitsSetMode(WIFI_AIRLINK_MODE); //使用AirLink方式配置入网 // gizwitsSetMode(WIFI_RESET_MODE); //复位
上述代码移植到main.c文件的主函数内、while循环外,如图:
-
用户可以自行选用一种配网方式,如果感兴趣,可以根据官方指导书使用按键选择方式,或者自行设计代码。
⑥实现下行动作执行
-
这一步需要完善gizwits_product.c 文件中的gizwitsEventProcess()函数,目的是根据指令完成相应的操作。在前面的步骤中,我们创建了一个数据点Light_On_Off,它是布尔类型的,它只有两个值1或0,现在要做的就是编写代码,完成功能:当Light_On_Off为1时做什么,当Light_On_Off为0时做什么。其他类型的数据点类似。
-
现在,配置一个控制LED开关的模块,其c文件如下:
#include "stm32f10x.h" // Device header void LED_Init(void) { RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA ,ENABLE ); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode =GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_SetBits (GPIOA ,GPIO_Pin_1); } void LED_ON(void ) { GPIO_ResetBits(GPIOA ,GPIO_Pin_1); } void LED_OFF(void ) { GPIO_SetBits (GPIOA,GPIO_Pin_1); }
其头文件如下:
#ifndef _LED_H #define _LED_H void LED_Init(void); void LED_ON(void ); void LED_OFF(void ); #endif
-
然后,在gizwits_product.c文件中找到gizwitsEventProcess()函数,在相应位置编写所需代码,如图:
-
同时,需要在main.c文件的主函数中声明全局变量LED_Switch,用于控制LED的亮灭。
-
然后,在gizwits_product.c文件中也声明这个变量。
-
在主函数的while循环中编写根据变量LED_Switch控制LED亮灭的程序。一些头文件的包含什么的就不多说了,包括前面的串口和定时器的头文件,也要包含到main.c文件中,还要将对应的初始化函数在主函数中写出来。
⑦实现上行数据采集
- 这里需要将数据采集起来,即根据LED的亮灭更改数据点的值。现在要做的就是完善userHandle()函数,即更改currentDataPoint(数据点)结构体中的变量的值。
- 在gizwits_product.c文件中找到userHandle()函数的定义位置,编写如下代码:
⑧初始化
- 这里需要给数据点中的变量赋一个初始值。
- 在gizwits_product.c文件中找到userInit()函数的定义位置,编写如下代码:
⑨主函数
最后的主函数如下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "gizwits_product.h"
#include "common.h"
#include "Serial1.h"
#include "Serial3.h"
#include "TIM4.h"
#include "LED.h"
uint8_t LED_Switch=0;
int main(void)
{
userInit(); //数据点初始化,需用户编写
gizwitsInit(); //完成Gizwits协议相关初始化
Serial1_Init();
Serial3_Init();
TIM4_Init();
LED_Init();
/*配置入网*/
gizwitsSetMode(WIFI_SOFTAP_MODE); //使用SoftAP方式配置入网
// gizwitsSetMode(WIFI_AIRLINK_MODE); //使用AirLink方式配置入网
// gizwitsSetMode(WIFI_RESET_MODE); //复位
while(1)
{
if(LED_Switch==1)
{
LED_ON();
}
else
{
LED_OFF();
}
userHandle(); //用于数据采集,需用户编写
gizwitsHandle((dataPoint_t *)¤tDataPoint);//用于数据处理和上传到机智云平台
}
}
文件有:
四、手机APP测试
- 手机APP可以使用前面要求下载的机智云产品调试APP,也可以另外为这个产品生成一个APP。这里两种都介绍一下。
- 首先,可以打开机智云产品调试APP,点击右上角的“+”号,选择前面的代码中自己指定的配网方式,然后一步步进行。
- 或者,还可以为这个产品创建一个APP,在“开发者中心”,点击“创建应用”(当然,也可以使用微信小程序,这里不再介绍),填写信息,然后点击确认。
- 下面,点开该应用,点击上方的“构建应用”。
- 然后,点击左侧的“构建应用”。
- 之后,点击“构建测试版”。
- 之后就是等待构建完成,然后下载到手机上。
- 最后,回到开发者中心,点开左侧我们创建的产品,然后点击上方的“控制页面”,点击“关联产品”,然后,就可以在已经下载的手机APP上完成配网等操作了,之后就可以在手机上完成通过WiFi控制STM32的功能了。
- 需要注意的是,无论使用哪个APP、哪种配网方式来配网,都需要连接2.4GHz的WiFi,如果连接的是5G的WiFi,那么配网将不成功,注意查看自己所连WiFi是不是5G的。如果没有2.4GHz的WiFi,可以用另一个手机打开热点,在热点设置中将热点设为2.4GHz,然后就可以使用该热点来操作了。