学完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作为设备端,实现对门,窗帘,风扇的远程控制,实现智能家居