1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取!1、作品介绍 首先,看一下作品的演示视频:演示视频
温馨提示:因为是进行人机对话演示,所以应打开音量观看哈。
这是本人的毕业设计,一个智能的天气预报系统。显示屏上显示各种天气指标及实时显示时间日期等。可以使用触摸屏输入城市名称搜索天气,也可以使用语音搜索天气。
1.1 系统功能
作品包含的的功能有:
(1)实时天气显示,温湿度显示,日历显示;
(2)收音机功能;
(3)人机对话功能。
系统框图如下:
文章图片
1.2 系统GUI界面
(1)主界面
文章图片
你没有看错,就是99℃,就是星期八。但这不是系统出错,而是本人故意设置的初始值,每当开机收到天气数据之后就可以看出有明显的变化。
(2)菜单界面
文章图片
(3)wifi设置界面
【电赛|作品 | 基于STM32的智能天气预报系统(源码开源)】
文章图片
点击文本框会进入键盘界面,输入WiFi信息之后返回,再点击
Add
按钮即可发送WiFi名称与密码给控制器,控制器控制WiFi模块连接WiFi热点。(4)收音机界面
文章图片
通过点击下方频率点跳到相应频率,再通过左右按钮调节频率至所需频率。
2、作品实现 2.1 天气数据获取及解析
2.1.1 天气数据从哪来?
天气数据可以从一些专门做天气预报的网站获取,如心知天气、和风天气等。本人选择的是心知天气
https://www.seniverse.com/网站首页如下:
文章图片
我们是通过其
API密钥
才能获取得到其天气数据,而只有注册的用户才拥有API密钥,所以必须得注册,可以点击右上角进行注册。2.1.2 天气数据是什么格式?
登录
心知天气
网站之后,点击菜单导航中的数据->常规数据
即可查看API文档
。在API文档页面的左侧可看到一些可查看的条目,如:文章图片
可点击
天气实况
查看其相关说明,可以看到其天气数据格式如下图所示:文章图片
这就是JSON格式的数据,不了解JSON的朋友可查看上一篇笔记:JSON的简单认识
2.1.3 如何解析得到有用的数据?
从上图中的JSON格式天气数据包中我们可以看出:我们需要用到的数据就是
冒号后面的字符串数据
,这些数据是我们需要获取并显示到屏幕上的数据。那么,我们该怎么从这一堆JSON格式数据中解析出冒号后面的字符串呢?并且,这个系统是基于单片机的天气预报系统。而单片机使用C语言进行编程开发的,所以我们得使用C语言对这些JSON天气数据包进行解析。
其实,有一个专门解析JSON数据包的第三方C语言库。我们可以使用这个库进行解析,这个CJSON库的下载链接为:
链接:https://pan.baidu.com/s/1DQynsdlNyIvsVXmf4W5b8Q只要把
提取码:ww4z
cJSON.c
与cJSON.h
放到工程主程序所在目录,然后在主程序中包含头文件JSON.h
即可引入该库。如:文章图片
下面给出一个实例:
测试代码:
/****************************************************************************************------------------------------------------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
#include
#include
#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 = https://www.it610.com/article/(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格式的天气预报数据复制到该文件中:文章图片
把
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
。该程序的运行结果如下:
文章图片
可见,解析完全正确!解析结果中冒号后面的数据就是我们可以选择使用的数据。这是解析当天的天气实况数据,解析未来几天的天气数据包或是其它天气数据包的方法都是类似的。
2.2 显示部分 2.2.1 几类常用的显示屏
液晶显示屏的接口较为常见的有 3 种类型:
RGB 接口
,MCU 总线接口
,串口 HMI
。(1)RGB 接口
文章图片
RGB 接口必须用在
带有RGB驱动的ARM芯片
上,一般的 ARM9 芯片有少许支持 RGB 的,ARM9 以上的芯片多数支持 RGB.但是此类接口的驱动是最复杂
的,对硬件要求也是最高
的。(2)MCU 总线接口
文章图片
MCU 总线接口驱动比 RGB 简单一些,对硬件也基本没有任何要求,简单的 MCU 就可以驱动。但是界面的显示
驱动工作量很大
。总线型接口的屏只提供点阵的操作。图片,字符等任何显示内容都是通过取模数据,在屏幕上相应的位置把点阵一个一个的打出来。在此基础上再来实现人机界面的逻辑。工作量很大。
(3)串口 HMI
文章图片
串口HMI是一种新的显示方案。首先它跟MCU总线屏一样
对硬件没有任何要求
,其次。它没有速度瓶颈
,因为界面的显示是设备内部自己实现的,用户MCU只是发送指令,并不需要底层驱动。再次,针对显示的人机界面的布局和大多数的逻辑(比如界面背景,按钮效果,文本显示等)。全部都不需要用户的 MCU 参与,使用设备提供的上位软件,在电脑上点几下鼠标就完成了。制作好资源文件以后下载到屏幕即可自动运行,剩下的就是 USART 交互了。
2.2.2 本系统的显示方案
本系统选择的是
串口HMI
这一显示方案。因为这种方案确实是可以在短时间内设计出比较漂亮的GUI界面。GUI界面设计软件如下图:文章图片
这是串口屏商家给的配套的GUI设计软件,该软件的下载链接:
链接:https://pan.baidu.com/s/1uYFhF412WVkk0FuqeOAGCg我们可以从左侧的工具箱里往工作区里拖拽需要用到的控件,常用的控件有文本控件、文字控件、按钮控件等。可在右侧的属性窗口设置控件的属性。可以通过选择不同的字库来设置不同的字体样式。
提取码:e70r
控件、页面的切换或则触发可能会产生相应的事件,可以通过代码来控制。其中,页面、控件的背景是可以上传本地的图片的,所以可以事先通过PS或则其他作图软件设计出精美的背景图片,然后再把控件都设置为透明色,最终地显示效果就可以达到很好的效果。
总之,可以很方便很容易设计出精美的GUI界面。同时,这个GUI设计软件还具有模拟真实的屏幕的功能,可以很方便地与用户MCU进行联调。仿真界面如图所示:
文章图片
进入模拟器界面,可在下方选择数据的输入方式为
用户MCU输入
,然后设置相应的串口号和波特率即可。还可以实时查看用户MCU传给模拟器的数据。2.3 与天气服务器通信 每个问题的解决往往都不能一步到位,要把这个问题的所有关键点找出来,着手去解决这些关键点,最终问题自然会得到解决。
同样的,虽然我们最终是用单片机控制WiFi模块来获取天气数据的,但是我们首先应该确保在没有单片机的情况下能获取得到天气数据,确保能和天气服务器正常通信。只有这样,在使用单片机获取数据遇到问题时才知道出错的范围在哪,便于我们进行调试。下面,分享windows下与天气服务器通信的测试方法:
2.3.1 所需的工具
网络调试助手
。本人使用的是SocketTool
,SocketTool
是一款小巧实用且功能强大的TCP/UDP
网络通讯调试工具,可以帮助你检查网络应用软件及硬件的通讯情况,可以创建Socket服务器,如创建UDP组播地址及端口、创建UDP Client客户端、创建TCP Client、创建TCP Server。文章图片
该工具下载链接为:
链接:https://pan.baidu.com/s/1fgarl8xNb6nEAl3Ly0NQ3w2.3.2 测试方法
提取码:np5v
(1)首先,使用
SocketTool
工具建立一个TCP Client
,对方IP设为:116.62.81.138
(这是心知天气服务器的IP地址),对方端口设为80。如:文章图片
怎么才能知道一个网站的IP呢?在DOS黑窗口下输入
ping+域名
即可得该域名对应的IP,如我们ping
百度:文章图片
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地址可以上心知天气查看,如:
文章图片
URL中的几个参数是可以设置的:
key:你的API密钥在
location:所查询的地点
language:语言
unit:单位
SocketTool
工具中发送GET请求
(首先先得点击连接
按钮进行连接),发送格式如下:文章图片
需要注意的问题就是每个GET请求之后都需要空两行,这两个换行也是GET请求的一部分,所以在编写单片机代码时需要注意的是要在这个请求字符串后添加
\r\n\r\n
,表示换两行。服务器返回的天气数据为:
文章图片
看看服务器返回的数据,发现中文都是乱码。原因是获取得的天气数据是utf-8格式,必须转换为GBK格式中文才能正常显示。此处,我们只是测试与服务器是否能正常交互,测试结果显示有数据返回,说明通过以上的GET请求时可以获取到数据的。
在应用到单片机上时,还需要考虑的问题就是怎么把utf-8格式转换为GBK格式的问题,转换后中文才能正常显示在显示屏上。
2.4 语音对话功能 本系统人机对话功能采用了两个硬件模块:(1)语音识别模块:采用
LD3320
语音识别芯片;(2)语音合成模块:采用SYN6288
语音合成芯片。2.4.1 语音识别
本系统语音识别模块采用的语音芯片是LD3320。该芯片已经集成了语音识别的处理器,不需要外接其他的辅助芯片如Flash、 RAM 等,直接嵌入在现有的产品中就可以实现语音识别的功能。
语音识别的过程为:
(1)先预存要识别的关键词,如:
//-------------------------搜索天气-----------------------------------
#defineSTR00"xiao tian"// 小天
#defineSTR01"sou suo fu zhou tian qi"// 搜索福州天气
#defineSTR02"sou suo shang hai tian qi"// 搜索上海天气
#defineSTR03"sou suo shen zhen tian qi"// 搜索深圳天气
#defineSTR04"sou suo bei jing tian qi"// 搜索北京天气
#defineSTR05"sou suo guang zhou tian qi"// 搜索广州天气
#defineSTR06"sou suo nan ning tian qi"// 搜索南宁天气
#defineSTR07"sou suo xia men tian qi"// 搜索厦门天气
#defineSTR08"sou suo quan zhou tian qi"// 搜索泉州天气
#defineSTR09"sou suo pu tian tian qi"// 搜索莆田天气
#defineSTR10"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;
}
}
nAsrStatus
是用来表示语音识别的状态,不是LD3320
芯片内部的状态寄存器。nAsrStatus
有几种情况。我们比较关注的是LD_ASR_FOUNDOK
状态。LD_ASR_FOUNDOK
状态为识别成功,识别成功后将调用ASRSuccess_Handle
函数进行识别后的操作。(3)识别成功则执行相应操作,如
void ASRSuccess_Handle(uint8 asr_code)
{
printf("\r\n识别码:%d\n",asr_code);
if(0 == asr_code)
{
printf("我在,需要我的帮助吗?\n");
TTSPlay(0, "[t3][2]我在,[2]需要[2]我的[3]帮助吗");
RunFlag = TRUE;
}
else if(RunFlag)
{
RunFlag = FALSE;
/* 识别码0-10为搜索天气识别码 */
if(asr_code>=0&&asr_code<=10)
{
switch(asr_code)
{
case CODE01:
printf("“福州”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索福州天气");
memcpy(g_city,"fujianfuzhou",sizeof(g_place));
break;
case CODE02:
printf("“上海”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索上海天气");
memcpy(g_city,"shanghai",sizeof(g_place));
break;
case CODE03:
printf("“深圳”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索深圳天气");
memcpy(g_city,"shenzhen",sizeof(g_place));
break;
case CODE04:
printf("“北京”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索北京天气");
memcpy(g_city,"beijing",sizeof(g_place));
break;
case CODE05:
printf("“广州”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索广州天气");
memcpy(g_city,"guangzhou",sizeof(g_place));
break;
case CODE06:
printf("“南宁”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索南宁天气");
memcpy(g_city,"nanning",sizeof(g_place));
break;
case CODE07:
printf("“厦门”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索厦门天气");
memcpy(g_city,"xiamen",sizeof(g_place));
break;
case CODE08:
printf("“泉州”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索泉州天气");
memcpy(g_city,"quanzhou",sizeof(g_place));
break;
case CODE09:
printf("“莆田”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索莆田天气");
memcpy(g_city,"putian",sizeof(g_place));
break;
case CODE10:
printf("“南平”命令识别成功\r\n");
TTSPlay(0, "[t3][2]小天正在为您搜索南平天气");
memcpy(g_city,"nanping",sizeof(g_place));
break;
}
memset(&weather_data, 0, sizeof(weather_data));
GET_NowWeather();
GET_DailyWeather();
GetWeatherTimer = TIMER1_HOUR;
DisplayWeather(weather_data);
DisplayWeatherIcon(weather_data);
}
else
{
switch(asr_code)
{
case CODE11:
printf("“语音播报天气”命令识别成功\r\n");
printf("%s\n",g_WeatherText);
//TTSPlay(0, (uint8_t*)g_WeatherText);
break;
case CODE12:
printf("“今天的气温是多少”命令识别成功\r\n");
break;
default:
TTSPlay(0, "语音识别失败,请对准麦克风说话!");
break;
}
} }
}
2.4.2 语音合成
本系统的语音合成模块采用SYN6288语音合成芯片,支持文本直接转化为语音。其与单片机的通信方式为串口通信。向该模块发送以下格式的数据包:
5字节帧头+文本+1字节校验,文本字节数小于等于200字节即可合成语音。代码如:
void TTSPlay(uint8_t Music,uint8_t *Text)
{
/****************需要发送的文本**********************************/
uint8_t DataPacket[50];
//
uint8_t Text_Len;
uint8_t ecc= 0;
//定义校验字节
uint8_t i=0;
Text_Len =strlen((const char*)Text);
//需要发送文本的长度 /*****************帧固定配置信息**************************************/
DataPacket[0] = 0xFD ;
//构造帧头FD
DataPacket[1] = 0x00 ;
//构造数据区长度的高字节
DataPacket[2] = Text_Len + 3;
//构造数据区长度的低字节
DataPacket[3] = 0x01 ;
//构造命令字:合成播放命令
DataPacket[4] = 0x01 | Music<<4 ;
//构造命令参数:背景音乐设定 /*******************校验码计算***************************************/
for(i = 0;
i<5;
i++)//依次发送构造好的5个帧头字节
{
ecc=ecc^(DataPacket[i]);
//对发送的字节进行异或校验
} for(i= 0;
i
调用方式如:
TTSPlay(0, "[t3][2]小天正在为您搜索福州天气");
3、代码获取 以上就是关于该作品比较重要的一些内容的分享,由于篇幅太长其他功能不做介绍,有兴趣的朋友可以查看源码。源码链接:
https://github.com/zhengnianli/stm32_weather我的微信公众号:嵌入式大杂烩
文章图片