智能家居系统(基于STM32F103C8T6标准库+FreeRTOS+Qt串口开发实现)

视频演示:基于STM32F103C8T6标准库+FreeRTOS+Qt串口开发实现的智能家居项目_哔哩哔哩_bilibili

基于STM32F103C8T6标准库+FreeRTOS+Qt串口开发实现的智能家居项目: https://pan.baidu.com/s/1f41gAfOOnlcQoKoMx3o84A?pwd=6j2g 提取码: 6j2g

 注:本项目为学习完《江科大STM32教程》实战项目。受限于个人水平,还有很多需要改进的地方,还请读者见谅,如有不当之处,还请指证。

01 项目需求

1.主控:STM32F103C8T6;

2.按键+LED:模拟开关灯,支持本地控制和上位机控制;

3.按键+直流电机驱动模块+直流电机+风扇叶片:模拟降温系统,支持本地控制和上位机控制,本地控制实现直流电机转或不转,上位机控制带调速功能;

4.按键+无源蜂鸣器:模拟音乐播放系统,支持本地控制和上位机控制;

5.DHT11温湿度传感器:测量温湿度并上报;

6.BH1750光照传感器:测量光照强度并上报;

7.OLED显示屏:显示温湿度、光照强度、音乐播放状态;

8.Qt串口上位机:Qt上位机实现,LED、风扇、音乐播放控制以及温湿度采集。

02 硬件连接

注意:本电路仅体现个模块之间的电路连接。原理图绘制参看:

嘉立创EDA使用流程

本来一开始想用ESP8266使用mQTT协议进行远程控制的,不过在做项目过程中,ESP8266似乎被玩坏了,所以临时改用串口控制,使用STM32的串口1,上图中ESP8266模块就没用到了,后期看情况更新MQTT版本。

03 驱动部分编写

3.1 照明系统

功能:实现本地按键开关灯,远程开关灯。本节先实现本地任务。

硬件:按键+LED

因为测量温度、亮度、播放音乐等任务执行后一直进行,所以需要使用中断来处理开关灯任务。

3.1.1 LED驱动

LED.c

/**
*Encoding:GB2312
*文件功能:LED开关灯功能实现
**/

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:LED初始化,LED接在PC13上
  * 参    数:无
  * 返 回 值:无
  */
void LED_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);		 //开启GPIOC的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);						//将PC13引脚初始化为推挽输出
	
	/*设置GPIO初始化后的默认电平*/
	GPIO_SetBits(GPIOC, GPIO_Pin_13);				          //设置PC13引脚为高电平,不亮,因为我们的引脚正极接高电平,负极接PC13
}

/**
  * 函    数:LED1开启
  * 参    数:无
  * 返 回 值:无
  */
void LED_ON(void)
{
	GPIO_SetBits(GPIOC, GPIO_Pin_13);		//设置PC13引脚为低电平
}

/**
  * 函    数:LED1关闭
  * 参    数:无
  * 返 回 值:无
  */
void LED_OFF(void)
{
	GPIO_ResetBits(GPIOC, GPIO_Pin_13);		//设置PC13引脚为高电平
}

/**
  * 函    数:LED状态翻转
  * 参    数:无
  * 返 回 值:无
  */
void LED_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13) == 0)	//获取输出寄存器的状态,如果当前引脚输出低电平
	{
		GPIO_SetBits(GPIOC, GPIO_Pin_13);					//则设置PC13引脚为高电平
	}
	else													//否则,即当前引脚输出高电平
	{
		GPIO_ResetBits(GPIOC, GPIO_Pin_13);					//则设置PC13引脚为低电平
	}
}

LED.h

#ifndef __LED_H
#define __LED_H

void LED_Init(void);
void LED_ON(void);
void LED_OFF(void);	
void LED_Turn(void);

#endif

 3.1.2 LED按键驱动

这个项目中一共使用了3个按键,本章先给出LED按键驱动的代码,最后会整合到一起去。按键接在PC15引脚上。

Key1.c

/**
*Encoding:GB2312
*文件功能:按键1功能实现
**/
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"

/**
*函数:LED按键1初始化函数
*参数:无
*返回值:无
**/
void Key1_Init(void)
{
	/*第1步:打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);         //开启GPIOC的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);          // 开启AFIO的时钟
	/*EXTI和NVIC的时钟一直是打开的,不需要开启:
	EXTI作为一个独立外设,按理说应该是需要开启时钟的但是寄存器里没有EXTI时钟的控制位
	NVIC是内核的外设,内核的外设是不需要开启时钟的
	RCC管的都是内核外的外设*/
	/*第2步:配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);						//将PC15引脚初始化为上拉输入	
	/*第3步:配置AFIO:
	它的库函数是和GPIO在一个文件里的*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource15);
	/*第4步:配置EXTI*/
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line15;              //指定中断线为第15个线路
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      //指定中断线的模式为中断触发
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;  //指定中断触发方式为上升沿和下降沿均触发
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;                //指定选择的中断线的新状态
	EXTI_Init(&EXTI_InitStruct);
	/*第5步:配置NVIC
	因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,在misc.h文件里*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
	/*设置优先级分组方式,需要注意的是,这个分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要
	执行依次就行了,如果把它放在模块里面进行分组,要确保每个模块分组都是选的同一个,也可以把这个分组代码
	放在主函数的最开始,这样模块里就不用在再进行分组了*/	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;   //指定中断通道  
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;   //将抢占优先级设置为1,优先级是在多个中断源同时申请,产生拥挤的时候才有作用
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;    //将响应优先级设置为1
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;       //使能中断通道
	NVIC_Init(&NVIC_InitStruct);
}

/*中断函数:中断函数的名字是固定的,在"startup_stm32f10x_md.s"文件里查看*/
void EXTI15_10_IRQHandler(void)   //中断函数的名字都是固定的,并且无参无返回值
{
	if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15) == 0)			//读PC15输入寄存器的状态,如果为0,则代表按键1按下
	{
		LED_Turn();			    //LED翻转
	}
	EXTI_ClearITPendingBit(EXTI_Line15);     //将通道15中断标志位清除 
}	

Key1.h

#ifndef __KEY_H
#define __KEY_H

void Key1_Init(void);

#endif

通过以上代码,可以实现按键开关灯。需要改进的是,这里按键上升沿下降沿都能触发,因为是在中断函数里实现的LED状态翻转,所以没有进行消抖。

3.2 降温系统

功能:实现本地能够按键开关风扇,远程开关风扇+调速;高于/低于温度阈值自动开启/关闭风扇。本节先实现本地按键开关风扇。

硬件:按键+直流电机驱动模块+直流电机+风扇叶片

3.2.1 直流电机驱动

电路连接,参考江科大教程:

因为要实现电机的调速功能,所以需要使用PWM驱动。

PWM.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA2引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;                 //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;               //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/ 
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);		//设置CCR3的值
}

PWM.h

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);

#endif

Motor.c

/**
*Encoding:GB2312
*文件功能:直流电机驱动功能实现
**/


#include "stm32f10x.h"                  // Device header
#include "PWM.h"
#include "stdbool.h"

bool MotorState;                //定义一个全局变量用来记录电机的状态

/**
*函数:直流电机驱动模块初始化函数
*参数:无
*返回值:无
**/
void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	PWM_Init();
}

/**
*函数:直流电机转速设置函数
*参数:Speed设置电机转速,返回state电机状态
*返回值:返回state电机状态:转/不转
**/
bool Motor_SetSpeed(int8_t Speed)
{
	if (!MotorState)
	{
		if (Speed >= 0)
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_4);
			GPIO_ResetBits(GPIOA, GPIO_Pin_5);
			PWM_SetCompare2(Speed);
		}
		else
		{
			GPIO_ResetBits(GPIOA, GPIO_Pin_4);
			GPIO_SetBits(GPIOA, GPIO_Pin_5);
			PWM_SetCompare2(-Speed);
		}
		MotorState = true;
	}
	return MotorState;                         //如果风扇处于转动过程中返回真,用来处理中断状态翻转
}

Motor.h 

#ifndef __MOTOR_H
#define __MOTOR_H
#include "stdbool.h"

void Motor_Init(void);
bool Motor_SetSpeed(int8_t Speed);

#endif

3.2.2 风扇电机按键驱动 

Key2.c

/**
*Encoding:GB2312
*文件功能:按键功能实现
**/
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Key2.h"
#include "Motor.h"
#include "stdbool.h"


extern bool MotorState;  


/**
*函数:风扇按键2初始化函数
*参数:无
*返回值:无
**/
void Key2_Init(void)
{
	/*第1步:打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);         //开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);          // 开启AFIO的时钟
	/*EXTI和NVIC的时钟一直是打开的,不需要开启:
	EXTI作为一个独立外设,按理说应该是需要开启时钟的但是寄存器里没有EXTI时钟的控制位
	NVIC是内核的外设,内核的外设是不需要开启时钟的
	RCC管的都是内核外的外设*/
	/*第2步:配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1引脚初始化为上拉输入	
	/*第3步:配置AFIO:
	它的库函数是和GPIO在一个文件里的*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
	/*第4步:配置EXTI*/
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line1;              //指定中断线为第1个线路
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      //指定中断线的模式为中断触发
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;  //指定中断触发方式为上升沿和下降沿均触发
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;                //指定选择的中断线的新状态
	EXTI_Init(&EXTI_InitStruct);
	/*第5步:配置NVIC
	因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,在misc.h文件里*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
	/*设置优先级分组方式,需要注意的是,这个分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要
	执行依次就行了,如果把它放在模块里面进行分组,要确保每个模块分组都是选的同一个,也可以把这个分组代码
	放在主函数的最开始,这样模块里就不用在再进行分组了*/	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn;             //指定中断通道  
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;   //将抢占优先级设置为1,优先级是在多个中断源同时申请,产生拥挤的时候才有作用
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;    
	//将响应优先级设置为1
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;       //使能中断通道
	NVIC_Init(&NVIC_InitStruct);
}


void EXTI1_IRQHandler(void)
{
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键2按下
	{
		if(MotorState)
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_4);
			GPIO_SetBits(GPIOA, GPIO_Pin_5);
      //按键按下后,如果原来是转的,那么设置PWM为0,电机停止转动
			MotorState = false;
		}
		else
		{
			Motor_SetSpeed(60);                                //按下按键后,如果电机原来不转,设置转速默认为60,电机开始转动
		}
		 
	}
	EXTI_ClearITPendingBit(EXTI_Line1);     //将通道1中断标志位清除 
}


Key.h 

#ifndef __KEY_H
#define __KEY_H

void Key2_Init(void);

#endif

通过以上文件,我们能够实现按键控制直流电机转或不转,当然因为在中断里面使用按键,所以也没有实现避免按键消抖的问题。 

3.3 音乐播放系统 

功能:实现本地能够按键开始或停止播放音乐,远程开始或停止播放音乐。本节先实现本地任务。

硬件:按键+无源蜂鸣器

3.3.1 驱动蜂鸣器实现音乐播放功能

参考连接:

STM32+无源蜂鸣器播放音乐参考

这里我直接使用了以上up的源代码。

Buzzer.c

/**
*Encoding:GB2312
*文件功能:蜂鸣器硬件功能实现
**/

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
*函数:蜂鸣器所使用外设初始化函数
*参数:无
*返回值:无
**/
void Music_init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);          //开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);         //开启GPIOA的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;                     
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;             //将PA0引脚初始化为复用推挽输出	
	GPIO_Init(GPIOA, &GPIO_InitStructure);                        //受外设控制的引脚,均需要配置为复用模式	
	TIM_InternalClockConfig(TIM2);                                //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 99;	
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1439;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 50;	
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	

	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 50;		//CCR
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	TIM_Cmd(TIM2, ENABLE);
}

/**
*函数:蜂鸣器频率设置
*参数:无
*返回值:无
**/
void Sound_SetHZ(uint16_t a)
{
	TIM_PrescalerConfig(TIM2,a,TIM_PSCReloadMode_Immediate);
}

/**
*函数:播放音乐
*参数:无
*返回值:无
**/
void Play_Music(int a,int b,int c)
{
	Music_init();
	Sound_SetHZ(a);Delay_ms(b);Sound_SetHZ(20);Delay_ms(c);
}

/**
*函数:暂停音乐
*参数:无
*返回值:无
**/
void Stop_Music(void)
{
	Play_Music(20,10,10);  //将蜂鸣器频率改为0,实现停止功能
}

Buzzer.h

#ifndef __PLAYMUSIC_H__
#define __PLAYMUSIC_H__

void Music_init(void);
void Sound_SetHZ(uint16_t a);
void Play_Music(int a,int b,int c);
void Stop_Music(void);

#endif

Music.c

/**
*Encoding:GB2312
*文件功能:音乐播放软件功能实现
**/

#include "stm32f10x.h"                  // Device header
#include "Buzzer.H"
#include "stm32f10x.h"           
#include "Delay.h"
#include "OLED.h"

uint8_t MusicState = 0;                    // 定义一个全局变量来记录音乐播放的状态 

int i,time,j,a[]={                                                           //音调
		    1635,1376, 917,1376,2062,1836,1635, 917,1376,1376,1836, 917,1376,1376,
		     917,1456, 917,1635,1376, 917,1376,2062,1836,1635, 917,1376,1376,1836,
	         917,1376,1376, 917,1836,1376, 917,                             //前奏 34
	          30, 917, 917,1376,1376,1226,1092,                              //故事的小黄花 41
	          30, 917, 917,1376,1376,1226,1092,1226,1376,1836,               //从出生那年就飘着 51  
	          30, 917, 917,1376,1376,1226,1092,                              //童年的荡秋千 58 
	          30,1092,1226,1092,1031,1092,1226,1031,1092,1226,1376,          //随记忆一直晃到现在 69
	        1836,1376,1376,1092,1031,1092,1226,                              //Re So So Si Do Si La 76
	        1376,1226,1092,1092,1092,1092,1226,1092,1226,1376,               //So La Si Si Si Si La Si La So 86 
	        1836,1376,1226,1092,1031,1092,1226,1376,                         //吹着前奏 望着天空 94
	        1226,1092,1092,1092,1092,1226,1092,1226,1376,                    //我想起花瓣试着掉落 103 
	          30,1376,1376,1376,1376,1635,1456,1376, 917,1031,1092,1376,1367,//没想到 失去的勇气我还留着 116
	          30,1376,1376,1376,1376,1092,1376,                               //好想再问一遍 123
	        1635,1456,1376, 917,1031,1092,1376,1226,                          //你会等待还是离开 131
	        1092,1226,1031,1092,1376, 917, 728, 687, 728, 917,1376,           //刮风这天 我试过握着你手 142
        	  30,1376, 817, 817,  30, 817, 917, 917,                          //但偏偏 雨渐渐 150
	          30, 917,1031,1092,1226,1092,1031,1092,                          //大到我看你不见 158
	          30,1092,1031, 917,1092,  30,1031, 917, 728, 612, 728, 687, 687, //还要多久 我才能在你身边 171 
	          30, 687, 687, 917, 917, 817, 917,1031,                          //等到放晴的那天 179
	        1226,1092, 1031, 917, 817,1376,817, 728, 728,                     //也许我会比较好一点 188
	        1092,1226,1031,1092,  30,1376, 917, 728, 687, 728, 917,1376,      //从前从前 有个人爱你很久 200
     		  30,1376, 817, 817,  30, 817, 917, 917,                          //但偏偏 风渐渐 208
			  30, 917,1031,1092,1226,1092,1031,1092,                          //大到我看你不见 216
			  30,1092,1031, 917,1092,  30,1031, 917, 728, 612, 728, 687, 687, //好不容易 又能再多爱一天 229
			  30,1376, 687, 917, 917, 817, 917,                               //但故事的最后 236
			1031,1635,1456,1376,1226,1092,1226,30,1092,1376                   //你好像还是说了 拜拜 246
		  };

int tm[]={                                                  //音调时长
		  50, 50, 50, 50, 50, 25, 25, 50, 50, 50, 50, 50, 50, 50,
	      50, 50, 50, 50, 50, 50, 50, 50, 25, 25, 50, 50, 50, 50,
	      50, 50, 50, 50, 25, 25, 50,                       //前奏  
	      50, 50, 50, 50,100, 50, 50,                       //故事的小黄花
          50, 50, 50, 50, 50, 25, 25, 25, 25, 50,           //从出生那年就飘着
       	  50, 50, 50, 50,100, 50, 50,                       //童年的荡秋千  
	      50,100, 25, 25, 25, 25, 25, 25, 25, 25, 50,       //随记忆一直晃到现在
     	  50, 50, 50, 50, 50, 50, 50,                        //Re So So Si Do Si La
      	  25, 25, 50, 50, 50, 50, 25, 25, 50,100,            //So La Si Si Si Si La Si La So 
	      50, 50, 50, 50, 50, 50, 50, 25,                    //吹着前奏 望着天空
     	  25, 50, 50, 50, 50, 25, 25, 50, 75,                //我想起花瓣试着掉落 
	     400, 25, 25, 25, 25, 50, 50, 50, 50, 50, 50, 50,125,//没想到 失去的勇气我还留着 
	      50, 25, 25, 25, 25, 50, 50,                        //好想再问一遍
	      50, 50, 50, 50, 50, 50, 50,130,                    //你会等待还是离开
	      50, 50, 50,100, 50, 50, 50, 50, 50, 50, 50,        //刮风这天 我试过握着你手
	      50, 50, 50, 50, 50, 50, 50, 50,                    //但偏偏 雨渐渐 
	      50, 50, 50, 50, 50, 50, 50,125,                    //大到我看你不见
	      75, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,125,//还要多久 我才能在你身边  
	      50, 50, 50, 50, 50, 50, 50, 50,                    //等到放晴的那天
	      50, 50, 50, 50, 50, 50, 75, 25,100,                //也许我会比较好一点
	      50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,    //从前从前 有个人爱你很久
		  50, 50, 50, 50, 50, 50, 50, 50,                    //但偏偏 风渐渐 
		  50, 50, 50, 50, 50, 50, 50,150,                    //大到我看你不见
		  50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,125,//好不容易 又能再多爱一天 
		  50, 50, 50, 50, 50, 50, 50,                        //但故事的最后
		  50, 50, 50, 50, 50, 50,100, 20, 50,200,            //你好像还是说了 拜拜
		};


/**
  * 函    数:音乐播放
  * 参    数:无
  * 返 回 值:无
  */				
void B_Music(void)
{
	MusicState++;
	int c;
	for(i=0;i<=246;i++)
	{     
		c=5;j=tm[i]/25;
		time=j*180;
		if(i==49||i==67||i==178)
		{
			c=0;
		}	
		Play_Music(a[i],time,c);
	}	
}

Music.h

#ifndef __SOUND_H__
#define __SOUND_H__

void B_Music(void);

#endif

3.3.2 蜂鸣器按键功能实现

Key3.c

/**
*Encoding:GB2312
*文件功能:按键3功能实现
**/

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Key.h"
#include "LED.h"
#include "Buzzer.h"
#include "Music.h"
#include "stdio.h"
#include "usart1.h"

extern uint8_t MusicState;

/**
*函数:蜂鸣器按键3初始化函数
*参数:无
*返回值:无
**/
void Key3_Init(void)
{
	/*第1步:打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);         //开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);          // 开启AFIO的时钟
	/*EXTI和NVIC的时钟一直是打开的,不需要开启:
	EXTI作为一个独立外设,按理说应该是需要开启时钟的但是寄存器里没有EXTI时钟的控制位
	NVIC是内核的外设,内核的外设是不需要开启时钟的
	RCC管的都是内核外的外设*/
	/*第2步:配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA0引脚初始化为上拉输入	
	/*第3步:配置AFIO:
	它的库函数是和GPIO在一个文件里的*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7);
	/*第4步:配置EXTI*/
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line7;              //指定中断线为第0个线路
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      //指定中断线的模式为中断触发
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;  //指定中断触发方式为上升沿和下降沿均触发
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;                //指定选择的中断线的新状态
	EXTI_Init(&EXTI_InitStruct);
	/*第5步:配置NVIC
	因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,在misc.h文件里*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
	/*设置优先级分组方式,需要注意的是,这个分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要
	执行依次就行了,如果把它放在模块里面进行分组,要确保每个模块分组都是选的同一个,也可以把这个分组代码
	放在主函数的最开始,这样模块里就不用在再进行分组了*/	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;   //指定中断通道  
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;   //将抢占优先级设置为1,优先级是在多个中断源同时申请,产生拥挤的时候才有作用
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;    //将响应优先级设置为1
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;       //使能中断通道
	NVIC_Init(&NVIC_InitStruct);
}





/*中断函数:中断函数的名字是固定的,在"startup_stm32f10x_md.s"文件里查看*/
/**
*函数:按键开始或播放音乐
*参数:无
*返回值:无
**/
void EXTI9_5_IRQHandler(void)
{
	  if(EXTI_GetITStatus(EXTI_Line7)==SET)
		{
			if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == 0)			//读PA7输入寄存器的状态,如果为0,则代表按键1按下
			{
				printf("MusicState原始状态:%d\r\n",MusicState);
				if(MusicState)
				{				
//					Delay_ms(50);
					Stop_Music();                                      //如果本来就在播放音乐,按键按下那就停止播放音乐,并将音乐状态标志清零				
					MusicState = 0;
					printf("如果原来大于1,MusicState按下暂停后归0:%d\r\n",MusicState);
				}
				else if(MusicState == 0)
				{
					B_Music();
					printf("MusicState播放音乐后:%d\r\n",MusicState);
				}
			}  
			EXTI_ClearITPendingBit(EXTI_Line7);     //将通道7中断标志位清除 
		}	
}

这里我们使用了串口1打印了一下音乐播放状态标志,用来确定一下按键无法正常工作的原因。 

Key3.h

#ifndef __KEY_H
#define __KEY_H

void Key3_Init(void);
void Key_StopMusic(void);

#endif

通过以上函数,我们实现了按键控制蜂鸣器播放音乐或暂停,但是是有很大缺陷的代码,只能实现按键功能一次性使用,什么意思呢?如果我的音乐上电后初始状态为播放状态,我能够实现暂停以后再开启,然后需要等整首音乐播放完才能通过按键重启,并不能中途暂停;如果我的音乐初始状态非播放,我能通过按键控制开始播放音乐,然后需要等整首音乐播放完才能通过按键重启,并不能中途暂停。这是因为代码是从上往下执行的,而我们用来记录音乐播放状态的变量没办法再音乐播放过程中修改,所以用这种方法没办法实现顺利实现我们想要的功能。我们使用中断进入了一个时间很长的中断程序进入了音乐播放状态,如果能够生效,就相当于我在A中断运行的时候再去触发A中断,这显示是无法实现的。当然,使用定时器定时中断的功能或许能够实现,但是太过复杂,这里我们先不管,后面用FreeRtos解决这个问题。

3.4 合并LED+直流电机+蜂鸣器功能

LED和直流电机、蜂鸣器我们都使用了按键进行控制,我们将它们的按键驱动程序合并到一块。

这时候就会出现一个问题,当我使用中断进入音乐播放功能在工作时,我按下LED和直流电机的按键会发现无效,这是因为中断优先级的问题,因为使用中断进入音乐播放本来就在中断中,我们必须使用比音乐播放更高优先级的中断才能跳出音乐播放。进行如下修改:

Key.c

/**
*Encoding:GB2312
*文件功能:按键功能实现
**/
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Key.h"
#include "LED.h"
#include "Motor.h"
#include "stdbool.h"
#include "Buzzer.h"
#include "Music.h"
#include "Motor.h"
#include "stdio.h"
#include "usart1.h"

extern bool MotorState;  
extern uint8_t MusicState;

/**
*函数:LED按键1初始化函数
*参数:无
*返回值:无
**/
void Key1_Init(void)
{
	/*第1步:打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);         //开启GPIOC的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);          // 开启AFIO的时钟
	/*EXTI和NVIC的时钟一直是打开的,不需要开启:
	EXTI作为一个独立外设,按理说应该是需要开启时钟的但是寄存器里没有EXTI时钟的控制位
	NVIC是内核的外设,内核的外设是不需要开启时钟的
	RCC管的都是内核外的外设*/
	/*第2步:配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);						//将PC15引脚初始化为上拉输入	
	/*第3步:配置AFIO:
	它的库函数是和GPIO在一个文件里的*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource15);
	/*第4步:配置EXTI*/
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line15;              //指定中断线为第15个线路
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      //指定中断线的模式为中断触发
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;  //指定中断触发方式为上升沿和下降沿均触发
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;                //指定选择的中断线的新状态
	EXTI_Init(&EXTI_InitStruct);
	/*第5步:配置NVIC
	因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,在misc.h文件里*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
	/*设置优先级分组方式,需要注意的是,这个分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要
	执行依次就行了,如果把它放在模块里面进行分组,要确保每个模块分组都是选的同一个,也可以把这个分组代码
	放在主函数的最开始,这样模块里就不用在再进行分组了*/	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;   //指定中断通道  
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;   //将抢占优先级设置为1,优先级是在多个中断源同时申请,产生拥挤的时候才有作用
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;    //将响应优先级设置为2
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;       //使能中断通道
	NVIC_Init(&NVIC_InitStruct);
}

/**
*函数:风扇按键2初始化函数
*参数:无
*返回值:无
**/
void Key2_Init(void)
{
	/*第1步:打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);         //开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);          // 开启AFIO的时钟
	/*EXTI和NVIC的时钟一直是打开的,不需要开启:
	EXTI作为一个独立外设,按理说应该是需要开启时钟的但是寄存器里没有EXTI时钟的控制位
	NVIC是内核的外设,内核的外设是不需要开启时钟的
	RCC管的都是内核外的外设*/
	/*第2步:配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1引脚初始化为上拉输入	
	/*第3步:配置AFIO:
	它的库函数是和GPIO在一个文件里的*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
	/*第4步:配置EXTI*/
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line1;              //指定中断线为第1个线路
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      //指定中断线的模式为中断触发
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;  //指定中断触发方式为上升沿和下降沿均触发
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;                //指定选择的中断线的新状态
	EXTI_Init(&EXTI_InitStruct);
	/*第5步:配置NVIC
	因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,在misc.h文件里*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
	/*设置优先级分组方式,需要注意的是,这个分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要
	执行依次就行了,如果把它放在模块里面进行分组,要确保每个模块分组都是选的同一个,也可以把这个分组代码
	放在主函数的最开始,这样模块里就不用在再进行分组了*/	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn;             //指定中断通道  
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;   //将抢占优先级设置为2,优先级是在多个中断源同时申请,产生拥挤的时候才有作用
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;    
	//将响应优先级设置为1
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;       //使能中断通道
	NVIC_Init(&NVIC_InitStruct);
}

/**
*函数:蜂鸣器按键3初始化函数
*参数:无
*返回值:无
**/
void Key3_Init(void)
{
	/*第1步:打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);         //开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);          // 开启AFIO的时钟
	/*EXTI和NVIC的时钟一直是打开的,不需要开启:
	EXTI作为一个独立外设,按理说应该是需要开启时钟的但是寄存器里没有EXTI时钟的控制位
	NVIC是内核的外设,内核的外设是不需要开启时钟的
	RCC管的都是内核外的外设*/
	/*第2步:配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA0引脚初始化为上拉输入	
	/*第3步:配置AFIO:
	它的库函数是和GPIO在一个文件里的*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7);
	/*第4步:配置EXTI*/
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line7;              //指定中断线为第0个线路
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      //指定中断线的模式为中断触发
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;  //指定中断触发方式为上升沿和下降沿均触发
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;                //指定选择的中断线的新状态
	EXTI_Init(&EXTI_InitStruct);
	/*第5步:配置NVIC
	因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,在misc.h文件里*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
	/*设置优先级分组方式,需要注意的是,这个分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要
	执行依次就行了,如果把它放在模块里面进行分组,要确保每个模块分组都是选的同一个,也可以把这个分组代码
	放在主函数的最开始,这样模块里就不用在再进行分组了*/	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;   //指定中断通道  
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;   //将抢占优先级设置为1,优先级是在多个中断源同时申请,产生拥挤的时候才有作用
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;    //将响应优先级设置为1
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;       //使能中断通道
	NVIC_Init(&NVIC_InitStruct);
}


/*中断函数:中断函数的名字是固定的,在"startup_stm32f10x_md.s"文件里查看*/
/**
*函数:按键翻转LED状态和音乐播放状态,因为LED和音乐播放共用了同一个中断函数
*参数:无
*返回值:无
**/
void EXTI15_10_IRQHandler(void)   //中断函数的名字都是固定的,并且无参无返回值
{
	if(EXTI_GetITStatus(EXTI_Line15)==SET)
	{
		if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15) == 0)			//读PC15输入寄存器的状态,如果为0,则代表按键1按下
		{
			LED_Turn();			    //LED翻转
		}
		EXTI_ClearITPendingBit(EXTI_Line15);     //将通道15中断标志位清除 
	}
}	


/*中断函数:中断函数的名字是固定的,在"startup_stm32f10x_md.s"文件里查看*/
/**
*函数:按键翻转风扇状态
*参数:无
*返回值:无
**/
void EXTI1_IRQHandler(void)
{
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键2按下
	{
		if(MotorState)
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_4);
			GPIO_SetBits(GPIOA, GPIO_Pin_5);
      //按键按下后,如果原来是转的,那么设置PWM为0,电机停止转动
			MotorState = false;
		}
		else
		{
			Motor_SetSpeed(60);                                //按下按键后,如果电机原来不转,设置转速默认为60,电机开始转动
		}
		 
	}
	EXTI_ClearITPendingBit(EXTI_Line1);     //将通道1中断标志位清除 
}


/*中断函数:中断函数的名字是固定的,在"startup_stm32f10x_md.s"文件里查看*/
/**
*函数:按键开始或播放音乐
*参数:无
*返回值:无
**/
void EXTI9_5_IRQHandler(void)
{
	  if(EXTI_GetITStatus(EXTI_Line7)==SET)
		{
			if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == 0)			//读PA7输入寄存器的状态,如果为0,则代表按键1按下
			{
				printf("MusicState原始状态:%d\r\n",MusicState);
				if(MusicState)
				{				
					Stop_Music();                                      //如果本来就在播放音乐,按键按下那就停止播放音乐,并将音乐状态标志清零				
					MusicState = 0;
					printf("如果原来大于1,MusicState按下暂停后归0:%d\r\n",MusicState);
				}
				else if(MusicState == 0)
				{
					B_Music();
					printf("MusicState播放音乐后:%d\r\n",MusicState);
				}
			}  
			EXTI_ClearITPendingBit(EXTI_Line7);     //将通道7中断标志位清除 
		}	
}

Key.h

#ifndef __KEY_H
#define __KEY_H

void Key1_Init(void);
void Key2_Init(void);
void Key3_Init(void);

#endif

修改后,在main.c 中初始化上述几节程序并下载到STM32后,可以发现在播放音乐的同时能够控制LED和直流电机,但是就是音乐播放功能无法达到我们想要的效果。

3.5 DHT11温湿度传感器驱动

DHT11.c

/**
*Encoding:GB2312
*文件功能:温湿度传感器功能实现
**/

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "stdio.h"
#include "DHT11.h"

uint8_t DHT11_Result[5] = {0};


/**
*函数:DHT11初始化函数
*参数:无
*返回值:无
**/
void DHT11_Init(void)
{
	/*打开时钟外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);         //开启GPIOA的时钟
	/*配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						
	GPIO_SetBits(GPIOA,GPIO_Pin_8);                             //DHT11采用单总线协议进行数据传输,DHT11空闲状态置为高电平               
	
}

/**
*函数:DHT11初始化函数
*参数:无
*返回值:无
*注释:总线空闲状态应为高电平,主机把总线拉低等待DHT11响应,且拉低时间必须大于18ms
**/
void DHT11_Start(void)
{
	GPIO_ResetBits(GPIOA,GPIO_Pin_8);        
	Delay_ms(20);                           //主机拉低电平至少18ms发出开始信号
	GPIO_SetBits(GPIOA,GPIO_Pin_8);   
	Delay_us(20);                           //主机拉高电平延时20~40us等待DHT11发出响应信号
}

/**
*函数:DHT11响应函数
*参数:无
*返回值:无
*注释:DHT11接收到主机开始信号后进行响应
**/
uint8_t DHT11_Response(void)
{
	uint16_t time = 0;
	while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)&& time < 100)	           // 开始信号发出后处于高电平状态,接收信号后主机应该收到低电平,但是可能不会立即切换,因此用循环消耗一下这个切换时间
	{
		Delay_us(1);
		time++;
	}	
	if (time >= 100)
		return 1;                                                         // 返回1说明响应超时,可能电路出现问题
	time = 0;
	while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)&& time < 100)	      // 真正的DHT11响应信号,80us低电平信号
	{
		Delay_us(1);
		time++;
	}	
	if (time >= 100)
		return 1;                     // 返回1说明响应超时,可能电路出现问题
	return 0;
}

/**
*函数:接收DHT11数据函数
*参数:无
*返回值:无
**/
uint8_t DHT11_Read_Bit(void)
{
	uint16_t time = 0;
	
	while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)&& time < 100)       //DHT11响应信号发出结束后,先输出80us高电平,准备发送数据
	{
		Delay_us(1);
		time++;
	}
	
	if (time >= 100)
		return 2;                    //返回2说明响应超时,可能电路出现问题	
	time = 0;
	while (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) && time < 100)      //DHT11发出50us低电平信号后发送传感器测量数据
	{
		Delay_us(1);
		time++;
	}
	if (time >= 30)                 //返回2说明响应超时,可能电路出现问题	
		return 2;
	
	Delay_us(30);                   //延时30us
	
	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == 0)     
		return 0;
	else                                                     // 如果30us后还是高电平,说明DHT11发送的是1
		return 1;
}


/**
*函数:接收DHT11字节数据函数
*参数:无
*返回值:无
**/
uint8_t DHT11_Read_Byte(void)
{
	uint8_t data = 0;
	uint8_t i = 0;
	
	for (i = 0; i < 8; i++)
	{
		data <<= 1;
		data = data | DHT11_Read_Bit();
	}
	
	return data;
}

/**
*函数:将接收到的DHT11数据转换为温湿度
*参数:无
*返回值:无
**/
void DHT11_Read_Data(uint8_t *pData)
{
	DHT11_Start();
	
	if (DHT11_Response())
		return;
	
	uint8_t i;
	for (i = 0; i < 5; i++)
	{
		pData[i] = DHT11_Read_Byte();
	}
	
	if (pData[4] != pData[0] + pData[1] + pData[2] + pData[3])
	{
		for (i = 0; i < 5; i++)
		{
			pData[i] = 0;
		}
	}
}

/**
*函数:变换温湿度数据为常规数据
*参数:接收温湿度数据的结构体
*返回值:温湿度数据结构体
**/
dht11_result DHT11_GetResult(dht11_result *result)
{
	DHT11_Read_Data(DHT11_Result);
	result->humi = DHT11_Result[0]+DHT11_Result[1]/10.0;
	result->temp = DHT11_Result[2]+DHT11_Result[3]/10.0;
	return *result;
}

DHT11.h

#ifndef __DHT11_H
#define __DHT11_H

typedef struct{
	float humi;
	float temp;
}dht11_result;

void DHT11_Init(void);
void DHT11_Read_Data(uint8_t *pData);
dht11_result DHT11_GetResult(dht11_result *result);

#endif

3.6 OLED驱动 

江科大OLED显示屏教程

我们直接移植江科大写好的驱动即可,我做了下改动,把显示正负号那里改成了空格,效果如下:

 main.c

/**
*Encoding:GB2312
*文件功能:项目功能实现
**/

#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "String.h"
#include "Delay.h"
#include "usart1.h"                //这个串口是给电脑使用的,用来在串口助手显示信息
#include "usart2.h"                //这个串口是给ESP8266使用的
#include "timer.h"
#include "Key.h"
#include "LED.h"
#include "OLED.h"
#include "Buzzer.h"
#include "Music.h"
#include "Motor.h"
#include "DHT11.h"
//#include "BH1750.h"
//#include "ESP8266.h"


int main(void)
{	                   
//	Key1_Init();                         //按键1初始化,用于控制LED灯
//	Key2_Init();                         //按键2初始化,用来控制直流电机,模拟风扇
//	Key3_Init();                         //按键3初始化,用来控制蜂鸣器播放音乐
//	LED_Init();	                         //LED_Init初始化
//	Motor_Init();                        //直流电机初始化
//	B_Music();	                         //音乐播放功能初始化,初始化后直接开始播放音乐
	Serial_Init();                       //串口1初始化,用于在串口显示数据
	DHT11_Init();                        //DHT11温湿度传感器初始化
	OLED_Init();                         //OLED初始化函数
//	bh1750_init();
//	USART2_Init(115200);                 //串口2初始化,波特率为115200,用于ESP8266与STM32交互
//	WIFI_GPIO_Init();
//	Rst_WIFI();
//	WIFI_Init();
//	TIM3_Int_Init(9999,35999);
	
	OLED_ShowChinese(0,0,"温度:");
	OLED_ShowChinese(0,17,"湿度:");
	OLED_ShowChinese(0,33,"亮度:");
	OLED_ShowChinese(0,49,"音乐:停止播放");
	OLED_Update();
	while (1)
	{
		dht11_result MyDHT11Result;
		MyDHT11Result= DHT11_GetResult(&MyDHT11Result);
		printf("温度:%.1f;湿度:%.1f\r\n",MyDHT11Result.temp,MyDHT11Result.humi);
		OLED_ShowFloatNum(49, 0, MyDHT11Result.temp, 2, 1, OLED_8X16);
		OLED_ShowChinese(92,0,"℃");
		OLED_ShowFloatNum(49, 17, MyDHT11Result.humi, 2, 1, OLED_8X16);
		OLED_ShowString(92,17, "%RH", OLED_8X16);
		OLED_Update();	
	}
}

3.7 BH1750光照传感器

 BH1750驱动参考

BH1750使用I2C传输数据,我移植了上述博主的代码。不过在我这里好像有问题,感觉结果不是很准确,先不管了,我先把系统跑起来再说。

MyI2C.c

/**************************************************************************
 * 文件名  :MyI2C.c
 * 描述    :软件模拟IIC程序  
****************************************************************************/

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "Delay.h"

/*引脚配置层*/

/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(I2C_GPIO_PORT, I2C_SCL_GPIO_PIN, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(I2C_GPIO_PORT, I2C_SDA_GPIO_PIN, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_GPIO_PIN);		//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}

/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(I2C_GPIO_CLK, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = I2C_SCL_GPIO_PIN | I2C_SDA_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_GPIO_PIN | I2C_SDA_GPIO_PIN);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
  * 函    数:SDA引脚输入输出模式配置
  * 参    数:mode,定义SDA引脚模式,大于0为输出模式,否则为输入模式
  * 返 回 值:无
  */
void Set_I2C_SDAMode(uint8_t mode)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    if(mode > 0)
    {// 设置为输出模式
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏或推挽,外部需要接上拉电阻
    }
    else
    {// 设置为输入模式
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 浮空或上拉,外部需要接上拉电阻
    }
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = I2C_SDA_GPIO_PIN;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct);
}

/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	Delay_us(5);
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
	Delay_us(5);
}

/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop(void)
{
	MyI2C_W_SCL(0);	
	MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平
	Delay_us(5);
	MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
	Delay_us(5);
}

/**
  * 函    数:I2C发送应答信号
  * 参    数:ack,主机应答信号,1或0,1表示下一个时序不需要再接收数据,0表示下一个时序继续接收数据
  * 返 回 值:返回1,表示程序运行正常
  */
uint8_t MyI2C_SendAck(int ack)
{
	Set_I2C_SDAMode(1);               
	if(ack == 1)
    {// 发送ACk
        MyI2C_W_SDA(1); 
    }
	else if(ack == 0)
	{
		// 发送ACk
        MyI2C_W_SDA(0); 
	}
	else
    {
        return 0;  // 入参有误,发送失败
    }  
	MyI2C_W_SCL(1);	
	Delay_us(5);
	MyI2C_W_SCL(0);	
	Delay_us(5);
	return 1;     //发送成功返回1为真	
}

/**
  * 函    数:I2C接收应答信号
  * 参    数:无
  * 返 回 值:返回1,表示程序运行正常
  */
uint8_t MyI2C_ReciveAck(void)
{
	uint8_t ack = 0;
    uint8_t timeout = 5;

    MyI2C_W_SDA(1); 
    Set_I2C_SDAMode(0);  // SDA输入模式
    MyI2C_W_SCL(1);           
    Delay_us(5);  
    while(timeout--)       
    {// 等待从设备发送ACK
        if(MyI2C_R_SDA() == 0)
        {// 读到应答信号
            ack = 1;  
        }
        else
        {// 没有读到应答信号,继续等待
            Delay_us(1);
            if(timeout == 0)
            {// 等待超时
                ack = 0; 
            }
        }
    }           
    MyI2C_W_SCL(0);             
    Delay_us(5);        
    Set_I2C_SDAMode(1); // SDA输出模式 
    return ack;
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
uint8_t MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		if(0x80 & Byte)
        {	
			MyI2C_W_SDA(1);
		}	
		else
		{
			MyI2C_W_SDA(0);
		}
		Byte <<= 1;
		MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		Delay_us(5);  
		MyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
		Delay_us(5);  
	}
	return MyI2C_ReciveAck();
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0,bit;				//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	Set_I2C_SDAMode(0);                     //SDA输入模式 
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		Byte <<= 1;
		MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		Delay_us(5);
		if (MyI2C_R_SDA() == 1)
		{
			bit = 0x80;
		}	
		else
		{
			bit = 0x00;
		}
		Byte |= bit; 
		MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
		Delay_us(5);
	}
	Set_I2C_SDAMode(1);                     //SDA输出模式 
	return Byte;							//返回接收到的一个字节数据
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:addr:发送设备地址,
  * 返 回 值:返回应答信号,返回1说明发送成功,返回0发送失败
  */
uint8_t MyI2C_SendBytes(uint8_t addr, uint8_t *buf, uint8_t buf_size)
{
    uint8_t i;
    uint8_t result = 0;
	MyI2C_Start();
    if(MyI2C_SendByte(addr << 1))    // 发送设备地址(7bit地址)
    {// 收到应答,发送成功
        for (i = 0; i < buf_size; i++)  // 发送数据
        {
            if(MyI2C_SendByte(buf[i]))
            {// 收到应答,发送成功
                result = 1;
            }
            else
            {// 没有收到应答,发送失败
                result = 0; 
            }
        }
    }
    MyI2C_Stop();                   // 发送停止信号
    return result;
}

/* IIC接收多个数据 */
uint8_t MyI2C_ReceiveBytes(uint8_t addr, uint8_t *buf, uint8_t buf_size)
{
    uint8_t i;    
    uint8_t result = 0;
	MyI2C_Start();
    if(MyI2C_SendByte((addr << 1) | 1))  // 发送设备地址(7bit地址)
    {
        for (i = 0; i < buf_size; i++)    // 连续读取数据
        {
            *buf++ = MyI2C_ReceiveByte(); 
            if (i == buf_size - 1)
            {
                MyI2C_SendAck(1);        // 最后一个数据需要回NACK
            }
            else
            {        
                MyI2C_SendAck(0);        // 发送ACK
            }
        }
        result = 1;
    }
    MyI2C_Stop();                   // 发送停止信号

	return result;
}

MyI2C.h

#ifndef __MYI2C_H
#define __MYI2C_H

/*这里将软件I2C使用引脚及外设定义在.H文件里,方便以后更换外设时修改,移植时只需要对这四个定义修改即可*/
#define      I2C_GPIO_CLK                	RCC_APB2Periph_GPIOB
#define      I2C_GPIO_PORT                 	GPIOB
#define      I2C_SCL_GPIO_PIN            	GPIO_Pin_13
#define      I2C_SDA_GPIO_PIN               GPIO_Pin_14

void MyI2C_W_SCL(uint8_t BitValue);
void MyI2C_W_SDA(uint8_t BitValue);
uint8_t MyI2C_R_SDA(void);
void MyI2C_Init(void);
void Set_I2C_SDAMode(uint8_t mode);
void MyI2C_Start(void);
void MyI2C_Stop(void);	
uint8_t MyI2C_SendAck(int ack);
uint8_t MyI2C_ReciveAck(void);
uint8_t MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
uint8_t MyI2C_SendBytes(uint8_t addr, uint8_t *buf, uint8_t buf_size);
uint8_t MyI2C_ReceiveBytes(uint8_t addr, uint8_t *buf, uint8_t buf_size);

#endif

BH1750.c

/**************************************************************************
 * 文件名  :bh1750.c
 * 描述    :光强传感模块     
****************************************************************************/
#include "stm32f10x.h"                  // Device header
#include "bh1750.h"
#include "Delay.h"
#include "MyI2C.h"


#define LOG_ENABLE

#ifdef LOG_ENABLE
    #include "stdio.h"
    #define LOG   printf
#else
    #define LOG(format, ...)    
#endif

uint8_t current_mode;    // BH1750的测量模式
float current_light;     // BH1750的测量光照值

// BH1750延时函数
void bh1750_Delay(uint16_t ms)
{// ms级延时,BH1750每次测量都需要时间,该函数用于等待测量结果
    Delay_ms(ms);
}

// 写命令
uint8_t bh1750_write_cmd(uint8_t cmd)
{
    return MyI2C_SendBytes(BH1750_ADDRESS_LOW, &cmd, 1);
}

// 写寄存器
uint8_t bh1750_read_regs(uint8_t *buf, uint8_t buf_size)
{
    return MyI2C_ReceiveBytes(BH1750_ADDRESS_LOW, buf, buf_size);
}

// 复位
uint8_t bh1750_reset(void)
{
    return bh1750_write_cmd(BH1750_RESET);
}
// 打开电源
uint8_t bh1750_power_on(void)
{
    return bh1750_write_cmd(BH1750_POWER_ON);
}

// 关闭电源
uint8_t bh1750_power_down(void)
{
    return bh1750_write_cmd(BH1750_POWER_DOWN);
}

// 设置测量模式
uint8_t bh1750_set_measure_mode(uint8_t mode)
{
    uint8_t result = 0;

    if(bh1750_write_cmd(mode))
    {
        result =  1;
    }
    return result;
}

// 单次读取光照值
uint8_t bh1750_single_read_light(uint8_t mode, float *light)
{// 单次测量模式在测量后会自动设置为断电模式   
    uint8_t temp[2];
    uint8_t result = 0;
    
    if(mode != BH1750_ONE_H_RES_MODE && mode != BH1750_ONE_H_RES_MODE2 && mode != BH1750_ONE_L_RES_MODE)
    {
        LOG("bh1750 single read measure mode error! mode:0x%02x\r\n", mode);
        return result;
    }
    if(bh1750_set_measure_mode(mode)) // 每次采集前先设置模式
    {
        LOG("bh1750 set measure mode success! mode:0x%02x\r\n", mode);
        current_mode = mode;
        switch (mode)
        {
        case BH1750_ONE_H_RES_MODE:  // 单次H分辨率模式(精度1lx,测量时间120ms)
            bh1750_Delay(120);  // 等待采集完成
            break;
        case BH1750_ONE_H_RES_MODE2: // 单次H分辨率模式(精度0.5lx,测量时间120ms)
            bh1750_Delay(120);  // 等待采集完成
            break;
        case BH1750_ONE_L_RES_MODE:  // 单次L分辨率模式(精度4lx,测量时间16ms)
            bh1750_Delay(16);  // 等待采集完成
            break;
        default:
            break;
        }
        if(bh1750_read_regs(temp, 2))  // 读取测量结果
        {
            *light = ((float)((temp[0] << 8) + temp[1]) / 1.2); // 换算成光照值
            result = 1;
        }
        else
        {
            LOG("bh1750 read light failed!\r\n");
        }
    }
    else
    {
        LOG("bh1750 set measure mode failed! mode:0x%02x\r\n", mode);
        return result;
    }
    
    return result;
}

// 连续读取光照值
uint8_t bh1750_continuous_read_light(uint8_t mode, float *light)
{   
    uint8_t temp[2];
    uint8_t result = 0;
    
    if(mode != BH1750_CON_H_RES_MODE && mode != BH1750_CON_H_RES_MODE2 && mode != BH1750_CON_L_RES_MODE)
    {
        LOG("bh1750 continuous read measure mode error! mode:0x%02x\r\n", mode);
        return result;
    }

    if(mode != current_mode)
    {// 要使用的测量模式和BH1750当前的模式不同,则配置成相同模式
        if(bh1750_set_measure_mode(mode))
        {
            LOG("bh1750 set measure mode success! mode:0x%02x\r\n", mode);
            current_mode = mode;
        }
        else
        {// 模式设置失败
            LOG("bh1750 set measure mode failed! mode:0x%02x\r\n", mode);
            return result;
        }
        switch (mode)
        {
        case BH1750_CON_H_RES_MODE:  // 连续H分辨率模式(精度1lx,测量时间120ms)
            bh1750_Delay(120);  // 等待采集完成
            break;
        case BH1750_CON_H_RES_MODE2: // 连续H分辨率模式(精度0.5lx,测量时间120ms)
            bh1750_Delay(120);  // 等待采集完成
            break;
        case BH1750_CON_L_RES_MODE:  // 连续L分辨率模式(精度4lx,测量时间16ms)
            bh1750_Delay(16);  // 等待采集完成
            break;
        default:
            break;
        }
    }
    if(bh1750_read_regs(temp, 2))  // 读取测量结果
    {
        *light = ((float)((temp[0] << 8) + temp[1]) / 1.2); // 换算成光照值
        result = 1;
    }
    else
    {
        LOG("bh1750 read light failed!\r\n");
    }
    
    return result;
}

// BH1750初始化
uint8_t bh1750_init(void)
{
    uint8_t result = 0;
    MyI2C_Init();        // IIC初始化
    result = bh1750_power_on(); // 打开BH1750电源
    current_mode = 0;
    return result;
}

// 单次读取BH1750例程
void bh1750_read_example(void)
{
	bh1750_single_read_light(BH1750_ONE_H_RES_MODE2, &current_light);
	printf("光照强度为:%0.1f\r\n",current_light);
	Delay_us(100);
}

BH1750.h

#ifndef __BH1750_H__
#define __BH1750_H__

#include "stm32f10x.h"

#define BH1750_ADDRESS_LOW      0x23    // addr low  7bit:0x23 8bit:0x46
#define BH1750_ADDRESS_HIGH     0x5C    // addr high 7bit:0x5C 8bit:0xB8

/*bh1750 registers define */
#define BH1750_POWER_ON			0x01	// power on
#define BH1750_POWER_DOWN   	0x00	// power down
#define BH1750_RESET			0x07	// reset	
#define BH1750_CON_H_RES_MODE	0x10	// Continuously H-Resolution Mode
#define BH1750_CON_H_RES_MODE2	0x11	// Continuously H-Resolution Mode2 
#define BH1750_CON_L_RES_MODE	0x13	// Continuously L-Resolution Mode
#define BH1750_ONE_H_RES_MODE	0x20	// One Time H-Resolution Mode
#define BH1750_ONE_H_RES_MODE2	0x21	// One Time H-Resolution Mode2
#define BH1750_ONE_L_RES_MODE	0x23	// One Time L-Resolution Mode

uint8_t bh1750_init(void);
void bh1750_read_example(void);

#endif

04 STM32F103移植FreeRTOS

上面我们完成所有外设驱动程序得编写,那么为了解决上面提到的问题,现在我们进行移植FreeRTOS了。

参考教程:

FreeRTOS入门与工程实践-韦东山老师

FreeRTOS实践教程

FreeRTOS书籍-正点原子

上述资料先看韦东山老师的课程,韦东山老师讲解了一些必要的理论知识,有助于第二个实践课程的理解;建议直接刷完两套视频,刷视频过程中理解即可,不用跟着做,然后对着书自己做FreeRTOS项目。

这个版本的智能家居项目仅仅用到了FreeRTOS的任务创建、挂起与恢复以及中断,还有很多需要改进的地方,缺点也比较明显,后期看情况进行迭代。

05 Qt上位机开发

参考:

Qt仪表盘开发

Qt串口助手开发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/875080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构之红黑树的 “奥秘“

目录&#xff1a; 一.红黑树概念 二. 红黑树的性质 三.红黑树的实现 四.红黑树验证 五.AVL树和红黑树的比较 一.红黑树概念 1.红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何 一条从根…

MySQL--库的操作

文章目录 1.创建数据库2.创建数据库案例3.字符集和校验规则3.1默认字符集3.2默认校验规则3.3查看系统默认字符集以及校验规则3.4查看数据库支持的字符3.5查看数据库支持的字符集校验规则3.6校验规则对数据库的影响不区分大小写查询&#xff1a;排序结果&#xff1a;区分大小写查…

综合案例-数据可视化-地图

一、pyecharts—地图快速入门 假设我们要将6个地区的某种数量在地图上标注出来&#xff0c;首先导入pyecharts包内地图相关模块&#xff0c;然后准备地图数据&#xff08;数据类型是列表&#xff0c;列表的元素类型为元组&#xff09;&#xff0c;然后把准备好的数据添加进地图…

51单片机个人学习笔记11(AT24C02-I2C总线)

前言 本篇文章属于STC89C52单片机&#xff08;以下简称单片机&#xff09;的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 [1-1] 课程简介_哔哩…

Ubuntu查看系统用户信息

0 Preface/Foreword 1 查看方式 1.1 查看系统用户 getent passwd getent: Get entries for Name Service Switch Libraries. 该命令会列出系统上所有用户的详细信息&#xff0c;包括用户名、密码、用户ID&#xff08;UID&#xff09;、组ID&#xff08;GID&#xff09;、用户描…

0基础学习爬虫系列:程序打包部署

1.目标 将已经写好的python代码&#xff0c;打包独立部署或运营。 2. 环境准备 1&#xff09;通义千问 &#xff1a;https://tongyi.aliyun.com/qianwen 2&#xff09;0基础学习爬虫系列–网页内容爬取&#xff1a;https://blog.csdn.net/qq_36918149/article/details/14199…

Pytest-@pytest.fixture夹具篇(一)

一、定义 在Python的pytest测试框架中&#xff0c;pytest.fixture是一个&#xff08;不是唯一&#xff09;装饰器&#xff0c;用于定义一个测试夹具。 二、简单实例 使用参数autouserTrue pytest.fixture(autouseTrue) def my_fixture():print("Setup: 准备测试环境&q…

前端框架有哪些?

前言 用户体验是每个开发网站的企业中的重中之重。无论后台有多方面的操作和功能&#xff0c;用户的视图和体验都必须是无缝的最友好的。这需要使用前端框架来简化交互式、以用户为中心的网站的开发。 前端框架是一种用于简化Web开发的工具&#xff0c;它提供了一套预定义的代…

[环境配置]ubuntu20.04安装后wifi有图标但是搜不到热点解决方法

最近刚入手一台主机&#xff0c;暗影精灵8plus电竞主机&#xff0c;安装ubuntu后wifi怎么都搜不到热点&#xff0c;前后重装系统6次才算解决问题。这个心酸历程只有搞技术人才明白。下面介绍我解决过程。 首先主机到手后是个windows10系统&#xff0c;我用无线网连接了一下&am…

基于SpringBoot+Vue+MySQL的垃圾分类回收管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域的鸿沟&#xff0c;信息的…

Redis面试题整理

Redis 1、Redis主从集群 1.1、搭建主从集群 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离 1.2、主从同步原理 当主从第一次同步连接或断开重连时&#xff0c;从节点都会发送psync请求&…

sass实现文字两侧横线

sass实现文字两侧横线 自我记录 mixin 的基本作用&#xff1a; 代码复用&#xff1a;把常用的样式封装在一起&#xff0c;不需要重复写相同的代码。参数化&#xff1a;可以通过参数动态生成样式&#xff0c;提高灵活性。逻辑处理&#xff1a;结合 Sass 的控制语句&#xff0…

【最新华为OD机试E卷-支持在线评测】计算疫情扩散时间(200分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,…

【JavaScript】LeetCode:31-35

文章目录 31 反转链表32 回文链表33 环形链表34 环形链表Ⅱ35 合并两个有序链表 31 反转链表 初始化&#xff1a;cur head&#xff0c;pre null。pre和cur一起向前移。由于反转链表时&#xff0c;cur.next指向pre&#xff0c;导致cur在下次循环中就找不到了原来的cur.next&am…

微擎忘记后台登录用户名和密码怎么办?解决方法

微擎忘记后台登录名和登录密码是很常见的&#xff0c;服务器百科网fwqbk.com告诉你找回后台登录用户名和密码的方法&#xff1a; 一&#xff1a;找回微擎后台用户名 &#xff08;如果只是忘记了后台登录密码&#xff0c;请忽略此步骤&#xff0c;跳转到第二步&#xff09; 通…

2.ChatGPT的发展历程:从GPT-1到GPT-4(2/10)

引言 在人工智能领域&#xff0c;自然语言处理&#xff08;NLP&#xff09;是连接人类与机器的重要桥梁。随着技术的不断进步&#xff0c;我们见证了从简单的文本分析到复杂的语言理解的转变。ChatGPT&#xff0c;作为自然语言处理领域的一个里程碑&#xff0c;其发展历程不仅…

C++ | Leetcode C++题解之第395题至少有K个重复字符的最长子串

题目&#xff1a; 题解&#xff1a; class Solution { public:int longestSubstring(string s, int k) {int ret 0;int n s.length();for (int t 1; t < 26; t) {int l 0, r 0;vector<int> cnt(26, 0);int tot 0;int less 0;while (r < n) {cnt[s[r] - a];…

[Golang] goroutine

[Golang] goroutine 文章目录 [Golang] goroutine并发进程和线程协程 goroutine概述如何使用goroutine 并发 进程和线程 谈到并发&#xff0c;大多都离不开进程和线程&#xff0c;什么是进程、什么是线程&#xff1f; 进程可以这样理解&#xff1a;进程就是运行着的程序&…

yolov5 +gui界面+单目测距 实现对图片视频摄像头的测距

可实现对图片&#xff0c;视频&#xff0c;摄像头的检测 项目概述 本项目旨在实现一个集成了YOLOv5目标检测算法、图形用户界面&#xff08;GUI&#xff09;以及单目测距功能的系统。该系统能够对图片、视频或实时摄像头输入进行目标检测&#xff0c;并估算目标的距离。通过…

基于vue框架的城市网约车管理系统v34td(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,司机,订单评价,完成订单,司机接单,打车订单 开题报告内容 基于Vue框架的城市网约车管理系统开题报告 一、研究背景与意义 1.1 研究背景 随着城市化进程的加速和互联网技术的飞速发展&#xff0c;网约车服务作为一种新兴的出行方…