提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
我建了一个群,分享我个人做项目的经历和资源,纯个人爱好,一切免费,看自己空闲时间答疑,有想法的可以加QQ群280730348
本篇文章主要是分享最近刚刚做好的一个项目,这个项目主要实现的目标就是通过超声波和红外监测农田附近的情况,若检测到生物通过则通过蜂鸣器报警驱赶动物。
一、硬件设备
1.STM32F103RCT6
2.0.96寸蓝色OLED模块/4P
3.HC-SR04超声波测距模块
4.电容式土壤传感器模块
5.微型人体红外感应模块
6.ATK-ESP8266
二、硬件电路
电路图如上所示,供电电源采用的是LM2596-5.0S,无需反馈电阻即可输出5V电压,各个模块接口如图所示。
三、软件
1.超声波计算距离代码
代码如下:
#include "timer.h"
#include "sys.h"
//初始化定时器
void Timer_SRD_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//设置缺省值,这一步最好加上
TIM_TimeBaseStructure.TIM_Period = arr; //自动重装载寄存器周期的值,溢出值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //时钟频率预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:输入捕获模式用来滤波
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;//设置重复溢出次数,就是多少次溢出后进入中断,一般为0,只有高级定时器才有用
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE ); //允许更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM1, ENABLE);
}
//中断服务函数
void TIM1_UP_IRQHandler(void)
{
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查更新中断发生与否
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); //清除TIMx更新中断标志
}
}
此处定时器的代码是用来计算时间用的,要开一个中断
下图是超声波监测的主代码,其中TRIG口定义为PB7,ECHO口定义为PB6。关键点在于外部中断9-5的中断程序,里面通过开启定时器1和关闭定时器,计算定时器的计时值从而得出最终采集的超声波时长,再经过主函数里面的数据处理即可得到采集到的距离。
#include "wave.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#define Trig GPIO_Pin_7//超声发射引脚pb7
#define Echo GPIO_Pin_6//超声波接受引脚pb6
void Wave_SRD_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
//初始化超声波
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStruct.GPIO_Pin=Trig;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=Echo;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//外部中断与引脚的配置,中断与端口6的映射
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource6);
//外部中断配置
EXTI_InitStruct.EXTI_Line=EXTI_Line6;
EXTI_InitStruct.EXTI_LineCmd= ENABLE;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStruct);
//中断优先级管理
NVIC_InitStruct.NVIC_IRQChannel=EXTI9_5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
}
//外部中断服务函数
void EXTI9_5_IRQHandler(void)
{
delay_us(10);
if(EXTI_GetITStatus(EXTI_Line6)!=RESET)//Echo检测到高电平
{
TIM_SetCounter(TIM1,0);//开启定时器
TIM_Cmd(TIM1,ENABLE);//使能定时器
while(GPIO_ReadInputDataBit(GPIOB,Echo));//等待Echo出现低电平
TIM_Cmd(TIM1,DISABLE);//关闭定时器
EXTI_ClearITPendingBit(EXTI_Line6);//清除中断标志
}
}
//超声波工作条件
//Trig产生一个不低于10us的高电平信号
u8 Wave_SRD_Strat(u8 time)
{
//delay_ms(1000);
if(time>10)
{
GPIO_SetBits(GPIOB,Trig);//设置Trig为高电平输出
delay_us(time);
GPIO_ResetBits(GPIOB,Trig);
return 0;//产生高于10us高电平,返回0
}
else return 1;//为产生高于10us高电平返回1
}
2.红外传感器代码
红外传感器代码的原理是基于此传感器的特性而言,当检测到物体(带有温度的)经过时,传感器将输出延时2s的高电平,重复检测重复清零延时2s。因此最简单的方法就是通过读取引脚电平的高低,从而判断是否有物体经过。也可通过定时器的输入捕获,捕获上升沿判断物体经过。代码如下:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA6设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.6
#define hongwai GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)//读取按键0
3.土壤湿度传感器代码
这地方原理基于ADC的采集,土壤的湿度越高采集到的的模块输出电压就越低,湿度越低采集到的模块输出电压就越高。因此我们可以拟定3.3V对应湿度为0,0V对应湿度为100%RH,得到对应的线性关系,近似的反映出土壤湿度的大小。参考的例程可以参考正点原子的ADC采集代码,将采集得到的电压数据做一个运算即可得到我们所需要的ADC数据。
4.主函数代码
代码如下,主要就是将模块采集到的数据集中到一起通过OLED屏幕显示,同时输送到阿里云物联网平台上去,基本上都是一些数据处理和通信协议等等。
/*
STM32F103RCT6
iic通信0.96寸OLED显示屏;显示中英文、数字、图片
HC-SR04超声波传感器
OLED接线定义:
VCC--3.3V/5V
GND--GND
SCL--PB8
SDA--PB9
HC-SR04超声波接线定义:
Trig--PB7
Echo--PB6
土壤湿度传感器接线定义:
VOUT--PA1
红外线传感器接线定义:
hongwai--PA6
LED指示灯定义:
baojing1--PA5
baojing2--PB5
蜂鸣器定义:
BEEP--PA7
*/
#include "stm32f10x.h" //包含需要的头文件
#include "sys.h"
#include "delay.h"
#include "oled_iic.h"
#include "stdio.h"
#include "wave.h"
#include "timer.h"
#include "usart1.h"
#include "usart2.h"
#include "mqtt.h" //包含需要的头文件
#include "timer1.h" //包含需要的头文件
#include "timer2.h" //包含需要的头文件
#include "timer3.h" //包含需要的头文件
#include "timer4.h" //包含需要的头文件
#include "wifi.h" //包含需要的头文件
#include "adc.h"
#include "led.h"
#include "beep.h"
u8 xianshi_status=0;
u8 turang_shidu=0;
u8 CurrentTemperature=38; //温度
u8 CurrentHumidity=28; //湿度
int main(void)
{
u16 adcx;
float shidu=0.0f;
u8 t=20;//trig引脚高电平持续时间
u8 Distance;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
delay_init(); //延时功能初始化
Usart1_Init(9600); //串口1功能初始化,波特率9600
Usart2_Init(115200); //串口2功能初始化,波特率115200
TIM4_Init(300,7200); //TIM4初始化,定时时间 300*7200*1000/72000000 = 30ms
Wave_SRD_Init();//初始化超声波
Timer_SRD_Init(9998,7199);//定时器初始化
Adc_Init(); //ADC初始化
LED_Init(); //LED端口初始
BEEP_Init(); //蜂鸣器初始化
WiFi_ResetIO_Init(); //初始化WiFi的复位IO
MQTT_Buff_Init(); //初始化接收,发送,命令数据的 缓冲区 以及各状态参数
AliIoT_Parameter_Init(); //初始化连接OneNet云IoT平台MQTT服务器的参数
extern const u8 BMP1[];
OLED_Init(); //OLED屏幕初始化
// while(Wave_SRD_Strat(t)) //等待初始化
// {
// }
OLED_Clear();
OLED_ShowCH(0,0,"土壤湿度:");
OLED_ShowCH(88,0,"%");
OLED_ShowCH(0,3,"动物方位:");
OLED_ShowCH(0,6,"距离:");
OLED_ShowCH(100,6,"cm");
while(1)
{
/*--------------------------------------------------------------------*/
/* Connect_flag=1同服务器建立了连接,我们可以发布数据和接收推送了 */
/*--------------------------------------------------------------------*/
if(Connect_flag==1){
/*-------------------------------------------------------------*/
/* 处理发送缓冲区数据 */
/*-------------------------------------------------------------*/
if(MQTT_TxDataOutPtr != MQTT_TxDataInPtr){ //if成立的话,说明发送缓冲区有数据了
//3种情况可进入if
//第1种:0x10 连接报文
//第2种:0x82 订阅报文,且ConnectPack_flag置位,表示连接报文成功
//第3种:SubcribePack_flag置位,说明连接和订阅均成功,其他报文可发
if((MQTT_TxDataOutPtr[1]==0x10)||((MQTT_TxDataOutPtr[1]==0x82)&&(ConnectPack_flag==1))||(SubcribePack_flag==1)){
u1_printf("发送数据:0x%x\r\n",MQTT_TxDataOutPtr[1]); //串口提示信息
MQTT_TxData(MQTT_TxDataOutPtr); //发送数据
MQTT_TxDataOutPtr += BUFF_UNIT; //指针下移
if(MQTT_TxDataOutPtr==MQTT_TxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_TxDataOutPtr = MQTT_TxDataBuf[0]; //指针归位到缓冲区开头
}
}//处理发送缓冲区数据的else if分支结尾
/*-------------------------------------------------------------*/
/* 处理接收缓冲区数据 */
/*-------------------------------------------------------------*/
if(MQTT_RxDataOutPtr != MQTT_RxDataInPtr){ //if成立的话,说明接收缓冲区有数据了
u1_printf("接收到数据:");
/*-----------------------------------------------------*/
/* 处理CONNACK报文 */
/*-----------------------------------------------------*/
//MQTT_RxDataOutPtr[0]为从MQTT_RxDataOutPtr[1]开始直到最后的数据的字节数
//if判断,如果一共接收了4个字节,第一个字节是0x20,表示收到的是CONNACK报文
//接着我们要判断第4个字节,看看CONNECT报文是否成功
if((MQTT_RxDataOutPtr[0]==4)&&(MQTT_RxDataOutPtr[1]==0x20)){
switch(MQTT_RxDataOutPtr[4]){
case 0x00 : u1_printf("CONNECT报文成功\r\n"); //串口输出信息
ConnectPack_flag = 1; //CONNECT报文成功,订阅报文可发
break; //跳出分支case 0x00
case 0x01 : u1_printf("连接已拒绝,不支持的协议版本,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x01
case 0x02 : u1_printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x02
case 0x03 : u1_printf("连接已拒绝,服务端不可用,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x03
case 0x04 : u1_printf("连接已拒绝,无效的用户名或密码,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x04
case 0x05 : u1_printf("连接已拒绝,未授权,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x05
default : u1_printf("连接已拒绝,未知状态,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case default
}
}
//if判断,如果一共接收了5个字节,第一个字节是0x90,表示收到的是SUBACK报文
//接着我们要判断订阅回复,看看是不是成功
else if((MQTT_RxDataOutPtr[0]==5)&&(MQTT_RxDataOutPtr[1]==0x90)){
switch(MQTT_RxDataOutPtr[5]){
case 0x00 :
case 0x01 : u1_printf("订阅成功\r\n"); //串口输出信息
SubcribePack_flag = 1; //SubcribePack_flag置1,表示订阅报文成功,其他报文可发送
Ping_flag = 0; //Ping_flag清零
TIM3_ENABLE_30S(); //启动30s的PING定时器
break; //跳出分支
default : u1_printf("订阅失败,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支
}
}
//if判断,如果一共接收了2个字节,第一个字节是0xD0,表示收到的是PINGRESP报文
else if((MQTT_RxDataOutPtr[0]==2)&&(MQTT_RxDataOutPtr[1]==0xD0)){
u1_printf("PING报文回复\r\n"); //串口输出信息
if(Ping_flag==1){ //如果Ping_flag=1,表示第一次发送
Ping_flag = 0; //要清除Ping_flag标志
}else if(Ping_flag>1){ //如果Ping_flag>1,表示是多次发送了,而且是2s间隔的快速发送
Ping_flag = 0; //要清除Ping_flag标志
TIM3_ENABLE_30S(); //PING定时器重回30s的时间
}
}
//if判断,如果第一个字节是0x30,表示收到的是服务器发来的推送数据
//我们要提取控制命令
else if((MQTT_RxDataOutPtr[1]==0x30)){
u1_printf("服务器等级0推送\r\n"); //串口输出信息
MQTT_DealPushdata_Qs0(MQTT_RxDataOutPtr); //处理等级0推送数据
}
MQTT_RxDataOutPtr += BUFF_UNIT; //指针下移
if(MQTT_RxDataOutPtr==MQTT_RxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_RxDataOutPtr = MQTT_RxDataBuf[0]; //指针归位到缓冲区开头
}//处理接收缓冲区数据的else if分支结尾
/*-------------------------------------------------------------*/
/* 处理命令缓冲区数据 */
/*-------------------------------------------------------------*/
// if(MQTT_CMDOutPtr != MQTT_CMDInPtr){ //if成立的话,说明命令缓冲区有数据了
// u1_printf("命令:%s\r\n",&MQTT_CMDOutPtr[1]); //串口输出信息
// if(strstr((char *)MQTT_CMDOutPtr+1,"\"params\":{\"Switch1\":1}")){ //如果搜索到"params":{"PowerSwitch":1}说明服务器下发打开开关1
// LED1_ON; //打开LED1
// LED1_State(); //判断开关状态,并发布给服务器
// }else if(strstr((char *)MQTT_CMDOutPtr+1,"\"params\":{\"Switch1\":0}")){ //如果搜索到"params":{"PowerSwitch":0}说明服务器下发关闭开关1
// LED1_OFF; //关闭LED1
// LED1_State(); //判断开关状态,并发布给服务器
// }else if(strstr((char *)MQTT_CMDOutPtr+1,"\"params\":{\"Switch2\":1}")){ //如果搜索到"params":{"PowerSwitch_2":1}说明服务器下发打开开关2
// LED2_ON; //打开LED2
// LED2_State(); //判断开关状态,并发布给服务器
// }else if(strstr((char *)MQTT_CMDOutPtr+1,"\"params\":{\"Switch2\":0}")){ //如果搜索到"params":{"PowerSwitch_2":0}说明服务器下发关闭开关2
// LED2_OFF; //关闭LED2
// LED2_State(); //判断开关状态,并发布给服务器
// }
//
// MQTT_CMDOutPtr += BUFF_UNIT; //指针下移
// if(MQTT_CMDOutPtr==MQTT_CMDEndPtr) //如果指针到缓冲区尾部了
// MQTT_CMDOutPtr = MQTT_CMDBuf[0]; //指针归位到缓冲区开头
// }//处理命令缓冲区数据的else if分支结尾
}//Connect_flag=1的if分支的结尾
/*--------------------------------------------------------------------*/
/* Connect_flag=0同服务器断开了连接,我们要重启连接服务器 */
/*--------------------------------------------------------------------*/
else{
u1_printf("需要连接服务器\r\n"); //串口输出信息
TIM_Cmd(TIM4,DISABLE); //关闭TIM4
TIM_Cmd(TIM3,DISABLE); //关闭TIM3
WiFi_RxCounter=0; //WiFi接收数据量变量清零
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE); //清空WiFi接收缓冲区
if(WiFi_Connect_IoTServer()==0){ //如果WiFi连接云服务器函数返回0,表示正确,进入if
u1_printf("建立TCP连接成功\r\n"); //串口输出信息
Connect_flag = 1; //Connect_flag置1,表示连接成功
WiFi_RxCounter=0; //WiFi接收数据量变量清零
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE); //清空WiFi接收缓冲区
MQTT_Buff_ReInit(); //重新初始化发送缓冲区
TIM2_ENABLE_1S();
//LED_State();
}
}
/*--------------------------------------------------------------------*/
/* 其他控制程序 */
/*--------------------------------------------------------------------*/
//湿度显示
adcx=Get_Adc_Average(ADC_Channel_1,10);//得到采集以后的电压数据
shidu=(float)adcx*(1.0/4096);
shidu*=100;//得到湿度百分比
turang_shidu=(int)shidu;
OLED_ShowNum(72,0,(int)shidu,2,16); //显示湿度数据
//显示模式判断
switch(xianshi_status)
{
case 1:OLED_ShowCH(72,3," "),OLED_ShowCH(72,3,"超声波"),LED0=0,LED1=1,BEEP=1;break;
case 2:OLED_ShowCH(72,3," "),OLED_ShowCH(72,3,"红外"),LED1=0,LED0=1,BEEP=1;break;
case 3:OLED_ShowCH(72,3," "),OLED_ShowCH(72,3,"超&红"),LED0=0,LED1=0,BEEP=1;break;
default:OLED_ShowCH(72,3," "),OLED_ShowCH(72,3,"无"),LED0=1,LED1=1,BEEP=1;break;
}
//产生高于10Us的高电平与OLED上显示超声波所测距离
Wave_SRD_Strat(t);
Distance = TIM_GetCounter(TIM1)*340/200.0;
if(Distance>0)
{
OLED_ShowNum(50,6,Distance,3,16); //显示超声波测量距离
delay_ms(10);
}
//判断超声波是否检测到物体
if(Distance<=10)
{
if(hongwai==1)
xianshi_status=3;
else
xianshi_status=1;
}
//判断红外传感是否检测到物体
if(hongwai==1)
{
if(Distance<=10)
xianshi_status=3;
else
xianshi_status=2;
}
if(Distance>10&&hongwai==0)
{
xianshi_status=0;
}
}
}
四、实际效果
在代码里面设定超声波监测距离小于10cm即会报警,实物图如下: