基于STM32+ESP8266+TFTLCD的天气预报显示

前言

最近发现自己学的东西都太杂了,真正开始找工作,才意识到自己应该精通某样技能。认真开始使用STM32做小项目后才发现自己有好多东西不知道,感觉自己是真的菜。虽然这也不是新的技术,但是真正要做到精通,其中道理要研究的东西还是蛮多的。并且好久没有更新文章了,最近在知乎上看到别人使用STM32做了一个实时疫情的显示屏,想着自己也动手做一个显示天气预报的装置。工程源码见文末。

项目思路

STM32与ESP8266通过串口连接,STM32通过串口向ESP8266发送指令:连接AP,创建TCP连接,创建SSL连接,发送GET请求获取天气数据,STM32解析JSON数据,将天气数据显示在TFTLCD屏幕上。屏幕可显示最近三天的天气情况和显示实时24小时天气(最多显示至第12小时),使用按键来切换,显示不同的天气页面。

效果演示

系统启动默认显示当前时间之后三个小时的天气信息
在这里插入图片描述
按下切换按钮显示下三个小时的天气信息
在这里插入图片描述
按下切换按钮显示逐日天气信息
在这里插入图片描述

心知天气API

我使用心知天气获取天气信息,由于STM32使用cJSON需要消耗大量内存,如果获取的数据量过大会导致JSON数据解析失败的结果,而这个API可以根据自己的要求获取一定数量的数据,不仅可以获取24小时逐小时天气数据,还可以获取逐日天气数据。
逐日天气预报参数表
在这里插入图片描述
24小时逐小时天气预报
在这里插入图片描述

硬件部分

材料

STM32F103ZET6开发板,ESP8266-01,2.8寸TFTLCD屏幕。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

硬件连接

ESP8266-RX->PB10
ESP8266-TX->PB11
ESP8266-CH_PD->3V3
ESP8266-3V3->3V3
ESP8266-GND->GND在这里插入图片描述

软件部分

ESP8266AT固件指令

  1. 连接ap指令:AT+CWJAP=“SSID”,“password”
  2. 创建TCP连接:AT+CIPSTART=“TCP”,“www.domain.com”,80
  3. 创建SSL连接:AT+CIPSTART=“SSL”,“www.domain.com”,443
  4. 设置为透传模式:AT+CIPMODE=1
  5. 发送数据:AT+CIPSEND
  6. 直接向串口发送GET请求
  7. 退出透传模式:
    +++
    AT+CIPMODE=0
  8. 断开TCP连接:AT+CIPCLOSE
  9. 断开SSL连接:AT+CIPCLOSE

页面布局

/*
显示实时天气
*/
void showRTweather(){
	char path[15];
	u8 i,x0=20,x1=50;
	LCD_Fill(0,22,320,240,WHITE);	
	for(i=0;i<3;i++){
		LCD_ShowString(20+i*110,22,64,16,16,(u8*)weather.time[i]);
		sprintf(path,"0:/ICONS/%s.bmp",weather.code[i]);
		ai_load_picfile((const unsigned char*)path,x0+i*110,40,60,60,1);	
		
		Show_Str(10+i*110,130,32,16,"天气",16,0);
		LCD_ShowChar(42+i*110,130,':',16,0);
		Show_Str(x1+i*110,130,32,16,(u8*)WeatherCode[u8Tou16(weather.code[i])],16,0);
		Show_Str(10+i*110,150,32,16,"温度",16,0);
		LCD_ShowChar(42+i*110,150,':',16,0);
		LCD_ShowString(x1+i*110,150,32,16,16,(u8*)weather.temperature[i]);
		Show_Str(10+i*110,170,32,16,"湿度",16,0);
		LCD_ShowChar(42+i*110,170,':',16,0);
		LCD_ShowString(x1+i*110,170,32,16,16,(u8*)weather.humidity[i]);
	}		
}
/*
显示逐日天气
*/
void showRDweather(){
	char path[15];
	u8 i,x0=20,x1=50;
	LCD_Fill(0,22,320,240,WHITE);
	for(i=0;i<3;i++){
		Show_Str(30+i*110,22,32,16,(u8*)date[i],16,0);
		sprintf(path,"0:/ICONS/%s.bmp",RDweather.code_day[i]);
		ai_load_picfile((const unsigned char*)path,x0+i*110,40,60,60,1);
		
		Show_Str(10+i*110,130,32,16,"白天",16,0);
		LCD_ShowChar(42+i*110,130,':',16,0);
		Show_Str(x1+i*110,130,32,16,(u8*)WeatherCode[u8Tou16(RDweather.code_day[i])],16,0);
		Show_Str(10+i*110,150,32,16,"夜晚",16,0);
		LCD_ShowChar(42+i*110,150,':',16,0);
		Show_Str(x1+i*110,150,32,16,(u8*)WeatherCode[u8Tou16(RDweather.code_night[i])],16,0);
		Show_Str(10+i*110,170,32,16,"最高",16,0);
		LCD_ShowChar(42+i*110,170,':',16,0);
		LCD_ShowString(x1+i*110,170,32,16,16,(u8*)RDweather.high[i]);
		Show_Str(10+i*110,190,32,16,"最低",16,0);
		LCD_ShowChar(42+i*110,190,':',16,0);
		LCD_ShowString(x1+i*110,190,32,16,16,(u8*)RDweather.low[i]);
		Show_Str(10+i*110,210,32,16,"湿度",16,0);
		LCD_ShowChar(42+i*110,210,':',16,0);
		LCD_ShowString(x1+i*110,210,32,16,16,(u8*)RDweather.humidity[i]);
	}
}
/*
刷新当前页面
*/
void refresh(){
	if(thispage==-1){
		getRDWeather();
		showRDweather();
	}else{
		getRTWeather(thispage);
		showRTweather();
	}
}
/*
thispage=-1,0,3,6,9
*/
void switchWeather(){
	u8 key=0; 
		key=KEY_Scan(0);  //扫描按键按下
		if(key==1){  
				if(thispage<0){  //-1代表逐日天气页面,按下KEY1跳转0页面
					getRTWeather(0);
					showRTweather();	
					thispage=0;
				}else{  //跳转至-1页面
					getRDWeather();  //获取逐日时间
					showRDweather(); //显示逐日时间
					thispage=-1;
				}					
		}				
		if(key==2){
				if(thispage==-1){  
					getRTWeather(0);
					showRTweather();
					thispage=0;
				}else{
					if(thispage==9){ //最多显示到第9+3小时的天气
						thispage=0;
						getRTWeather(thispage);
						showRTweather();		
					}else{  //每个页面显示三个小时的天气
						thispage=thispage+3;
						getRTWeather(thispage);
						showRTweather();					
					}
				}
		}		
}
/*
日期时间显示
*/
void gui_load(){
	LCD_DrawLine(0,20,320,20);							//
	LCD_ShowNum(0,0,Localtime.year,4,16);
	LCD_ShowStr(32,0,3);  //年
	LCD_ShowNum(48,0,Localtime.month,2,16);
	LCD_ShowStr(64,0,2);  //月
	LCD_ShowNum(80,0,Localtime.day,2,16);
	LCD_ShowStr(96,0,4);  //日
	
	LCD_ShowNum(130,0,Localtime.hour,2,16);
	LCD_ShowStr(146,0,5); //时
	LCD_ShowNum(162,0,Localtime.min,2,16);
	LCD_ShowStr(178,0,6); //分
	LCD_ShowNum(194,0,Localtime.sec,2,16);
	LCD_ShowStr(210,0,7); //秒	
}

JSON数据解析

时间JSON数据格式:

{
  "code": 200,
  "msg": "success",
  "newslist": [
    {
      "country": "中国",
      "city": "上海",
      "timeZone": "GMT +8",
      "strtime": "2020-09-26 15:35:07",
      "timestamp": 1601105707,
      "weeknum": "6"
    }
  ]
}

天气JSON数据格式:

{
	"results": [{
		"location": {
			"id": "WTW3SJ5ZBJUY",
			"name": "上海",
			"country": "CN",
			"path": "上海,上海,中国",
			"timezone": "Asia/Shanghai",
			"timezone_offset": "+08:00"
		},
		"hourly": [{
			"time": "2020-09-30T17:00:00+08:00",
			"text": "晴",
			"code": "0",
			"temperature": "25",
			"humidity": "55",
			"wind_direction": "北",
			"wind_speed": "19.80"
		}, {
			"time": "2020-09-30T18:00:00+08:00",
			"text": "晴",
			"code": "1",
			"temperature": "23",
			"humidity": "57",
			"wind_direction": "北",
			"wind_speed": "16.56"
		}]
	}]
}

解析数据并储存在结构体中

u8 getRDWeather(){	
	cJSON *root,*results,*result_arr,*result0,*results_arr,*item;
	u8 error=0;
	int i=0;
	ConSSL();
	delay_ms(500);
	SetPassThrough();
	memset(USART_RX_BUF3,0,strlen((const char*)USART_RX_BUF3));
	USART_RX_STA3 = 0;
	Usart_SendString(USART3,"GET https://api.seniverse.com/v3/weather/daily.json?key=SRpZAIwb07j1twRHA&location=shanghai&language=zh-Hans&unit=c&start=0&days=3\r\n\r\n");   //获取当地天气
	delay_ms(1000);
	root=cJSON_Parse((char*)USART_RX_BUF3); //解析收到的JSON数据
	RstPassThrough(); //关闭透传
	if(root!=0){			
			results=cJSON_GetObjectItem(root,"results");  //获取JSON对象的results属性
			results_arr=cJSON_GetArrayItem(results,0);
			result_arr=cJSON_GetObjectItem(results_arr,"daily");
			if(result_arr->type==cJSON_Array){
				for(i=0;i<3;i++){
					result0=cJSON_GetArrayItem(result_arr,i);
					
					item=cJSON_GetObjectItem(result0,"code_day");
					memcpy(RDweather.code_day[i],item->valuestring,strlen(item->valuestring)); //属性数据复制到结构体中
					
					item=cJSON_GetObjectItem(result0,"code_night");
					memcpy(RDweather.code_night[i],item->valuestring,strlen(item->valuestring));		
					printf("temperature:%s",weather.temperature[i]);

					item=cJSON_GetObjectItem(result0,"high");
					memcpy(RDweather.high[i],item->valuestring,strlen(item->valuestring));

					item=cJSON_GetObjectItem(result0,"low");
					memcpy(RDweather.low[i],item->valuestring,strlen(item->valuestring));					
					
					item=cJSON_GetObjectItem(result0,"humidity");
					memcpy(RDweather.humidity[i],item->valuestring,strlen(item->valuestring));	
					printf("humidity:%s",weather.humidity[i]);
								
					item=cJSON_GetObjectItem(result0,"wind_speed");
					printf("windspeed:%s\r\n",item->valuestring);  //
					memcpy(RDweather.wind_speed[i],item->valuestring,strlen(item->valuestring));		
					printf("wind_speed:%s\r\n",weather.wind_speed[i]); //wind_speed:傀?
				}
			}
		}else{
			error=1;
			printf("Error before: [%s]\n",cJSON_GetErrorPtr());
		}	
	cJSON_Delete(root);  //一定要释放JSON对象
	USART_RX_STA3 = 0;
	Usart_SendString(USART3,"AT+CIPCLOSE\r\n");	 //关闭连接
	return error;
}

时间数据解析与计时

/*
将u8字符串数字转换成u16类型数字
*/
u16 u8Tou16(char *str){
	u8 i;
	u16 num=0;
	u8 len=strlen(str);
	for(i=len;i>0;i--){
		num+=(*str-'0')*pow(10,i-1);  //pow次方函数
		str++;
	}
	return num;
}
/*
转换成字符串时间
*/
void Parse_Time(char *str){
	u8 i=0,j=0,k=0;
	char time[6][5]={"","","","","",""};
	//time=malloc();
	u8 len=strlen(str);
	for(i=0;i<len+1;i++){  //拆分字符串
		if(*str=='-'||*str==0x20||*str==':'||*str=='\0'){  
			time[j][k]='\0';
			j++;
			str++;
			k=0;
		}
		time[j][k]=*str;
		k++;
		str++;
	}	
	Localtime.year=u8Tou16(time[0]);
	Localtime.month=u8Tou16(time[1]);	
	Localtime.day=u8Tou16(time[2]);
	Localtime.hour=u8Tou16(time[3]);
	Localtime.min=u8Tou16(time[4]);
	Localtime.sec=u8Tou16(time[5]);	
}
/*
获取串口数据转换可用时间常数
0:获取成功
1:接口请求错误
2:非JSON格式
*/
u8 getTime(){
	cJSON *root,*result_arr,*result;
	u8 code=0,error=0;
	const char *TimeStr;
	char *time = (char *)malloc(sizeof(char) * 20);
	ConTCP();
	delay_ms(100);
	SetPassThrough();  //透传模式
	memset(USART_RX_BUF3,0,strlen((const char*)USART_RX_BUF3));
	USART_RX_STA3 = 0;
	Usart_SendString(USART3,"GET http://api.tianapi.com/txapi/worldtime/index?key=652e7ab06ae02d789f04e2b08dbfd5f6&city=上海\r\n\r\n"); //发送GET请求获取当前时间
	delay_ms(1000);
	if(USART_RX_BUF3[0]=='{'){	
		root=cJSON_Parse((const char*)USART_RX_BUF3);  //"{\"code\": 200,\"newslist\": [{\"strtime\": \"2020-09-26 19:02:16\",\"weeknum\": \"6\"}]}"
		RstPassThrough();
		if(root!=0){	
				code=cJSON_GetObjectItem(root,"code")->valueint;				
				if(code==200){
					result_arr=cJSON_GetObjectItem(root,"newslist");
					if(result_arr->type==cJSON_Array){					
						result=cJSON_GetObjectItem(result_arr,0);
						TimeStr=(const char *)cJSON_GetObjectItem(result,"strtime")->valuestring;
						Localtime.weeknum=(u8 *)cJSON_GetObjectItem(result,"weeknum")->valuestring;
				}			
			}else{ error=1;}
		}else{
			error=3;
			printf("JSON format error:%s\r\n", cJSON_GetErrorPtr());  //堆尺寸太小,导致转换失败
		}
		strcpy(time,TimeStr);	
		cJSON_Delete(root);
		Parse_Time(time);	
		TIM_Cmd(TIM2,ENABLE);  //获取到时间后,启动TIM2
	}else{ error=2;}
	USART_RX_STA3 = 0;
	Usart_SendString(USART3,"AT+CIPCLOSEMODE=0\r\n");  //关闭连接
	delay_ms(500);
	Usart_SendString(USART3,"AT+CIPCLOSE=0\r\n");
	return error;
}
void TimeRun(){
	if(Localtime.sec<60){
		Localtime.sec++;
	}
	if(Localtime.sec==60){  //分进位
		Localtime.sec=0;
		if(Localtime.min<60){
			Localtime.min++;
		}	
	}
	if(Localtime.min==60){  //时进位
		Localtime.min=0;
		if(Localtime.hour<24){
			Localtime.hour++;
		}
	}
	if(Localtime.hour==24){
		Localtime.hour=0;
	}	
}

遇到的问题

1、关于字符串操作内存溢出问题
一开始对指针的理解不深,调试时总是造成内存溢出的情况,还以为是分配的栈空间太小,最后查资料慢慢找到问题所在。字符串复制函数:memcpy函数,strcpy函数,两者都可以实现字符串的复制,由于C语言中字符串是以字符数组的形式储存,strcpy(time,TimeStr);不需要定义字符串长度,字符串长度函数中可以根据“\0”位置计算出来,如果time是一个没有定义的字符串指针,容易导致内存冲突,因为没有给time指向的数据分配空间。正确使用应该先给变量分配一定空间,要么使用动态内存分配函数分配一定长度的内存,要么定义一定长度的数组,如“char time[20];”或者“char *time = (char *)malloc(sizeof(char) * 20);”,在调用复制字符串函数就不会内存冲突。
2、sprintf函数内存溢出问题
使用sprintf函数也要注意先申请足够空间的内存来储存拼接后的字符串,因为sprintf函数不会考虑申请储存空间是否充足,不断地向后占用空间,如果超出了申请的内存空间就会导致内存冲突。
3、cJSON 解析时打印的串口JSON数据明明是对的,但是解析之后却是空的
因为是引用cJSON需要根据json数据动态申请大量的内存空间,动态申请的内存空间都是储存在堆区,如果分配的堆区尺寸过小就会导致转换结果为空。
在这里插入图片描述

4、使用指针总结
指针的本质就是地址,而字符串指针本质是字符串首字符地址,字符串指针和字符串数组差不多,也就是说,定义的字符串数组地址也是数组第一位的地址。至于像print函数传入一个字符串指针,就可以打印出整个字符串而不是打印出地址,因为字符串一般以’\0’结束,所以函数中会首先计算出字符串长度,根据长度一个字符一个字符的打印出来。
5、中文解码问题
在这里插入图片描述

LCD中文显示的字库采用的是GBK编码,GBK是兼容GB2312的,有时如果解码有问题就会导致屏幕上显示出与自己定义的文字不一致。发送HTTP请求其中采用的TTF-8编码,如果使用GB2312的编码发送GET请求就会请求不到数据。

工程源码
链接:https://pan.baidu.com/s/17sOk2epQEiIyu6FQoNo5gQ
提取码:81kj

个人能力有限,有什么错误的地方欢迎指正,有问题也可以提,可以一起探讨

  • 13
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
你可以使用 ESP8266 来获取心知天气的数据。以下是一些步骤供参考: 1. 首先,你需要在心知天气官网上注册并获取 API 密钥。这个密钥将用于访问心知天气的 API 接口。 2. 确保你的 ESP8266 设备已经连接到互联网,并且你已经在 Arduino IDE 中安装了 ESP8266 的开发环境。 3. 在 Arduino IDE 中创建一个新的项目,并添加 ESP8266 相关的库。 4. 在代码中引入必要的库,并定义你的 Wi-Fi SSID 和密码,以及心知天气的 API 密钥。 ```cpp #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <ArduinoJson.h> const char* ssid = "YourWiFiSSID"; const char* password = "YourWiFiPassword"; const char* apiKey = "YourHeartknowWeatherAPIKey"; ``` 5. 在 `setup()` 函数中,连接到 Wi-Fi 网络。 ```cpp void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi"); } ``` 6. 在 `loop()` 函数中,使用 HTTPClient 库发送 GET 请求获取心知天气的数据。 ```cpp void loop() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = "https://api.seniverse.com/v3/weather/now.json?key=" + String(apiKey) + "&location=your_location"; http.begin(url); int httpCode = http.GET(); if (httpCode > 0) { if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); // 在这里解析返回的 JSON 数据并提取相应的天气信息 // 使用 ArduinoJson 库进行 JSON 解析 StaticJsonDocument<200> doc; deserializeJson(doc, payload); JsonObject weather = doc["results"][0]["now"]; String weatherText = weather["text"]; int temperature = weather["temperature"]; Serial.print("Weather: "); Serial.println(weatherText); Serial.print("Temperature: "); Serial.println(temperature); } } else { Serial.println("Error on HTTP request"); } http.end(); delay(60000); // 每隔一分钟获取一次天气数据 } } ``` 请注意,上述代码中的 `your_location` 应替换为你想要获取天气信息的目标位置。 这只是一个简单的示例,你可以根据自己的需求进行修改和扩展。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值