这个多功能风扇是我在学完江科大的STM32入门教程后自己编写的第一个小项目,难度不大,但是在完成这个项目的过程中,我对涉及到的外设有了更加深刻的理解,尤其是TIM定时器,并且在此次项目的过程中我学会了逻辑分析仪的使用。在这个项目完成后,我的第一感觉是自己终于学到了一点实实在在的东西,并且能运用于实际了。天助自助者,望诸君共勉!
回归正题,接下来开始详细讲解这个项目。
多功能风扇功能概述:1.通过舵机实现摇头功能; 2.风扇6档调速; 3.实时温湿度监测; 4.高温自动打开风扇并告警,在温度降低后自动关闭
当然,最初的设想是可以用手机通过进行蓝牙控制,但是当时还完全没有接触过蓝牙模块,以为这个HC-05会很难呢,就暂时没有加上,后来着实没有时间且自己也觉得没有太大的必要再加,如果有兴趣的小伙伴可以试着将这个功能加上去。
实物图如下

模块部分难度不大,就不再赘述了,直接放代码。
按键模块
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//PB1-控制电机
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//PB11—控制舵机
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
电机模块
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GOIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//开启TIM3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GOIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
GPIO_InitTypeDef GPIO_InitStruct; //初始化PB0-提供PWM波
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP ;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//PB1-按下引发中断
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP ;//初始化PA4,PA5为电机提供电压差
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
// TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//初始化时基单元
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=36-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
TIM_OCInitTypeDef TIM_OCInitStruct;//配置输出比较寄存器
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0;//CCR
TIM_OC3Init(TIM3,&TIM_OCInitStruct);
TIM_Cmd(TIM3,ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);//选择PB1管脚用作AFIO外设中断线路
EXTI_InitTypeDef EXTI_InitStruct;//初始化EXTI
EXTI_InitStruct.EXTI_Line=EXTI_Line1;
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStruct;//初始化NVIC
NVIC_InitStruct.NVIC_IRQChannel=EXTI1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
}
void Motor_SetSpeed(int8_t Speed)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
TIM_SetCompare3(TIM3,Speed);
}
void EXTI1_IRQHandler(void)//中断函数
{
if(EXTI_GetITStatus(EXTI_Line1)==SET)
{
Servo_OFF();
KeyNum_Motor=1;
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
void Motor_SpeedAdd(void)
{
Speed+=20;
if(Speed>100)
{
Speed=0;
}
Motor_SetSpeed(Speed);
}
void Motor_ON(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
TIM_SetCompare3(TIM3,100);
}
void Motor_OFF(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
TIM_SetCompare3(TIM3,0);
}
舵机模块
void Servo_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GOIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
GPIO_InitTypeDef GPIO_InitStruct;//初始化PA1-驱动舵机
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP ;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
// TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//初始化时基单元
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=20000-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=72-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_OCInitTypeDef TIM_OCInitStruct;//配置输出比较寄存器
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0;//CCR
TIM_OC2Init(TIM2,&TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource11);//选择PB11管脚用作AFIO外设中断线路
EXTI_InitTypeDef EXTI_InitStruct;//初始化EXTI
EXTI_InitStruct.EXTI_Line=EXTI_Line11;
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStruct;//初始化NVIC
NVIC_InitStruct.NVIC_IRQChannel=EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
}
void PWM_SetCompare2(uint16_t Compare)//舵机
{
TIM_SetCompare2(TIM2,Compare);
}
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
void Servo_ON(void)
{
dirction=1;
while(dirction==1)
{
Angle+=2;
Delay_ms(20);
Servo_SetAngle(Angle);
if(Angle>135)
{
dirction=-1;
Delay_ms(1000);
}
}
while(dirction==-1)
{
Angle-=2;
Delay_ms(20);
Servo_SetAngle(Angle);
if(Angle<1)
{
dirction=1;
Delay_ms(1000);
}
}
}
void Servo_OFF(void)
{
dirction=0;
}
void EXTI15_10_IRQHandler(void)//中断函数
{
if(EXTI_GetITStatus(EXTI_Line11)==SET)
{
Servo_OFF();
KeyNum_Servo++;
EXTI_ClearITPendingBit(EXTI_Line11);
}
}
DHT11模块
void DHT11_GPIO_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启挂载GPIO的总线APB2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出,输入也是有效
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//初始化为输入
void DHT11_GPIO_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入,不可以输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t DHT11_ReadByte(void)
{
uint8_t temp;
uint8_t ReadDat=0;
uint8_t retry = 0;
uint8_t i;
//二进数除以十即为数据
for(i=0;i<8;i++)
{
//消耗准备信号低电平50us
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==0&&retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
//虽然0信号持续时间为28us,retry循环非常巧妙,
//避免问题,相当于如果一直是0的话可以避免延时带来的多出时间
Delay_us(30);//如果是0,那temp=0起作用,非0,仍然在高电平时间内
//因为如果数据是0的话持续时间短,故先预置0,简化代码。
temp=0;//数字信号0,temp=0
//数字0信号高电平持续28us,数字1信号高电平70us,延时30us以确认数字0或1
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==1)
{
temp=1;
}
//数字1信号高电平剩余40us
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==1&&retry<100)
{
Delay_us(1);
retry++;
}
retry=0;
//<<=左移赋值符号,|=先或再赋值,得到1位数据,即8bit
ReadDat<<=1;
ReadDat|=temp;
}
return ReadDat;
}
void DHT11_ReadData(uint8_t *temp, uint8_t *humi)
{
uint8_t i,data[5];
uint8_t retry=0;
//stm32 PB14设置为输出,发送开始信号低电平18ms,高电平40us
DHT11_GPIO_OUT();
GPIO_ResetBits(GPIOB,GPIO_Pin_14);//拉低引脚输出电平
Delay_ms(18);//保证dht11能收到起始信号
GPIO_SetBits(GPIOB,GPIO_Pin_14);//拉高引脚输出电平
Delay_us(40);//延时40us,等待dht11响应信号
//stm32 PB14设置为输入,检查并接收响应信号低电平80us,高电平80us
DHT11_GPIO_IN();//可以不切换,直接输出高电平,输入也是开启的
Delay_us(20);//延时20us,dht11响应低电平80us,还剩60us,
//检查是否是低电平以确定是否有响应信号
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==0)
{
//第一个循环消耗响应低电平60us
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==0&&retry<100)//接收响应信号低电平剩余60us
{
Delay_us(1);
retry++;
}
retry=0;//超过100us自动向下运行,以免卡死
//消耗dht11拉高80us
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==1&&retry<100)//接收响应信号高电平80us
{
Delay_us(1);
retry++;
}
retry=0;
//接收8字节数据,每传送1bit之前先进行拉低50us
//接收了5位数据,40bit
for(i=0;i<5;i++)
{
data[i]=DHT11_ReadByte();
}
Delay_us(50);//DHT11拉低总线50us作为一字节结束信号,或者使用以下语句
if ((data[0] + data[1] + data[2] + data[3]) == data[4])
{
*humi = data[0];
*temp = data[2];
}
}
}
有源蜂鸣器
void Beep_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_SetBits(GPIOA,GPIO_Pin_2);//默认不叫
}
void Beep_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
void Beep_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
主函数
int Stage=0;
int Flag=0;
int main(void)
{
OLED_Init();
Servo_Init();
Motor_Init();
Key_Init();
Beep_Init();
while(1)
{
//温湿度
DHT11_ReadData(&temp, &humi);
Delay_ms(1000);
DHT11_ReadData(&temp, &humi);
if(temp<30)
{
OLED_ShowString(1, 1, "Snap:");
OLED_ShowString(2, 1, "Stage:");
OLED_ShowString(3, 1, "temp:");
OLED_ShowString(4, 1, "humi:");
OLED_ShowNum(3, 6,temp,2);
OLED_ShowNum(4, 6,humi,2);
if(Flag == 1)
{
Motor_OFF();
Beep_OFF();
Flag = 0;
}
//舵机
if(KeyNum_Servo%2==0)
{
OLED_ShowString(1, 6, "OK");
Servo_ON();
}
else{
Servo_OFF();
OLED_ShowString(1, 6, "NO");
}
//电机
if(KeyNum_Motor==1)
{
Stage++;
if(Stage>5)
{
Stage=0;
}
OLED_ShowNum(2, 7,Stage,1);
Motor_SpeedAdd();
KeyNum_Motor=0;
if(KeyNum_Servo%2==0)
{
Servo_ON();
}
}
}
if(temp>30)
{
OLED_ShowString(1, 1, "Snap:");
OLED_ShowString(2, 1, "Stage:");
OLED_ShowString(3, 1, "temp:");
OLED_ShowString(4, 1, "humi:");
OLED_ShowNum(3, 6,temp,2);
OLED_ShowNum(4, 6,humi,2);
OLED_ShowNum(2, 7,5,1);
Motor_ON();
Beep_ON();
Flag = 1;
}
}
}
到这里,这篇文章就结束了,感谢各位的观看,如果觉得文章对你有帮助,可以点赞,如果有什么问题,大家也可以留言一起探讨,但请营造一个和谐友好的交流环境。当然,如果发现了什么问题也欢迎各位指正,多谢!
1万+

被折叠的 条评论
为什么被折叠?



