关于ESP8266+OLED显示网络天气+天气图标,我掉进过的一些坑

就权当是给自己写的一个总结吧。
中间也走过不少的弯路,没人指导,只有自己慢慢摸索,网上看到的教程也是零零散散,或许是自己C代码功底不够硬,总之照搬别人的结果坑到最后还是自己,搞得一身疲惫,最后索性,按自己的想法来搞,废话不多说,先上几张图:
上边是0.96寸的OLED模块,下边是ESP8266,本页是开机默认页
第二页显示时间,日期和星期
第三页显示深圳今天天气,在此说明天气图标与心知天气返回的代号不一致,但原理一样,只是有用另一张图替换了
第四页,深圳明天天气预报,图标代码,温度,湿度数据都是心知天气上返回来的数据
后天天气预报,格式与前两页一样
最后现页显示更新时间
共有6页,MCU上两个按键控制,K1向前翻页,K2向后翻,第1页为默认页,是switch控制的默认页,然后第二页是RTC实时时钟,翻到这页数字时钟就会自动地跑起来--
其实它是在后台跑的,永不停歇,除非没电池了!第三页为今天的天气预报,其实是由ESP8266访问心知天气服务端口后返回来的数据解析得来,说起来这也是一个坑!因为心知天气返回来的是cJSON数据,要分析它的代码谈何容易,对于只会C代码的我来说除了用strstr来截取关键字外别无它法,后来不服呀,索性又学下怎么去解析cJSON数据,这一学又花了我一天时间,学着学着发现自己又掉入了另一个坑:因为要解析cJSON之前必须要先检查下返回来的数据包是否是cJSON数据包,明明是原封不动的返回来的数据包,得到的却是乱码,没错,中文全是乱码!--可想知,英文的方便,那些说用中文来替代代码的不知道他是怎么想,不是我看得起英文,而是我还得再花功夫去把这一包cJSON数据中的中文转换为GBK格式!--网上说cJSON的数据是UTF8格式的。
真是越搞事越多,一个头两个大!
要想转换为GBK,我发现并不是那么容易,纯C语言好难搞,即使弄好了,你会发现,为了那三五个中文正常显示,你的代码量或文字表格会多出几十K出来!这是我不能容忍的,索性用英文吧,把GET XXX,中的语言换为en,这下子整个代码过程顺利多了!有人说Linux上很简单呀,你是这么说,但我是用KEIL5.0开发的,那些个头文件看着都陌生!难道还要去找,去下载这些个鬼东西?英文他不香吗?
最后在取字模软件上取自己想要的文字然后再替换一些英文,一切得到完美解决!
我以为OLED上要显示的天气图标网络上会提供数据的,结果找了好久,没有,想想也对,人家怎么知道你要多大像素的?一想到这里发现自己好笨,笨哭了!一边用取模软件提取图片数据包一边在暗骂自己,活该你单身!--等等?关单身什么事?好像我总是把对方想得很完美?
就像这些代码,本身也不能算是很完美,ESP8266要玩得好,不是一朝一夕的事,可用命令指令集来开发也可以直接开发,将代码烤进ESP8266,用寄存器来开发,这是我下一个学习的目标。现在先看下用AT指令集的:
首先是我写了一个指针数组,将需要用到的指令全都集中在一起,不会指针数组的网上也有很多教程哈,这里就不再啰嗦了。

const u8 *WIFI_CMD_TAB[]={     //指针数组
	"AT",
	"AT+CWMODE=1",//设置为STA模式
	"AT+RST",//复位
	"AT+CIPMUX=0",//单连接模式
	"AT+CWJAP=\"iPhone\",\"123456788\"",//连入WIFI热点
	"AT+CIPSTART=\"TCP\",\"api.seniverse.com\",80",//建立TCP连接
	"AT+CIPMODE=1",//开启透传模式
	"AT+CIPSEND",//发送数据
	"GET https://api.seniverse.com/v3/weather/now.json?key=SPidi8cEM8Os4k3vn&location=shenzhen&language=en&unit=c",//这里语言改为英文,我的密钥也提供给大家用了。
//	"+++",//不能放这里,因为不需要换行回车
//	"AT+CIPCLOSE",
};
const u8 *WIFI_CMD_INFO[]={//一些信息提示
    "开始启动...",
	"启动成功!",
	"STA模式配置...",
	"STA模式配置成功!",
	"复位中...",
	"复位成功!",
	"配置为单连接模式...",
	"单连接模式配置成功!",
	"接入WIFI热点...",
	"连接热点成功!",
	"建立TCP连接...",
	"TCP链接成功!",
	"开启透传模式...",
	"透传模式开启成功!",
	"发送数据...",
	"信息返回成功!",

};

第二个指针数组是提示。这里多说一句,本人不才,在这里问一下大家,这个串口通信能不能同时复用两组引脚?比如USART1,它可以复用PA9,PA10还有PD5,PD6,如果我两组都想一起用行不行?我自己试了一下,结果搞得两组引脚都失灵了,屏闭掉其中一组,另外一组又好了,实在无语!如果不用两组引脚,那么我就无法在串口助手上打印我需要的信息,我记得在做GPS实验时是可行的,为什么这里就不行了呢?仔细缕一下思路发现了问题:就是说两组引脚共用一个USART,会出现两个输入和两个输出,USART在串口打印出来变成输出,而只要有输出了,它又会变成是给ESP8266输出命令的!这有点乱套了,反正我是试不出来,不知道哪位兄弟做出来过,告知我一下。
对了,这里我要说一下,我用的是STM32F407VET6。
后来,我又充分挖掘了我的这颗小脑袋的潜能,我找到了一种方法,用两个串口通信:USART1,和USART2。
USART1连接ESP8266,USART2配合USART1和串口助手,这样我就能在串口助手上看到了调试信息,还能用串口助手给ESP8266发送指令!具体做法请看本文后边的代码,总
之呢,没有人带,靠自己去摸索,坑真的是一个接着一个,比如,ESP8266,要用AT退出透传真的好难,发了三个“+++”--此处没有回车换行,它是一点反应都没有,看来是我对ESP8266学得不够精,我承认了!我只会按MCU上的复位键,然后再“+++”,这样好多了,每次都能退出了。
回到正题:我用一个for循环就把所有的AT指令发送出去,因为我发现ESP8266也不是吃素的,它反应很快,都是秒应答的,既然如此,我为什么不发快一点呢,所以我用for循环把指令发送出去,而且后期上边的设置可能也不会再走一遍吧?因为每次只有连上网了,前边的设置不已经是默认的吗?

void WIFI_Init(void)
{
	int i;
	u8 mark = 0;

	for(i=0;i<WIFI_CMD_TAB_LEN;i++)
	{
		
		u2_printf("%s\r\n",WIFI_CMD_INFO[i]);

		if(ESP8266_SendCmd((u8*)WIFI_CMD_TAB[i],"OK",20))
		{
			if((i == (WIFI_CMD_TAB_LEN-1))&& strstr((const char*)Rx_Buff,"res"))
			{
				u2_printf("数据接收成功!\r\n");
				Analyze_The_Current_Weather();
				break;
			}
			else if(ESP8266_SendCmd((u8*)WIFI_CMD_TAB[i],"OK",300))//再试一次
			{
				if(strstr((const char*)Rx_Buff,"res"))
				{
					u2_printf("数据接收成功!\r\n");
					Analyze_The_Current_Weather();
					Quit_tran();
					break;
				}
				u2_printf("%d)指令超时,连接失败!\r\n",i);
				OLED_ShowStr(3,3,"WIFI DISCONNECT",1);
				Quit_tran();
				break;
			}
		}

		GPIO_ToggleBits(GPIOA,GPIO_Pin_7);
	}
	if(mark)u2_printf("WIFI连接错误,请复位重新连接!\r\n");
}

这里再多说一句关于按键的,按键还是少用中断为好,因为你一旦用了中断就会去设置中断服务函数,就会有可能与其它的中断优先级产生冲突,比如,刚开始做OLED显示的时候,我是用K1,和K2的外部中断来控制换页的,按一下K1进入K1的中断服务函数,然后清OLED屏,然后页编号—++,(page为全局变量),那中断优先级怎么设置?因为清屏里边涉及到IIC,如果是软件模拟IIC还好一点,偶尔不会出问题,如果是硬件IIC,问题就出来了,一旦按键的中断优先级比IIC中断优先级高,那么它就会卡死在K1清屏函数这里!另一方面:中断服务函数中执行另一个函数的中断服务函数总觉得哪里怪怪的,想了一下干脆不用了,按键这么简单的东西,总不能栽在这里对吧?用KeySCan()轮询方法多好,省事又方便!代码很简单:

void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);

	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_4;
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOE,&GPIO_InitStructure);
	GPIO_SetBits(GPIOE,GPIO_Pin_3|GPIO_Pin_4);
}
u8 page=0;//page为全局变量
void KeyScan(void)
{
	if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==RESET)
	{
		delay_ms(10);
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==RESET)
		{
			OLED_Clear();
			page++;
		  if(page>6)page=1;
			
		}
	}
	else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==RESET)
	{
		delay_ms(10);
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==RESET)
		{
			OLED_Clear();
			page--;
			if(page==0)page=6;
		}
	}		
}

最后主函数用switch()达到换页显示的效果:

	while(1)
	{
		
		KeyScan();
		switch(page)
		{
			
			case 1:
			{
				RTC_ShowTime();
				break;
			}
			case 2:
			{
				Current_Weather_Show_0();
				break;
			}
			case 3:
			{
				Current_Weather_Show_1();
				break;
			}
				case 4:
			{
				Current_Weather_Show_2();
				break;
			}
			case 5:
			{
				Current_Weather_Show_3();
				break;
			}
			default:
			{
				OLED_ShowStr(30,3," WELCOME",2);
				break;
			}
}

case 1是显示RTC时钟的,网络上返回的时钟仅用来校正;


```c
void Current_Weather_Show_0(void)
{
	if(code>10)code=0;//本语句后期加入图标调整
	OLED_ShowBMP(15,1,code);//天气图标
	OLED_ShowCHinese(54,1,10);//深
	OLED_ShowCHinese(72,1,11);//圳
	OLED_ShowCHinese(90,1,12);//今
	OLED_ShowCHinese(108,1,13);//天
	OLED_ShowStr(10,6,buff[1],1);//天气状态
	OLED_ShowCHinese(54,4,17);//温
	OLED_ShowCHinese(72,4,19);//度
	OLED_ShowStr(92,5,buff[2],2);//温度值
	OLED_ShowCHinese(108,4,16);//温度单位
	OLED_ShowCHinese(54,6,18);//湿
	OLED_ShowCHinese(72,6,19);//度
	OLED_ShowStr(92,7,buff[3],2);//湿度值
}

csae 2显示深圳今天的天气,由心知天气返回来的数据包解析得到,显示天气图标,温度,湿度和天气状态;
case 3 是显示明天的天气状态,格式和case2一样,只是数据不一样而已;
case 4也是同理;
case 5显示数据更新时间。默认页显示“WELCOME”字样
```c
void Current_Weather_Show_3(void)
{

	OLED_ShowCHinese(24,1,23);//上
	OLED_ShowCHinese(44,1,24);//次
	OLED_ShowCHinese(60,1,25);//更
	OLED_ShowCHinese(78,1,26);//新
  OLED_ShowStr(44,5,buff[6],2);
}

需要说明的是天气图标我并没有全部做成16进制的数据格式,因为太多了,所以只做了一部分。同时我也会把天气图标全部放传上,供大家参考。
总之,ESP8266模块想玩好并不容易,说难不难,说不难又难,它涉及到IIC或SPI,USART,OLED,ESP8266,RTC,当然还有按键的写法,如果是参考正点原子的还会涉及到定时器TIM,解析数据包cJSON的两个文件,网上很难找,也附上供大家使用,千万不要傻傻的用strstr()函数,会被人笑的。
文章的最后,附上全部代码,喜欢的就下载吧。相互学习,知无不答。
ESP8266+OLED+显示天气图标

  • 15
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
很高兴听到您对ESP8266 OLED屏幕的应用感兴趣。下面是一个简单的程序示例,它可以实现天气预报、温度显示、NTP时间同步和中文显示。 首先,您需要准备以下材料: - ESP8266开发板(如NodeMCU) - OLED屏幕(如0.96英寸128x64 OLED) - DHT11温湿度传感器 - 一些杜邦线 - Arduino IDE(或其他适当的IDE) 接下来,让我们开始编写程序: 1. 引入所需库文件 ```c++ #include <ESP8266WiFi.h> #include <WiFiUdp.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <DHT.h> ``` 2. 定义OLED屏幕对象和温湿度传感器对象 ```c++ #define OLED_RESET 4 Adafruit_SSD1306 display(OLED_RESET); #define DHTPIN 5 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); ``` 3. 定义WiFi连接信息和NTP服务器信息 ```c++ const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; const char* ntpServerName = "cn.pool.ntp.org"; const int timeZone = 8; WiFiUDP udp; IPAddress localIP; ``` 4. 连接WiFi和获取本地IP地址 ```c++ void connectWiFi() { Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); localIP = WiFi.localIP(); Serial.println(localIP); } ``` 5. 从NTP服务器获取当前时间 ```c++ void getNTPTime() { byte packetBuffer[NTP_PACKET_SIZE]; memset(packetBuffer, 0, NTP_PACKET_SIZE); packetBuffer[0] = 0b11100011; packetBuffer[1] = 0; packetBuffer[2] = 6; packetBuffer[3] = 0xEC; packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; udp.beginPacket(ntpServerName, 123); udp.write(packetBuffer, NTP_PACKET_SIZE); udp.endPacket(); delay(1000); int cb = udp.parsePacket(); if (cb) { udp.read(packetBuffer, NTP_PACKET_SIZE); unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); unsigned long secsSince1900 = highWord << 16 | lowWord; const unsigned long seventyYears = 2208988800UL; unsigned long epoch = secsSince1900 - seventyYears; epoch += timeZone * SECS_PER_HOUR; setTime(epoch); } } ``` 6. 定义获取天气信息的函数 ```c++ void getWeather() { WiFiClient client; const int httpPort = 80; if (!client.connect("api.openweathermap.org", httpPort)) { Serial.println("connection failed"); return; } String url = "/data/2.5/weather?q=Beijing,cn&lang=zh_cn&units=metric&appid=your_APPID"; Serial.print("Requesting URL: "); Serial.println(url); client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: api.openweathermap.org\r\n" + "Connection: close\r\n\r\n"); while (!client.available()) { delay(100); } String line; while (client.available()) { line = client.readStringUntil('\r'); if (line.indexOf("\"temp\":") >= 0) { String temp = line.substring(line.indexOf("\"temp\":") + 7, line.indexOf(",")); Serial.println("Temperature: " + temp + "°C"); display.setCursor(0, 10); display.print("Temperature: "); display.print(temp); display.print("C"); } if (line.indexOf("\"description\":") >= 0) { String desc = line.substring(line.indexOf("\"description\":") + 15, line.indexOf("\",")); Serial.println("Description: " + desc); display.setCursor(0, 20); display.print("Description: "); display.print(desc); } } } ``` 7. 在setup函数中初始化各组件 ```c++ void setup() { Serial.begin(115200); dht.begin(); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.display(); delay(2000); display.clearDisplay(); connectWiFi(); udp.begin(2390); setSyncProvider(getNTPTime); display.setCursor(0, 0); display.print("IP address: "); display.println(localIP); display.display(); delay(2000); display.clearDisplay(); display.setCursor(0, 0); display.print("NTP time: "); display.println(getFormattedTime()); display.display(); delay(2000); display.clearDisplay(); getWeather(); } ``` 8. 在loop函数中更新温湿度和时间 ```c++ void loop() { delay(2000); float t = dht.readTemperature(); float h = dht.readHumidity(); display.setCursor(0, 30); display.print("Temp: "); display.print(t); display.print("C"); display.setCursor(0, 40); display.print("Humidity: "); display.print(h); display.print("%"); display.setCursor(0, 50); display.print("Time: "); display.println(getFormattedTime()); display.display(); } ``` 9. 定义一个辅助函数,将当前时间格式化为字符串 ```c++ String getFormattedTime() { char buffer[20]; sprintf(buffer, "%02d:%02d:%02d", hour(), minute(), second()); return String(buffer); } ``` 这只是一个简单的程序示例,您可以根据自己的需要进行修改和扩展。希望对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值