本項目使用的開發板是正點原子的阿波羅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詳細介紹
感謝這些前輩的經驗以及總結,讓我少走了很多彎路。
本次內容包含兩部分:
下面開始講解第一部分的內容。
- 固件燒寫
- 寫入AT指令
- 網絡調試
- 創建產品信息
- 虛擬調試
固件燒寫
一般初次使用的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 個子模式。
- 串口無線 WIFI(COM-AP)模式(AT+CWMODE=2),模塊作爲無線 WIFI 熱點,允許其他 WIFI 設備連接到本模塊,實現串口與其他設備之間的無線(WIFI)數據轉換互傳。該模式下,根據應用場景的不同,可以設置 3 個子模式: TCP 服務器、 TCP 客戶端, UDP。
- 串口無線 STA(COM-STA)模式(AT+CWMODE=1),模塊作爲無線 WIFI STA, 用於連接到無線網絡,實現串口與其他設備之間的無線(WIFI) 數據轉換互傳。該模式下,根據應用場景的不同,可以設置 3 個子模式: TCP 服務器、 TCP 客戶端, UDP。
- 串口無線 AP+STA(COM-AP+STA)模式(AT+CWMODE=3),模塊既作無線 WIFI AP,又作無線 STA,其他 WIFI 設備可以連接到該模塊,模塊也可以連接到其他無線網絡,實現串口與其他設備之間的無線(WIFI)數據轉換互傳。該模式下,根據應用場景的不同,可以設置 9 個子模式:(TCP 服務器、 TCP 客戶端, UDP) ||(TCP 服務器、 TCP 客戶端, UDP)。
- 透傳模式:透傳就是指不需要關心WIFI協議是如何實現的。所需要做的就是A通過串口發數據,B通過串口收數據,整個過程中A串口和B串口就像是用導線直接連接起來了一樣。則對於開發人員來看,就是完全透明的。退出透傳模式:發送數據"+++",注意:此時“+++”後面,不接“發送新行”。
網絡調試:
首先找到自己電腦的IP地址:
然後配置1、2、3(要讓電腦和WiFi模塊連在同一個熱點)
第一次配置IP值時出錯,圈圈裏多了一個空格,書寫一定要嚴謹,最好自己打IP。可以看到串口調試和網絡調試實現了互相通信。網絡調試助手可以一直髮數據給串口調試助手,但是串口調試助手需要發數據就的每次輸入“AT+CIPSEND=4”,出現OK後纔可以發數據
在onenet上創建產品信息:
上面的這些隨便填一下就行了,主要是要獲得產品ID和API-KEY。
最後下載虛擬調試軟件:虛擬調試工具(uf9n)
進行虛擬調試:
按步驟完成1、2、3:
可以看到在開發者中心處設備已經接通了:
以上,虛擬調試的部分完成了,前期的調試準備工作也完成了,第一部分結束。
下面開始講解第二部分的內容:(代碼部分)
- 連接圖
- esp8266程序的編寫
- onenet程序的編寫
- DS18B20程序的編寫
- 主程序邏輯簡單說明
連接圖
簡單畫了個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。
踩過的一個坑
如果想傳輸多個數據,記得: