1 stm32小项目--环境检测+智能控制

学完stm32,计划做一个温度检测加光照检测的项目:

        1.能够检测温度和光照度并实时显示在oled屏上

        2.温度超过设定的上限时,开启电机控制风扇转动,同时串口发送警报给电脑

        3.能够使用旋转编码器调整温度上限的值,并将这个值存储在W25Q64中

        4.当光照过低时开启led灯,同时串口发送led灯状态改变的信息给电脑

1 使用AD+DMA实现实时显示温度和光照

        淘宝买的dht11还没到,先用stm32内部自带的温度传感器获取温度

        AD和DMA模块,在我的stm32学习笔记stm32学习笔记_×_×625的博客-CSDN博客中有记录初始化过程,这里就不在说明,这里我的光敏传感器接的是A3口,将AD.c中的PIN口改为3就可以。

        内部自带的温度传感器获取温度使能:

ADC_TempSensorVrefintCmd(ENABLE);

        由于实时获取的时间太快,会导致我们的数据变化大,所以再加一个获取平均值的函数:

u16 Get_ADC0_Aver(u8 times){
	u32 ad_val=0;
	u8 t;
	for(t=0;t<times;t++){
		ad_val+=AD_Value[0];
		Delay_ms(5);
	}
	return ad_val/times;
}

u16 Get_ADC1_Aver(u8 times){
	u32 ad_val=0;
	u8 t;
	for(t=0;t<times;t++){
		ad_val+=AD_Value[1];
		Delay_ms(5);
	}
	return ad_val/times;
}

        在主函数中获取平均后的AD16和AD3的值,这里AD16是因为stm32内部自带的温度传感器是连接在ADC1_IN16口上的。

        AD16 = Get_ADC0_Aver(10);
		AD3 = Get_ADC1_Aver(10);

        对获取到的AD16值进行数据转换为温度值:

//显示温度
		temp=((1.43-AD16*(3.3/4096))/0.0043+25)*100;
		OLED_ShowNum(1, 6, temp/100, 2);
		OLED_ShowNum(1, 9, (int)temp%100, 2);

         对获取到的AD3的值进行判断,结果用于表示当前亮度是亮,暗或适中:

//显示光照情况
		if (AD3>3500)
		{
			OLED_ShowString(2, 7, "       ");
			OLED_ShowString(2, 7, "dark");
			Motor_Start();
		}else if(AD3<1000){
			OLED_ShowString(2, 7, "       ");
			OLED_ShowString(2, 7, "bright");
		}else {
			OLED_ShowString(2, 7, "       ");
			OLED_ShowString(2, 7, "middle");
		}

最后效果如下:

不知道为啥,这个温度偶尔会跳到10几度,不知道什么原因。 

dht11获取实时温度和湿度

        淘宝买的货到了,直接调用店里给的资料或者去网上找dht.c,直接拿来用就可以了,dht11发送数据给主机时有五个数据,分别是湿度的整数部分,湿度的小数部分,温度的整数部分,温度的小数部分和校验位,定义一个结构体来接收五个数据就可以了。

typedef struct
{
	uint8_t  humi_int;		//湿度的整数部分
	uint8_t  humi_deci;	 	//湿度的小数部分
	uint8_t  temp_int;	 	//温度的整数部分
	uint8_t  temp_deci;	 	//温度的小数部分
	uint8_t  check_sum;	 	//校验和
		                 
} DHT11_Data_TypeDef;

数据接收:

        DHT11_Data->humi_int= DHT11_ReadByte();
		DHT11_Data->humi_deci= DHT11_ReadByte();
		DHT11_Data->temp_int= DHT11_ReadByte();
		DHT11_Data->temp_deci= DHT11_ReadByte();
		DHT11_Data->check_sum= DHT11_ReadByte();

main.c中,定义DHT11_Data_TypeDef类型数据DHT11_Data,直接在oled上显示即可:

if (DHT11_Read_TempAndHumidity(&DHT11_Data)){
			OLED_ShowNum(1,6,DHT11_Data.temp_int,2);
			OLED_ShowNum(1,9,DHT11_Data.temp_deci,1);
			OLED_ShowNum(2,6,DHT11_Data.humi_int,2);
			OLED_ShowNum(2,9,DHT11_Data.humi_deci,1);
}

效果图:

2 使用定时器实现led的自动开关

使用定时器定时中断,让单片机每过1s检测一次光照值,若光线暗,则调用LED_ON实现灯的控制,这里定时器我使用TIM3,TIM2留着以后pwm控制电机。

定时器初始化,LED模块依然在我之前的学习笔记有,这里不再写了。

中断函数:

void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{
		if (AD9>3500)
		{
			LED_ON();
		}else{
			LED_OFF();	
		}
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

效果:

3 使用PWM控制电机转动

与led控制一样,使用定时器让单片机每隔1s将当前温度与设定的最高温度比较,若高温则开启电机,这里我们使用pwm控制电机的旋转速度,温度超出的越多,speed值越大,电机转动越快。

这里使用TIM2,选择PA2对应TIM2的通道3:

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM2);
	
	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_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 = 0;		//CCR
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);
	
	TIM_Cmd(TIM2, ENABLE);
}

void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);
}

Motor.c调用PWM.c模块初始化PWM,电机驱动接PIN0和PIN1:

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_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	PWM_Init();
}

电机速度控制模块,设定初始速度为10,最大速度为100:

void Motor_SetSpeed(int8_t Speed)
{
	if (Speed<10) Speed=0;
	if(Speed>=100) Speed=100;
	if (Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_0);
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);
		PWM_SetCompare3(Speed);
	}
}
void Motor_Stop(void)
{
	PWM_SetCompare3(0);
}

在main.c中的定时器中断函数中添加电机控制:

if ((DHT11_Data.temp_int*10+DHT11_Data.temp_deci)>MAX_Temp)
		{
			Motor_SetSpeed((DHT11_Data.temp_int*10+DHT11_Data.temp_deci-MAX_Temp));
		}else{
			Motor_Stop();
		}

 实现效果:温度超过设定温度1°时电机开始转动,每超1°电机速度+10

4 使用旋转编码器调整温度上限

使用外部中断实现旋转编码器修改MAX_Temp的值,旋转编码器的A,C口随便接两个GPIO口就行,这里我接的和学习笔记里的一样(PB0和PB1),配置外部中断:

    EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStructure);
    
	//这里注意所有模块都要用同一个中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);

判断旋转方向:

//判断正转
void EXTI0_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
			{
				Encoder_Count --;
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}
//判断反转
void EXTI1_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
			{
				Encoder_Count ++;
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}

利用中间变量获取变化值并清空变化值:

int16_t Encoder_Get(void)
{
	int16_t Temp;
	Temp = Encoder_Count;
	Encoder_Count = 0;
	return Temp;
}

在main函数中更新温度上限:

        //获取显示温度上限
		MAX_Temp += Encoder_Get();

 实现效果正向旋转MAX增加,反向旋转MAX减小。

5 将上限的值存入存储器

使用SPI通信协议读写W25Q64,旋转编码器调整温度上限后,按下按键实现上限写入,每次开启时,单片机读取W25Q64里的数据赋给MAX_TEMP并显示。

失败了。W25Q64读写的数据是u8类型的,直接读写会报错,我定义了两个max_temp来进行强转换:

uint8_t max_temp;
int16_t MAX_Temp;

然后初始化时读取数据:

 W25Q64_ReadData(0x000000, &max_temp, 1);
 MAX_Temp=(int16_t)max_temp;

按下按键时写数据:

 max_temp=(uint8_t)MAX_Temp;	
 W25Q64_SectorErase(0x000000);
 W25Q64_PageProgram(0x000000, &max_temp, 1);

 但结果是断电后重新上电,读取数据的结果是错误的,不知道什么原因,或许和ACII码有关,但这个部分已经卡了我两天了,不搞了。

6 串口发送部分

配置GPIO,串口:

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

串口发送字符,数组,字符串,数字(略) 

printf函数重定向:

点击工程设计魔术棒勾选lib:

在c/c++处加上 --no-multibyte-chars:

添加头文件stdio.h后修改printf函数的底层:

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

 在dht11数据获取处添加:

if (DHT11_Read_TempAndHumidity(&DHT11_Data)){
			OLED_ShowNum(1,6,DHT11_Data.temp_int,2);
			OLED_ShowNum(1,9,DHT11_Data.temp_deci,1);
			OLED_ShowNum(2,6,DHT11_Data.humi_int,2);
			OLED_ShowNum(2,9,DHT11_Data.humi_deci,1);
			printf("Temp:%d.%d°humi:%d.%d%%\r\n",
         DHT11_Data.temp_int,DHT11_Data.temp_deci,
        DHT11_Data.humi_int,DHT11_Data.humi_deci);
		}

在中断函数改变灯状态处添加:

        if (AD3>3500)
		{
			LED_ON();
			printf("LED_ON\r\n");
		}else{
			LED_OFF();	
			printf("LED_OFF\r\n");
		}

在电机打开处添加:

Motor_SetSpeed((DHT11_Data.temp_int*10+DHT11_Data.temp_deci-MAX_Temp));
			printf("Temp too high\r\n");

最后效果: 

 拓展

           使用esp8266实现联网

           使用qt制作用户界面,下载到Linux开发板中作为中控,利用mqtt连接stm32实现物联网

            多加几个stm32作为设备端,实现对门,窗帘,风扇的远程控制,实现智能家居

            

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

×_×625

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值