STM32与心知天气结合实现天气预报功能(HAL库详解)
一、简介
最近刚学完ESP-01S模块,想做点小东西,用STM32+心知天气+ESP-01s实现在OLED屏幕上显示最近天气。效果如图所示:
通过ESP-01S模块获取心知天气传回来的JSON数据,再通过解析数据显示在OLED屏幕上。
二、心知天气
首先,在心知天气获取API:
心知天气官网【控制台】【添加产品】【免费版】然后复制API密钥中的私钥
在心知天气操作手册中获取接口:
我们需要记录和了解以下信息:
经过修改接口连接,链接如下:我们可以获取到成都 明天 只获取一天的天气预报内容
https://api.seniverse.com/v3/weather/daily.json?key=S3sPxT5_RyH0If&location=chengdu&language=zh-Hans&unit=c&start=1&days=1
(需要用自己的API,上述接口连接不能直接使用)
将上段连接复制到网页,我们可以得到一串JSON数据包:
如果有错误会返回错误代码,错误代码的详细解释如下:
三、STM32实战代码(HAL库)
其中ESP-01S的驱动代码在:
(首先获取上面连接的代码,然后按下面的修改就可以了)
(1)初始化代码
首先,我们需要设置ESP8266的模式,代码如下:
/**
* @brief ESP8266初始化
* @param None
* @retval None
**/
void ESP8266_Init(void)
{
__HAL_UART_ENABLE_IT(&ESP01_UART_HANDLE, UART_IT_IDLE);
HAL_UART_Receive_DMA(&ESP01_UART_HANDLE, g_uart2_rx_buffer, ESP01_BUFFER_SIZE);
//ESP测试AT指令
while (!ESP8266_TEST());
HAL_Delay(500);
//配置为STA模式
while (!ESP8266_Net_Mode_Choose(STA));
HAL_Delay(500);
//设置单链路模式
while (!ESP8266_Set_MultiplesMode(0));
HAL_Delay(500);
//连接WIFI
while (! ESP8266_JoinAP());
HAL_Delay(2000);
//连接TCP服务器
while (!ESP8266_Connect_TCPServer(0, IP, PORT));
HAL_Delay(500);
while(!ESP8266_Send_Cmd("AT+CIPMODE=1\r\n", "OK")) HAL_Delay(500);
while(!ESP8266_Send_Cmd("AT+CIPSEND\r\n", ">")) HAL_Delay(500);
}
初始化流程为:1.配置STA 2.设置为单链路模式 3.连接WIFI(需改为自己的密码和ID) 4.连接TCP服务器(服务器为心知天气地址 域名:api.seniverse.com 端口:80)5.设置为透传模式
(2)写获取天气JSON的函数
/**
* @brief 获取天气
* @param None
* @retval None
**/
void ESP8266_GetWeather(uint8_t day) {
// 构造符合HTTP标准的请求
char request[256];
snprintf(request, sizeof(request),"GET https://api.seniverse.com/v3/weather/daily.json?key=S3sPxT5_Exr0If&location=chengdu&language=zh-Hans&unit=c&start=%d&days=1\r\n",day);
ESP8266_Send_Cmd(request, "results");
}
(接口连接为:GET + 自己的接口连接)
day表示获取那一天的天气 0为今天 1为明天 …
(3)写获取收到的JSON天气预报的函数
/**
* @brief 复制处理缓冲区数据到data
* @param data: 要发送的JSON数据
* @retval 数据长度
*/
uint16_t esp8266_copy_rxdata(char *data, uint16_t max_len) {
if (!data || max_len == 0) return 0;
__disable_irq(); // 防止中断修改数据
uint16_t valid_len = (ESP01_RX_LEN <= max_len) ? ESP01_RX_LEN : max_len;
memcpy(data, g_esp8266_rx_buffer, valid_len);
__enable_irq();
return valid_len;
}
我们通过ESP8266发送给TCP一段 GET + 自己的接口连接的函数,如果代码正确,此时会在串口助手上返回给我们天气的JSON包。(这段代码的作用就是复制JSON数据到指针data中,然后我们通过解析JSON数据显示在OLED上)
JSON具体含义如下:
我只显示了 date日期、code_day天气状况、high最高温度、low最低温度显示在OLED屏上
接着,我们编写Weather函数(解析获取到的JSON)
weather.c如下:
#include "weather.h"
#include "esp01_uart.h"
#include "stdlib.h"
#include "cJSON.h"
#include "stdio.h"
#include "OLED.h"
#include <string.h>
#include "UART3_PRINTF.h"
#define ESP01_BUFFER_SIZE 1024
#define MAX_STR_LEN 32
char weather_data[ESP01_BUFFER_SIZE] = {0};
daily_weather_t today_weather = {0};
// 天气代码转换函数
const char* code_to_text(const char* code)
{
if(!code) return "Unknown";
// 根据心知天气API代码表编写(常用代码示例)
if(strcmp(code, "0") == 0) return "Sunny"; // 晴
if(strcmp(code, "1") == 0) return "Clear"; // 晴
if(strcmp(code, "4") == 0) return "Cloudy"; // 多云
if(strcmp(code, "9") == 0) return "Shower"; // 阵雨
if(strcmp(code, "10") == 0) return "Storm"; // 雷阵雨
if(strcmp(code, "11") == 0) return "Thundersht"; // 雷阵雨伴有冰雹
if(strcmp(code, "13") == 0) return "Foggy"; // 雾
if(strcmp(code, "14") == 0) return "Haze"; // 霾
if(strcmp(code, "15") == 0) return "Dust"; // 浮尘
if(strcmp(code, "19") == 0) return "Blizzard"; // 暴雪
if(strcmp(code, "20") == 0) return "Fog"; // 浓雾
return "Unknown";
}
void ESP8266_GetWeatherData(uint8_t day)
{
memset(weather_data, 0, sizeof(weather_data));
ESP8266_GetWeather(day);
uint16_t len = esp8266_copy_rxdata(weather_data, sizeof(weather_data));
if (len == 0) {
printf("获取天气数据失败\n");
return;
}
cJSON *root = cJSON_Parse(weather_data);
if (!root) {
printf("JSON解析失败\n");
return;
}
cJSON *results = cJSON_GetObjectItem(root, "results");
if (!results || !cJSON_IsArray(results)) {
printf("找不到results字段\n");
cJSON_Delete(root);
return;
}
cJSON *first_result = cJSON_GetArrayItem(results, 0);
if (!first_result) {
printf("results数组为空\n");
cJSON_Delete(root);
return;
}
cJSON *daily = cJSON_GetObjectItem(first_result, "daily");
if (!daily || !cJSON_IsArray(daily)) {
printf("找不到daily字段\n");
cJSON_Delete(root);
return;
}
cJSON *today = cJSON_GetArrayItem(daily, 0);
if (!today) {
printf("daily数组为空\n");
cJSON_Delete(root);
return;
}
// 解析具体字段(添加所有必要的安全检查)
cJSON *date = cJSON_GetObjectItem(today, "date");
cJSON *code_day = cJSON_GetObjectItem(today, "code_day");
cJSON *high = cJSON_GetObjectItem(today, "high");
cJSON *low = cJSON_GetObjectItem(today, "low");
if (date && date->valuestring)
strncpy(today_weather.date, date->valuestring, MAX_STR_LEN);
if (code_day && code_day->valuestring)
strncpy(today_weather.code_day, code_day->valuestring, MAX_STR_LEN);
if (high && high->valuestring)
strncpy(today_weather.high, high->valuestring, MAX_STR_LEN);
if (low && low->valuestring)
strncpy(today_weather.low, low->valuestring, MAX_STR_LEN);
cJSON_Delete(root);
}
void ESP8266_ShowWeather(void)
{
// 获取天气描述(限制最大显示长度)
char weather_desc[16] = {0};
const char* raw_desc = code_to_text(today_weather.code_day);
strncpy(weather_desc, raw_desc, 15);
// 显示布局调整(假设OLED分辨率128x64,8x16字体)
OLED_ShowString(0, 0, "Date: ", OLED_8X16);
OLED_ShowString(40, 0, today_weather.date, OLED_8X16);
OLED_ShowString(64, 16, weather_desc, OLED_8X16);
OLED_ShowString(72, 32, today_weather.high, OLED_8X16);
OLED_ShowString(72, 48, today_weather.low, OLED_8X16);
OLED_Update();
}
weather.h如下:
#ifndef __WEATHER_H
#define __WEATHER_H
#include "main.h"
typedef struct
{
char date[20];
char code_day[10];
char high[10];
char low[10];
} daily_weather_t;
// 添加函数声明
const char* code_to_text(const char* code);
void ESP8266_GetWeatherData(uint8_t day);
void ESP8266_ShowWeather(void);
#endif
Main.c中代码如下:
OLED_Init();
OLED_ShowString(0, 16, "成都", OLED_8X16);
OLED_ShowString(0, 32, "最高气温 ℃", OLED_8X16);
OLED_ShowString(0, 48, "最低气温 ℃", OLED_8X16);
OLED_Update();
ESP8266_Init();
ESP8266_GetWeatherData(2);
ESP8266_ShowWeather();
解析JSON数据的库是开源代码:
连接如下:DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C
作者:m0_63127436