ESP8266与STC8H8K单片机联动——天气时钟

基于ESP8266与STC单片机的天气时钟(包括DS18B20、TFT、串口、外部中断、ESP8266、STC、API等)

文章目录

  • 基于ESP8266与STC单片机的天气时钟(包括DS18B20、TFT、串口、外部中断、ESP8266、STC、API等)
    • 一、设计背景
    • 二、设计目标
    • 三、框图以及部分连接方式
      • 1.STC的TXD与RXD口反接ESP8266TXD和RXD
      • 2.单片机于TFT彩屏对应接口
      • 3.DS18B20
    • 四、单片机及模块功能介绍
    • 五、代码
      • 1.ESP8266主函数代码(不包含库的部分)
      • 2.STC8H8K主函数代码
      • 3.TFT显示函数
    • 六、结果展示
  • 系列文章——STC8H8K汇编51实战


一、设计背景

本人经过这学期课程学习,虽然熟练了51单片机的使用,但是仅仅局限于不同模块的使用,并没有将模块与模块之间联动起来,此次借着期末大作业的机会,本人将模块之间的联动延伸至单片机之间的联动,这些年火热的“物联网”、“万物互联”等名词促使本人除了STC8H8K64U外,选择了ESP8266NodeMCU这块单片机来完成第一步——两物互连。一个多功能显示的智慧屏幕方便人们生活,减少获取信息的流程。

二、设计目标

1. 实现STC8H8K64U与ESP8266NodeMCU串口通信
2. 让ESP8266NodeMCU成功连接WiFi
3. 利用ESP8266NodeMCU获取区域天气、温度和时间等信息
4. 在STC8H8K64U上实现DS18B20实时检测环境温度
5. 利用STC8H8K64U将所有数据传输至TFT并显示
6. 利用外部中断按键控制TFT显示屏的亮灭

三、框图以及部分连接方式

在这里插入图片描述

1.STC的TXD与RXD口反接ESP8266TXD和RXD

在这里插入图片描述
在这里插入图片描述

2.单片机于TFT彩屏对应接口

在这里插入图片描述

3.DS18B20

在这里插入图片描述

四、单片机及模块功能介绍

1、STC8H8K64U
STC8H8K64U是本学期一直使用的以超强抗干扰、低价、高速、低功耗为目标的8051单片机(8位机),此项目中大部分的数据接收、处理在此单片机上进行。

2、 ESP8266NodeMCU
ESP8266NodeMCU是一款集成了Wifi功能的MCU开发板(32位机),可以直接连接wifi,开发环境多元化,也是表较受欢迎的物联网芯片。在此项目中主要使用ESP8266连接到电脑热点(已联网),然后借助网络利用心知天气的API,获取时间、天气、地区温度等信息。

3、 DS18B20
DS18B20是一款常用的高精度的单总线数字温度测量芯片。具有体积小,硬件开销低,抗干扰能力强,精度高的特点。在此项目中,DS18B20与STC8H8K64U配合实时测量环境温度。

4、TFT液晶显示器
TFT液晶显示器主要的构成包括:萤光管、导光板、偏光板、滤光板、玻璃基板、配向膜、液晶材料、薄模式晶体管等。在此项目中,使用的是1.44寸TFT彩屏(IIC协议),用于显示单片机发送过来的数据信息,包括显示图片、地区、温度、天气文字信息。

Img2Lcd软件
在这里插入图片描述

5、 DS18B20作为温度对比
先前已经在TFT屏幕上显示出区域温度,后续增加一个环境温度更加人性化,显示室温。单总线的DS18B20操作较为简单,注意好延时和数据格式即可。DS18B20精度为11位,且分为两个字节存储,在最后获取数据时需要把高位左移8位后再或低8位,然后乘以0.625(为了保留一位小数点),将每一位取出来放到数组中,在TFT显示的时候数组每一位加0x30转化位字符串形式,成功显示。

DS18B20数据存储格式
在这里插入图片描述

五、代码

由于TFT函数以及其他头文件和ESP8266的库文件太多,我已打包上传,欢迎各位前往主页下载。

1.ESP8266主函数代码(不包含库的部分)

#include <ESP8266WiFi.h>                    //ESP8266自带库,无需安装
#include <ESP8266_Seniverse.h>              //https://github.com/taichi-maker/ESP8266-Seniverse
#include <WiFiUdp.h>
#include <NTPClient.h>
const char* ssid     = "xxxxxxxxx";       // 连接WiFi名(输入自己的WiFi名)
                                           
const char* password = "xxxxxxxxx";          // 连接WiFi密码(输入自己的WiFi密码)
                                          

// 心知天气HTTP请求所需信息
// 请对以下信息进行修改,填入您的心知天气私钥以及需要获取天气信息的城市和温度单位
// 如需进一步了解心知天气API所提供的城市列表等信息,请前往心知天气官方产品文档网址:
// https://www.seniverse.com/docs
const char* reqUserKey = "StYr2Swd9ftD4b7-H";   // 私钥
const char* reqLocation = "shenzhen";            // 城市,可使用"ip"自动识别请求 IP 地址
const char* reqUnit = "c";                      // 摄氏(c)/华氏(f)

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp.aliyun.com"); //NTP地址

WeatherNow weatherNow;  // 建立WeatherNow对象用于获取心知天气信息

void setup(){
  Serial.begin(9600);          
  Serial.println("");

  connectWiFi();    // 连接wifi

  // 配置心知天气请求信息
  weatherNow.config(reqUserKey, reqLocation, reqUnit);

  timeClient.begin();
  timeClient.setTimeOffset(28800); //+1区,偏移3600,+8区,偏移3600*8
}
 
void loop(){

  if(weatherNow.update()){  // 更新天气信息
    //Serial.println(F("======Weahter Info======"));(由于我要从串口发数据过去,测试的时候输出提示,传输的时候就把这些提示都注释了)
    //Serial.print("Server Response: ");
    //Serial.println(weatherNow.getServerCode()); // 获取服务器响应码
    //Serial.print(F("Weather Now: "));
    Serial.println(weatherNow.getWeatherText());  // 获取当前天气(字符串格式)
    //Serial.print(F(" "));
    //Serial.println(weatherNow.getWeatherCode());// 获取当前天气(整数格式)
    //Serial.print(F("Temperature: "));
    Serial.println(weatherNow.getDegree());     // 获取当前温度数值
    //Serial.print(F("Last Update: "));
    //Serial.println(weatherNow.getLastUpdate()); // 获取服务器更新天气信息时间(不知道为什么我的电脑获取时间不正确,时间戳不对,所以换了下面的方法获取时间)
    //Serial.println(F("========================"));     
  } else {    // 更新失败
    //Serial.println("Update Fail...");   
    //Serial.print("Server Response: ");          // 输出服务器响应状态码供用户查找问题
    //Serial.println(weatherNow.getServerCode()); // 心知天气服务器错误代码说明可通过以下网址获取
  }                                             // https://docs.seniverse.com/api/start/error.html

  timeClient.update();
  unsigned long epochTime = timeClient.getEpochTime();
  //打印时间
  int currentHour = timeClient.getHours();
  Serial.print(currentHour);
  Serial.print(F(":"));
  int currentMinute = timeClient.getMinutes();
  Serial.println(currentMinute);

  delay(500);
}

// 连接WiFi
void connectWiFi(){
  WiFi.begin(ssid, password);                  // 启动网络连接
  Serial.print("Connecting to ");              // 串口监视器输出网络连接信息
  Serial.print(ssid); Serial.println(" ...");  // 告知用户NodeMCU正在尝试WiFi连接
  
  int i = 0;                                   // 这一段程序语句用于检查WiFi是否连接成功
  while (WiFi.status() != WL_CONNECTED) {      // WiFi.status()函数的返回值是由NodeMCU的WiFi连接状态所决定的。 
    delay(1000);                               // 如果WiFi连接成功则返回值为WL_CONNECTED                       
    Serial.print(i++); Serial.print(' ');      // 此处通过While循环让NodeMCU每隔一秒钟检查一次WiFi.status()函数返回值
  }                                            // 同时NodeMCU将通过串口监视器输出连接时长读秒。
                                               // 这个读秒是通过变量i每隔一秒自加1来实现的。                                              
  Serial.println("");                          // WiFi连接成功后
  Serial.println("Connection established!");   // NodeMCU将通过串口监视器输出"连接成功"信息。
  Serial.print("IP address:    ");             // 同时还将输出NodeMCU的IP地址。这一功能是通过调用
  Serial.println(WiFi.localIP());              // WiFi.localIP()函数来实现的。该函数的返回值即NodeMCU的IP地址。  
}

上面这部分需要注意的是,由于获取心知天气时间戳有问题,只有每次烧录的时候从才能更新时间,而且慢5-15分钟,所以我的时间获取采用了另外的方法

2.STC8H8K主函数代码

#include<stc8h.h>
#include<intrins.h>
#include<TFT.h>
#include<string.h>
#include<image.h>
#include<onewire.h>

#define uchar unsigned char
#define uint unsigned int
bit busy;

sbit DQ = P3^3;  //单总线接口

uchar j = 0;
uchar i = 0;


char xdata tem[3][15];
char Rev[15];
int datacount;
unsigned int templen;

uint flag = 0;//定时器计数
uchar sec = 15,min = 20,hour = 8;

uchar code t_display[]={                       //标准字库
//   0    1    2    3    4    5    6    7    8    9    
    0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
    0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x00,0x40};    //0. 1. 2. 3. 4. 5. 6. 7. 8. 9.  -

uchar code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};      //位码

void DS18B20_Reset();
void DS18B20_WriteByte(u8 dat);
u8 DS18B20_ReadByte();

void delay_us(u8 us);
u16 ReadTemperature();
void DisplayTemperature(u16 temp);


void Delay1ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 15;
	j = 90;
	do
	{
		while (--j);
	} while (--i);
}

void Nixie(uchar i, uint j)
{
	P7=~T_COM[i];
	P6=~t_display[j];
	Delay1ms();
	P6 = 0xFF;
}


/*----------------------------
gpio初始化为准双向口
----------------------------*/
void gpio()	
{
    P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
    P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
    P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
    P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
    P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
    P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
    P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
    P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
}


/*----------------------------
发送字节
----------------------------*/
void SendData(uchar dat)
{
    while (busy);               
    busy = 1;
    SBUF = dat;                 //要发送的数据存入SBUF
}


/*----------------------------
UART1初始化
-----------------------------*/
void InitUART(void)
{
    SCON = 0x50;                //8位数据	
	P_SW1= P_SW1 & 0x3F; 
    AUXR |= 0x40;                //定时器1T模式
	AUXR &= 0xFE;	
    TMOD &= 0x0F;
    TMOD |= 0x20;                //8位自动重装载模式
    TL1 = 0xDC;   				//
    TH1 = 0xDC;
    TR1 = 1;                    //开启定时器1
    ES = 1;                     //开启串口中断
    EA = 1;

}

uchar menu[4];
uchar DS18TFT[5];
void main(void)
{
	unsigned int temp = 0;
	unsigned char i = 0;
	unsigned char flag = 1;
	uchar select = 1;
	gpio();		//gpio初始化
	InitUART();	 //串口初始化
	initTFT();	//初始化TFT
	//Timer0Init();	//定时器0初始化
	renovateTFT(WHITE);	//用白色刷新屏幕
	bl=1;	 	//打开屏幕灯
	showimage(gImage_1);
	IT0=1;//跳沿触发
	EX0=1;
	for(i=0;i<15;i++)
	{
		tem[0][i]=' ';
		tem[1][i]=' ';
		tem[2][i]=' ';
	}

	tem[1][3] = 'C';		//温度只有两位数,把第三位预设为摄氏度单位
	DS18TFT[4] = 'C';
	
  	while(1)
  	{	
		temp = ReadTemperature();  
		/*        
		menu[0] = temp/100;
		menu[1] = temp%100/10 + 10;	//+10是为了数码管显示出点
		menu[2] = temp%10;
		Nixie(2,menu[0]);
		Nixie(1,menu[1]);
		Nixie(0,menu[2]);
		*/
		DS18TFT[0] = temp/100 + 0x30;
		DS18TFT[1] = temp%100/10 + 0x30;
		DS18TFT[2] = '.';
		DS18TFT[3] = temp%10 + 0x30;
		
		//displayASCII8X16(10, 0, time, 16, BLACK, WHITE);
		displayASCII8X16(10, 0, tem[2], 6 , BLACK, WHITE);
		displayASCII8X16(60, 0, "ShenZhen ", 9, BLACK, WHITE);
		displayASCII8X16(10, 20, "weather:", 9, BLACK, WHITE);
		displayASCII8X16(10, 40, tem[0], 10, BLACK, WHITE);
		displayASCII8X16(10, 60, "tempture:", 9, BLACK, WHITE);
		displayASCII8X16(10, 80, tem[1], 4, BLACK, WHITE);
		displayASCII8X16(50, 80, DS18TFT, 5, BLACK, WHITE);
		

		
	}
}


/*----------------------------
UART 中断
-----------------------------*/
void Uart() interrupt 4 using 1
{

	uchar temp;
	uchar i=0;
    if (RI)
    {
        RI = 0;                 //接收到字符后,RI清0
		P40=0;
        temp = SBUF; 
		if(temp!='\n')
		{
			Rev[datacount] = temp;
			datacount++;
		}else
		{
			templen = datacount;
			datacount = 0;
			
			if(templen==3)
			{
				tem[1][0] = Rev[0];
				tem[1][1] = Rev[1];
				tem[1][2] = Rev[2];
			}else if((Rev[0]-48)<=9)
			{
				tem[2][0] = Rev[0];
				tem[2][1] = Rev[1];
				tem[2][2] = Rev[2];
				tem[2][3] = Rev[3];
				tem[2][4] = Rev[4];
			}else
			{
				for(i=0;i<10;i++)
				{
					tem[0][i] = Rev[i];
				}
			}
		}

    }
    if (TI)
    {
        TI = 0;                 //发送完字符后TI清0
        busy = 0;               //发送完一个字符后busy清0
    }
}

void delay_us(u8 us)
{
    do{
        _nop_();_nop_();
		_nop_();_nop_();
    }while(--us);
}

/**************************************
复位DS18B20,并检测设备是否存在
**************************************/
void DS18B20_Reset()
{
    CY = 1;
    while (CY)
    {
        DQ = 0;                     //送出低电平复位信号
        delay_us(240);              //延时至少480us
        delay_us(240);
        DQ = 1;                     //释放数据线
        delay_us(60);               //等待60us
        CY = DQ;                    //检测存在脉冲
        delay_us(240);              //等待设备释放数据线
        delay_us(180);
    }
}

/**************************************
从DS18B20读1字节数据
**************************************/
u8 DS18B20_ReadByte()
{
    u8 i;
    u8 dat = 0;

    for (i=0; i<8; i++)             //8位计数器
    {
        dat >>= 1;
        DQ = 0;                     //开始时间片
        delay_us(1);                //延时等待
        DQ = 1;                     //准备接收
        delay_us(1);                //接收延时
        if (DQ) dat |= 0x80;        //读取数据
        delay_us(60);               //等待时间片结束
    }

    return dat;
}

/**************************************
向DS18B20写1字节数据
**************************************/
void DS18B20_WriteByte(u8 dat)
{
    char i;

    for (i=0; i<8; i++)             //8位计数器
    {
        DQ = 0;                     //开始时间片
        delay_us(1);                //延时等待
        dat >>= 1;                  //送出数据
        DQ = CY;
        delay_us(60);               //等待时间片结束
        DQ = 1;                     //恢复数据线
        delay_us(1);                //恢复延时
    }
}

//========================================================================
// 函数: u16 ReadTemperature()
// 描述: 读取温度函数。
// 参数: none.
// 返回: 温度值.
//========================================================================
u16 ReadTemperature()
{
    u16 TempH, TempL, Temperature;
	
    DS18B20_Reset();                //设备复位
    DS18B20_WriteByte(0xCC);        //跳过ROM命令
    DS18B20_WriteByte(0x44);        //开始转换命令
    while (!DQ);                    //等待转换完成

    DS18B20_Reset();                //设备复位
    DS18B20_WriteByte(0xCC);        //跳过ROM命令
    DS18B20_WriteByte(0xBE);        //读暂存存储器命令
    TempL = DS18B20_ReadByte();     //读温度低字节
    TempH = DS18B20_ReadByte();     //读温度高字节
	
    if(TempH & 0xf8)    //判断是否位负数
    {
        MinusFlag = 1;  //设置负数标志
        Temperature = (TempH<<8) | TempL;
        Temperature = ~Temperature + 1;
        Temperature *= 0.625;       //0.0625 * 10,保留1位小数点
    }
    else
    {
        MinusFlag = 0;  //清除负数标志
        Temperature = (((TempH<<8) | TempL) * 0.625); //0.0625 * 10,保留1位小数点
    }

    return Temperature;
}

void INT0()		interrupt 0
{
	flag++;		//每按下一次就加1
	
	if(flag==2)
	{
		flag = 0;
	}
	if(flag == 1)
	{
		bl = 0;
	}else
	{
		bl = 1;
	}
	
	for(i=0;i<20;i++)
	{
		Delay1ms();
	}
}

STC的这部分代码同样存在一个显示小bug,由于深圳天气种类较少,我便偷懒将接收串口的数据固定为某个长度的数组,显示也是固定某个长度,如果换做其他地区的话可能天气种类较多,出现显示不全等情况,只是个小问题,各位可以稍加修改。

3.TFT显示函数

刷屏函数

/*************************************************
函数名:renovateTFT
功能:用颜色刷新显示屏
入口参数:int color 颜色选择
返回值:无
*************************************************/
void renovateTFT(int color)
{
 	unsigned char i,j;
	setTFTRegion(0, 0, 128-1, 128-1);
 	for (i = 0; i < 128; i ++)
    	for (j = 0;j < 128; j ++)
        	writeTFTData16(color);
}

显示文本函数

/*************************************************
函数名:displayASCII8X16
功能:显示ASCII码,大小为8*16像素
入口参数:	unsigned int x0,				放置坐标x 
			unsigned int y0, 				放置坐标y
			unsigned char *s, 				显示的字符串
			unsigned int forecolor, 		前景颜色
			unsigned int backgroundcolor	背景颜色
返回值:无
*************************************************/
void displayASCII8X16(unsigned int x0, unsigned int y0, unsigned char *s, unsigned int size, unsigned int forecolor, unsigned int backgroundcolor)
{
	int i,j,x,y,xx;
	
	long int ulOffset;
	
	char  xdata ywbuf[32];
	
	for(i = 0; i< size; i++)
	{
		if(s[i] >= 161)
			return;
		else
		{
			ulOffset = (long int)s[i] * 16;
			for (j = 0; j < 16; j ++)
			{
				ywbuf[j] = Zk_ASCII8X16[ulOffset+j];
			}
			
			for(y = 0; y < 16; y++)
			{
				for(x = 0; x < 8; x++) 
				{
					if(ywbuf[y] & (0x80 >> x))
					{
						xx = x0 + x + i * 8;
						putPixel(xx, y + y0, forecolor);
					}
					else
					{
						xx=x0 + x + i * 8;
						putPixel(xx, y + y0, backgroundcolor);	
					}
				}
			}
		
		}
	}     	
}

背景图片函数,需要先用Img2Lcd将图片取模(注意图片大小,一般取得数组几千元素,需要加code存放到ROM中)

void showimage(const unsigned char *p) //显示40*40 
{
  	int i,j,k; 
	unsigned char picH,picL;
	dsp_single_colour(WHITE); //清屏  
	
	for(k=0;k<3;k++)
	{
	   	for(j=0;j<3;j++)
		{	
			setTFTRegion(45*j,45*k,45*j+39,45*k+39);		//坐标设置
		    for(i=0;i<45*45;i++)
			 {	
			 	picL=*(p+i*2);	//数据低位在前
				picH=*(p+i*2+1);				
				writeTFTData16(picH<<8|picL);  						
			 }	
		 }
	}		
}

六、结果展示

注意:此处两条线连接电脑仅仅是为了供电,在下载烧录的时候一定要断开一个,否则会串口冲突,烧录失败,如果各位无法成功测试,欢迎评论区留言或者私信。
在这里插入图片描述
在这里插入图片描述
感悟:
这是我见过作业最多的一门课了,三学分的课比两门五学分课程加起来的作业还要多的多,这也是我学到东西最多的一门课,51汇编、Proteus、内存关系、数码管的各种操作、按键细节操作、中断、串口、AD转换、DS18B20、LCD1602、LCD12864、TFT、ESP8266NodeMCU、API等等等等,虽然一周三份代码+实验+选择填空(找不到答案的那种)真的很伤头发哈哈哈,但是真的真的非常非常感谢这位老师。

非常感谢各位的观看,总有一天,您会看见我长成参天大树的。

系列文章——STC8H8K汇编51实战

STC8H8K系列汇编和C51实战——实现跑马灯(51版)

STC8H8K系列汇编和C51实战——实现跑马灯(汇编版)

STC8H8K系列汇编和C51实战——实现键控不同方式数码管动态显示(C51版与汇编版)

STC8H8K系列汇编和C51实战——开关控制定时器秒表(C51版)

STC8H8K系列汇编和C51实战——开关控制定时器秒表(汇编版)

STC8H8K系列汇编和C51实战——双中断控制定时器流水灯

STC8H8K系列汇编和C51实战——双中断加减计数器

STC8H8K系列汇编和C51实战——简易频率计

STC8H8K系列汇编和C51实战——秒倒计时器(汇编版)

STC8H8K系列汇编和C51实战——秒倒计时器(51版)

STC8H8K系列汇编和C51实战——秒倒计时器(可自行设定初值)(51版)

STC8H8K系列汇编和C51实战——按键允许按键计数(51版)

STC8H8K系列汇编和C51实战——按键允许按键计数(汇编版)

STC8H8K系列汇编和C51实战——按键允许按键计数(定时器去抖动51版)

STC8H8K系列汇编和C51实战——按键允许按键计数(利用下降沿中断控制)

STC8H8K系列汇编和C51实战——计算机串口控制单片机LED

STC8H8K系列汇编和C51实战——串口发送菜单界面选择不同功能

STC8H8K系列汇编和C51实战——数码管显示ADC、串口显示ADC按键与数值

  • 10
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不知何人

万分感谢诸位观看

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值