前言:本文為手把手教學著名開源項目——太空人WiFi天氣時鐘,不同的是本次項目采用的是STM32作為MCU 。兩者開發過程中有因為各自芯片的特點(時鐘頻率,內存大小等),導致開發程序大不相同,很多地方需要特殊設計一下 。而作者使用STM32開發的原因很簡單,雖然計算能力等方面優于,但是弊端也很明顯 。其所具備的引腳和外設太少,擴展性一般(ESP32算是二者優點兼備) 。加之網上的太空人WiFi天氣時鐘已經開源的很完善了,所以嘗試用STM32實現一下,也方便后續利用STM32拓展開發 。(文末有代碼開源?。?
實驗硬件:;7針1.3寸TFT-LCD(240×240);
硬件實物圖:
效果圖:
引腳連接:
LCD顯示引腳:
VCC –> 3.3V
GND –> GND
CLK –> PA5
DIN –> PA7
RES –> PB0
DC –> PB1
CS –> PA4
模塊引腳:
VCC –> 3.3V
GND –> GND
RX–> PB10
TX –> PB11
RST –> PB9
EN –> PB7
一、簡介與使用1.1 簡介
是一款超低功耗的UART-WiFi透傳模塊,擁有業內極富競爭力的封裝尺寸和超低能耗技術 , 專為移動設備和物聯網應用設計,可將用戶的物理設備連接到Wi-Fi無線網絡上,進行互聯網或局域網通信,實現聯網功能 。
是上海樂鑫信息科技(國產)設計的低功耗WiFi芯片,集成完整的TCP/IP協議棧和MCU(網上型號很多,基本都具備聯網功能 , 部分型號可以直接作為MCU使用) 。而模塊是深圳安信可公司基于芯片研發(增加必要外圍電路、串口flash、板載天線等)的串口WiFi模塊,成本低、使用簡便、功能強大 。
一般是模塊固件損壞或者買回來里面可能被別人刷過固件需要擦除或者增加固件才用,作者的是因為燒寫了 IDE的例程進去,不能識別AT指令,后來用不到了才想到刷回AT固件 。刷固件有風險?。。。ㄈ綣?蠹衣虻氖?可能就需要刷AT固件)
1.2 硬件與網絡的橋梁——
模塊和串口藍牙JDY-31模塊一樣,串口WiFi模塊也是擴展單片機功能的又一神器 。小巧的WiFi模塊通過串口AT指令與單片機通訊 , 實現串口透傳 , 非常好上手(部分型號可以直接當MCU,無需再通過串口與其他MCU通訊) 。
透傳,又稱透明傳輸,具體來說就是“輸入即輸出(如從WiFi模塊串口輸入的字符會透傳到服務器端)”,數據不改變,不同協議之間的轉換(如串口到WiFi、藍牙等)由模塊完成 。使用者無需關心內部具體實現 , 因此模塊對于使用者是“透明的”、似乎不存在的(因為可無視中間的實現原理) 。一個高度封裝的模塊,應該隱藏內部實現細節 , 僅對外提供使用接口 。
把硬件聯網之后,就再也不是“玩單機”了 。配合服務器端的網絡編程,可以玩許多東西 。所以我覺得WiFi模塊是連接軟件(網絡編程)與硬件(單片機)的橋梁,把所學的單片機(MCU)和Web知識聯系起來了 。
如今大火的物聯網等概念都屬于“智能硬件”,等模塊的出現大大減少了網絡開發的難度系數,也進一步促進了技術下放 。而且,通過學習/ESP32等模塊,可以熟悉大量TCP/IP等網絡協議,對后續Linux系統板網絡開發也是極具意義的 。
1.3 使用——AT指令
AT指令最早在藍牙模塊上接觸過,所謂AT指令實質上就是一些起控制作用的特殊字符串 。模塊可以通過AT指令控制搭配使用源代碼API函數開發 , 總體開發速度快,難度較低 。
說明:下面僅列舉一些最常用的AT指令及用法,指令的詳細參數及使用說明請參考官方文檔: AT指令集 。
基礎AT指令
指令
描述
AT
測試AT啟動
AT+RST
重啟模塊
AT+GMR
查看版本信息
AT 是最常用的指令,用于測試模塊能否正常接受指令 。在sscom中向串口發送指令 AT , 若收到模塊返回的 OK 則說明模塊的AT指令可正常工作 。發送 AT+GMR 可查看AT指令及SDK的版本號,一般最新版指令會增加一些新功能 , 可隨時官方的更新 。
WiFi功能AT指令
WiFi是讓硬件聯網的基?。?和其他功能一樣 , 這里僅列舉所需的常用指令,更詳細指令說明還得查閱文檔 。
指令
描述
AT+
設置WiFi模式(sta/AP/sta+AP)
AT+CWLAP
掃描附近的AP信息
AT+CWJAP
連接AP
AT+CWQAP
與AP斷開連接
AT+CWSAP
設置 配置
AT+CWLIF
獲取連接到的的信息
關于WiFi模式這里要說明一下,sta模式下模塊相當于客戶端,像我們手機平板一樣是要去連接路由器的,而AP模式下模塊相當于路由器,是發射WiFi被別人連的 。支持兩種模式并存(模塊出廠默認的是AP模式)。另外 , 掃描WiFi指令 AT+CWLAP 只能在sta模式下使用,否則會報ERRO錯誤, AT+CWJAP 和 AT+CWQAP 指令也同理 。
sta模式連接WiFi演示
發送 AT+=1 指令配置模塊為sta模式(參數1,2,3分別對應模式sta,AP和sta/AP) 。發送 AT+CWLAP 指令掃描當前附近WiFi,模塊會返回可用AP列表 。使用 AT+CWJAP=”WiFi名稱”,”WiFi密碼” 連接到指定的路由器,比如我在圖書館的WiFi是 “Wang”,密碼是“” , 實際連接WiFi發送的指令就是 AT+CWJAP=”Wang”,””。返回的“WIFI ”說明連接成功 , “WIFI GOT IP”代表模塊分配到了IP 。最后可使用 AT+CWQAP 斷開當前連接的WiFi 。
TCP/IP相關AT指令
傳輸控制協議(英語: , 縮寫為 TCP)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議,由IETF的RFC 793定義 。在簡化的計算機網絡OSI模型中,它完成第四層傳輸層所指定的功能,用戶數據報協議(UDP)是同一層內另一個重要的傳輸協議 。在因特網協議族(suite)中,TCP層是位于IP層之上,應用層之下的中間層 。不同主機的應用層之間經常需要可靠的、像管道一樣的連接,但是IP層不提供這樣的流機制,而是提供不可靠的包交換 。
我們常說互聯網互聯網,那兩個連接到互聯網的設備該如何相互“交流”呢?TCP連接就是其中一種最常用的方式 。TCP是面向連接的傳輸層協議,通信雙方都要實現TCP協議,其中一方只需目標ip地址和端口號就能發起連接,連接一旦建立 , 就像在雙方之間拉了一條管子 , 管子兩端可進行全雙工(雙向同時收發)通信 。
TCP是傳輸層協議,是在網絡層IP協議的基礎上封裝而來 。而這些封裝的實現細節也是與我們無關,我們只需使用系統所提供的相關接口“拿來即用” , 比如網絡編程中的 。模塊中也實現了TCP/IP協議棧 , 模塊作為客戶端可輕松使用AT指令向服務端發起TCP連接 。連接TCP服務器并開啟透傳模式后,模塊串口收到的數據就會通過TCP連接透傳到服務端,這樣就完成了數據從硬件串口通過網絡到程序進程的傳輸,實現軟硬結合 。
指令
描述
AT+
查詢網絡連接信息
AT+
設置多連接模式
AT+
建立TCP連接UDP傳輸或者SSL連接
AT+
關閉TCP/UDP/SSL傳輸
AT+
設置透傳模式
AT+
發送數據
把WiFi模塊和電腦連接,在sscom確定AT指令能正常使用后,就可以開始配置TCP連接了,具體步驟如下:
根據上面“sta模式連接WiFi演示”一節把模塊連上WiFi輸入指令 AT+=0 設置單連接從“網絡調試助手”得知本機IP和端口,輸入指令 AT+=”TCP”,”192.168.43.140″,1234 (指令參數分別為連接類型、目標IP地址和端口號)向服務器發起TCP連接請求,握手成功并建立連接后 , 服務器端的“網絡調試助手”就會顯示客戶端IP和端口信息 , 此時雙方已做好收發數據的準備(根據實際需要連接的IP地址來)輸入指令 AT+=1 開啟透傳模式輸入命令 AT+ 進入透傳模式 , 此時模塊會把所有串口收到的數據都從TCP端口發送至服務器,同樣的,從服務器收到的數據也會從模塊串口發送出去打印到sscom上 。這樣WiFi模塊就真正成為了連接硬件與網絡的橋梁500內部服務器錯誤,實現了串口到TCP的協議轉換
以上其實就是大概本次項目需要使用到的指令,配置代碼如下:
void esp8266_config(void){char str[200];sprintf(str, "AT+CWJAP="%s","%s"rn", WIFI_NAME, WIFI_PSW);//SendATCmd("+++", 500);// 退出透傳模式SendATCmd("ATrn", 2000);// 測試ESP01模塊是否存在//SendATCmd("AT+GMRrn",3000); // 查看模塊版本信息SendATCmd("AT+CWMODE=1rn", 2000); // 開啟STA+AP模式 ==================SendATCmd("AT+RSTrn", 3000);SendATCmd(str, 10000); // 連接無線路由器或者手機熱點,等待10秒 ============SendATCmd("AT+CIPMUX=0rn", 2000); // 關閉多連接SendATCmd("AT+CIPSTART="TCP","api.seniverse.com",80rn", 2000); // 連接心知天氣TCP服務器SendATCmd("AT+CIPMODE=1rn", 500); // 開啟透傳模式SendATCmd("AT+CIPSENDrn", 500);// 開始透傳}
二、知心天氣API使用
本項目為WiFi天氣時鐘,自然離不開需要從網頁上讀取天氣信息 。這里我們使用業內比較著名的知心天氣 。
2.1 登陸心知天氣官網,注冊
沒有賬號的朋友可以自己去注冊一下,流程很簡單 。不商用的話,知心天氣是免費的 , 還是比較良心的(網站響應率也很高) 。
點擊“立即免費試用”
點擊免費版的“免費申請”
申請后可查看到自己的私鑰(自行保存后面需要用到)
【代碼開源 基于STM32與ESP8266的太空人WiFi天氣時鐘】2.1 API函數的使用
目前,大部分網絡數據調用都是習慣性的調用數據提供商的API接口函數 。
重新點擊“產品”—>“天氣數據” , 點擊“查看API文檔”
點擊”天氣實況”,打開對應的API接口文檔
查看天氣實況的接口地址,以及返回的數據結果示例(自行保存后面需要用到)
(1)上述知心天氣API接口函數的尋找和使用通用性很高,大部分網絡數據讀取的流程與之類似 。
(2)嵌入式開發大部分情況下一般都是C語言進行開發的,由于C語言的局限性 , 沒有直接的字典類型處理(),所以,對于服務器返回給的JSON數據一般是無法直接解碼讀取的 。目前有2種方法處理:①、移植CJSON去解碼;②取巧去比對字符串(本次使用的方法);
項目使用過程中直接使用知心天氣自帶的API函數,項目大致流程:開啟STA模式后 , 成功連上WiFi后500內部服務器錯誤,通過TCP協議去訪問執行天氣網站的服務器,在發送特定的API接口函數 , 服務器響應后返回需要的結果信息 。
三、UART串口通訊
STM32作為MCU與直接的通訊就是簡單的UART(串口)通信,這一點依舊與藍牙模塊很類似 。使用方法:通過串口連接的,然后單片機通過串口發送AT指令集 。后續從服務器接受的數據信息也從的傳輸給單片機 。后續只需要使用自己的方法去解析串口接收到的數據,即可得到自己想要的數據信息 。
可以初步使用電腦串口去讀取MCU接收到返回的信息:
四、配置
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug設置成 Wire(否則可能導致芯片自鎖);
3、GPIO配置:此處模擬使用SPI通信 , 并且設置的EN和RST;
4、RTC配置:年月日,時分秒;
5、UART1和UART3配置:MCU分別與電腦和通訊(記得開啟串口通信中斷);
6、時鐘樹配置
7、工程配置
五、代碼與解析5.1 TFT-LCD顯示代碼
LCD顯示部分其實都是非常基礎的操作,不熟悉的可以去看看筆者另一篇文章了解一下 。作者這里主要把工程中不一樣的地方指出來一下 。【強烈推薦】基于STM32的TFT-LCD各種顯示實現(內容詳盡含代碼)_混分巨獸龍某某的博客-CSDN博客_lcd顯示屏顯示代碼
5.1.1 UI設計
WiFi天氣時鐘中最要的點——UI設計,需要去設計很多界面圖標,作者這里耗費了超級多的時間 , 翻遍了和視覺中國 。最后找到了差不多符合作者要求的UI庫(有需要的可以評論區留下郵箱),如下:
5.1.2 GIF動圖實現
目前,由于STM32自身內存的緣故,其實STM32是不太適合實現GIF動圖的 。所以,網上這方面的資料和代碼都很少 。目前,較為主流的方法:(1)enWin或者Lvgl庫實現GIF動圖;(2)從SD卡讀取數據去顯示 。
作者這里用了一直笨方法去實現了GIF顯示,就是去循環遍歷GIF動圖的每一幀 。
使用GIF分離器去分離GIF動圖的每一?。?
再利用 2.9(破解版)去提前圖模;
將取模代碼變為2維數組 , 第一維度為幀數,第二維度為每幀圖片的取模 。
之后循環顯示該GIF數組的每一幀,即可實現GIF動圖顯示 。
代碼:
void showimage4(const unsigned char *p) {int i;unsigned char picH,picL;Address_set(180,146,228,195); for(i=0;i<49*50;i++){picL=*(p+i*2);picH=*(p+i*2+1);LCD_WR_DATA(picH<<8|picL);} }for(int a=0;a<11;a++){showimage4(gImage_1[a]);}
5.2 代碼
部分代碼主要是配置后負責和目標服務器去實現通訊,當然還有需要解碼服務器返回信息 。
5.2.1 配置代碼(含UART處理)
UART回調處理函數:
/* USER CODE BEGIN 4 */void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){/* Prevent unused argument(s) compilation warning */UNUSED(huart);/* NOTE: This function Should not be modified, when the callback is needed,the HAL_UART_TxCpltCallback could be implemented in the user file*/ if(huart == &huart1) {g_uart1_rx.buf[g_uart1_rx.size++] = aRxBuffer_rx1;if((g_uart1_rx.buf[g_uart1_rx.size-1] == 0x0A)&&(g_uart1_rx.buf[g_uart1_rx.size-2] == 0x0D)){HAL_UART_Transmit(&huart1, (uint8_t *)&g_uart1_rx.buf, g_uart1_rx.size,0xFFFF);while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);g_uart1_rx.size = 0;memset(g_uart1_rx.buf,0x00,sizeof(g_uart1_rx.buf));}HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer_rx1, 1);} if(huart == &huart3) {g_uart3_rx.buf[g_uart3_rx.size++] = aRxBuffer_rx3;if((g_uart3_rx.buf[g_uart3_rx.size-1] == 'K')&&(g_uart3_rx.buf[g_uart3_rx.size-2] == 'O')){HAL_UART_Transmit(&huart1, (uint8_t *)&g_uart3_rx.buf, g_uart3_rx.size,0xFFFF);while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);g_uart3_rx.size = 0;memset(g_uart3_rx.buf,0x00,sizeof(g_uart3_rx.buf));}else if((g_uart3_rx.buf[g_uart3_rx.size-2] == ']')&&(g_uart3_rx.buf[g_uart3_rx.size-1] == '}')&&(g_uart3_rx.buf[g_uart3_rx.size-3] == '}')){HAL_UART_Transmit(&huart1, (uint8_t *)&g_uart3_rx.buf, g_uart3_rx.size,0xFFFF);while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//strcpy(Data_buff,(char *)g_uart3_rx.buf);temp = 1;g_uart3_rx.size = 0;memset(g_uart3_rx.buf,0x00,sizeof(g_uart3_rx.buf));}HAL_UART_Receive_IT(&huart3, (uint8_t *)&aRxBuffer_rx3, 1);} }/* USER CODE END 4 */
.h(AT控制):
#ifndef __ESP8266_H#define __ESP8266_H //#include "stdint.h" //uint8_t aRxBuffer_rx1;//接收中斷緩沖//uint8_t aRxBuffer_rx3;//接收中斷緩沖 //typedef struct {// uint16_t size;// uint8_t buf[1022]; // 接收緩沖數組//} UART_RXDATA; //UART_RXDATA g_uart1_rx;//UART_RXDATA g_uart3_rx; //char Data_buff[1022]; //char weather[10];//存儲天氣 //uint8_t temperature[2]={0,0};//儲存最高氣溫和最低氣溫//uint8_t temp = 0; //需要連接的wifi賬號和密碼 , 需要修改,且WiFi頻段不支持5GHz#define WIFI_NAME "Wang"#define WIFI_PSW"123456" 心知天氣api,注意key=后面需要替換成自己賬號的密鑰//char *get="GET https://api.seniverse.com/v3/weather/daily.json?key=SkV9zIBpwJAOixrJZ&location=chongqing&language=en&unit=crn"; //void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);void SendATCmd(char *cmd, int waitms); void esp8266_config(void);#endif
.c:
#include "esp8266.h"#include "usart.h"#include #include #include #include "lcd.h"void SendATCmd(char *cmd, int waitms) { // 發送AT指令給串口3 if (NULL != cmd) {HAL_UART_Transmit(&huart3, (uint8_t *)cmd, strlen(cmd), 0xFFFF);if (waitms > 0)HAL_Delay(waitms);// 延時等待ESP01模塊應答時間 }} void esp8266_config(void){char str[200];sprintf(str, "AT+CWJAP="%s","%s"rn", WIFI_NAME, WIFI_PSW);//SendATCmd("+++", 500);// 退出透傳模式SendATCmd("ATrn", 2000);// 測試ESP01模塊是否存在//SendATCmd("AT+GMRrn",3000); // 查看模塊版本信息SendATCmd("AT+CWMODE=1rn", 2000); // 開啟STA+AP模式 ==================SendATCmd("AT+RSTrn", 3000);SendATCmd(str, 10000); // 連接無線路由器或者手機熱點,等待10秒 ============SendATCmd("AT+CIPMUX=0rn", 2000); // 關閉多連接SendATCmd("AT+CIPSTART="TCP","api.seniverse.com",80rn", 2000); // 連接心知天氣TCP服務器SendATCmd("AT+CIPMODE=1rn", 500); // 開啟透傳模式SendATCmd("AT+CIPSENDrn", 500);// 開始透傳SendATCmd("GET https://api.seniverse.com/v3/weather/daily.json?key=SkV9zIBpwJAOixrJZ&location=zhenjiang&language=en&unit=crn", 2000);}
注意,key=后面盡量換成自己的密鑰,=后面也可以換成自己所在城市的字母 。
5.2.2 信息解碼
這部分作者取巧,使用了字符串對比和指針取值的操作 。
()函數:
atoi()函數:
代碼:
char *p;p = strstr(Data_buff,"text_day");//查找天氣sscanf(p+11,"%[^"]",weather); //LCD_ShowString(40,80,(uint8_t*)weather);p = strstr(Data_buff,"high");//查找氣溫temperature[0]=atoi(p+7);p = strstr(Data_buff,"low");temperature[1]=atoi(p+6);//LCD_ShowxNum2(45,40,temperature[1],2,24,0);LCD_ShowxNum2(160,207,temperature[0],2,24,0);//溫度value = https://www.jianzixun.com/(temperature[1]+temperature[0])/2;LCD_ShowxNum2(52,160,value,2,24,0);//濕度p = strstr(Data_buff,"humidity");humidity=atoi(p+11);LCD_ShowxNum2(132,160,humidity,2,24,0);LCD_ShowNew(161,160,'%',24,0);if((strstr(weather,"Overcast")) || (strstr(weather,"Mostly Cloudy")) || (strstr(weather,"Partly Cloudy")) || strstr(weather,"Cloudy")){Overcast();}if((strstr(weather,"Sunny")) || (strstr(weather,"Clear")) || (strstr(weather,"Fair")))//??ìì{Sunny();}if((strstr(weather,"Shower"))){Shower();}if((strstr(weather,"Thundershower")) || (strstr(weather,"Thundershower with Hail"))){Thundershower();}if((strstr(weather,"Light rain")) || (strstr(weather,"Moderate Rain"))){smallrain();}if((strstr(weather,"Heavy Rain")) || (strstr(weather,"Storm")) || (strstr(weather,"Heavy Storm")) || (strstr(weather,"Severe Storm"))){Bigrain();}if((strstr(weather,"Ice Rain")) || (strstr(weather,"Sleet")) || (strstr(weather,"Snow Flurry")) || (strstr(weather,"Light Snow")) || (strstr(weather,"Moderate Snow")) || (strstr(weather,"Heavy Snow")) || (strstr(weather,"Snowstorm"))){snow();}
5.3 RTC代碼
.h:
#ifndef __RTCDISPLAY_H#define __RTCDISPLAY_H void RTC_display(); #endif
.c:
#include "rtcdisplay.h"#include "rtc.h"#include "lcd.h" RTC_DateTypeDef GetData;//獲取日期結構體RTC_TimeTypeDef GetTime;//獲取時間結構體 void RTC_display()//RTC DISPLAY{/* Get the RTC current Time */HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);/* Get the RTC current Date */HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);/* Display date Format : yy/mm/dd *///OLED_ShowNum(0,0,2000+GetData.Year,4,16);//year//OLED_ShowStr(35,30,".",2);//OLED_ShowNum(45,0,GetData.Month,2,16);//month//OLED_ShowStr(60,30,".",2);//OLED_ShowNum(70,0,GetData.Date,2,16);//date/* Display time Format : hh:mm:ss */LCD_ShowxNum2(15,75,GetTime.Hours,2,60,0);//hour//LCD_ShowNew(75,65,':',60,0);LCD_ShowxNum2(105,75,GetTime.Minutes,2,60,0);//minLCD_ShowxNum2(180,105,GetTime.Seconds,2,32,0);//seconds }
這里RTC的時鐘顯示,作者也去網上找了專門的LED數字字體,如果需要LED數字字體庫的也可以評論留言,作者把安裝腳本發你 。
六、項目效果
太空人WIFI天氣時鐘
七、項目代碼
代碼地址:
本文到此結束 , 希望對大家有所幫助 。
- 沒有可以編寫代碼的操作系統,微軟是如何開發操作系統的?
- 用最古老的 WordPress 系統,寫最現代的 PHP 代碼!
- 不到50行代碼就能實現一個 WordPress 主題的選項框架
- 格力空調故障代碼h4是什么原因
- 不需寫代碼,Filter函數公式,制作模糊查詢器
- Microsoft store加載不出來重試或者有錯誤代碼
- 電腦主機用著就自己藍屏了,出現代碼 電腦重裝系統中藍屏怎么辦
- 《都挺好》中的那些話:你給你的孩子投毒了嗎?
- 思維性格天注定純屬無稽之談,寫好這個代碼,孩子謝你一生一世
