智能家居,基于stm32f103c8t6+温湿度传感器+超声波传感器+光敏 智能家居物联网的一个课题项目,然后接下来我就介绍一下我在做课设的时候,一些步骤,最后将完整代码放入最后,方便各类爱好者提出问题以及修改。
主要功能
通过阿里云控制继电器,在阿里云云端上面显示当前传感器数据 通过连接阿里云物联网中心的通过mqtt协议进行连接, 单片机选用stm32f103c8t6, 传感器有温湿度传感器、超声波传感器、光敏传感器、(后续可以自己添加传感器数据,基本原理差不多,这里) 温湿度传感器的传输协议采用单总线协议,下面就是这个设计的方框图
1.DHT11传感器
首先就是配置当前的,通过时序图来看可以需要将总线的电平拉低,进入主机的开始信号,单号主机开始发送信号,等待DHT11响应输出,然后DHT11输出80us低电平响应信号,然后进过开始准备输出信号DHT11发送80us高电平信号,开始发送数据信号,每一个bit数据以50us低电平时隙开始,然后根据高电平的长短判断是数据位0还是1,以此类推当最后一组信号发送完成的时候DHT11拉低总线50us,然后总线进入空闲状态。通过数据将温湿度数据传入单片机,
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include <string.h>
#include "OLED.H"
unsigned char Data[5];
uint16_t dhti;
char DHT11_GetData(void)
{
memset(Data,0,5);
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //配置GPIOB Pin12
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init (GPIOB,&GPIO_InitStructure); //初始化GPIO为推挽输出模式
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(20);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_us(25); //发送开始信号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init (GPIOB,&GPIO_InitStructure); //切换到输入模式
dhti=0;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0)
{dhti++;if(dhti>10000) {break;}}
dhti=0;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==1)
{dhti++;if(dhti>10000) {break;}} //DHT11响应信号
for(char j=0;j<5;j++) //获取数据
{
for(char i=0;i<8;i++)
{
dhti=0;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0)
{dhti++;if(dhti>10000) {break;}}
Delay_us(30);
Data[j] <<=1;
Data[j] |= (unsigned char)GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12);
dhti=0;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==1)
{dhti++;if(dhti>10000) {break;}};//消除为1时的余时
}
}
if(Data[4]==(char)(Data[0]+Data[1]+Data[2]+Data[3]))
return 1;
else
return 0;
}
2.超声波传感器
超声波传感器采用HC-SR04模块如图所示,其工作原理类似于蝙蝠或海豚利用鸣叫声或声纳技术进行远距离测量。主要是超声波传感器发出的脉冲信号遇到障碍物,然后反射回来的回波,然后利用这段发射和回波之间的时间差来来计算距离
图2.1
超声波传感器工作原理是利用声波回弹的时间来判断当前距离,通过公式s=340t/2得出距离,然后我们可以利用代码显示出现
#define HCSR04_PORT GPIOB //宏定义端口
#define HCSR04_CLK RCC_APB2Periph_GPIOB
#define HCSR04_TRIG GPIO_Pin_11 //发送
#define HCSR04_ECHO GPIO_Pin_10 //返回
u16 msHcCount = 0;
void HC_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(HCSR04_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin =HCSR04_TRIG; //GPIO初始化
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
GPIO_ResetBits(HCSR04_PORT,HCSR04_TRIG);
GPIO_InitStructure.GPIO_Pin = HCSR04_ECHO;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
GPIO_ResetBits(HCSR04_PORT,HCSR04_ECHO);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //定时器初始化
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Period = (1000-1);
TIM_TimeBaseStructure.TIM_Prescaler =(72-1);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM4,DISABLE);
}
static void OpenTimerForHc() //打开时钟
{
TIM_SetCounter(TIM4,0);
msHcCount = 0;
TIM_Cmd(TIM4, ENABLE);
}
static void CloseTimerForHc() //关闭时钟
{
TIM_Cmd(TIM4, DISABLE);
}
void TIM4_IRQHandler(void) //时钟计数
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update );
msHcCount++;
}
}
u32 GetEchoTimer(void) //返回的时间
{
u32 t = 0;
t = msHcCount*1000;
t += TIM_GetCounter(TIM4);
TIM4->CNT = 0;
Delay_ms(50);
return t;
}
float HC_Get(void ) //计算距离
{
int t = 0;
int i = 0;
float lengthTemp = 0;
float sum = 0;
while(i!=5) //循环5次减少误差
{
TRIG_Send = 1; //发送声波
Delay_us(20);
TRIG_Send = 0; //停止发送
while(ECHO_Reci == 0); //判断接受声波
OpenTimerForHc(); //接受到打开定时器进行计数
i = i + 1;
while(ECHO_Reci == 1); /接受完则关闭定时器
CloseTimerForHc();
t = GetEchoTimer(); //将接受的时间赋值给T
lengthTemp = ((float)t/58.0);//cm 进行计算当前的距离
sum = lengthTemp + sum ;
}
lengthTemp = sum/5.0;
return lengthTemp;
}
3.光敏电阻
光敏传感器是利用ADC,通过光敏电阻的变化来改变输出端的电压差,通过ADC然后计算出当前光敏传感器传入的数据,这里我进行百分比计算,GL5516光敏电阻的光度检测范围为200 Lux至20,000 Lux之间。本文设计电路光线检测模块图3.18所示,主要是利用光敏电阻的感光作用,对电流起着一个作用比,利用分压公式来计算光敏电阻发送给单片机PA0引脚的电压数值
图3.1
uint16_t AD_Value[4];
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //时钟初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1|GPIO_Pin_2; //设置三个端口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //打开ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_NbrOfChannel = 3;
ADC_Init(ADC1, &ADC_InitStructure);
DMA_InitTypeDef DMA_InitStructure; //DMA数据转运
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 3;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
float ADC_Getvalue(void) //烟雾传感器
{
uint32_t value =0;
uint32_t Value=0;
float ret =0.0;
for(uint8_t i=0;i<30;i++)
{
value+=AD_Value[0];
}
value/=30;
ret=30.303*(3.3-value*(3.3/4095));
return ret;
}
float ADC_Getvalue1(void) //将光敏电阻的接收到的电压值换算为百分比
{
uint32_t value =0;
uint32_t Value=0;
float ret =0.0;
for(uint8_t i=0;i<30;i++)
{
value+=AD_Value[2];
}
value/=30;
ret=value*99/4096;
return ret;
}
4.HX711压力传感器
本文采用的压力传感器由压力应变器、信号放大器、AD转换芯片组成,其中选用HX711作为信号放大和A/D转换,其芯片主要用于称重传感器设计该芯片有这128倍信号增益能将应变片上的微小信号进行放大,然后采集24bitAD转换数据,就可以利用单片机读取当前应变器上面物体的重量。选用的应变器上面的最大重量0-5000g。下述是传感器代码。
oid Init_HX711pin(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//HX711_SCK
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //
//HX711_DOUT
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//ÊäÈëÉÏÀ
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_0);
}
u32 HX711_Read(void)
{
unsigned long count;
unsigned char i;
HX711_DOUT=1;
Delay_us(1);
HX711_SCK=0;
count=0;
while(HX711_DOUT);
for(i=0;i<24;i++)
{
HX711_SCK=1;
count=count<<1;
Delay_us(1);
HX711_SCK=0;
if(HX711_DOUT)
count++;
Delay_us(1);
}
HX711_SCK=1;
count=count^0x800000;
Delay_us(1);
HX711_SCK=0;
return(count);
}
void Get_Maopi(void)
{
Weight_Maopi = HX711_Read();
}
void Get_Weight(void)
{
HX711_Buffer = HX711_Read();
if(HX711_Buffer > Weight_Maopi)
{
Weight_Shiwu = HX711_Buffer;
Weight_Shiwu = Weight_Shiwu - Weight_Maopi;
Weight_Shiwu = (s32)((float)Weight_Shiwu/GapValue);
}
5.1WiFi以及阿里云连接
首先阿里云连接是利用WiFi模块,我这边选用esp12f,通过串口连接单片机的串口,串口传输是全双工异步时序,因此要将esp12f的rx和tx连接单片机的PA9,PA10 这一步算是完成了硬件部分,软件部分也要调试好,通过利用stm32串口代码,连接当前阿里云端,下图是当前阿里云上传云端的部分代码。用于配置WiFi模块连接阿里云
#include "stm32f10x.h" // Device header
#include "MyUSART.h"
#include <stdio.h>
#include <string.h>
#include "Delay.h"
#include "OLED.H"
extern int Temperature;
extern int Shidu;
extern int guangdu;
extern float length;
extern uint8_t Lock;
extern uint8_t Switch2;
extern uint8_t switch1;
extern int Weight_Shiwu;
extern float Soil;
extern float Yanwu;
extern char RECS[250];
const char* WIFI ="A";
const char* WIFIASSWORD="1234567890.";
const char* ClintID="a1o0AJyuvAm.Home|securemode=2\\,signmethod=hmacsha256\\,timestamp=1677308302161|";
const char* username="Home&a1o0AJyuvAm";
const char* passwd="3380cad55acca6607696ffd0c565bb62fe08639bfb6744942fa2a689e22c8706";
const char* Url="a1o0AJyuvAm.iot-as-mqtt.cn-shanghai.aliyuncs.com";
const char* pubtopic="/sys/a1o0AJyuvAm/Home/thing/event/property/post";
const char* pub="/a1o0AJyuvAm/Home/user/subb";
const char* subtopic="/sys/a1o0AJyuvAm/Home/thing/event/property/post_reply";
const char* func1="temperature";
const char* func2="humidity";
const char* func3="PowerSwitch_1";
const char* func4="PowerSwitch_2";
const char* func5="MeasuredIlluminance";
const char* func6="Pressure";
const char* func7="DetectDistance";
const char* fun8="DetectingWaterlLevel";
const char* fun9="SmokeDetection";
const char* fun10="PowerSwitch_3";
//const char* func5="DetectDistance";
int fputc(int ch,FILE *f ) //printf重定向
{
USART_SendData(USART1,(uint8_t)ch);
while(USART_GetFlagStatus (USART1,USART_FLAG_TC) == RESET);
return ch;
}
char esp_Init(void)
{
memset(RECS,0,sizeof(RECS));
printf("AT+RST\r\n"); //重启
Delay_ms(2000);
memset(RECS,0,sizeof(RECS));
printf("ATE0\r\n"); //关闭回显
Delay_ms(10);
if(strcmp(RECS,"OK"))
return 1;
printf("AT+CWMODE=1\r\n"); //Station模式
Delay_ms(1000);
if(strcmp(RECS,"OK"))
return 2;
memset(RECS,0,sizeof(RECS));
printf("AT+CWJAP=\"%s\",\"%s\"\r\n",WIFI,WIFIASSWORD); //连接热点
Delay_ms(2000);
if(strcmp(RECS,"OK"))
return 3;
memset(RECS,0,sizeof(RECS));
printf("AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"\r\n",ClintID,username,passwd);//用户信息配置
Delay_ms(10);
if(strcmp(RECS,"OK"))
return 4;
memset(RECS,0,sizeof(RECS));
printf("AT+MQTTCONN=0,\"%s\",1883,1\r\n",Url); //连接服务器
Delay_ms(1000);
if(strcmp(RECS,"OK"))
return 5;
printf("AT+MQTTSUB=0,\"%s\",1\r\n",subtopic); //订阅消息
Delay_ms(500);
if(strcmp(RECS,"OK"))
return 6;
memset(RECS,0,sizeof(RECS));
return 0;
}
//功能:esp发送消息
//参数:无
//返回值:0:发送成功;1:发送失败
char Esp_PUB(void)
{
memset(RECS,0,sizeof(RECS));
printf("AT+MQTTPUB=0,\"%s\",\"{\\\"method\\\":\\\"thing.event.property.post\\\"\\,\\\"params\\\":{\\\"%s\\\":%d\\,\\\"%s\\\":%d\\,\\\"%s\\\":%d\\,\\\"%s\\\":%d\\,\\\"%s\\\":%d\\,\\\"%s\\\":%d\\}}\",0,0\r\n",
pubtopic,func1,Temperature,func2,Shidu,func3,Lock,func4,Switch2,func5,guangdu,func6,Weight_Shiwu);
//while(RECS[0]);//等待ESP返回数据
Delay_ms(200);//延时等待数据接收完成
if(strcmp(RECS,"ERROR")==0)
return 1;
return 0;
}
char Esp_PUB1(void)
{
memset(RECS,0,sizeof(RECS));
printf("AT+MQTTPUB=0,\"%s\",\"{\\\"method\\\":\\\"thing.event.property.post\\\"\\,\\\"params\\\":{\\\"%s\\\":%.2f\\,\\\"%s\\\":%.2f\\,\\\"%s\\\":%.2f\\,\\\"%s\\\":%d\\}}\",0,0\r\n",
pub,func7,length,fun8,Soil,fun9,Yanwu,fun10,switch1);
//while(RECS[0]);//等待ESP返回数据
Delay_ms(200);//延时等待数据接收完成
if(strcmp(RECS,"ERROR")==0)
return 1;
return 0;
}
void CommandAnalyse(void)
{
if(strncmp(RECS,"+MQTTSUBRECV:",13)==0)//判断
{
uint8_t i=0;
while(RECS[i++] != '\0')
{
if(strncmp((RECS+i),func3,13)==0)
{
while(RECS[i++] != ':');
Lock=RECS[i];
}
if(strncmp((RECS+i),func4,13)==0)
{
while(RECS[i++] != ':');
Switch2=RECS[i];
}
if(strncmp((RECS+i),fun10,13)==0)
{
while(RECS[i++] != ':');
switch1=RECS[i];
}
}
}
}
本文是采用阿里云云平台的数据显示,通过AT指令将消息发布到阿里云服务器。首先打开阿里云官网使用支付宝注册用户,然后在搜索界面查找物联网开发,点击开发项目,填入所需要开发的项目名称如图5.1。
图5.1创建产品信息
此外还要创建产品,创建产品是为了把设备加入到阿里云中,并且定义消息通信的主题(Topic)。订阅主题是阿里云和单片机之间通信的关键。通过阿里云的物模型功能,把单片机上传的数据展示在阿里云的Web或移动应用上如图5.2。
图5.2阿里云创建项目
接着创建设备,创建设备的作用主要是为了将单片机和APP进行数据连接,这里的设备主要是连接单片机的设备,一个设备连接一个用户如图5.3。
图5.3创建设备
创建设备之后,就可以开始进行MQTT连接了。连接可以利用阿里云设备中的三元组信息进行。阿里云设备提供了MQTT连接参数,将这些参数对应到单片机的AT指令上,从而实现设备的正常连接。如图5.4。
图5.4MQTT连接参数
设备正常连接后需要将数据传输给阿里云,需要利用Topic对应的指令,当阿里云云平台接受到设备发来的数据包的时候,就可以显示出来当前数据如图5.5所示流程图,需要单片机配置好连接的MQTT指令,接着将阿里云收到传来的指令就会返回消息,进行数据上传。
图5.5连接MQTT链接流程图
上传后的数据会根据定义的类型将数据显示出来,消息订阅和发布消息的订阅是AT+MQTTSUB=<Topic>,<Qos>指令,因此要告知订阅和发布的Topic如图5.6所示。智能宠物屋环境数据信息:有湿度取值范围0.100%RH、温度0.100摄氏度,烟雾取值范围0.100%距离检测0.500cm,还有压力取值范围0.5000g,光照强度0.100%,风扇、舵机、继电器都是利用布尔值进行开关状态1或者0。
图5.6设备Topic信息
5.2手机APP软件与实现
APP软件设计是使用APP Invertor,APPInventor是一种可以快速开发、构建和发布AndriodAPP.使用逻辑块编程,是需要逻辑功能的使用就可以完成当前软件的功能实现
本文开发的APP是利用MQTT协议开发出来的对此需要借助一些MQTT 插件的使用,连接阿里云云平台,阿里云云平台通过接入单片机的数据,因为阿里云的订阅服务器不允许两个连接,所以需要利用云流转的功能在创建一个新的设备进行将单片机的数据转入这个云流转的设备中然后将这个数据云流转的设备连接APP就完成数据连接。设计流程图如图5.7所示。
图5.7 APP流程图
然后就是MQTT插件,APP Invertor的MQTT插件主要连接逻辑和单片机连接阿里云一样只需要添加阿里云连接设备的MQTT的连接参数。利用其逻辑块编程如图5.8将功能实现出来。通过APP Invertor逻辑块连接阿里云,将阿里云接受到的数据转到APP显示出来。
图5.8 APP逻辑功能块
APP主要功能是按键控制连接阿里云,然后将单片机的数据上传至APP上,可以用按键控制风扇开关、LED开关、舵机转动,还有温湿度、重量、距离、光照、烟雾状态等数据,并且根据上传数据的大小设置阈值,当到达一定的阈值的时候就会有对应的提示功能。然后用户界面实现功能如图5.9所示。
图5.9 APP使用界面
此时阿里云和软件也配置好了,将阿里云配置好之后就实现了当前的数据传输具体连接图,此时我们的数据连接已经完成了, 接着就需要传感器数据显示到阿里云上面,通过阿里云的订阅当前数据pub是发布, Sub订阅,通过单片机发布传感器数据连接阿里云,阿里云就能显示当前数据, 然后要实现控制继电器功能,需要利用到sub当单片机发送数据给阿里云,阿里云接受到会发送一段数据给单片机,所以阿里云也是可以发送数据给单片机,通过阿里云发送数据给单片机,然后单片机接受函数数据,判断当前数据是否为1或为0,
下述是全部代码的链接
链接:https://pan.baidu.com/s/1eBGBLy9W6DYG5iWV8eQw4w?pwd=j97c
提取码:j97c