基于STM32的家用多功能温湿度计开发

基于STM32的家用多功能温湿度计设计

Als ich ging, sah ich eine schöne Kiste, und du sagtest, du könntest sie mir geben.
Von da an habe ich mich entschieden, dir so ein Geschenk zu machen, vielleicht das letzte für dich.
Pass auf dich auf!Komm in die Zukunft.

产品效果图
开发板测试结果——人海茫茫,走走散散。
从开始设想到今天凌晨5:40的成功装配,
收获了很多知识。

1.设计需求

  • 实现正常生活环境下温度测量与显示
  • 实现正常生活环境下空气湿度测量与显示
  • 实现电子时钟实时显示

2.总体方案

向前测控通道
主机及其接口
向后测控通道

考虑应用实际,本项目选用以下模块:

MCU主控芯片STM32F103C8T6 1温度传感器DS18B20 2湿度传感器DHT11 3实时时钟源DS1302 4显示 >>OLED_7P>> size(0.96)

*项目首先基于开发版测试系统,完成软件设计后,装载入新设计的目标硬件。

3.硬件设计

3.1系统电路原理图

系统电路原理图

3.2主机STM32F103C8T6及其接口电路

STM32F103C8T6
STM32F103C8T6是一款性能优越的主控芯片,是嵌入式设计的优秀选项。

最小系统

3.3 测量电路

虽然DHT11自带温度测量功能,但其温度测量精度较低,因此,本设计采用DS18B20温度传感器测量高精度温度值;湿度值 采用DHT11直接测量。

本文设计、搭建了DHT11和DS18B20的面包板电路如下图所示。
温湿度采集面包板

总体实物图

4.软件开发

4.1 DS18B20数据读取

见附录

4.2 DHT11数据读取

见附录

4.3 DS1302数据读写

见附录

4.4 OLED显示操作

见附录

5.经费总结

器件 单价/元
STM32F103C8T6 最小系统板 37.72
OLED_P7 11.83
DHT11 5.61
DS18B20 5.41
DS1302 3.31
电池盒 1.64
总计 65.52 元

6.附录

6.1主函数代码

主函数

/*
Title:基于STM32的家用多功能温湿度计设计
Author:小呼呼哈哈哈
Date:2021-02-28
Das letzte Geschenk für Soffieg.
*/
/*main.c*/
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "usart.h"
#include "ds18b20.h"
#include "dht11.h"
#include "ds1302.h"

int main(void)
{
    
    
	u8 t=0;
	double temp;
	u16 temper;
	int ten,ge,feng;
	u16 Year;
	u8  Month;
	u8  Day;
	u8  Hour;
	u8  Minute;
	u8  Second;
	u8  Week;
	u8  DHT11_humidity1=0,DHT11_humidity2=0;
	u8  DHT11_temper1=0,DHT11_temper2=0;

	delay_init();
	NVIC_Configuration();
	uart_init(115200);//9600?	
	OLED_Init();
	OLED_ColorTurn(0);//0正常显示,1 反色显示
  OLED_DisplayTurn(1);//0正常显示 1 屏幕翻转显示
	
  DHT11_Init();delay_ms(5);
	DS1302_Init();delay_ms(5);
	/*以下两函数调用 用以设置时间和开启掉电保护*/
//	DS1302_WriteTime();
	ds1302_Init_time();
	
	while(1)
	{
    
    
		//DS18B20数据处理
		temp=DS18B20_Get_Temp();
		temper=temp*10;
		//温度位数分解
		ten = temper/1000;
		ge = temper%1000/100;
		feng = temper%1000%100/10;
		//温度显示
		OLED_ShowNum(0+5,42,ten,1,24,1);
		OLED_ShowNum(12+5,42,ge,1,24,1);
		OLED_ShowChar(24+5,40,'.',24,1);
		OLED_ShowNum(34+5,48,feng,1,16,1);
		OLED_ShowChar(44+5,40,'.',16,1);
		OLED_ShowChar(49+5,48,'C',16,1);
				
		//湿度数据处理 ,有采样周期要求		
		DHT11_Read_Data(&DHT11_humidity1,&DHT11_humidity2,&DHT11_temper1,&DHT11_temper2);			
		OLED_ShowNum(78-1,42,DHT11_humidity1,2,24,1);//湿度值
		OLED_ShowChar(102,48,'%',16,1);
		OLED_ShowString(112,48,"RH",16,1);
		
		//日期显示
		DS1302_GetTime();
		Year=TimeData.year;
		Month=TimeData.month;
		Day=TimeData.day;
		Hour=TimeData.hour;
		Minute=TimeData.minute;
		Second=TimeData.second;
		Week=TimeData.week;
		
//		OLED_ShowNum(0,0,Year,4,16,1);//显示年份
		OLED_ShowNum(80,0,Month,2,16,1);//显示月份
		OLED_ShowChar(100,0,'-',16,1);
		OLED_ShowNum(110,0,Day,2,16,1);//显示天
		
		
		OLED_ShowNum(0,5,Hour,2,32,1);//显示时
		OLED_ShowString(32,5,":",32,1);
		OLED_ShowNum(46,5,Minute,2,32,1);//显示分
//		OLED_ShowString(65,18,":",24,1);
//		OLED_ShowNum(128-24*2,18,Second,2,24,1);//显示秒
		
	  OLED_ShowChinese(80,20,0,24,1);//显示“周”,24*24
		OLED_ShowChinese(101,20,Week,24,1);//显示“*”,24*24
		OLED_Refresh();
		delay_ms(10);					
	}
}

6.2 OLED显示代码

OLED显示函数

/*oled.c*/
#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"  	 
#include "delay.h"

u8 OLED_GRAM[144][8];

//反显函数
void OLED_ColorTurn(u8 i)
{
    
    
	if(i==0)
		{
    
    
			OLED_WR_Byte(0xA6,OLED_CMD);//正常显示
		}
	if(i==1)
		{
    
    
			OLED_WR_Byte(0xA7,OLED_CMD);//反色显示
		}
}

//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
    
    
	if(i==0)
		{
    
    
			OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
			OLED_WR_Byte(0xA1,OLED_CMD);
		}
	if(i==1)
		{
    
    
			OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
			OLED_WR_Byte(0xA0,OLED_CMD);
		}
}

void OLED_WR_Byte(u8 dat,u8 cmd)
{
    
    	
	u8 i;			  
	if(cmd)
	  OLED_DC_Set();
	else 
	  OLED_DC_Clr();		  
	OLED_CS_Clr();
	for(i=0;i<8;i++)
	{
    
    			  
		OLED_SCL_Clr();
		if(dat&0x80)
		   OLED_SDA_Set();
		else 
		   OLED_SDA_Clr();
		OLED_SCL_Set();
		dat<<=1;   
	}				 		  
	OLED_CS_Set();
	OLED_DC_Set();   	  
}

//开启OLED显示 
void OLED_DisPlay_On(void)
{
    
    
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x14,OLED_CMD);//开启电荷泵
	OLED_WR_Byte(0xAF,OLED_CMD);//点亮屏幕
}

//关闭OLED显示 
void OLED_DisPlay_Off(void)
{
    
    
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x10,OLED_CMD);//关闭电荷泵
	OLED_WR_Byte(0xAE,OLED_CMD);//关闭屏幕
}

//更新显存到OLED	
void OLED_Refresh(void)
{
    
    
	u8 i,n;
	for(i=0;i<8;i++)
	{
    
    
	   OLED_WR_Byte(0xb0+i,OLED_CMD); //设置行起始地址
	   OLED_WR_Byte(0x00,OLED_CMD);   //设置低列起始地址
	   OLED_WR_Byte(0x10,OLED_CMD);   //设置高列起始地址
	   for(n=0;n<128;n++)
		 OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
  }
}
//清屏函数
void OLED_Clear(void)
{
    
    
	u8 i,n;
	for(i=0;i<8;i++)
	{
    
    
	   for(n=0;n<128;n++)
			{
    
    
			 OLED_GRAM[n][i]=0;//清除所有数据
			}
  }
	OLED_Refresh();//更新显示
}

//画点 
//x:0~127
//y:0~63
//t:1 填充 0,清空	
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
    
    
	u8 i,m,n;
	i=y/8;
	m=y%8;
	n=1<<m;
	if(t){
    
    OLED_GRAM[x][i]|=n;}
	else
	{
    
    
		OLED_GRAM[x][i]=~OLED_GRAM[x][i];
		OLED_GRAM[x][i]|=n;
		OLED_GRAM[x][i]=~OLED_GRAM[x][i];
	}
}

//画线
//x1,y1:起点坐标
//x2,y2:结束坐标
void OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2,u8 mode)
{
    
    
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance;
	int incx,incy,uRow,uCol;
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1;
	uRow=x1;//画线起点坐标
	uCol=y1;
	if(delta_x>0)incx=1; //设置单步方向 
	else if (delta_x==0)incx=0;//垂直线 
	else {
    
    incx=-1;delta_x=-delta_x;}
	if(delta_y>0)incy=1;
	else if (delta_y==0)incy=0;//水平线 
	else {
    
    incy=-1;delta_y=-delta_x;}
	if(delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y;
	for(t=0;t<distance+1;t++)
	{
    
    
		OLED_DrawPoint(uRow,uCol,mode);//画点
		xerr+=delta_x;
		yerr+=delta_y;
		if(xerr>distance)
		{
    
    
			xerr-=distance;
			uRow+=incx;
		}
		if(yerr>distance)
		{
    
    
			yerr-=distance;
			uCol+=incy;
		}
	}
}
//x,y:圆心坐标
//r:圆的半径
void OLED_DrawCircle(u8 x,u8 y,u8 r)
{
    
    
	int a, b,num;
    a = 0;
    b = r;
    while(2 * b * b >= r * r)      
    {
    
    
        OLED_DrawPoint(x + a, y - b,1);
        OLED_DrawPoint(x - a, y - b,1);
        OLED_DrawPoint(x - a, y + b,1);
        OLED_DrawPoint(x + a, y + b,1);
 
        OLED_DrawPoint(x + b, y + a,1);
        OLED_DrawPoint(x + b, y - a,1);
        OLED_DrawPoint(x - b, y - a,1);
        OLED_DrawPoint(x - b, y + a,1);
        
        a++;
        num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
        if(num > 0)
        {
    
    
            b--;
            a--;
        }
    }
}


//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size1:选择字体 6x8/6x12/8x16/12x24
//mode:0,反色显示;1,正常显示
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1,u8 mode)
{
    
    
	u8 i,m,temp,size2,chr1;
	u8 x0=x,y0=y;
	if(size1==8)size2=6;
	else size2=(size1/8+((size1%8)?1:0))*(size1/2);  //得到字体一个字符对应点阵集所占的字节数
	chr1=chr-' ';  //计算偏移后的值
	for(i=0;i<size2;i++)
	{
    
    
		if(size1==8)
			  {
    
    temp=asc2_0806[chr1][i];} //调用0806字体
		else if(size1==12)
        {
    
    temp=asc2_1206[chr1][i];} //调用1206字体
		else if(size1==16)
        {
    
    temp=asc2_1608[chr1][i];} //调用1608字体
		else if(size1==24)
        {
    
    temp=asc2_2412[chr1][i];} //调用2412字体	
		else if(size1==32)
        {
    
    temp=asc2_3216[chr1][i];} //调用3216字体
		else if(size1==40)
        {
    
    temp=asc2_4020[chr1][i];} //调用4020字体	
		else return;
		for(m=0;m<8;m++)
		{
    
    
			if(temp&0x01)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp>>=1;
			y++;
		}
		x++;
		if((size1!=8)&&((x-x0)==size1/2))
		{
    
    x=x0;y0=y0+8;}
		y=y0;
  }
}


//显示字符串
//x,y:起点坐标  
//size1:字体大小 
//*chr:字符串起始地址 
//mode:0,反色显示;1,正常显示
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 size1,u8 mode)
{
    
    
	while((*chr>=' ')&&(*chr<='~'))//判断是不是非法字符!
	{
    
    
		OLED_ShowChar(x,y,*chr,size1,mode);
		if(size1==8)x+=6;
		else x+=size1/2;
		chr++;
  }
}

//m^n
u32 OLED_Pow(u8 m,u8 n)
{
    
    
	u32 result=1;
	while(n--)
	{
    
    
	  result*=m;
	}
	return result;
}

//显示数字
//x,y :起点坐标
//num :要显示的数字
//len :数字的位数
//size:字体大小
//mode:0,反色显示;1,正常显示
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1,u8 mode)
{
    
    
	u8 t,temp,m=0;
	if(size1==8)m=2;
	for(t=0;t<len;t++)
	{
    
    
		temp=(num/OLED_Pow(10,len-t-1))%10;
			if(temp==0)
			{
    
    
				OLED_ShowChar(x+(size1/2+m)*t,y,'0',size1,mode);
      }
			else 
			{
    
    
			  OLED_ShowChar(x+(size1/2+m)*t,y,temp+'0',size1,mode);
			}
  }
}

//显示汉字
//x,y:起点坐标
//num:汉字对应的序号
//mode:0,反色显示;1,正常显示
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1,u8 mode)
{
    
    
	u8 m,temp;
	u8 x0=x,y0=y;
	u16 i,size3=(size1/8+((size1%8)?1:0))*size1;  //得到字体一个字符对应点阵集所占的字节数
	for(i=0;i<size3;i++)
	{
    
    
		if(size1==16)
				{
    
    temp=Hzk1[num][i];}//调用16*16字体
		else if(size1==24)
				{
    
    temp=Hzk2[num][i];}//调用24*24字体
		else if(size1==32)       
				{
    
    temp=Hzk3[num][i];}//调用32*32字体
		else if(size1==64)
				{
    
    temp=Hzk4[num][i];}//调用64*64字体
		else return;
		for(m=0;m<8;m++)
		{
    
    
			if(temp&0x01)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp>>=1;
			y++;
		}
		x++;
		if((x-x0)==size1)
		{
    
    x=x0;y0=y0+8;}
		y=y0;
	}
}


//num 显示汉字的个数
//space 每一遍显示的间隔
//mode:0,反色显示;1,正常显示
void OLED_ScrollDisplay(u8 num,u8 space,u8 mode)
{
    
    
	u8 i,n,t=0,m=0,r;
	while(1)
	{
    
    
		if(m==0)
		{
    
    
	    OLED_ShowChinese(128,24,t,16,mode); //写入一个汉字保存在OLED_GRAM[][]数组中
			t++;
		}
		if(t==num)
			{
    
    
				for(r=0;r<16*space;r++)      //显示间隔
				 {
    
    
					for(i=1;i<144;i++)
						{
    
    
							for(n=0;n<8;n++)
							{
    
    
								OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
							}
						}
           OLED_Refresh();
				 }
        t=0;
      }
		m++;
		if(m==16){
    
    m=0;}
		for(i=1;i<144;i++)   //实现左移
		{
    
    
			for(n=0;n<8;n++)
			{
    
    
				OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
			}
		}
		OLED_Refresh();
	}
}

//x,y:起点坐标
//sizex,sizey,图片长宽
//BMP[]:要写入的图片数组
//mode:0,反色显示;1,正常显示
void OLED_ShowPicture(u8 x,u8 y,u8 sizex,u8 sizey,u8 BMP[],u8 mode)
{
    
    
	u16 j=0;
	u8 i,n,temp,m;
	u8 x0=x,y0=y;
	sizey=sizey/8+((sizey%8)?1:0);
	for(n=0;n<sizey;n++)
	{
    
    
		 for(i=0;i<sizex;i++)
		 {
    
    
				temp=BMP[j];
				j++;
				for(m=0;m<8;m++)
				{
    
    
					if(temp&0x01)OLED_DrawPoint(x,y,mode);
					else OLED_DrawPoint(x,y,!mode);
					temp>>=1;
					y++;
				}
				x++;
				if((x-x0)==sizex)
				{
    
    
					x=x0;
					y0=y0+8;
				}
				y=y0;
     }
	 }
}
//OLED的初始化
void OLED_Init(void)
{
    
    
	GPIO_InitTypeDef  GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);	 //使能A端口时钟
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_15;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
 	GPIO_Init(GPIOA, &GPIO_InitStructure);	  //初始化GPIOA
 	GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_15);
	
	OLED_RES_Clr();
	delay_ms(200);
	OLED_RES_Set();
	
	OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
	OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
	OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
	OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	OLED_WR_Byte(0x00,OLED_CMD);//-not offset
	OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
	OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
	OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
	OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
	OLED_WR_Byte(0x12,OLED_CMD);
	OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
	OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
	OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
	OLED_WR_Byte(0x02,OLED_CMD);//
	OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
	OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
	OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
	OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
	OLED_Clear();
	OLED_WR_Byte(0xAF,OLED_CMD);
}


oled.h函数

/*oled.h*/
#ifndef __OLED_H
#define __OLED_H 

#include "sys.h"
#include "stdlib.h"	

//-----------------OLED端口定义---------------- 

#define OLED_SCL_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_0)//SCL
#define OLED_SCL_Set() GPIO_SetBits(GPIOA,GPIO_Pin_0)

#define OLED_SDA_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_1)//SDA
#define OLED_SDA_Set() GPIO_SetBits(GPIOA,GPIO_Pin_1)

#define OLED_RES_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_2)//RES
#define OLED_RES_Set() GPIO_SetBits(GPIOA,GPIO_Pin_2)

#define OLED_DC_Clr()  GPIO_ResetBits(GPIOA,GPIO_Pin_3)//DC
#define OLED_DC_Set()  GPIO_SetBits(GPIOA,GPIO_Pin_3)
 		     
#define OLED_CS_Clr()  GPIO_ResetBits(GPIOA,GPIO_Pin_4)//CS
#define OLED_CS_Set()  GPIO_SetBits(GPIOA,GPIO_Pin_4)


#define OLED_CMD  0	//写命令
#define OLED_DATA 1	//写数据

void OLED_ClearPoint(u8 x,u8 y);
void OLED_ColorTurn(u8 i);
void OLED_DisplayTurn(u8 i);
void OLED_WR_Byte(u8 dat,u8 mode);
void OLED_DisPlay_On(void);
void OLED_DisPlay_Off(void);
void OLED_Refresh(void);
void OLED_Clear(void);
void OLED_DrawPoint(u8 x,u8 y,u8 t);
void OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2,u8 mode);
void OLED_DrawCircle(u8 x,u8 y,u8 r);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1,u8 mode);
void OLED_ShowChar6x8(u8 x,u8 y,u8 chr,u8 mode);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1,u8 mode);
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1,u8 mode);
void OLED_Showlargenum(u8 x,u8 y,u8 num,u8 size1,u8 mode);
void OLED_ScrollDisplay(u8 num,u8 space,u8 mode);
void OLED_ShowPicture(u8 x,u8 y,u8 sizex,u8 sizey,u8 BMP[],u8 mode);
void OLED_Init(void);

#endif


6.3 DS18B20温度测量代码

#include "ds18b20.h"
#include "delay.h"

//复位DS18B20
void DS18B20_Rst(void)	   
{
    
                     
	DS18B20_IO_OUT(); 
    DS18B20_DQ_OUT=0; 	//拉低DQ
    delay_us(750);    	//拉低750us
    DS18B20_DQ_OUT=1; 	//DQ=1 
	delay_us(15);     	//15US
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void) 	   
{
    
       
	u8 retry=0;
	DS18B20_IO_IN();		 
    while (DS18B20_DQ_IN&&retry<200)
	{
    
    
		retry++;
		delay_us(1);
	};	 
	if(retry>=200)return 1;
	else retry=0;
    while (!DS18B20_DQ_IN&&retry<240)
	{
    
    
		retry++;
		delay_us(1);
	};
	if(retry>=240)return 1;	    
	return 0;
}
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void) 	 
{
    
    
  u8 data;
	DS18B20_IO_OUT();	
  DS18B20_DQ_OUT=0; 
	delay_us(2);
  DS18B20_DQ_OUT=1; 
	DS18B20_IO_IN();	
	delay_us(12);
	if(DS18B20_DQ_IN)data=1;
    else data=0;	 
    delay_us(50);           
    return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void)     
{
    
            
    u8 i,j,dat;
    dat=0;
	for (i=1;i<=8;i++) 
	{
    
    
        j=DS18B20_Read_Bit();
        dat=(j<<7)|(dat>>1);
    }						    
    return dat;
}
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)     
 {
    
                 
    u8 j;
    u8 testb;
	DS18B20_IO_OUT();	//SET PG11 OUTPUT;
    for (j=1;j<=8;j++) 
	{
    
    
        testb=dat&0x01;
        dat=dat>>1;
        if (testb) 
        {
    
    
            DS18B20_DQ_OUT=0;	// Write 1
            delay_us(2);                            
            DS18B20_DQ_OUT=1;
            delay_us(60);             
        }
        else 
        {
    
    
            DS18B20_DQ_OUT=0;	// Write 0
            delay_us(60);             
            DS18B20_DQ_OUT=1;
            delay_us(2);                          
        }
    }
}
//开始温度转换
void DS18B20_Start(void) 
{
    
       						               
    DS18B20_Rst();
	  DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);	// skip rom
    DS18B20_Write_Byte(0x44);	// convert
} 

//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在    	 
u8 DS18B20_Init(void)
{
    
    
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
		 
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);//把调试设置普通IO口

 	GPIO_InitStructure.GPIO_Pin = dq;			
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		  
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIO_ds18b20, &GPIO_InitStructure);

 	GPIO_SetBits(GPIO_ds18b20,dq);    //输出1

	DS18B20_Rst();

	return DS18B20_Check();
}  
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250) 
short DS18B20_Get_Temp(void)
{
    
    
    u8 temp;
    u8 TL,TH;
	short tem;
    DS18B20_Start ();  			// ds1820 start convert
    DS18B20_Rst();
    DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);	// skip rom
    DS18B20_Write_Byte(0xbe);	// convert	    
    TL=DS18B20_Read_Byte(); 	// LSB   
    TH=DS18B20_Read_Byte(); 	// MSB  
	    	  
    if(TH>7)
    {
    
    
        TH=~TH;
        TL=~TL; 
        temp=0;					//温度为负  
    }else temp=1;				//温度为正	  	  
    tem=TH; 					//获得高八位
    tem<<=8;    
    tem+=TL;					//获得底八位
    tem=(float)tem*0.625;		//转换     
	if(temp)return tem; 		//返回温度值
	else return -tem;    
}

DS18B20.h函数

#ifndef _DS18B20_H
#define _DS18B20_H

#include "sys.h"

#define dq (GPIO_Pin_15) 
#define GPIO_ds18b20 GPIOA

//IO方向设置
#define DS18B20_IO_IN()  {GPIOA->CRH&=0X0FFFFFFF;GPIOA->CRH|=8<<28;}
#define DS18B20_IO_OUT() {GPIOA->CRH&=0X0FFFFFFF;GPIOA->CRH|=3<<28;}

//IO操作函数											   
#define	DS18B20_DQ_OUT PAout(15) //数据端口	 
#define	DS18B20_DQ_IN  PAin(15)  //数据端口	 

u8 DS18B20_Init(void);//初始化DS18B20
short DS18B20_Get_Temp(void);//获取温度
void DS18B20_Start(void);//开始温度转换
void DS18B20_Write_Byte(u8 dat);//写入一个字节
u8 DS18B20_Read_Byte(void);//读出一个字节
u8 DS18B20_Read_Bit(void);//读出一个位
u8 DS18B20_Check(void);//检测是否存在DS18B20
void DS18B20_Rst(void);//复位DS18B20 


#endif

6.4 DHT11湿度测量代码

#include "DHT11.h"
#include "delay.h"

GPIO_InitTypeDef GPIO_InitStructure;	//后面会改变输入输出状态

static void GPIO_SETOUT(void);
static void GPIO_SETIN(void);
static u8 DHT11_Check(void);


/**********************************************
函数名:static void DHT11_Rst(void)
参数说明:无
返回值:无
函数作用:主机发送开始信号
***********************************************/
static void DHT11_Rst(void)
{
    
                     
		GPIO_SETOUT();											//配置成输出模式
    GPIO_ResetBits(DHT11_IO,DHT11_PIN); //拉低数据线
    delay_ms(20);    										//拉低至少18ms
    GPIO_SetBits(DHT11_IO,DHT11_PIN); 	//拉高数据线 
	  delay_us(30);     									//主机拉高20~40us
		GPIO_ResetBits(DHT11_IO,DHT11_PIN);
}


/**********************************************
函数名:u8 DHT11_Init(void)
参数说明:无
返回值:u8 ,返回1代表初始化成功,0则失败
函数作用:配置IO口,并发送开始信号
***********************************************/
u8 DHT11_Init(void){
    
    
	
	//IO口配置
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//换IO口需要修改
	
	GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出,如果需要考虑到IC的电流驱动能力时要接上拉电阻(5K)
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(DHT11_IO,&GPIO_InitStructure);
	
	DHT11_Rst();//发送开始信号
	
	return DHT11_Check();//检测DHT11的响应
}


/**********************************************
函数名:static void GPIO_SETOUT(void)
参数说明:无
返回值:无
函数作用:配置IO口为推挽输出模式
***********************************************/
static void GPIO_SETOUT(void)
{
    
    
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出,如果需要考虑到IC的电流驱动能力时要接上拉电阻(5K)
	GPIO_Init(DHT11_IO,&GPIO_InitStructure);
	
}


/**********************************************
函数名:static void GPIO_SETIN(void)
参数说明:无
返回值:无
函数作用:配置IO口为浮空输入模式
***********************************************/
static void GPIO_SETIN(void)
{
    
    
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式
	GPIO_Init(DHT11_IO,&GPIO_InitStructure);
}


/**********************************************
函数名:static u8 DHT11_Check(void)
参数说明:无
返回值:检测到回应-->返回1,否则0
函数作用:检测DHT11的响应信号
***********************************************/
static u8 DHT11_Check(void) 	   
{
    
       
	u8 retry=0;
	GPIO_SETIN();			//设置为输入模式	
	
  while (!GPIO_ReadInputDataBit(DHT11_IO,DHT11_PIN) && retry<100)//DHT11会拉低40~50us
	{
    
    
		retry++;
		delay_us(1);
	}
	if(retry >= 100)	//超时未响应/未收到开始信号,退出检测
		return 0;
	else 
		retry = 0;
  while (GPIO_ReadInputDataBit(DHT11_IO,DHT11_PIN) && retry<100)//DHT11拉低后会再次拉高40~50us
	{
    
    
		retry++;
		delay_us(1);
	}
	if(retry>=100)		//超时,DHT11工作出错,退出检测
		return 0;
	return 1;					//设备正常响应,可以正常工作
}


/**********************************************
函数名:static u8 DHT11_Read_Bit(void)
参数说明:无
返回值:返回从DHT11上读取的一个Bit数据
函数作用:从DHT11上读取一个Bit数据
***********************************************/
static u8 DHT11_Read_Bit(void)
{
    
    
 	u8 retry = 0;
	//DHT11的Bit开始信号为12-14us低电平
	while(GPIO_ReadInputDataBit(DHT11_IO,DHT11_PIN) && retry<100)//等待变为低电平(等待Bit开始信号)
	{
    
    
		retry++;
		delay_us(1);
	}
	retry = 0;
	while(!GPIO_ReadInputDataBit(DHT11_IO,DHT11_PIN) && retry<100)//等待变高电平(代表数据开始传输)
	{
    
    
		retry++;
		delay_us(1);
	}
	delay_us(30);//等待30us
	//0信号为26-28us,1信号则为116-118us,所以说超过30us去读取引脚状态就可以知道传输的值了
	if(GPIO_ReadInputDataBit(DHT11_IO,DHT11_PIN)) return 1;
	else return 0;		   
}


/***********************************************************************
函数名:static u8 DHT11_Read_Byte(void)
参数说明:无
返回值:返回从DHT11上读取的一个byte数据
函数作用:从DHT11上读取一个byte数据
************************************************************************/
static u8 DHT11_Read_Byte(void)    
{
    
            
  u8 i,dat;
  dat=0;
	
	for (i=0;i<8;i++) 
	{
    
    
   	dat<<=1; 
	  dat|=DHT11_Read_Bit();
  }	
	
  return dat;
}


/**************************************************************************
函数名:u8 DHT11_Read_Data(u8 *temp,u8 *humi)
参数说明:temp:用于存放温度值(范围:0~50°),humi:用于存放湿度值(范围:20%~90%)
返回值:1:成功读取数据,0:读取数据出错
函数作用:从DHT11上读取温湿度数据(这里省略小数值)
***************************************************************************/
u8 DHT11_Read_Data(u8 *humi1,u8 *humi2,u8 *temp1,u8 *temp2)
{
    
            
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	if(DHT11_Check()==1)	//设备响应正常
	{
    
    
		for(i=0;i<5;i++)//读取40位数据
		{
    
    
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])//进行校验
		{
    
    
			*humi1=buf[0];
			*humi2=buf[1];
			*temp1=buf[2];
			*temp2=buf[3];
		}
	}else return 0;		//设备未成功响应,返回0
	return 1;					//读取数据成功返回1
}


dht11.h头文件

#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"
#include "delay.h"


/* 设置GPIO脚,默认为PB7 */
#define DHT11_IO 		GPIOB
#define DHT11_PIN		GPIO_Pin_7

/* 初始化函数,如果DHT11存在响应则返回1,否则0 */
u8 DHT11_Init(void);
/* 从DHT11读取数据,没有小数部分 */
u8 DHT11_Read_Data(u8 *humi1,u8 *humi2,u8 *temp1,u8 *temp2);

#endif

6.5 DS1302实时时钟操作代码

#include "DS1302.h"
#include "delay.h"
#include "usart.h"
/*
DS1302接口:
	GPIOA_5 ->DS1302_RST
	GPIOA_6 ->DS1302_DAT
	GPIOA_7 ->DS1302_CLK
*/
struct TIMEData TimeData;

//DS1302地址定义
#define DS1302_SEC_ADDR           0x80		//秒数据地址
#define DS1302_MIN_ADDR           0x82		//分数据地址
#define DS1302_HOUR_ADDR          0x84		//时数据地址
#define DS1302_DAY_ADDR           0x86		//日数据地址
#define DS1302_MONTH_ADDR         0x88		//月数据地址
#define DS1302_WEEK_ADDR          0x8a		//星期数据地址
#define DS1302_YEAR_ADDR          0x8c		//年数据地址
#define DS1302_CONTROL_ADDR       0x8e		//控制数据地址
#define DS1302_CHARGER_ADDR       0x90 		//充电功能地址			 
#define DS1302_CLKBURST_ADDR      0xbe

#define WRITE_FLAG_ADDR 0xc0
#define READ_FLAG_ADDR  0xc1
#define FLAG_VAL        0x3a
 
//初始时间定义
u8 time_buf[8] = {
    
    0x20,0x21,0x03,0x04,0x04,0x43,0x00,0x04};//初始时间2021年3月4日04点**分00秒 星期四
u8 readtime[15];//当前时间
u8 sec_buf=0;  //秒缓存
u8 sec_flag=0; //秒标志位
 
//DS1302初始化函数
void DS1302_Init() 
{
    
    
	/*1.GPIOA时钟*/
	RCC->APB2ENR |= 1<<2;
	/*2. 配置GPIOA_5/6/7模式*/
	GPIOA->CRL &= 0X000FFFFF;
	GPIOA->CRL |= 0X33300000;
	GPIOA->ODR |=1<<6;
	//printf("DS1302_Init OK!\n");
 
}
//向DS1302写入一字节数据
void DS1302_WriteByte(u8 addr,u8 data) 
{
    
    
	u8 i;
	DS1302_RST=0; //禁止数据传输 !!!这条很重要
	DS1302_CLK=0; //确保写数据前SCLK为低电平
	DS1302_RST=1;	//启动DS1302总线	
	DS1302_OutPut_Mode();
	addr=addr&0xFE;  //最低位置零,寄存器0位为0时写,为1时读
	for(i=0;i<8;i++) //写入目标地址:addr
	{
    
    
		if (addr&0x01) DS1302_OUT=1;
		else DS1302_OUT=0;
		DS1302_CLK=1; //时钟上升沿写入数据
		DS1302_CLK=0;
		addr=addr>>1;
	}	
	for (i=0;i<8;i++) //写入数据:data
	{
    
    
		if(data&0x01) DS1302_OUT=1;
		else DS1302_OUT=0;
		DS1302_CLK=1;    //时钟上升沿写入数据
		DS1302_CLK=0;
		data = data >> 1;
	}
	DS1302_CLK=1;    // 将时钟电平置于高电平状态 ,处于已知状态
	DS1302_RST=0;	//停止DS1302总线
}
 
//从DS1302读出一字节数据
u8 DS1302_ReadByte(u8 addr) 
{
    
    
	u8 i,temp;	
	DS1302_RST=0; //这条很重要
	DS1302_CLK=0; //先将SCLK置低电平,确保写数居前SCLK被拉低
	DS1302_RST=1; //启动DS1302总线
	DS1302_OutPut_Mode();
	//写入目标地址:addr
	addr=addr|0x01; //最低位置高,寄存器0位为0时写,为1时读
	for(i=0;i<8;i++) 
	{
    
    
		if (addr&0x01) DS1302_OUT=1;
		else DS1302_OUT=0;
		DS1302_CLK=1; //写数据
		DS1302_CLK=0;
		addr = addr >> 1;
	}	
	//从DS1302读出数据:temp
	DS1302_InPut_Mode();
	for(i=0;i<8;i++)
	{
    
    
		temp=temp>>1;
		if (DS1302_IN) temp|=0x80;
		else temp&=0x7F;
		DS1302_CLK=1;
		DS1302_CLK=0;
	}	
	DS1302_CLK=1;  //将时钟电平置于已知状态
	DS1302_RST=0;	//停止DS1302总线
	return temp;
}
//向DS1302写入时钟数据,用于时间校准修改
void DS1302_WriteTime() 
{
    
    
	DS1302_WriteByte(DS1302_CONTROL_ADDR,0x00);       //关闭写保护
	DS1302_WriteByte(WRITE_FLAG_ADDR,FLAG_VAL); 			//写入已经设置时间标记,新增的
	DS1302_WriteByte(DS1302_SEC_ADDR,0x80);           //暂停时钟 
	//DS1302_WriteByte(DS1302_CHARGER_ADDR,0xa9);     //涓流充电 
	DS1302_WriteByte(DS1302_YEAR_ADDR,time_buf[1]);   //年 
	DS1302_WriteByte(DS1302_MONTH_ADDR,time_buf[2]);  //月 
	DS1302_WriteByte(DS1302_DAY_ADDR,time_buf[3]);    //日 
	DS1302_WriteByte(DS1302_HOUR_ADDR,time_buf[4]);   //时 
	DS1302_WriteByte(DS1302_MIN_ADDR,time_buf[5]);    //分
	DS1302_WriteByte(DS1302_SEC_ADDR,time_buf[6]);    //秒
	DS1302_WriteByte(DS1302_WEEK_ADDR,time_buf[7]);	  //周 
	DS1302_WriteByte(DS1302_CHARGER_ADDR,0xA5);//打开充电功能 选择2K电阻充电方式
	DS1302_WriteByte(DS1302_CONTROL_ADDR,0x80);//打开写保护     
}
//从DS1302读出时钟数据
void DS1302_ReadTime(void)  
{
    
    
	time_buf[1]=DS1302_ReadByte(DS1302_YEAR_ADDR);          //年 
	time_buf[2]=DS1302_ReadByte(DS1302_MONTH_ADDR);         //月 
	time_buf[3]=DS1302_ReadByte(DS1302_DAY_ADDR);           //日 
	time_buf[4]=DS1302_ReadByte(DS1302_HOUR_ADDR);          //时 
	time_buf[5]=DS1302_ReadByte(DS1302_MIN_ADDR);           //分 
	time_buf[6]=(DS1302_ReadByte(DS1302_SEC_ADDR))&0x7f;    //秒,屏蔽秒的第7位,避免超出59
	time_buf[7]=DS1302_ReadByte(DS1302_WEEK_ADDR);          //周 	
}

void ds1302_Init_time(void)
{
    
    
	if(DS1302_ReadByte(READ_FLAG_ADDR)!= FLAG_VAL)
	{
    
    
		DS1302_WriteTime();
	}
	else printf("OK!");
}
//主函数
void DS1302_GetTime()
{
    
     
    DS1302_ReadTime(); //读取时间
    TimeData.year=(time_buf[0]>>4)*1000+(time_buf[0]&0x0F)*100+(time_buf[1]>>4)*10+(time_buf[1]&0x0F); //计算年份
    TimeData.month=(time_buf[2]>>4)*10+(time_buf[2]&0x0F);  //计算月份
    TimeData.day=(time_buf[3]>>4)*10+(time_buf[3]&0x0F);    //计算日期
    TimeData.hour=(time_buf[4]>>4)*10+(time_buf[4]&0x0F);   //计算小时
    TimeData.minute=(time_buf[5]>>4)*10+(time_buf[5]&0x0F); //计算分钟
    TimeData.second=(time_buf[6]>>4)*10+(time_buf[6]&0x0F); //计算秒钟
    TimeData.week=(time_buf[7]&0x0F);                       //计算星期
																											
}

DS1302实时时钟头文件

#ifndef DS1302_H
#define DS1302_H
#include "stm32f10x.h"
#include "sys.h"
//DS1302引脚定义,可根据实际情况自行修改端口定义
#define DS1302_OutPut_Mode() {GPIOA->CRL &= 0xF0FFFFFF;GPIOA->CRL |= 0x03000000;}
#define DS1302_InPut_Mode()  {GPIOA->CRL &= 0xF0FFFFFF;GPIOA->CRL |= 0x08000000;}
 
#define DS1302_IN  PAin(6)
#define DS1302_OUT PAout(6)
#define DS1302_RST PAout(5)
#define DS1302_CLK PAout(7)

 
struct TIMEData
{
    
    
	u16 year;
	u8  month;
	u8  day;
	u8  hour;
	u8  minute;
	u8  second;
	u8  week;
};
extern struct TIMEData TimeData;
extern u8 readtime[15];
void DS1302_Init();
void DS1302_WriteByte(u8 addr,u8 data);
u8   DS1302_ReadByte(u8 addr);
void DS1302_WriteTime();
void DS1302_ReadTime(void);
void ds1302_Init_time(void);
void DS1302_GetTime();
 
#endif

致谢

感谢各位前辈对相关程序的开源,感谢我们的相遇。

猜你喜欢

转载自blog.csdn.net/weixin_51261221/article/details/114368017