STM32F429使用HAL库把温度上传到onenet

本项目使用的开发板是正点原子的阿波罗STM32F429开发板,是基于HAL库编写的接入中国移动物联网平台——onenet的项目,用到的通讯模块是esp8266-12F。下面是接入平台的一些步骤及讲解,如果有不懂的地方,大家一起讨论(因为我也是初学者,很多地方也理解得不够透彻,流下了知识贫乏的眼泪)。

实验前准备工作:

  • STM32F429开发板
  • ATK-esp8266-12F WiFi模块
  • DS18B20数字温度传感器
  • 中国移动onenet开发者产品
  • USB转TTL
  • 杜邦线

首先贴出一些我在本次项目所参考过的一些网址:
https://v.qq.com/x/page/i0814q78no3.html //视频教程地址
https://open.iot.10086.cn/bbs/thread-22449-1-1.html
https://open.iot.10086.cn/bbs/forum.php?mod=viewthread&tid=408&highlight=stm32F4
https://open.iot.10086.cn/develop/global/product/#/device/list?pid=217851
https://www.cnblogs.com/luxiaoguogege/p/10136996.html
https://blog.csdn.net/q361750389/article/details/79362565
https://open.iot.10086.cn/bbs/thread-35887-1-1.html
https://blog.csdn.net/qq_38410730/article/details/86538288 //esp8266与stm32通信
https://www.cnblogs.com/kinging/p/5865649.html //onenet详细介绍

感谢这些前辈的经验以及总结,让我少走了很多弯路。

本次内容包含两部分:

下面开始讲解第一部分的内容

  1. 固件烧写
  2. 写入AT指令
  3. 网络调试
  4. 创建产品信息
  5. 虚拟调试

固件烧写
一般初次使用的esp8266-12F WIFI模块默认烧写了固件,那怎么判断需不需要烧写呢?很简单,使用USB-TTL在串口调试助手上输入AT指令就可以了,若是返回OK等信息,就说明可以直接用。我的WiFi模块快由于以前烧写过机智云的固件,因此这次重新刷了一个固件,下面把用到的几个软件提供给大家:
安信可固件(1wud)
烧写工具(rgme)
串口调试助手(hxo4)
网络调试助手(y9t4)

esp8266-12F与USB-TTL连接线如下:
VCC ----> VCC
TX ----> RX
RX ----> TX
GND ----> GND

进入烧写窗口:
在这里插入图片描述
写入AT指令:
在这里插入图片描述
依次配置以下指令:
AT
AT+CWMODE=3 //STA+AP模式
AT+RST
AT+CIFSR
AT+CWJAP=“DC”,“14785269” //这个是我的手机热点,当然要提前打开热点才能连上

这里说明一下:
ATK-ESP8266 WIFI 模块,包含:串口无线 AP(COM-AP)、串口无线 STA(COM-STA)和串口无线 AP+STA(COM-AP+STA)这 3 个模式,每个模式又包含 TCP 服务器、 TCP客户端和 UDP 这 3 个子模式。

  1. 串口无线 WIFI(COM-AP)模式(AT+CWMODE=2),模块作为无线 WIFI 热点,允许其他 WIFI 设备连接到本模块,实现串口与其他设备之间的无线(WIFI)数据转换互传。该模式下,根据应用场景的不同,可以设置 3 个子模式: TCP 服务器、 TCP 客户端, UDP。
  2. 串口无线 STA(COM-STA)模式(AT+CWMODE=1),模块作为无线 WIFI STA, 用于连接到无线网络,实现串口与其他设备之间的无线(WIFI) 数据转换互传。该模式下,根据应用场景的不同,可以设置 3 个子模式: TCP 服务器、 TCP 客户端, UDP。
  3. 串口无线 AP+STA(COM-AP+STA)模式(AT+CWMODE=3),模块既作无线 WIFI AP,又作无线 STA,其他 WIFI 设备可以连接到该模块,模块也可以连接到其他无线网络,实现串口与其他设备之间的无线(WIFI)数据转换互传。该模式下,根据应用场景的不同,可以设置 9 个子模式:(TCP 服务器、 TCP 客户端, UDP) ||(TCP 服务器、 TCP 客户端, UDP)。
  4. 透传模式:透传就是指不需要关心WIFI协议是如何实现的。所需要做的就是A通过串口发数据,B通过串口收数据,整个过程中A串口和B串口就像是用导线直接连接起来了一样。则对于开发人员来看,就是完全透明的。退出透传模式:发送数据"+++",注意:此时“+++”后面,不接“发送新行”。

网络调试

首先找到自己电脑的IP地址:
在这里插入图片描述
然后配置1、2、3(要让电脑和WiFi模块连在同一个热点)
在这里插入图片描述
第一次配置IP值时出错,圈圈里多了一个空格,书写一定要严谨,最好自己打IP。可以看到串口调试和网络调试实现了互相通信。网络调试助手可以一直发数据给串口调试助手,但是串口调试助手需要发数据就的每次输入“AT+CIPSEND=4”,出现OK后才可以发数据
在这里插入图片描述
在onenet上创建产品信息:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面的这些随便填一下就行了,主要是要获得产品ID和API-KEY。

最后下载虚拟调试软件:虚拟调试工具(uf9n)

进行虚拟调试:
在这里插入图片描述
按步骤完成1、2、3:
在这里插入图片描述
可以看到在开发者中心处设备已经接通了:
在这里插入图片描述
以上,虚拟调试的部分完成了,前期的调试准备工作也完成了,第一部分结束。

下面开始讲解第二部分的内容:(代码部分)

  1. 连接图
  2. esp8266程序的编写
  3. onenet程序的编写
  4. DS18B20程序的编写
  5. 主程序逻辑简单说明

连接图
简单画了个esp8266WiFi模块、stm32和onenet连接的图:
在这里插入图片描述
软件部分要实现的就是驱动esp8266-12F模块和接入EDP协议,下面就是有关程序的一些说明。

esp8266程序的编写

ATK-ESP8266是ALIENTEK推出的一款高性能的UART-WiFi(串口-无线)模块,ATK-ESP8266板载ai-thinker公司的ESP8266模块,该模块通过FCC, CE认证,可直接
用于产品出口欧美地区。
ATK-ESP8266模块采用串口(LVTTL)与MCU(或其他串口设备)通信,内置TCP/IP
协议栈,能够实现串口与WIFI之间的转换。通过ATK-ESP8266模块,传统的串口设备只是需要简单的串口配置,即可通过网络(WIFI)传输自己的数据。
ATK-ESP8266模块支持LVTTL串口,兼容3.3V和5V单片机系统,可以很方便的与你
的产品进行连接。模块支持串口转WIFI STA、串口转AP和WIFI STA+WIFI AP的模式,
从而快速构建串口-WIFI数据传输方案,方便你的设备使用互联网传输数据。

在这里插入图片描述
模块与开发板连接如下:
在这里插入图片描述
注意:记得检查开发板 P9 的跳线帽, 必须短接: PB11(RX) 和 GBC_TX 以及 PB10(TX) 和 GBC_RX

首先初始化esp8266,用于配置AT指令,使WiFi模块能够正常接网,也就是在代码实现前面第一部分写入AT指令操作:(“AT+CIPSTART=“TCP”,“183.230.40.39”,876\r\n”)

void ESP8266_Init(void)
{
	ESP8266_Clear();	
	UsartPrintf(USART_DEBUG, "1. AT\r\n");
	while(ESP8266_SendCmd("AT\r\n", "OK"))
		Delay_ms(500);
	Usart_Onenet("AT+RST\r\n");
		Delay_ms(500);
	UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
	while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
		Delay_ms(500);	
	UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
	while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
		Delay_ms(500);
	UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "OK"))//wifi名称以及密码,在程序开头已定义
		Delay_ms(500);
	UsartPrintf(USART_DEBUG, "5. CIPSTART\r\n");
	while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))//服务器地址,在程序开头已定义
		Delay_ms(500);	
	UsartPrintf(USART_DEBUG, "6. ESP8266 Init OK\r\n");
}

上面程序中用到了一个串口发送命令函数,该函数定义如下:

_Bool ESP8266_SendCmd(char *cmd, char *res)
{	
	unsigned short int timeOut = 1000;
	Usart_Onenet((char *)cmd);	
	while(timeOut--)
	{
		if(ESP8266_WaitRecive() == REV_OK)							//如果收到数据
		{
            //strstr函数功能:用来检索子串在字符中首次出现的位置,原型:char *strstr(char *str,char *substr);参数说明:str为要检索的字符串
           //substr为要检索的子串;返回值:返回字符串str中第一次出现子串substr的地址,如果没有检索到子串,则返回NULL
			if(strstr((const char *)esp8266_buf, res) != NULL)		//如果从esp8266_buf[]中检索到关键词
			{
				ESP8266_Clear();									//清空缓存				
				return 0;
			}
		}
		
		Delay_ms(10);
	}	
	return 1;

接下来还需要定义一个发送数据函数,用于后面上传数据到onenet平台:

void ESP8266_SendData(unsigned char *data, unsigned short len)
{

	char cmdBuf[32];
	
	ESP8266_Clear();								//清空接收缓存
	sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len);		//发送命令
	if(!ESP8266_SendCmd(cmdBuf, ">"))				//收到‘>’时可以发送数据
	{
		Usart_SendString(USART3, data, len);		//发送设备连接请求数据
	}
}

onenet程序的编写
接下来就是需要建立与平台的连接,这个函数与平台封装的函数基本类似:

_Bool OneNet_DevLink(void)
{	
	EDP_PACKET_STRUCTURE edpPacket = {NULL, 0, 0, 0};				//协议包
	unsigned char *dataPtr;	
	unsigned char status = 1;	
	UsartPrintf(USART_DEBUG, "OneNet_DevLink\r\n"
                "DEVID: %s,     APIKEY: %s\r\n"
                ,DEVID, APIKEY);

	if(EDP_PacketConnect1(DEVID, APIKEY, 256, &edpPacket) == 0)		//根据devid 和 apikey封装协议包
	{		
		ESP8266_SendData(edpPacket._data, edpPacket._len);			//上传平台		
		dataPtr = ESP8266_GetIPD(250);								//等待平台响应
		if(dataPtr != NULL)
		{
			if(EDP_UnPacketRecv(dataPtr) == CONNRESP)
			{
				switch(EDP_UnPacketConnectRsp(dataPtr))
				{
					case 0:UsartPrintf(USART_DEBUG, "Tips:	连接成功\r\n");status = 0;break;			
					case 1:UsartPrintf(USART_DEBUG, "WARN:	连接失败:协议错误\r\n");break;
					case 2:UsartPrintf(USART_DEBUG, "WARN:	连接失败:设备ID鉴权失败\r\n");break;
					case 3:UsartPrintf(USART_DEBUG, "WARN:	连接失败:服务器失败\r\n");break;
					case 4:UsartPrintf(USART_DEBUG, "WARN:	连接失败:用户ID鉴权失败\r\n");break;
					case 5:UsartPrintf(USART_DEBUG, "WARN:	连接失败:未授权\r\n");break;
					case 6:UsartPrintf(USART_DEBUG, "WARN:	连接失败:授权码无效\r\n");break;
					case 7:UsartPrintf(USART_DEBUG, "WARN:	连接失败:激活码未分配\r\n");break;
					case 8:UsartPrintf(USART_DEBUG, "WARN:	连接失败:该设备已被激活\r\n");break;
					case 9:UsartPrintf(USART_DEBUG, "WARN:	连接失败:重复发送连接请求包\r\n");break;					
					default:UsartPrintf(USART_DEBUG, "ERR:	连接失败:未知错误\r\n");break;
				}
			}
			else
			{
				UsartPrintf(USART_DEBUG, "chf_error  !!! \r\n");
			}
		}
		else
		{
			UsartPrintf(USART_DEBUG, "平台不响应\r\n");
		}
		
		EDP_DeleteBuffer(&edpPacket);								//删包
	}
	else
		UsartPrintf(USART_DEBUG, "WARN:	EDP_PacketConnect Failed\r\n");
	
	return status;
	
}

建立连接后,再看上传数据接口,该函数也是移植平台封装好的上传数据接口函数 :

void OneNet_SendData(void)
{	
	EDP_PACKET_STRUCTURE edpPacket = {NULL, 0, 0, 0};												//协议包	
	char buf[128];	
	short body_len = 0, i = 0;	
	UsartPrintf(USART_DEBUG, "Tips:	OneNet_SendData-EDP\r\n");	
	memset(buf, 0, sizeof(buf));	
	body_len = OneNet_FillBuf(buf);																	//获取当前需要发送的数据流的总长度	
	if(body_len)
	{
		if(EDP_PacketSaveData(DEVID, body_len, NULL, kTypeSimpleJsonWithoutTime, &edpPacket) == 0)	//封包
		{
			for(; i < body_len; i++)
				edpPacket._data[edpPacket._len++] = buf[i];			
			ESP8266_SendData(edpPacket._data, edpPacket._len);										//上传数据到平台
			UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", edpPacket._len);			
			EDP_DeleteBuffer(&edpPacket);															//删包
		}
		else
			UsartPrintf(USART_DEBUG, "WARN:	EDP_NewBuffer Failed\r\n");
	}	
}

**body_len = OneNet_FillBuf(buf);**用于在平台上创建数据流,该函数定义如下:

unsigned char OneNet_FillBuf(char *buf)
{	
	char text[16];	
	memset(text, 0, sizeof(text));	
	strcpy(buf, "{");	
	memset(text, 0, sizeof(text));
    sprintf(text, "\"WENDU\":%.2f", temperature/10.0);
	strcat(buf, text);	
	strcat(buf, "}");	
	return strlen(buf);
}

"WENDU为数据流名称,temperature/10.0为温度值,后面程序会定义。

DS18B20程序的编写
该函数比较常见,原子的例程也有详细的讲解,这里就直接贴出来了:

//复位DS18B20
void DS18B20_Rst(void)	   
{                 
	DS18B20_IO_OUT();   //设置为输出
  DS18B20_DQ_OUT=0;  //拉低DQ
  delay_us(750);      //拉低750us
  DS18B20_DQ_OUT=1;  //DQ=1 
	delay_us(15);       //15US
}

//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void) 	   
{   
	u8 retry=0;
	DS18B20_IO_IN();    //设置为输入
    while (DS18B20_DQ_IN&&retry<200)
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=200)return 1;
	else retry=0;
    while (!DS18B20_DQ_IN&&retry<240)
	{
		retry++;
		delay_us(1);
	};
	if(retry>=240)return 1;	    
	return 0;
}

//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void) 
{
  u8 data;
	DS18B20_IO_OUT();   //设置为输出
  DS18B20_DQ_OUT=0; 
	delay_us(2);
  DS18B20_DQ_OUT=1; 
	DS18B20_IO_IN();    //设置为输入
	delay_us(12);
	if(DS18B20_DQ_IN)data=1;
  else data=0;	 
  delay_us(50);           
  return data;
}

//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void)   
{        
  u8 i,j,dat;
  dat=0;
	for (i=1;i<=8;i++) 
	{
        j=DS18B20_Read_Bit();
        dat=(j<<7)|(dat>>1);
    }						    
  return dat;
}

//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)     
 {             
    u8 j;
    u8 testb;
    DS18B20_IO_OUT();     //设置为输出
    for (j=1;j<=8;j++) 
	{
        testb=dat&0x01;
        dat=dat>>1;
        if(testb)       // 写1
        {
            DS18B20_DQ_OUT=0;
            delay_us(2);                            
            DS18B20_DQ_OUT=1;
            delay_us(60);             
        }
        else            //写0
        {
            DS18B20_DQ_OUT=0;
            delay_us(60);             
            DS18B20_DQ_OUT=1;
            delay_us(2);                          
        }
    }
}
 
//开始温度转换
void DS18B20_Start(void)
{   						               
    DS18B20_Rst();	   
    DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);// skip rom
    DS18B20_Write_Byte(0x44);// convert
}

//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在    	 
u8 DS18B20_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOB_CLK_ENABLE();			//开启GPIOB时钟
	
    GPIO_Initure.Pin=GPIO_PIN_12;           //PB12
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);     //初始化
 
 	  DS18B20_Rst();
	  return DS18B20_Check();
}

//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250) 
short DS18B20_Get_Temp(void)
{
    u8 temp;
    u8 TL,TH;
    short tem;
    DS18B20_Start ();           //开始转换
    DS18B20_Rst();
    DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);   // skip rom
    DS18B20_Write_Byte(0xbe);   // convert	    
    TL=DS18B20_Read_Byte();     // LSB   
    TH=DS18B20_Read_Byte();     // MSB   
    if(TH>7)
    {
        TH=~TH;
        TL=~TL; 
        temp=0;//温度为负  
    }else temp=1;//温度为正	  	  
    tem=TH; //获得高八位
    tem<<=8;    
    tem+=TL;//获得底八位
    tem=(double)tem*0.625;//转换     
	if(temp)return tem; //返回温度值
	else return -tem;    
}

主函数逻辑简单说明
最后的主函数基本上是对上面定义的函数的调用,由于串口定义那些比较基础,就另外列出来了,主函数大概的思路如下:
在这里插入图片描述
程序如下:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "ds18b20.h"
#include "pcf8574.h"

//图片
#include "image_2k.h"
#include "tx.h"

//网络协议层
#include "onenet.h"

//网络设备层
#include "esp8266.h"

int led_sta;
short temperature;

void Hardware_Init(void)
{
	HAL_Init();                     //初始化HAL库   
	Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
	delay_init(180);                //初始化延时函数
	uart_init(115200);              //初始化USART
	uart3_init(115200);
	LED_Init();                     //初始化LED 
	LCD_Init();	
    PCF8574_Init();                 //初始化PCF8574  		
    PCF8574_ReadBit(BEEP_IO);     //初始化LCD 
    while(DS18B20_Init());
	LCD_Clear(YELLOW);
	POINT_COLOR=BLACK;
	ESP8266_Init();					//初始化ESP8266
	UsartPrintf(USART_DEBUG,"Hard_init finish!\r\n");
}

int main(void)
{
	unsigned short timeCount = 0;	//发送间隔变量
	unsigned short timeCount_pic = 0;	//发送间隔变量
	int whitch_pic=0;
	unsigned char *dataPtr = NULL;
	u8 x=0;
	Hardware_Init();
	while(OneNet_DevLink())			//接入OneNET
	Delay_ms(500);
	LCD_ShowString(10,40,257,32,32,"Hello! OneNet ");
	while(1)
	{	   
        PCF8574_ReadBit(BEEP_IO);   //读取一次PCF8574的任意一个IO,使其释放掉PB12引脚,否则读取DS18B20可能会出问题
		temperature=DS18B20_Get_Temp();	
        if(temperature<0)
		{
			temperature=-temperature;					//转为正数
		}						
		if(++timeCount >= 500)									//发送间隔5s
		{
			UsartPrintf(USART_DEBUG, "OneNet_SendData\r\n");
			OneNet_SendData();									//发送数据			
			timeCount = 0;
			ESP8266_Clear();			
			led_sta=~led_sta;
		}
					
		dataPtr = ESP8266_GetIPD(0);
		if(dataPtr != NULL)
		OneNet_RevPro(dataPtr);
		Delay_ms(25);	
		x++;
		if(x==12)x=0;
		LED0=!LED0;	 
	}
}

最后效果如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后面修改了数据流的名称,因此上面看到的CHF_xixi就是WENDU。

踩过的一个坑
如果想传输多个数据,记得:
在这里插入图片描述

发布了17 篇原创文章 · 获赞 47 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_42827999/article/details/89684713
今日推荐