作品 基于STM32的智能天气预报系统(源码开源)_基于stm32天气,2024年最新物联网嵌入式开发开发从零开始

296 阅读16分钟

======005

通过点击下方频率点跳到相应频率,再通过左右按钮调节频率至所需频率。

2、作品实现

2.1 天气数据获取及解析

2.1.1 天气数据从哪来?

天气数据可以从一些专门做天气预报的网站获取,如心知天气、和风天气等。本人选择的是心知天气

www.seniverse.com/

网站首页如下:

======006

我们是通过其API密钥才能获取得到其天气数据,而只有注册的用户才拥有API密钥,所以必须得注册,可以点击右上角进行注册。

2.1.2 天气数据是什么格式?

登录心知天气网站之后,点击菜单导航中的数据->常规数据即可查看API文档。在API文档页面的左侧可看到一些可查看的条目,如:

======007

可点击天气实况查看其相关说明,可以看到其天气数据格式如下图所示:

======008

这就是JSON格式的数据,不了解JSON的朋友可查看上一篇笔记:JSON的简单认识

2.1.3 如何解析得到有用的数据?

从上图中的JSON格式天气数据包中我们可以看出:我们需要用到的数据就是冒号后面的字符串数据,这些数据是我们需要获取并显示到屏幕上的数据。

那么,我们该怎么从这一堆JSON格式数据中解析出冒号后面的字符串呢?并且,这个系统是基于单片机的天气预报系统。而单片机使用C语言进行编程开发的,所以我们得使用C语言对这些JSON天气数据包进行解析。

其实,有一个专门解析JSON数据包的第三方C语言库。我们可以使用这个库进行解析,这个CJSON库的下载链接为:

链接:pan.baidu.com/s/1DQynsdlN…
提取码:ww4z

只要把cJSON.ccJSON.h放到工程主程序所在目录,然后在主程序中包含头文件JSON.h即可引入该库。如:

======009

下面给出一个实例:

测试代码:

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*------------------------------------------STM32 Demo------------------------------------
\*
\* 工程说明:解析JSON天气数据包now.json(天气实况)
\* 作 者:ZhengNian
\* 博 客:zhengnianli.github.io
\* 公 众 号:嵌入式大杂烩
\*
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/

//1、数据来源:心知天气(api.seniverse.com)
//2、获取方法:GET https://api.seniverse.com/v3/weather/now.json?key=2owqvhhd2dd9o9f9&location=beijing&language=zh-Hans&unit=c
//3、返回的数据范例见文件test.txt

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "cJSON.h"

//函数声明
int cJSON\_WeatherParse(char \*JSON);

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* Function Name : main主函数
\* Parameter : NULL
\* Return Value : 0 
\* Function Explain : 
\* Create Date : 2017.12.6 by lzn
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
int main(int argc, char \*\*argv)
{
	FILE \*fp;
	char \*data;
	int len;
	int i;
	
	if((fp = fopen("now.txt","rb")) == NULL)
	{
		printf("Open error!\n");
		return 1;
	} 
	fseek(fp, 0, SEEK\_END);		//文件指针指向文件末尾
	len = ftell(fp);			//求文件长度 
	fseek(fp, 0, SEEK\_SET);		//文件指针指向文件开头
	data = (char\*)malloc(len+1);
	fread(data, len, 1, fp);
	fclose(fp);
	//printf("read file %s complete, len=%d.\n","now.txt",len);
	cJSON\_WeatherParse(data);	//解析天气数据
	free(data);
	
	system("pause");
	
	return 0;
}

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* Function Name : cJSON\_WeatherParse,解析天气数据
\* Parameter : JSON:天气数据包 results:保存解析后得到的有用的数据
\* Return Value : 0:成功 其他:错误
\* Function Explain : 
\* Create Date : 2017.12.6 by lzn
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
int cJSON\_WeatherParse(char \*JSON)
{
	cJSON \*json,\*arrayItem,\*object,\*subobject,\*item;
	
	json = cJSON\_Parse(JSON); //解析JSON数据包
	if(json == NULL)		  //检测JSON数据包是否存在语法上的错误,返回NULL表示数据包无效
	{
		printf("Error before: [%s]\n",cJSON\_GetErrorPtr()); //打印数据包语法错误的位置
		return 1;
	}
	else
	{
		if((arrayItem = cJSON\_GetObjectItem(json,"results")) != NULL); //匹配字符串"results",获取数组内容
		{
			int size = cJSON\_GetArraySize(arrayItem);     //获取数组中对象个数
			//printf("cJSON\_GetArraySize: size=%d\n",size); 
			
			if((object = cJSON\_GetArrayItem(arrayItem,0)) != NULL)//获取父对象内容
			{
				/\* 匹配子对象1 \*/
				if((subobject = cJSON\_GetObjectItem(object,"location")) != NULL)
				{
					printf("\n-------------------------------location-----------------------------\n");
					//匹配子对象1成员"id"
					if((item = cJSON\_GetObjectItem(subobject,"id")) != NULL)   
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象1成员"name"
					if((item = cJSON\_GetObjectItem(subobject,"name")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象1成员"country"
					if((item = cJSON\_GetObjectItem(subobject,"country")) != NULL)
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象1成员"timezone"
					if((item = cJSON\_GetObjectItem(subobject,"timezone")) != NULL)
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象1成员"timezone\_offset"
					if((item = cJSON\_GetObjectItem(subobject,"timezone\_offset")) != NULL)
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
				}
				/\* 匹配子对象2 \*/
				if((subobject = cJSON\_GetObjectItem(object,"now")) != NULL)
				{
					printf("---------------------------------now-------------------------------\n");
					//匹配子对象2成员"text"
					if((item = cJSON\_GetObjectItem(subobject,"text")) != NULL)
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"code"
					if((item = cJSON\_GetObjectItem(subobject,"code")) != NULL)
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"temperature"
					if((item = cJSON\_GetObjectItem(subobject,"temperature")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"feels\_like"
					if((item = cJSON\_GetObjectItem(subobject,"feels\_like")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"pressure"
					if((item = cJSON\_GetObjectItem(subobject,"pressure")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"humidity"
					if((item = cJSON\_GetObjectItem(subobject,"humidity")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"visibility"
					if((item = cJSON\_GetObjectItem(subobject,"visibility")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"wind\_direction"
					if((item = cJSON\_GetObjectItem(subobject,"wind\_direction")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"wind\_speed"
					if((item = cJSON\_GetObjectItem(subobject,"wind\_speed")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"wind\_scale"
					if((item = cJSON\_GetObjectItem(subobject,"wind\_scale")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"clouds"
					if((item = cJSON\_GetObjectItem(subobject,"clouds")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
					
					//匹配子对象2成员"dew\_point"
					if((item = cJSON\_GetObjectItem(subobject,"dew\_point")) != NULL) 
					{
						printf("%s : %s\n",item->string,item->valuestring);
					}
				}
				/\* 匹配子对象3 \*/
				if((subobject = cJSON\_GetObjectItem(object,"last\_update")) != NULL)
				{
					printf("----------------------------last\_update----------------------------\n");
					printf("%s : %s\n\n",subobject->string,subobject->valuestring);
				}
			} 
		}
	}
	
	cJSON\_Delete(json); //释放cJSON\_Parse()分配出来的内存空间
	
	return 0;
}


这个测试程序会去读取我们工程目录下的 now.txt件,所以事先我们需要把JSON格式的天气预报数据复制到该文件中:

======0010

now.txt里面的数据读出并保存到data指向的动态内存中。然后再把data中的数据传入我们事先编写好的解析天气数据的函数int cJSON_WeatherParse(char *JSON)中进行解析,最后把解析之后的数据给到该函数的返回值即可。

解析函数里主要用到以下函数:
1、cJSON_Parse函数

cJSON\*cJSON\_Parse(const char \*value);

该函数用来解析JSON数据包,并按照cJSON结构体的结构序列化整个数据包。

2、cJSON_GetObjectItem函数

cJSON\_GetObjectItem(cJSON \*object,const char \*string);

该函数可从cJSON结构体中查找某个子节点名称(键名称),如果查找成功可把该子节点序列化到cJSON结构体中。

3、cJSON_GetArraySize函数

cJSON\_GetArraySize(const cJSON \*array);

该函数可获取数组中元素个数。

4、cJSON_GetArrayItem函数

cJSON\_GetArrayItem(const cJSON \*array, int index);

该函数可获取数组中的内容。

5、cJSON_Delete函数

cJSON\_Delete(cJSON \*c); 

该函数用来释放cJSON_Parse函数内部申请的堆内存。

我们的解析函数主要运用多次cJSON_GetObjectItem来匹配各对象成员,然后取出各个键值对的值valuestring

该程序的运行结果如下:

======011

可见,解析完全正确!解析结果中冒号后面的数据就是我们可以选择使用的数据。这是解析当天的天气实况数据,解析未来几天的天气数据包或是其它天气数据包的方法都是类似的。

2.2 显示部分

2.2.1 几类常用的显示屏

液晶显示屏的接口较为常见的有 3 种类型:RGB 接口MCU 总线接口串口 HMI

(1)RGB 接口

======012

RGB 接口必须用在带有RGB驱动的ARM芯片上,一般的 ARM9 芯片有少许支持 RGB 的,ARM9 以上的芯片多数支持 RGB.但是此类接口的驱动是最复杂的,对硬件要求也是最高的。

(2)MCU 总线接口

======013

MCU 总线接口驱动比 RGB 简单一些,对硬件也基本没有任何要求,简单的 MCU 就可以驱动。但是界面的显示驱动工作量很大

总线型接口的屏只提供点阵的操作。图片,字符等任何显示内容都是通过取模数据,在屏幕上相应的位置把点阵一个一个的打出来。在此基础上再来实现人机界面的逻辑。工作量很大。

(3)串口 HMI

======014

串口HMI是一种新的显示方案。首先它跟MCU总线屏一样对硬件没有任何要求,其次。它没有速度瓶颈,因为界面的显示是设备内部自己实现的,用户MCU只是发送指令,并不需要底层驱动。

再次,针对显示的人机界面的布局和大多数的逻辑(比如界面背景,按钮效果,文本显示等)。全部都不需要用户的 MCU 参与,使用设备提供的上位软件,在电脑上点几下鼠标就完成了。制作好资源文件以后下载到屏幕即可自动运行,剩下的就是 USART 交互了。

2.2.2 本系统的显示方案

本系统选择的是串口HMI这一显示方案。因为这种方案确实是可以在短时间内设计出比较漂亮的GUI界面。GUI界面设计软件如下图:

======015

这是串口屏商家给的配套的GUI设计软件,该软件的下载链接:

链接:pan.baidu.com/s/1uYFhF412…
提取码:e70r

我们可以从左侧的工具箱里往工作区里拖拽需要用到的控件,常用的控件有文本控件、文字控件、按钮控件等。可在右侧的属性窗口设置控件的属性。可以通过选择不同的字库来设置不同的字体样式。

控件、页面的切换或则触发可能会产生相应的事件,可以通过代码来控制。其中,页面、控件的背景是可以上传本地的图片的,所以可以事先通过PS或则其他作图软件设计出精美的背景图片,然后再把控件都设置为透明色,最终地显示效果就可以达到很好的效果。

总之,可以很方便很容易设计出精美的GUI界面。同时,这个GUI设计软件还具有模拟真实的屏幕的功能,可以很方便地与用户MCU进行联调。仿真界面如图所示:

======016

进入模拟器界面,可在下方选择数据的输入方式为用户MCU输入,然后设置相应的串口号和波特率即可。还可以实时查看用户MCU传给模拟器的数据。

2.3 与天气服务器通信

每个问题的解决往往都不能一步到位,要把这个问题的所有关键点找出来,着手去解决这些关键点,最终问题自然会得到解决。

同样的,虽然我们最终是用单片机控制WiFi模块来获取天气数据的,但是我们首先应该确保在没有单片机的情况下能获取得到天气数据,确保能和天气服务器正常通信。只有这样,在使用单片机获取数据遇到问题时才知道出错的范围在哪,便于我们进行调试。下面,分享windows下与天气服务器通信的测试方法:

2.3.1 所需的工具

网络调试助手。本人使用的是SocketToolSocketTool是一款小巧实用且功能强大的TCP/UDP网络通讯调试工具,可以帮助你检查网络应用软件及硬件的通讯情况,可以创建Socket服务器,如创建UDP组播地址及端口、创建UDP Client客户端、创建TCP Client、创建TCP Server。

======017

该工具下载链接为:

链接:pan.baidu.com/s/1fgarl8xN…
提取码:np5v

2.3.2 测试方法

(1)首先,使用SocketTool工具建立一个TCP Client,对方IP设为:116.62.81.138(这是心知天气服务器的IP地址),对方端口设为80。如:

======100

怎么才能知道一个网站的IP呢?在DOS黑窗口下输入ping+域名即可得该域名对应的IP,如我们ping百度:

======018

ping是Windows、Unix和Linux系统下的一个命令,利用ping命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障,该命令还可以加许多参数使用,具体是键入Ping按回车即可看到详细说明。

(2)发送HTTP请求,向心知天气服务器请求天气数据。HTTP有几种请求方法,我们这里使用的是GET请求:

GET请求:从指定的资源请求数据。

具体的请求方法示例为:

GET https://api.seniverse.com/v3/weather/now.json?key=2owqvhhd2dd9o9f9&location=beijing&language=zh-Hans&unit=c

其中,GET后面的URL地址可以上心知天气查看,如:

======019

URL中的几个参数是可以设置的:

key:你的API密钥
location:所查询的地点
language:语言
unit:单位

SocketTool工具中发送GET请求(首先先得点击连接按钮进行连接),发送格式如下:

======020

需要注意的问题就是每个GET请求之后都需要空两行,这两个换行也是GET请求的一部分,所以在编写单片机代码时需要注意的是要在这个请求字符串后添加\r\n\r\n,表示换两行。

服务器返回的天气数据为:

======021

看看服务器返回的数据,发现中文都是乱码。原因是获取得的天气数据是utf-8格式,必须转换为GBK格式中文才能正常显示。此处,我们只是测试与服务器是否能正常交互,测试结果显示有数据返回,说明通过以上的GET请求时可以获取到数据的。

在应用到单片机上时,还需要考虑的问题就是怎么把utf-8格式转换为GBK格式的问题,转换后中文才能正常显示在显示屏上。

2.4 语音对话功能

本系统人机对话功能采用了两个硬件模块:(1)语音识别模块:采用LD3320语音识别芯片;(2)语音合成模块:采用SYN6288语音合成芯片。

2.4.1 语音识别

本系统语音识别模块采用的语音芯片是LD3320。该芯片已经集成了语音识别的处理器,不需要外接其他的辅助芯片如Flash、 RAM 等,直接嵌入在现有的产品中就可以实现语音识别的功能。

语音识别的过程为:
(1)先预存要识别的关键词,如:

//-------------------------搜索天气-----------------------------------
#define STR00 "xiao tian" // 小天
#define STR01 "sou suo fu zhou tian qi" // 搜索福州天气
#define STR02 "sou suo shang hai tian qi" // 搜索上海天气
#define STR03 "sou suo shen zhen tian qi" // 搜索深圳天气
#define STR04 "sou suo bei jing tian qi" // 搜索北京天气
#define STR05 "sou suo guang zhou tian qi" // 搜索广州天气
#define STR06 "sou suo nan ning tian qi" // 搜索南宁天气
#define STR07 "sou suo xia men tian qi" // 搜索厦门天气
#define STR08 "sou suo quan zhou tian qi" // 搜索泉州天气
#define STR09 "sou suo pu tian tian qi" // 搜索莆田天气
#define STR10 "sou suo nan ping tian qi" // 搜索南平天气

可以预存50条关键词(关键句),本人已经把关键词写死在程序里了,这显然就不能灵活的面对各种场景。其实可以通过代码编写一个学习功能,即识别之前首先进行学习一些即将要识别的关键词,然后在进行识别演示,这样就可以应对比较多的场景。

但是,这样还是不够智能,毕竟只能识别已经预存的关键词(关键句),要是没有预存就没办法识别了。所以真正的语音识别应该是在软件算法上下功夫,关于语音识别已然成为热门的一大研究专题,这就属于人工智能的范畴吧。希望以后可以有机会接触这一块,如有接触再做学习分享~

(2)开始识别,如:

static void Task\_ASR(void)
{
 switch(nAsrStatus)
 {
   case LD_ASR_RUNING:    
   case LD_ASR_ERROR:    
     break;
   case LD_ASR_NONE:
     nAsrStatus=LD_ASR_RUNING; 
     if (RunASR()==0)  // 启动一次ASR识别流程:ASR初始化,ASR添加关键词语,启动ASR运算
     {    
       nAsrStatus = LD_ASR_ERROR;
     }
     break;

   case LD_ASR_FOUNDOK:
     nAsrRes = LD\_GetResult( );  // 一次ASR识别流程结束,去取ASR识别结果 
     ASRSuccess\_Handle(nAsrRes);
     nAsrStatus = LD_ASR_NONE;  
   break;
   
   case LD_ASR_FOUNDZERO:
   default:
     nAsrStatus = LD_ASR_NONE;  
     break;
 }
}



![img](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/da18443a3ef74296b25096a1ddbc7986~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771259891&x-signature=gXTQ5FT%2BfFNP9QaCKfhUeVGFFIk%3D)
![img](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/cc884335c76a4494b36bfb3c8146861a~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771259891&x-signature=vvLQmbfcC1Z7auqd7dAmB60dbnU%3D)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://gitee.com/vip204888)**