【物联网开发】基于STM32和阿里云的室内温、湿、亮度监测系统(三)—— 设备端的传感器开发(DHT11和光敏电阻传感器)

这是我的物联网开发系列文章,将介绍如何从嵌入式开发、云平台开发、Android端开发来实现一个简单的物联网应用开发,体验物联网全栈开发的过程,积累开发的经验。
本篇文章为系列文章第三篇,主要介绍如何通过STM32来获取传感器(DHT11和光敏电阻传感器)的相关数据,开发工具是Keil MDK5和STM32CubeMX。
系列文章第一篇:物联网介绍和系统初步设计
系列文章第二篇:配置阿里云物联网平台及设备端连接测试

预警:本文内容比较长,有点罗嗦,包含较多图片,熟悉操作的小伙伴根据需要看。

一、生成初始化工程代码

1.主要配置内容

STM32CubeMX是一个非常好用的STM32配置初始化代码的工具。我们可以使用它来对芯片的时钟、引脚和外设进行初始化配置,然后再去Keil里面进行更深入的开发。
由于STM32F103C8T6的外设和引脚比较少,而我们使用的硬件又比较多,所以必须先对引脚的使用做好安排。根据我们的设计需要,我们的STM32F103C8T6需要配置的引脚如下表。表格里有些引脚和外设是在后续的文章中使用的,在这里也一并配置好。

GPIO口功能备注
PA9USART1_TXCH340的RX,连接串口调试助手
PA10USART1_RXCH340的TX,连接串口调试助手
PB10USART3_TXESP-12S的RX
PB11USART3_RXESP-12S的TX
PC13GPIOSTM32板上的LED
PA0GPIOSTM32板上的按键S2
PA1GPIO蜂鸣器
PA2GPIO风扇电机
PB0ADC1_IN8光敏电阻传感器
PA8OLED_RESOLED
PB15SPI2_MOSIOLED
PB14OLED_CSOLED
PB13SPI2_SCKOLED
PB12OLED_DCOLED
PA13SYS_JTMS-SWDIOST-Link
PA14SYS_JTCK-SWCLKST-Link
PB9GPIODHT11温湿度传感器

除了上述的引脚要配置之外,还要配置TIM时钟、NVIC中断、RCC的外部晶振和时钟树。

2.配置步骤【多图预警】

注意:STM32CubeMX在使用之前,需要安装好Java环境和STM32的MCU芯片包。具体的可以查看这篇博客:链接

2.1选取芯片创建工程

打开STM32CubeMX,然后选择从MCU新建一个工程。在左侧选择 STM32F1,然后在右边找到STM32F103C8选中,点击 Start Project。
在这里插入图片描述在这里插入图片描述

2.2配置引脚

注意:我在配置的时候并不是按照下文中的顺序,所以截图内容并不是按照我实际顺序来的。以下的外设(除了NVIC)都有配置就行,不用过分在意顺序。

  1. 配置RCC和外部晶振。点击左边的 RCC ,然把高速时钟和低速时钟都设置为外部晶振。如下图。
    在这里插入图片描述
  2. 配置DEBUG模式。点击左边的 SYS ,在Debug选项中选择SW模式(Serial Wire),下面的选项选择Systick。如果是使用JTAG调试的小伙伴就在Debug这里自行更改。
    在这里插入图片描述
  3. 配置USART串口。点击左边的USART1,在Mode选项中选择异步模式(Asynchronous),下面的参数保持默认就好。同样,USART3也是如此配置。
    在这里插入图片描述
  4. 配置TIM。点击左边的TIM2,在Clock Source选项选择内部时钟(Internal Clock),在下面的参数中,将PSC设置为7200-1,将ARR设置为1000-1,然后点击NVIC Settings页面,在enable那里打勾,开启TIM的中断,如下图。这个TIM2是用于串口USART3的通信接收的计时。
    在这里插入图片描述
    在这里插入图片描述
  5. 配置SPI。点击左边的SPI2,在Mode选项选择Transmit Only Master,下面的参数保持默认。这个SPI是用于OLED的。
    在这里插入图片描述
  6. 配置ADC。点击左边的ADC1,在Mode页面里将IN8打勾,开启ADC1的通道8,下面的参数保持默认。
    在这里插入图片描述
  7. 配置GPIO。在右边的芯片图那里,找到引脚PA8并点击,在选项中选择GPIO_Output模式,如下图。PB14、PB12、PA1、PA2、PC13也是如此设置为GPIO_Output模式。PA0、PB9引脚则设置为GPIO_Input模式。在这里插入图片描述
    然后点击左边的GPIO进入GPIO页面,点击PC13,将其的输出电平设置为高电平,并添加标签(User Label),标签的填写可以参照下图。使用标签的话,自动生成的代码会为该引脚添加标签的宏定义,方便我们后期使用。
    在这里插入图片描述
  8. 配置NVIC。点击左边的NVIC,将TIM2、USART1、USART3的中断打勾,然后设置中断优先级分别为1、3、2。
    在这里插入图片描述

最终配置完引脚的芯片图如下。
在这里插入图片描述

2.3配置时钟树

按照下图所示的5个地方,对应修改时钟树。
在这里插入图片描述

2.4生成代码

来到Project Manager,填写好工程名称和文件位置,选择IDE为MDK-ARM,选择最低版本为V5.27或者5.0都行。
在这里插入图片描述
点击左侧的Code Generator,将下图所示的两个选项选上。第一个选项表示只将我们用到的库函数代码保存到工程,可以节省工程的空间;第二个选项是表示生成代码时将各种外设的代码按照头文件和源文件的方式分开放,如果这个没勾的话,代码全都挤在main文件里面。
在这里插入图片描述
最后,点击右上角的GENERATE CODE就可以生成初始化的代码了。生成代码成功之后就可以在文件位置找到我们的工程了。
在这里插入图片描述

二、在Keil中进行初始配置

1.添加启动文件和DHT11的驱动程序

在STM32CubeMX生成的工程,我们需要自己将启动文件添加进工程,如下图添加到MDK-ARM分组。另外顺便将DHT11的相关驱动程序(dht11.c、dht11.h、delay.c、delay.h的代码详见本文后面的《DHT11驱动程序》一节)放到工程的文件夹中,同样也要将dht11.c和delay.c添加到工程的分组之中。
提醒:如果驱动程序放置的位置不在工程的头文件路径中,需要将放置驱动程序的位置添加到工程的头文件路径中。
在这里插入图片描述

2.配置调试串口(USART1)

由于我们需要使用串口将传感器数据输出打印到电脑,所以在这里先对用于调试的串口(USART1)进行简单配置。

  • usart.c
    首先我们要在usart.c中定义一个用于串口发送的缓存数组USART1_TX_BUF[USART1_MAX_SEND_LEN],然后添加一个用于串口输出的函数u1_printf(char* fmt,...),其中需要使用到头文件"string.h""stdio.h""stdarg.h",记得要include。
uint8_t  USART1_TX_BUF[USART1_MAX_SEND_LEN];
void u1_printf(char* fmt,...)  
{
	uint8_t i,j; 
	va_list ap; 
	va_start(ap,fmt);
	vsprintf((char*)USART1_TX_BUF,fmt,ap);
	va_end(ap);
	i=strlen((const char*)USART1_TX_BUF);//此次发送数据的长度
	for(j=0;j<i;j++)//循环发送数据
	{
		while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
		USART1->DR=USART1_TX_BUF[j];  
	}
}

注意:
1.使用STM32CubeMX生成的工程代码有严格的格式,很多的注释,我们自己在Keil上添加的代码一定要放在带有USER CODE BEGINUSER CODE END的注释之间的位置。没放在这些位置的代码,下次使用STM32CubeMX对本工程修改时会被清除掉。
2.在.c文件中添加的函数,如果是其他的文件也需要使用到的函数,比如上面说的u1_printf(char* fmt,...),需要在对应的头文件中添加函数的声明。
以上两点后面不再赘述。

在这里插入图片描述

  • usart.h
    在头文件中,要添加一个宏定义USART1_MAX_SEND_LEN,这个是表示缓存数组的长度的。另外,要将上面定义的缓存数组USART1_TX_BUF[USART1_MAX_SEND_LEN]给extern,方便别的文件可能会使用到。
#define USART1_MAX_SEND_LEN		256
extern uint8_t  USART1_TX_BUF[USART1_MAX_SEND_LEN];

三、获取温湿度数据

1.DHT11的介绍

1.1概述

DHT11是一款有已校准数字信号输出的温湿度传感器。 其精度湿度±5%RH, 温度±2℃,量程湿度20-90%RH, 温度0~50℃。
DHT11应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。1

1.2引脚信息

引脚注释
VDD供电 3-5.5VDC
DATA串行数据,单总线
NC空脚,请悬空
GND接地,电源负极

注:我这里使用的DHT11是经过再次封装的,在板子上已经将NC悬空,所以只有三个引脚(VDD、DATA、GND)。

1.3数据与通信

DATA引脚用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右。数据分小数部分和整数部分,具体格式如下:

  • 一次完整的数据传输为40bit,高位先出。
  • 数据格式:8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和
  • 数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。

DATA引脚的单总线通信需要使用到微秒级延时,基本过程如下图。后面的驱动程序也是要按照通信的过程原理进行拉高或者拉低,从而读取数据。
在这里插入图片描述

2.DHT11驱动程序

DHT11的驱动程序以及使用的延时程序来自于正点原子的程序,并修改了一些内容。

  • dht11.c
#include "dht11.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F103开发板
//DHT11驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/19
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

//复位DHT11
void DHT11_Rst(void)	   
{                 
	DHT11_IO_OUT(); 	//设置为输出
	DHT11_DQ_OUT=0; 	//拉低DQ
	delay_ms(20);    	//拉低至少18ms
	DHT11_DQ_OUT=1; 	//DQ=1 
	delay_us(30);     //主机拉高20~40us
}

//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void) 	   
{   
	u8 retry=0;
	DHT11_IO_IN();      //设置为输出	 
	while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=100)return 1;
	else retry=0;
	while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
	{
		retry++;
		delay_us(1);
	};
	if(retry>=100)return 1;	    
	return 0;
}

//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
 	u8 retry=0;
	while(DHT11_DQ_IN&&retry<100)//等待变为低电平
	{
		retry++;
		delay_us(1);
	}
	retry=0;
	while(!DHT11_DQ_IN&&retry<100)//等待变高电平
	{
		retry++;
		delay_us(1);
	}
	delay_us(40);//等待40us
	if(DHT11_DQ_IN)return 1;
	else return 0;		   
}

//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)    
{        
	u8 i,dat;
	dat=0;
	for (i=0;i<8;i++) 
	{
   		dat<<=1; 
	    dat|=DHT11_Read_Bit();
    }						    
    return dat;
}

//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)    
{        
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	if(DHT11_Check()==0)
	{
		for(i=0;i<5;i++)//读取40位数据
		{
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
		{
			*humi=buf[0];
			*temp=buf[2];
		}
	}else return 1;
	return 0;	    
}

//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在     	 
u8 DHT11_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
	
    __HAL_RCC_GPIOB_CLK_ENABLE();						//开启GPIOB时钟
	
    GPIO_Initure.Pin=GPIO_PIN_9;            //PB9
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);     //初始化
 
    DHT11_Rst();
	  return DHT11_Check();
}

  • dht11.h
#ifndef __DHT11_H
#define __DHT11_H
#include "delay.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F103开发板
//DHT11驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/19
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

//IO方向设置
#define DHT11_IO_IN()  {GPIOB->CRH &= 0xFFFFFF0F;GPIOB->CRH |= (8<<4);}
#define DHT11_IO_OUT() {GPIOB->CRH &= 0xFFFFFF0F;GPIOB->CRH |= (3<<4);}

//IO操作函数											   
#define	DHT11_DQ_IN     PBin(9) //数据端口	PB9
#define	DHT11_DQ_OUT    PBout(9)//数据端口	PB9

u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif

  • delay.c
#include "delay.h"
// 	 
//如果需要使用OS,则包括下面的头文件即可. 
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/17
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
//  

static u32 fac_us=0;							//us延时倍乘数

#if SYSTEM_SUPPORT_OS		
    static u16 fac_ms=0;				        //ms延时倍乘数,在os下,代表每个节拍的ms数
#endif
	
#if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
//    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
//  delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
//    delay_ostimedly:用于OS延时,可以引起任务调度.

//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif

//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif


//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedLock(&err);							//UCOSIII的方式,禁止调度,防止打断us延时
#else											//否则UCOSII
	OSSchedLock();								//UCOSII的方式,禁止调度,防止打断us延时
#endif
}

//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{	
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
#else											//否则UCOSII
	OSSchedUnlock();							//UCOSII的方式,恢复调度
#endif
}

//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
	OS_ERR err; 
	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
#else
	OSTimeDly(ticks);							//UCOSII延时
#endif 
}
 
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{	
	if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
	{
		OSIntEnter();							//进入中断
		OSTimeTick();       					//调用ucos的时钟服务程序               
		OSIntExit();       	 					//触发任务切换软中断
	}
}
#endif

			   
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
	u32 reload;
#endif
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
	fac_us=SYSCLK;						//不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
	reload=SYSCLK;					    //每秒钟的计数次数 单位为K	   
	reload*=1000000/delay_ostickspersec;	//根据delay_ostickspersec设定溢出时间
											//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右	
	fac_ms=1000/delay_ostickspersec;		//代表OS可以延时的最少单位	   
	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
	SysTick->LOAD=reload; 					//每1/OS_TICKS_PER_SEC秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}								    

#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
//延时nus
//nus:要延时的us数.	
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)	    								   
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;				//LOAD的值	    	 
	ticks=nus*fac_us; 						//需要的节拍数 
	delay_osschedlock();					//阻止OS调度,防止打断us延时
	told=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
		}  
	};
	delay_osschedunlock();					//恢复OS调度											    
}  
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u16 nms)
{	
	if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
	{		 
		if(nms>=fac_ms)						//延时的时间大于OS的最少时间周期 
		{ 
   			delay_ostimedly(nms/fac_ms);	//OS延时
		}
		nms%=fac_ms;						//OS已经无法提供这么小的延时了,采用普通方式延时    
	}
	delay_us((u32)(nms*1000));				//普通方式延时
}
#else  //不用ucos时

//延时nus
//nus为要延时的us数.	
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)	 
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;				//LOAD的值	    	 
	ticks=nus*fac_us; 						//需要的节拍数 
	told=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
		}  
	};
}

//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{
	u32 i;
	for(i=0;i<nms;i++) delay_us(1000);
}
#endif

  • delay.h
#ifndef __DELAY_H
#define __DELAY_H 			    
#include "main.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/17
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
// 

#define BITBAND(addr, bitnum) 	 ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  				 *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

typedef int32_t  s32;
typedef int16_t s16;
typedef int8_t  s8;

typedef const int32_t sc32;  
typedef const int16_t sc16;  
typedef const int8_t sc8;  

typedef __IO int32_t  vs32;
typedef __IO int16_t  vs16;
typedef __IO int8_t   vs8;

typedef __I int32_t vsc32;  
typedef __I int16_t vsc16; 
typedef __I int8_t vsc8;   

typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8;

typedef const uint32_t uc32;  
typedef const uint16_t uc16;  
typedef const uint8_t uc8; 

typedef __IO uint32_t  vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t  vu8;

typedef __I uint32_t vuc32;  
typedef __I uint16_t vuc16; 
typedef __I uint8_t vuc8;  	

void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);

#endif

3.获取数据并输出到串口

在main文件中,先定义temperature和humidity两个整数(uint_32)变量。
在main函数中,首先使用delay_init(72)对延时进行初始化,然后使用一个while循环来不断尝试将DHT11初始化直到成功。

delay_init(72);
u1_printf("启动传感器\r\n");
while(DHT11_Init())	//DHT11初始化
{
	HAL_Delay(200);
	u1_printf("DHT11故障\r\n");
}

在while(1)循环中,使用DHT11_Read_Data(&temperature, &humidity)读取DHT11的温湿度数据到变量temperature和humidity中,然后通过串口输出来。

while(1)
{
	HAL_Delay(1000);
	DHT11_Read_Data(&temperature, &humidity);
	u1_printf("temperature:%d, humidity:%d \r\n", temperature, humidity);
	HAL_Delay(2000);
}

程序文件完成后就进行编译和下载。将串口连接上,并打开调试助手进行接收。返回如下图的数据就是成功了。(温度29℃,湿度95%)
在这里插入图片描述

四、获取光照强度数据

1.光敏电阻传感器的介绍

光敏电阻传感器中最简单的电子器件是光敏电阻,它能感应光线的明暗变化,输出微弱的电信号,通过简单电子线路放大处理,可以控制LED灯具的自动开关。
该模块主要有以下的特点:

  1. 采用灵敏型光敏电阻传感器
  2. 比较器输出,信号干净,波形好,驱动能力强,超过 15mA。
  3. 配可调电位器可调节检测光线亮度
  4. 工作电压 3.3V-5V
  5. 输出形式 :DO 开关量输出(0 和 1)和 AO 模拟量输出(电压)
  6. 小板 PCB 尺寸:3.2cm x 1.4cm
  7. 使用宽电压 LM393 比较器

光敏电阻传感器模块对环境光线最敏感,一般用来检测周围环境的光线的亮度,触发单片机或继电器模块等。模块在环境光线亮度达不到设定阈值时,DO 端输出高电平,当外界环境光线亮度超过设定阈值时,DO 端输出低电平模拟量输出 AO 可以和 AD 模块相连,通过 AD 转换,可以获得
环境光强更精准的数值
。我这里使用的是它的模拟量输出 AO 。
在这里插入图片描述

2.通过ADC读取值获取光照强度

在adc.c文件中添加read_adc()get_light()两个函数。read_adc()是读取ADC口的电压数值的函数,get_light()是按照公式将读取的电压转换为光照强度(单位:勒克斯)。每个光照强度传感器的转换公式都存在一些差异,我这里的公式根据实验自己简单测算的。具体测算的方式可以查看这里:使用拟合方法实现光敏电阻传感器数值与光照强度的近似转换。如果不想要那么准的话,也可以直接使用我这里的公式。

uint16_t read_adc(void)
{
	uint16_t temp;
	HAL_ADC_Start(&hadc1);
	HAL_ADC_PollForConversion(&hadc1, 50);
    if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
	{
		temp = HAL_ADC_GetValue(&hadc1);
	}
	return temp;
}

uint16_t get_light(void)
{
	uint16_t adc = read_adc();
	uint16_t light = 200000000 * pow(adc, -2.086);
	return light;
}

3.获取数据并输出到串口

在main函数中使用get_light()函数读取光照强度,然后通过串口输出。

while(1)
{
	HAL_Delay(1000);
	DHT11_Read_Data(&temperature, &humidity);
	light = get_light();
	u1_printf("temperature:%d, humidity:%d, light:%d \r\n", temperature, humidity, light);
	HAL_Delay(2000);
}

在这里插入图片描述
本文到此结束,下一篇文章将介绍设备端的其他设备开发。

注释


  1. 内容主要来自百度百科:DHT11_百度百科 ↩︎

  • 22
    点赞
  • 110
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值