本篇目标:能够驱动WIFI模块(ESP8266),并且能够连接物联网平台(ONENET)上传传感器数据与图像
材料准备:
- stm32f4标准工程:stm32f407自建标准工程(stm32f4标准工程)
- ONENET平台资料:包含WIFI模块的驱动和ONENET平台的连接例程,还有修改后的移植代码(onenet资料)
- 最终工程:移植优化后的最终stm32f407工程(onenet接入修改工程)
WIFI模块硬件连接与接口
这里WIFI模块使用的是 安信可ESP-12F ESP8266,附上一张模块的最小系统图:
ps:模块的REST是复位引脚,可接高电平,也可以接IO口,来控制模块的复位;模块的GPIO0是烧写固件引脚,可以不接,也可以接IO口用作烧写固件使用;模块的TXD和RXD就是串口接口引脚,交叉对应接到STM32F4的引脚上即可,默认波特率115200.
在串口正常通信的情况下,STM32F4与WIFI模块用标准AT命令进行通信,具体AT指令可以参考资料里面的AT指令集。
ONENET平台资料说明
打开材料准备中的ONENET平台资料,对里面的文件夹进行相关说明:
onenet资料\onenet开发板资料:这个文件夹中包含了ONENET平台的开发板例程,里面包含了很多连接的例程。
onenet资料\移植相关的文件:这个文件中包含了三部分:
(1)第一部分为onenet平台EDP连接示例代码;
(2)第二部分为自己修改的onenet平台EDP连接的模块化代码,因为原来的示例代码是stm32f103的,所以修改成stm32f407的代码,可以直接移植使用,可以自己对比第一部分来看有哪些修改的地方;
(3)第三部分为onenet平台EDP接入的协议文档,里面介绍了相关指令,数据帧的意义,可以参考着看。
onenet资料\移植相关的文件\dev:里面有两个文件夹,wifi文件夹中为驱动WIFI模块(ESP8266)的底层驱动代码;onenet为连接平台的相关应用代码。
向标准工程中添加驱动代码
将下载的 stm32f407标准工程 重命名为 stm32f407_iot ,然后找到工程,用keil打开,可以看到标准的工程,里面包含了PLL倍频、串口USART1重定向到printf、精确delay函数、LED灯闪烁。需要修改的地方包括:
- stm32f4xx.h文件139行的HSE_VALUE值,修改为自己板载的晶振大小。
- LED的IO初始化和宏定义需要根据自己的IO口进行修改。
接下来就是往工程里添加WIFI模块和ONENET平台的代码了:
- 将准备好的 onenet资料文件夹->移植相关的文件下的dev文件夹复制到工程文件夹 stm32f407_iot 下。
在keil中点击 建立wifi和onenet文件夹,并向里面添加刚才对应文件夹的所有文件,如图:
点击 在C/C++ Include Paths添加wifi文件夹和onenet文件夹的路径。
wifi文件下的 net_device.c 和 net_io.c 是WIFI模块(ESP8266)的底层驱动函数,需要修改的包括RESET引脚、MODE引脚、串口相关的代码。打开net_device.h 和 net_io.h,需要修改的部分都有注释标注出来了,如图:
net_device.h:
net_io.h:
- 在main.c中添加头文件 #include “net_device.h” ,在main函数中添加 NET_DEVICE_IO_Init();函数,如下所示:
int main(void)
{
/* stm32系统配置 */
Sys_Config();
/* WIFI模块IO初始化配置 */
NET_DEVICE_IO_Init();
while(1)
{
LED1_ON;
delay_ms(500);
LED1_OFF;
delay_ms(500);
}
}
编译通过即可,这样就做好初步的移植工作了。
连接路由器
做好了初步工作,就要让WIFI模块连接路由器,并向ONENET发送连接请求:
- 在main.c中添加头文件 #include “onenet.h” 和 #include “fault.h”
删除 while(1) 函数中的代码,并添加如下函数:
while(1)
{
if(oneNetInfo.netWork == 0)
{
/*********** 检测WIFI模块状态 ***********/
if(checkInfo.NET_DEVICE_OK == DEV_ERR)
{
if(!NET_DEVICE_Exist())
{
NET_DEVICE_GetWifiIP();
printf("NET Device :Ok\r\n");
checkInfo.NET_DEVICE_OK = DEV_OK;
}
else
{
printf("NET Device :Error\r\n");
}
}
}
}
- 定位net_device.c第374行中的 WIFI_NAME 和 WIFI_PASSWORD ,这是你需要连接路由器的名称和密码,记得在 net_device.h 中修改宏定义。
- 编译成功后,烧写进stm32f407,在串口助手观察打印信息,如图成功:
ps:串口打印了WIFI的状态,包括IP地址,如果笔记本连的是同一个路由器,那么他们肯定在同一个网段上,用指令应该可以ping通:
连接ONENET平台
在连接ONENET平台之前,需要一些步骤:
- 需要去官网(中移物联网)注册一个帐号。
- 登录开发者中心
- 点击创建产品
- 填写基本信息,最后的设备接入协议选为“EDP”即可,确认。
- 立即添加设备,任意添加信息,接入设备即可。
- 右边部分点击产品概况,找到APIKey,记下来:
- 右边部分点击设备管理,找到设备ID,记下来:
好了,现在可以编写代码连接ONENET平台了!
- 向 while(1) 函数中添加登录平台代码,如下:
while(1)
{
/************** 平台登录初始化相关 **************/
if(oneNetInfo.netWork == 0)
{
/*********** 登录onenet平台 ***********/
if(!oneNetInfo.netWork && (checkInfo.NET_DEVICE_OK == DEV_OK))
{
if(!NET_DEVICE_Init(oneNetInfo.protocol, oneNetInfo.ip, oneNetInfo.port))
{
OneNet_DevLink(oneNetInfo.devID, oneNetInfo.apiKey);
if(oneNetInfo.netWork)
{
printf("Login in Onenet Succeed.\r\n");
}
else
{
printf("Login in Onenet Failed.\r\n");
}
}
}
/*********** 检测WIFI模块状态 ***********/
if(checkInfo.NET_DEVICE_OK == DEV_ERR)
{
if(!NET_DEVICE_Exist())
{
printf("NET Device :Ok\r\n");
checkInfo.NET_DEVICE_OK = DEV_OK;
NET_DEVICE_GetWifiIP();
}
else
{
printf("NET Device :Error\r\n");
}
}
}
}
- 修改结构体 oneNetInfo 的值,里面包含了devID、apiKey,将上面记下来的设备ID->对应devID,APIKey->对应apiKey 替换成自己申请的值:
ONETNET_INFO oneNetInfo = {"25739329", "iCljma3PqyPYAqNdIHpxtS79d60=",
"183.230.40.39", "876",
1,
NULL, 0, 0, 0, 1, 0};
- 编译,烧写成功后,串口将会打印信息:
ps:这时候登录ONENET个人开发者中心的设备管理,会发现设备名称旁边的小灰点变绿了,说明WIFI已经连接上了平台:
- - - - - - - - - - - - - - >
向平台发送测试数据
当WIFI已经可以连接上ONENET平台的时候,就可以添加代码测试向平台发送数据了,数据包括传感器点数据、图片数据(格式为图片格式,如jpg、bmp等)。
- 向 while(1) 添加数据发送的代码:
while(1)
{
/************** 打包数据发送平台 **************/
if(oneNetInfo.netWork == 1)
{
switch(oneNetInfo.sendData)
{
case SEND_TYPE_DATA:
OneNet_SendData(FORMAT_TYPE3, NULL, NULL, dataStream, dataStreamCnt);//上传数据到平台
printf("\r\nOnenet Data Ready.\r\n");
break;
case SEND_TYPE_HEART:
OneNet_SendData_Heart(); //心跳检测
printf("\r\nOnenet Heart Ready.\r\n");
break;
case SEND_TYPE_PICTURE:
oneNetInfo.sendData = OneNet_SendData(FORMAT_TYPE2, NULL, NULL, NULL, 0);
printf("\r\nOnenet Picture Ready.\r\n");
break;
default:
break;
}
if (oneNetInfo.sendData != SEND_TYPE_OK)
{
if(NET_DEVICE_CheckListHead())
{
//printf("Wifi Send Data Start.\r\n");
oneNetInfo.sendData = NET_DEVICE_SendData(NET_DEVICE_GetListHeadBuf(),
NET_DEVICE_GetListHeadLen());
NET_DEVICE_DeleteDataSendList();
//printf("Wifi Send Data Ok.\r\n\r\n");
}
}
}
/************** 平台登录初始化相关 **************/
//....
//此段代码与上部分相同,这里不展示
//....
}
ps:可以发现数据结构体dataStream未定义,所以在头文件后面定义一个这个变量,并且建立一个测试变量,添加定义代码:
u16 IOT_DATA_TEST = 0;
DATA_STREAM dataStream[] = {
{"IOT DATA TEST", &IOT_DATA_TEST, TYPE_USHORT, 1},
};
unsigned char dataStreamCnt = sizeof(dataStream) / sizeof(dataStream[0]);
while(1)
{
IOT_DATA_TEST++; //在while(1)中添加变量,让其能够发生变化,方便观察
/************** 打包数据发送平台 **************/
//下面代码如上
//......
}
- 这时候编译已经可以通过了,但是还是不能发送数据,观察一下代码,需要改变 oneNetInfo.sendData 这个数据请求变量,这个值分别有 SEND_TYPE_OK(代表发送成功,等待)、SEND_TYPE_DATA(代表发送传感器数据点)、SEND_TYPE_HEART(代表发送心跳)、SEND_TYPE_PICTURE(代表发送图片)。
在这里使用一个1s的定时器来分段发送不同数据,规划一下,每180s为周期:1s的时候发送传感器数据点;每25s发送心跳保持;90s的时候发送图片数据。 - 在 sys_cfg.c 开始添加定时器相关代码:
(1)向 NVIC_Configuration 添加如下代码:
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
(2)添加定时器初始化代码,并在Sys_Config 函数中添加定时器初始化函数,如下:
/***
* 函数名称 : Timer_Configuration();
*
* 函数描述 : 定时器初始化配置;
*
* 传递值 : 无;
*
* 返回值 : 无;
*
**/
static void Timer_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_DeInit(TIM3);
TIM_TimeBaseStructure.TIM_Period = 10000;
TIM_TimeBaseStructure.TIM_Prescaler = (84000000/10000 - 1);
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM3, ENABLE);
} //新添加
/***
* 函数名称 : Sys_Config();
*
* 函数描述 : 系统初始化配置;
*
* 传递值 : 无;
*
* 返回值 : 无;
*
**/
void Sys_Config(void)
{
RCC_Configuration();
GPIO_Configuration();
NVIC_Configuration();
USART_Configuration();
Delay_Configuration();
Timer_Configuration(); //新添加
printf("\r\n\r\n***********STM32 System Config!***********\r\n\r\n");
}
(3)向 net_io.c 中添加定时器中断相关函数,如下:
/************** 额外添加 **************/
#include "sys_cfg.h"
#define LED1_TOGGLE GPIO_ToggleBits(GPIOE,GPIO_Pin_0);
#define LED2_TOGGLE GPIO_ToggleBits(GPIOE,GPIO_Pin_1);
#define NET_TIME_DELAY 180 //180s数据间隔
u16 net_send_time = 0;
void TIM3_IRQHandler(void)
{
//清中断标识
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
//---------------- 中断处理 ------------------//
if(oneNetInfo.netWork == 1)
{
net_send_time++;
if (net_send_time == 1)
{
oneNetInfo.sendData = SEND_TYPE_DATA;
LED2_TOGGLE;
}
else if ((net_send_time % 25) == 0) //每25s发送心跳请求
{
oneNetInfo.sendData = SEND_TYPE_HEART;
LED2_TOGGLE;
}
else if (net_send_time == NET_TIME_DELAY/2)
{
oneNetInfo.sendData = SEND_TYPE_PICTURE;
LED2_TOGGLE;
}
else if (net_send_time == NET_TIME_DELAY)
{
net_send_time = 0;
}
}
LED1_TOGGLE;
OneNet_Check_Heart();
}
编译烧写,查看串口打印数据:
登录ONENET平台->开发者中心->自己创建的产品->设备管理->点进自己的设备里面->数据展示,可以发现有了一个新的数据点:
等待一会后,会发现另一个数据点:
ps:这时候基本就可以上传自己想要的传感器和图片数据了。
优化
在此之前,已经可以完成相关功能了,但是还是需要进行相关的优化,包括断网重连,故障优化等来完善整个系统,提高稳定性,当然这不是必要的。
- 向while(1)中添加部分代码,如下:
while(1)
{
IOT_DATA_TEST ++;
/************** 平台命令处理**************/
if(oneNetInfo.cmd_ptr)
{
OneNet_RevPro(oneNetInfo.cmd_ptr);
oneNetInfo.cmd_ptr = NULL;
}
/************** 错误处理函数 **************/
if(faultType != FAULT_NONE) //如果错误标志被设置
{
printf("WARN: Fault Process\r\n");
Fault_Process(); //进入错误处理函数
}
/************** 打包数据发送平台 **************/
//下面代码如上
//....
}
- 定位onenet.c第210行,在后面加上:
OneNet_SendData_Picture(devid, Array, sizeof(Array));
status = SEND_TYPE_OK; //新添加
- 定位onenet.c第234行,添加宏定义:
#define PKT_SIZE 1024
#define PKT_NAME "IOT_PIC_TEST" //新添加,可定义的图片数据名称
- 定位onenet.c第239行,添加修改部分代码:
char type_bin_head[25]; //新添加
sprintf(type_bin_head, "{\"ds_id\":\"%s\"}", PKT_NAME); //新修改
- 定位onenet.c第404行,添加部分代码:
if(strstr((char *)dataPtr, "SEND OK") != NULL)
{
netDeviceInfo.send_ok = 1;
}
else if (strstr((char *)dataPtr, "WIFI DISCONNECT") != NULL ) //新添加else if 分支
{
printf("WARN: WIFI断开,准备重连\r\n");
checkInfo.NET_DEVICE_OK = DEV_ERR;
oneNetInfo.netWork = 0;
NET_DEVICE_ReConfig(0);
}
else if(strstr((char *)dataPtr, "CLOSE") != NULL && netDeviceInfo.netWork)
{
printf("WARN: 连接断开,准备重连\r\n");
oneNetInfo.netWork = 0;
NET_DEVICE_ReConfig(0);
}
else
NET_DEVICE_CmdHandle((char *)dataPtr);
整体代码简单解析
上面完成了ONENET应用代码的移植和修改,能够上传数据点和图片点,下面有简单的流程图来解析一下发送的过程,和一些重要的函数:
ps:流程图只展示了大致的过程,更加详细的,比如协议等,需要对照着文档和代码,自己研究一下。
总结:上面的利用stm32f407+WIFI模块(ESP8266)接入ONENET,只使用了最基本的功能,还有很多其他协议方式也可以接入,代码也可能有些未知bug可以优化,但最终我们可以通过web或者手机端的onenet_app进行远程查看上传的数据。
这样就可以为后期上传一些传感器数据做好了准备,之后会写关于上传温湿度数据,以及摄像头图像数据,上面文章有不理解或者错误的地方请谅解,互相学习,共同进步,共勉!