查看: 7662|回复: 18

天气显示屏代码开源与部分说明

[复制链接]

该用户从未签到

发表于 2019-8-18 16:26 | 显示全部楼层 |阅读模式
前言:这个帖子可能有点乱(其实是第一次写那么长代码的帖子),本来想分几篇慢慢写的,最后还是决定一气呵成,快开学了~  希望喜欢的朋友就占个楼,算是鼓励一下小白我
其实代码还是比较冗杂,许多地方也是借鉴了博哥(单片机菜鸟)的帖子,且本帖会转载一下博哥的帖子,如有侵权的地方请告知,也希望各位朋友能够提出宝贵的意见和建议。

首先还是贴一下上一篇的链接和视频吧:
终于成功做出一个有界面的oled天气显示屏了!(小白流泪 https://www.arduino.cn/forum.php?mod=viewthread&tid=91239&fromuid=174576 (出处: Arduino中文社区)





那么开始吧!

代码,素材,文档及工具.rar (249.46 KB, 下载次数: 419)


材料准备nodemcu(ESP-12)板子一块 、 0.96寸128X64  ssd1306 oled显示屏( 四线spi接法 )、sd卡模块和sd卡一张( 格式化时显示是FAT16或 FAT32的 micro sd卡)、 四个按键及键帽,10k电阻8个,杜邦线、导线若干
开发方式ESP8266 core for Arduino
知识准备: ESP8266WiFi库,ArduinoJson库,U8G2库(这些在博哥的帖子里都有介绍),SD及SPI库,avr/pgmspace库
单片机菜鸟博客链接:https://blog.csdn.net/dpjcn1990/article/details/92831918

电路连接图
连接图.PNG

nodemcu引脚图
nodemcu引脚图.jpg


主要流程图
主要流程.png

下面我来逐一介绍一下代码内容:

先统一说明一下:
代码当中一大半是const unsigned char <name>[] U8X8_PROGMEM = {}; 这样的数组,数组名字<name>当中 pb_前缀 的是图片(picture box), lb_前缀 的是文字(label)。预定义中_bt后缀是button的意思,按钮。另外,Serial串口打印的东西都是为了debug,可以不要。

头文件
[mw_shl_code=arduino,true]#include<U8g2lib.h>
#include<ESP8266WiFi.h>
#include<avr/pgmspace.h>
#include<SPI.h>
#include<SD.h>
#include<ArduinoJson.h>[/mw_shl_code]

esp8266wifi库主要是与esp模块连接网络有关,spi和sd库则是与sd模块有关,arduinojson库则是可以直接对 json数据进行解析,而u8g2库是个强大的库,用于oled的驱动,pgmspace库则是与存储在flash里面的数组交互相关(存储在flash中的数组内容的读取不能按存储在堆栈中的数组简单来操作,之后会说到)

预定义
[mw_shl_code=arduino,true]/*按键模拟输入的值,bt代表button,按键的误差范围在之后会设置为正负30*/
#define exit_bt 930
#define left_bt 648
#define right_bt  393
#define enter_bt  144
#define null_bt 22      //不按按键时的值(由于会受外界干扰不为0)

#define City_Code_MAX 35              //城市数量
#define MAX_CONTENT_SIZE  1000        //接受http响应内容的最大字节数
#define HTTP_TIMEOUT  5000            //最大响应时间[/mw_shl_code]


天气屏要用到四个按键:退出,左移,右移,确定。
由于nodemcu'可用的引脚数太少了,为了节省引脚,直接用模拟输入口A0.。用analogRead(A0)读取的值便可区分四个按键,我这里测到的四个值分别是930,648,393,144,而波动范围设在正负30就比较稳定了。
City_Code_MAX是指我添加的城市数量 0~35(数字与城市的对应图看下面)
MAX_CONTENT_SIZE是发送请求后读取响应内容最大的字节数。  HTTP_TIMEOUT是http最大的响应时间。
城市编码.PNG
(城市编码图)

对象的定义
[mw_shl_code=arduino,true]/*几个要用到的类的对象定义*/
File myFile;                          //读取sd卡数据
U8G2_SSD1306_128X64_NONAME_1_4W_HW_SPI oled(U8G2_R0, 10, 9, 5);      //驱动屏幕
WiFiClient client;                    //客户端联接服务器[/mw_shl_code]

File对象用于对sd卡的文件操作。这里我把oled的cs,dc,复位引脚分别和nodemcu的10,9,5号引脚相连,而client则是nodemcu作为客户端与心知天气建立tcp连接


全局变量:
[mw_shl_code=arduino,true]/*全局变量*/
unsigned char selected[200];          //用于存放取反(黑白倒置)后的图像,做成被选择的效果
unsigned char weather_lb[64];         //存放从sd卡中读取的天气字样
unsigned char weather_pb[512];        //存放从sd卡中读取的天气图像
char response[MAX_CONTENT_SIZE];      //存放http响应的内容
char endOfHeaders[] = "\r\n\r\n";     //http响应头部结束的标志

bool has_net = false;           //判断是否联上网络的标志
bool sd_initial = false;            //判断初始化时是否成功读取sd卡保存的上一次设置的标志
short int s = 0;                      //主界面中选择的状态
short int City_Code = 35;               //当前设置城市对应的编码(开始时默认为佛山)
short int weather_Code = 99;            //http响应内容中天气对应的编码(开始默认为未知)
short int temper;                       //http响应内容中的温度

String City_ID = "WS06YNEMPP18";        //心知天气中城市对应的id,用于url合成,默认佛山的id
String ssid = "";                       //配网时保存账号
String password = "";                   //配网时保存密码

const char* host = "api.seniverse.com";      //服务器网址
const char* key = "smtq3n0ixdggurox";        //心知天气api的密匙
const char* language = "zh-Hans";            //设置响应的语言-简体中文
[/mw_shl_code]
函数:
[mw_shl_code=arduino,true]void Get_initial_data();         //读取上一次设置的城市
void main_Interface();          //主界面
void main_interface_draw();     //主界面绘图函数

void smart_Config();            //主界面中的“网络”,配网连接wifi
void config_draw(short int);    //“网络”绘图函数

void city_Set();                              //主界面中的“工具”,设置城市
void city_set_draw(short int, bool = false);  //“工具”绘图函数
void draw_city_character(short int, short int, short int);  //绘画城市字样函数,三个形参分别是基准点x,y坐标及城市字样对应的数字
void city_set_store();                                      //将设置城市保存到sd卡中

void weather_Report();                                //主界面中的“天气”,发送请求接收响应内容并显示天气
bool SendRequest();                                   //发送请求
bool skipResponseHeaders();                           //跳过响应头部
void readReponseContent(char* );                      //将响应的数据保存到content数组中
bool AnalyseData(char* , struct WeatherData*);        //解析content中的josn数据
void printData(const struct WeatherData* );           //打印解析后的数据(主要用来debug)
void exchange(const struct WeatherData* );            //将解析后存在struct中的code和温度从字符串转换为整数
void weather_Readpic();                               //读取sd卡中对应的天气图片及字样
void weather_report_draw();                           //绘制天气显示
void request_fail();                                  //异常退出函数[/mw_shl_code]


还有个结构体:
[mw_shl_code=arduino,true]//此结构在解析json数据时存放json中的"code",“温度”
struct WeatherData {
  char code[8];         //天气对应的编码
  char temperature[8];  //温度
};[/mw_shl_code]


哈哈哈,是不是看到这里有种秘密麻麻的感觉,先不急着逐一解释,之后我再慢慢说。直接从setup函数开始吧。(代码中 名字为ID 的数组在天气请求那块介绍)


setup函数代码:
[mw_shl_code=arduino,true]void setup() {
  delay(3000);
  ESP.wdtEnable(9000);             //设置看门狗复位的时间为9s
  Serial.begin(115200);
  pinMode(15,OUTPUT);               //将gpio15(SS)作为输出引脚,表明作为主机
  pinMode(10,OUTPUT);               //gpio10连接屏幕的片选(cs)
  pinMode(4,OUTPUT);                //gpio4连接sd卡模块片选(cs)
  client.setTimeout(HTTP_TIMEOUT);  //设置http最大响应时间为5s
  delay(100);

  if(SD.begin(4)){              //如果成功打开sd卡则读取上一次设置的城市
    sd_initial = true;          //成功初始化
    Get_initial_data();
  }
  else{                           //失败则拉高gpio4,拉低gpio10
    Serial.println("sd unopen");
    digitalWrite(4,HIGH);
    digitalWrite(10,LOW);
  }
  
  WiFi.mode(WIFI_STA);          //设置wifi为station模式
  WiFi.disconnect();            //断开网络连接,清除上一次保存的接入点
  delay(2000);
  
  oled.begin();                 //绘制开机图像
  oled.firstPage();
  do{
    oled.drawXBMP(20,7,88,50,pb_start);
  }while(oled.nextPage());

  while(analogRead(A0) <= null_bt + 30){ESP.wdtFeed();}     //随便按一个按键进入主界面,循环时要喂狗
  delay(700);
}
[/mw_shl_code]

就是流程图上初始化部分。
注意spi下nodemcu作为主机的时候,无论HCS(gpio15)那个引脚无论有没连接设备都要设置为输出模式,作为从机的sd模块和oled,当他们的cs脚为低电平的时候被选择,高电平则为不被选择。

这里主要说一下 Get_initial_data(); 这个函数吧,我当时的想法是想将上一次断电时设置的城市做为下一次开机时的默认城市,所以我在sd卡当中添加了一个叫做“INITIAL_DATA.txt”的文本,里面的格式是 #数字@,其中数字就是城市的编码(上一张图片)。每次在“工具”中设置好城市之后,都会把城市对应的编码覆盖写到这个文件当中。这个函数就是读取上次写入的城市编码,如果读取成功,就保存到全局变量City_Code中,City_Code存放的是当前的城市编码。

好了接下来接说说主界面 void main_Interface()
不贴代码了,我想大家最好奇的是那个被选择后呈现高亮效果是怎么实现的吧。其实很简单,当按下左移或右移键选择的时候,记录状态的全局变量s改变了,小循环打破,调用void main_interface_draw(); 使画面重绘,这时候全局变量中还没用过的seleted数组就发挥作用了。譬如选择了“工具”的图像,可以看到s==1时 主界面绘制函数void main_interface_draw()把“工具”图像数组pb_tool所有元素取反~后保存到seleted数组当中,用drawXBM绘制函数就可以做出被选择高亮的效果了。这里要注意一下drawXBMdrawXBMP的区别(全局数组在ram用drawXBM,PROGMEM的数组则用drawXBMP):


drawXBM和drawXBMP函数.png

多说一句,要这样才能正确读取flash中数组元素 selected = ~(unsigned char)pgm_read_byte(&pb_tool); 这在pgmspace.h 中定义了,具体可看看附件中的截图


接下来说一说“工具”部分:void city_Set()
函数void city_set_draw(short int, bool = false); 负责绘制画面,每个页面显示9个城市,dpl(dot place)这个局部变量记录当前选择的城市。每一页绘制的城市字样是固定的(见城市编码那张图,page1代表第一页,以此类推),而小点点绘制的位置可以由dpl%3,dpl/3确定。


由于oled是不带中文字库的,所以我分离了一个专门绘制城市字样的函数 void draw_city_character(short int, short int, short int); 三个参数分别是字样基准点的x,y坐标和城市编码。这样就方便在这里及天气显示的时候绘画城市字样。

(所有城市字样都是32X16的规格)(为了格式统一)
按确定键后,由函数void city_set_store();  按 #数字@ 的格式写进sd卡"INITIAL_DATA.txt"中。




然后是“网络”部分:void smart_Config();
ESP8266开发之旅 网络篇⑧ SmartConfig——一键配网链接:https://blog.csdn.net/dpjcn1990/article/details/92830098

基本沿用了博哥的一键配网的代码,手机上的“esp8266一键配置”软件也是博哥自己写的,但是我也发现很多次虽然是配置成功了,串口也打印出了WiFi的账号密码,但是连接还是不成功(博哥的帖子也说了不是百分百成功),所以我特地设置了两个全局变量String ssid和String password用以保存配网时得到的账号密码。当回到主界面发现联网失败时,再次进入“网络”,这时ssidpassword则不为null了,我就加了一个提示“重新配网?”,选‘是’则重新一键配网,选‘否’则用Wifi.begin(ssidpassword)的方法用之前保存的账号密码联网,基本能保证成功联网(只要密码不错误)。




“天气”部分:void weather_Report();

ESP8266开发之旅 网络篇⑦ TCP Server & TCP Client链接:https://blog.csdn.net/dpjcn1990/article/details/92830087

ESP8266开发之旅 应用篇② OLED显示天气屏 链接:https://blog.csdn.net/dpjcn1990/article/details/92830466
基本也是沿用了博哥的代码。
但是我的url中location=的部分用的不是城市的拼音,而是城市的id(具体见心知天气api文档 城市id的excel )

下载链接:https://docs.seniverse.com/api/start/start.html

36个城市(序号0~35)刚好对应数组ID中的城市id,由于ID也是存放在flash中,所以转换为String时要用  String(FPSTR(ID)),官网上esp-arduino文档那也有相应的说明。当City_Code改变时,全局变量City_ID存放的城市id相应改变
City_ID = String(FPSTR(ID[City_Code]));


我的天气显示屏要获取的信息只是心知天气返回的 天气现象编码 温度 ,也就是json数据中的“code”和“temperature”部分。
而sd卡中天气图像和文字数组文本的名字就是对应了code的数字,比如"多云"的编码是4,那我在sd卡中 多云图片数组文本命名为“4.txt”,多云文字数组文本为“4c.txt”。通过函数void weather_Readpic(); 就可从sd卡中将 天气图像、文字数组文本分别读取并存放到全局数组  weather_pb[512];   weather_lb[64]; 中(注意区分pb_weather数组是主菜单界面的图像,weather_pb是用于保存天气图像,lb同理,我命名的锅~)
而后由void weather_report_draw(); 进行绘制。


(所有天气图像都是64X64px,字样都是32X16px)也是为了格式统一
心知天气api天气现象说明链接:https://docs.seniverse.com/api/start/code.html


异常退出的函数是我自己添加的 void request_fail(); 主要用于没联网就请求,请求失败,解析数据失败等的异常退出。



大概就是这么多了(写不下去了~),有错误请指正,欢迎随时和大家交流,毕竟词不达意。



该用户从未签到

发表于 2019-8-19 10:16 | 显示全部楼层
效果非常棒 ,谢谢分享

该用户从未签到

发表于 2019-8-19 11:29 | 显示全部楼层
本帖最后由 新手之帆 于 2019-8-19 11:34 编辑

DD 顶顶帖子

该用户从未签到

 楼主| 发表于 2019-8-19 19:56 | 显示全部楼层
本帖最后由 小津哥哥 于 2019-8-19 20:00 编辑

捕获.JPG
啊啊,发现个小错误,应该是selected【i】 = ~(unsigned char)pgm_read_byte(&pb_tool【i】);
感谢大家的鼓励

该用户从未签到

发表于 2019-9-15 16:56 | 显示全部楼层
感谢,喜欢天气的图标

该用户从未签到

发表于 2019-11-26 23:29 | 显示全部楼层
ddd  同学 我想做个led灯带屏幕的钟  ~~能加q交流交流嘛

该用户从未签到

发表于 2019-11-27 21:35 | 显示全部楼层
不错,先点个赞

该用户从未签到

发表于 2020-1-2 13:34 | 显示全部楼层
这个有点复杂。楼主辛苦了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

热门推荐

智能“百味”勺子开发实战营,为你的生活添滋味!
智能“百味”勺子开发实战
想不想拥有一把神奇的“百味”勺子,把索然无味的食物变出酸甜苦辣咸的丰富滋味
请问怎么在arduino上实现串口助手中hex发送和hex接收显示功能
请问怎么在arduino上实现
如第一个图,黄色框中是我要发送的信息,绿色框中是想要接收到的信息,需要用到红色框
[项目]microbit 控制的第一人称视角3D太空飞行游戏
[项目]microbit 控制的第
前言 家里有本《揭秘宇宙》,娃娃很喜欢,即使爸爸讲的都听烦了,娃娃还是不厌其烦的
【Arduino】168种传感器模块系列实验(125)---WeMos D1开发板
【Arduino】168种传感器模
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是
【Arduino】168种传感器系列实验(171)---HLK-V20离线语音模块
【Arduino】168种传感器系
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是
Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
快速回复 返回顶部 返回列表