视频演示:基于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上位机实现。
02 硬件连接
注意:本电路仅体现个模块之间的电路连接。原理图绘制参看:
嘉立创EDA使用流程
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_Hvoid 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; //将响应优先级设置为1NVIC_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_Hvoid 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模式1TIM_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_Hvoid 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; //将响应优先级设置为1NVIC_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_Hvoid 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; //CCRTIM_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, //前奏 3430, 917, 917,1376,1376,1226,1092, //故事的小黄花 4130, 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, //随记忆一直晃到现在 691836,1376,1376,1092,1031,1092,1226, //Re So So Si Do Si La 761376,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, //吹着前奏 望着天空 941226,1092,1092,1092,1092,1226,1092,1226,1376, //我想起花瓣试着掉落 103 30,1376,1376,1376,1376,1635,1456,1376, 917,1031,1092,1376,1367,//没想到 失去的勇气我还留着 11630,1376,1376,1376,1376,1092,1376, //好想再问一遍 1231635,1456,1376, 917,1031,1092,1376,1226, //你会等待还是离开 1311092,1226,1031,1092,1376, 917, 728, 687, 728, 917,1376, //刮风这天 我试过握着你手 14230,1376, 817, 817, 30, 817, 917, 917, //但偏偏 雨渐渐 15030, 917,1031,1092,1226,1092,1031,1092, //大到我看你不见 15830,1092,1031, 917,1092, 30,1031, 917, 728, 612, 728, 687, 687, //还要多久 我才能在你身边 171 30, 687, 687, 917, 917, 817, 917,1031, //等到放晴的那天 1791226,1092, 1031, 917, 817,1376,817, 728, 728, //也许我会比较好一点 1881092,1226,1031,1092, 30,1376, 917, 728, 687, 728, 917,1376, //从前从前 有个人爱你很久 20030,1376, 817, 817, 30, 817, 917, 917, //但偏偏 风渐渐 20830, 917,1031,1092,1226,1092,1031,1092, //大到我看你不见 21630,1092,1031, 917,1092, 30,1031, 917, 728, 612, 728, 687, 687, //好不容易 又能再多爱一天 22930,1376, 687, 917, 917, 817, 917, //但故事的最后 2361031,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 La25, 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; //将响应优先级设置为1NVIC_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_Hvoid 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; //将响应优先级设置为2NVIC_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; //将响应优先级设置为1NVIC_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; //将响应优先级设置为1NVIC_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_Hvoid 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); //延时30usif (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == 0) return 0;else // 如果30us后还是高电平,说明DHT11发送的是1return 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_Htypedef 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){// 发送ACkMyI2C_W_SDA(1); }else if(ack == 0){// 发送ACkMyI2C_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--) {// 等待从设备发送ACKif(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高电平期间读取SDADelay_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高电平期间读取SDADelay_us(5);if (MyI2C_R_SDA() == 1){bit = 0x80;} else{bit = 0x00;}Byte |= bit; MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDADelay_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_14void 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, ...)
#endifuint8_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, ¤t_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 Modeuint8_t bh1750_init(void);
void bh1750_read_example(void);#endif
04 STM32F103移植FreeRTOS
上面我们完成所有外设驱动程序得编写,那么为了解决上面提到的问题,现在我们进行移植FreeRTOS了。
参考教程:
FreeRTOS入门与工程实践-韦东山老师
FreeRTOS实践教程
FreeRTOS书籍-正点原子
上述资料先看韦东山老师的课程,韦东山老师讲解了一些必要的理论知识,有助于第二个实践课程的理解;建议直接刷完两套视频,刷视频过程中理解即可,不用跟着做,然后对着书自己做FreeRTOS项目。
这个版本的智能家居项目仅仅用到了FreeRTOS的任务创建、挂起与恢复以及中断,还有很多需要改进的地方,缺点也比较明显,后期看情况进行迭代。
05 Qt上位机开发
参考:
Qt仪表盘开发
Qt串口助手开发