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。

踩過的一個坑
如果想傳輸多個數據,記得:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章