STM32F4+ESP8266+OLED的天气查询系统
在嵌入式系统领域,利用 STM32 和 ESP8266 实现天气查询并在 LCD 屏幕上显示的项目已经有很多文章和教程了。然而,我想在这篇博客中分享一个特定的项目,关于如何使用正点原子的 STM32F407 开发板和 ESP8266 模块来实现天气查询(无需手动发送 AT 指令),并将结果显示在 OLED 屏幕上。
开发板:STM32F407ZGT6(正点原子探索者)
外设:正点原子0.96’ OLED 显示器模块、ESP8266
软件:Keil MDK5、串口调试助手
项目流程图
一、注册“心知天气”
心知天气是一家提供高精度气象数据和行业气象解决方案的企业,通过标准的 Restful API 接口,为各行各业提供实时、准确、全面的天气数据服务。心知天气的数据覆盖全国的每个角落,实现分钟级天气数据更新频率,基于 AI 增强更准确的天气预报。
本项目是通过 ESP8266 连接网络,向“心知天气”请求天气信息。所以需要注册账号,得到自己的API 私钥。
二、ESP8266 连接网络
访问天气平台需要先连接网络。本项目使用 AT 指令控制 ESP8266 连接手机热点。关于 AT 指令,可以简单了解一下,本文最后给出相关文章。
- 在代码中定义 wifista_ssid 和 wifista_password,分别存储热点名称和密码。
- 发送 AT 指令检测 ESP8266 模块是否正常工作,并关闭回显功能。然后,发送AT+CWMODE=1 指令设置 ESP8266 模块为 STA 模式,即客户端模式。
- 发送 AT+CIPMUX=0 指令设置 ESP8266 模块为单连接模式,即只能同时连接一个服务器。最后,发送 AT+CWJAP 指令并附上热点名称和密码,让 ESP8266 模块连接到手机热点,并等待返回 WIFI GOT IP 的信息,表示连接成功并获取到 IP 地址。
具体代码如下:
u8 atk_8266_wifista_config(void)
{
u8 *p;
p=mymalloc(SRAMIN,50);
POINT_COLOR=RED;
while(atk_8266_send_cmd("AT","OK",20))
{
atk_8266_quit_trans();//Í˳ö͸´«
//atk_8266_send_cmd("AT+CIPMODE=0","OK",200);
delay_ms(800);
}
while(atk_8266_send_cmd("ATE0","OK",20));
atk_8266_send_cmd("AT+CWMODE=1","OK",50);
atk_8266_send_cmd("AT+RST","OK",20);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
atk_8266_send_cmd("AT+CIPMUX=0","OK",20);
sprintf((char*)p,"AT+CWJAP=\"%s\",\"%s\"",wifista_ssid,wifista_password);
printf((char*)p,"AT+CWJAP=\"%s\",\"%s\"",wifista_ssid,wifista_password);
while(atk_8266_send_cmd(p,"WIFI GOT IP",300));
myfree(SRAMIN,p);
return 0;
}
需要打开串口调试助手,看到交互过程。
三、获取当前天气信息
连接网络之后,向“心知天气”请求信息,返回 JSON 数据。具体步骤如下:
- 用 sprintf 函数拼接一个 AT+CIPSTART 指令,让 ESP8266 模块建立一个 TCP 连接到心知天气的服务器。然后调用 atk_8266_send_cmd 函数发送该指令,并等待返回 OK 的信息,表示连接成功。如果返回失败,则释放内存空间并返回1。
- 发送 AT+CIPMODE=1 指令,该指令的作用是设置 ESP8266 模块为透传模式,即直接将串口收到的数据发送到服务器。然后,清零 USART3_RX_STA 变量,该变量用来记录串口 3 接收到的数据状态。再发送 AT+CIPSEND 指令,该指令的作用是开始透传数据。然后打印一条提示信息 “start trans…”,表示开始传输数据。
- 用 u3_printf 函数发送一个 GET 请求,该请求的作用是向心知天气的 API 接口请求当前重庆市的天气信息,并指定语言为英文和单位为摄氏度。请求的格式为:GET https://api.seniverse.com/v3/weather/now.json?key=你的密钥&location=chongqing&language=en&unit=c\n\n,等待服务器返回数据。
- 判断 USART3_RX_STA 变量是否有 0X8000 位为1,表示接收到了数据。如果是,则在串口 3 接收缓冲区的末尾加上一个结束符 0,表示字符串结束。
具体代码如下:
u8 get_current_weather(void)
{
u8 *p;
u8 res;
p=mymalloc(SRAMIN,40);
sprintf((char*)p,"AT+CIPSTART=\"TCP\",\"%s\",%s",WEATHER_SERVERIP,WEATHER_PORTNUM);
res = atk_8266_send_cmd(p,"OK",500);
if(res==1)
{
myfree(SRAMIN,p);
return 1;
}
delay_ms(300);
atk_8266_send_cmd("AT+CIPMODE=1","OK",100);
USART3_RX_STA=0;
atk_8266_send_cmd("AT+CIPSEND","OK",100);
printf("start trans...\r\n");
u3_printf("GET https://api.seniverse.com/v3/weather/now.json?key=你的密钥&location=chongqing&language=en&unit=c\n\n");
delay_ms(20);
USART3_RX_STA=0;
delay_ms(1000);
if(USART3_RX_STA&0X8000)
{
USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;
}
parse_now_weather();
atk_8266_quit_trans();
atk_8266_send_cmd("AT+CIPCLOSE","OK",50);
myfree(SRAMIN,p);
return 0;
}
四、解析天气数据并显示
得到的 JSON 数据需要解析,本文只给出解析的步骤,其中有关 JSON 数据的内容可以参考附录中的文章。
- 定义一些变量和指针,用来存储和处理 JSON 数据。例如,pr 指针用来存储 JSON 字符串,strlocation 指针用来存储城市名称,strweather 指针用来存储天气状况,strtemp 指针用来存储温度,strtime 指针用来存储更新时间等。还有一些 cJSON 结构体的指针,用来表示 JSON 对象和数组。
- cJSON_Parse 函数将串口 3 接收到的数据转换为 cJSON 对象,并赋值给 root 指针。如果转换成功,则继续执行。
- 获取 results 对应的值。用 cJSON_GetObjectItem 函数从 root 对象中获取 results 键对应的值,并赋值给 pSub 指针。如果获取成功,则继续执行。获取数组中的第一个元素。接着用cJSON_GetArrayItem 函数从 pSub 数组中获取第0个元素,并赋值给 arrayItem 指针。然后用cJSON_Print 函数将 arrayItem 对象转换为字符串,并赋值给 pr 指针。再用 cJSON_Parse 函数将pr 字符串转换为 cJSON 对象,并赋值给 pItem 指针。如果转换成功,则继续执行。
- 获取 location 对应的值。接着用 cJSON_GetObjectItem 函数从 pItem 对象中获取 location 键对应的值,并赋值给 pSubItem 指针。如果获取成功,则继续执行。获取城市名称。获取天气状况和温度。获取更新时间。
- 显示城市名称、天气状况、温度和更新时间等信息。
具体代码如下:
u8 parse_now_weather(void)
{
printf("begin parse_now_weather\n");
char *pr,*strlocation, *strweather, *strtemp, *strtime;
u8 size = 0;
int len;
u8 res;
u8 temperature;
pr = mymalloc(SRAMIN,1000);
strlocation = mymalloc(SRAMIN,50);
strweather = mymalloc(SRAMIN,50);
strtemp = mymalloc(SRAMIN,50);
strtime = mymalloc(SRAMIN,50);
memset(pr,0,1000);
memset(strlocation,0,50);
memset(strweather,0,50);
memset(strtemp,0,50);
memset(strtime,0,50);
cJSON* root;
cJSON *pSub;
cJSON *arrayItem;
cJSON *pItem;
cJSON *pSubItem;
cJSON *pChildItem;
root = mymalloc(SRAMIN,sizeof(cJSON));
pSub = mymalloc(SRAMIN,sizeof(cJSON));
pItem = mymalloc(SRAMIN,sizeof(cJSON));
pSubItem = mymalloc(SRAMIN,sizeof(cJSON));
pChildItem = mymalloc(SRAMIN,sizeof(cJSON));
arrayItem = mymalloc(SRAMIN,sizeof(cJSON));
// 获取 json 对象
root = cJSON_Parse((const char*)USART3_RX_BUF);
if (root != NULL)
{
printf("begin GetObjectItem results\n");
// 获取 results 对应的值
pSub = cJSON_GetObjectItem(root, "results");
if (pSub != NULL)
{
printf("begin GetArrayItem 0\n");
// 根据下标获取 json 对象数组中的对象
arrayItem = cJSON_GetArrayItem(pSub, 0);
pr = cJSON_Print(arrayItem);
pItem = cJSON_Parse(pr);
if (pItem != NULL)
{
printf("begin GetObjectItem location\n");
/*-------------------------------城市名称---------------------------*/
//根据location 键获取对应的值(cjson对象)
pSubItem =cJSON_GetObjectItem(pItem, "location");
if (pSubItem != NULL)
{
printf("begin GetObjectItem location name\n");
pChildItem = cJSON_GetObjectItem(pSubItem, "name");
if (pChildItem != NULL)
{
printf("begin cout location name\n");
sprintf(strlocation,"%s", pChildItem->valuestring);
printf("%s\n", pChildItem->valuestring);
//OLED_Refresh_Gram();
//OLED_ShowString(0,0, strlocation, 16);
}
}
/*-------------------------------天气信息---------------------------*/
pSubItem = cJSON_GetObjectItem(pItem,"now");
if(pSubItem != NULL)
{
pChildItem = cJSON_GetObjectItem(pSubItem,"text");
if(pChildItem != NULL)
{
printf("begin cout weather\n");
sprintf(strweather,"%s", pChildItem->valuestring);
printf("%s\n", pChildItem->valuestring);
}
pChildItem = cJSON_GetObjectItem(pSubItem,"temperature");
if(pChildItem != NULL)
{
printf("begin cout temperature\n");
sprintf(strtemp,"%s", pChildItem->valuestring);
printf("%s\n", pChildItem->valuestring);
}
}
/*-------------------------------更新时间--------------------------*/
pSubItem = cJSON_GetObjectItem(pItem,"last_update");
if(pSubItem != NULL)
{
printf("begin cout time\n");
sprintf(strtime,"%s", pSubItem->valuestring);
printf("%s\n", pSubItem->valuestring);
}
}
}
while(1)
{
OLED_Refresh_Gram();
OLED_ShowString(0,0, strlocation, 12);
OLED_ShowString(0,16, strweather, 12);
OLED_ShowString(60,16, strtemp, 12);
OLED_ShowString(75,16, "*C", 12);
OLED_ShowString(0,32, strtime, 12);
}
cJSON_Delete(root);
myfree(SRAMIN,root);
myfree(SRAMIN,pSub);
myfree(SRAMIN,pItem);
myfree(SRAMIN,pSubItem);
myfree(SRAMIN,pChildItem);
myfree(SRAMIN,arrayItem);
myfree(SRAMIN,pr);
myfree(SRAMIN,strlocation);
myfree(SRAMIN,strweather);
myfree(SRAMIN,strtemp);
myfree(SRAMIN,strtime);
return 0;
}
}
串口调试助手的交互页面
OLED 屏幕显示效果图
附录
AT 指令:玩转ESP8266-01——AT指令集
JSON 数据:cJSON使用详细教程 | 一个轻量级C语言JSON解析器
本项目源码