关键词:STM32、蓝牙控制、传感器融合
一、应用背景
随着智能家居的发展,植物养护也走向自动化。本项目基于STM32C8T6设计了一款智能盆栽浇水系统,支持蓝牙远程控制、环境监测与自动灌溉,适用于家庭与办公场景。
二、硬件设计
1. 核心硬件清单
模块 | 型号/参数 | 功能说明 |
---|---|---|
主控芯片 | STM32F103C8T6 | 系统控制核心 |
蓝牙模块 | HC-05 | 手机指令接收 |
温湿度传感器 | DHT11 | 空气温湿度采集 |
土壤湿度传感器 | 模拟量传感器 | 土壤湿度检测(ADC读取) |
执行机构 | SG90舵机 | 控制水阀开关 |
显示模块 | 0.96寸OLED | 环境数据显示 |
报警模块 | 有源蜂鸣器+LED | 状态提示 |
2. 硬件连接
将DHT11温湿度传感器的VCC引脚连接到STM32的3.3V引脚,GND引脚连接到GND,数据引脚连接到STM32的GPIO引脚(例如PB0)。
将SG90舵机的输入引脚连接到STM32的GPIO引脚(例如PA8),VCC引脚连接到STM32的5V引脚,GND引脚连接到GND.
将OLED显示屏的VCC引脚连接到STM32的3.3V引脚,GND引脚连接到GND,SCL引脚连接到STM32的SCL引脚(例如PA0),SDA引脚连接到STM32的SDA引脚(例如PA1)。
将独立按键的一个引脚连接到STM32的GPIO引脚(例如PB6_9),另一个引脚连接到GND。
将LED的负极引脚连接到STM32的GPIO引脚(例如PA5),正极引脚连接到3.3V。
将HC-05蓝牙模块的VCC引脚连接到STM32的3.3V引脚,GND引脚连接到GND,RXD引脚连接到STM32的GPIO引脚(例如PB10),TXD引脚连接到STM32的GPIO引脚(例如PB11)。
将蜂鸣器的VCC引脚连接到STM32的3.3V引脚,GND引脚连接到GND,输入引脚连接到STM32的GPIO引脚(例如PA6)
将土壤湿度传感器VCC引脚连接到STM32的3.3V引脚,GND引脚连接到GND,模拟输出接口接入GPIO引脚(例如PB1)
/* 核心引脚定义(部分) */ // 舵机控制 -> PA8(TIM1_CH1) // 蓝牙模块 -> USART1_TX(PA9)/RX(PA10) // 土壤湿度 -> PB1(ADC1) // OLED显示 -> I2C1(SCL:PB6, SDA:PB7)
三、软件设计
1. 系统架构
2. 核心功能实现
(1) 蓝牙指令解析(关键代码)(中断代码)
void TIM2_IRQHandler (void)
{
if( TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
i++;
if(i==1000)//一秒钟计时
{
a++;//延时计时时常
s++;
if(s==60)
{ s=0; }
i=0;
}
}
}
void EXTI9_5_IRQHandler ()
{
if (EXTI_GetITStatus(Key2_EXTI_Line)==SET)
{
EXTI_ClearITPendingBit(Key2_EXTI_Line);
ch[4]='0';
ch[0]='0';
ch[2]='0';
ch[0]='O';//直接开启
}
if (EXTI_GetITStatus(Key4_EXTI_Line)==SET)
{
EXTI_ClearITPendingBit(Key4_EXTI_Line);
ch[4]='0';
ch[0]='0';
ch[2]='0';
ch[0]='S';;//强制关闭电机
}
}
void DEBUG_USART_IRQHandler(void)
{
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE)!=RESET)
{
h=0;
j=0;
ucTemp=USART_ReceiveData(DEBUG_USARTx);//接收数据
USART_SendData(DEBUG_USARTx, ucTemp); //发送数据
ch[v]=ucTemp;
v++;
if(ucTemp=='!')
{
if (ch[4]=='A')
{
h=((uint16_t)ch[2]-48)*10+((uint16_t)ch[3]-48);
t=((uint16_t)ch[0]-48)*10+((uint16_t)ch[1]-48);
printf("Automatic mode\n");
printf("Seting temperature %d minutesn \n",t);
printf("Seting humidness %d C` \n",h);
printf("temperature %d.%d\n",ht,lt);
printf("humidness %d.%d\n",hh,lh);
printf("soil humidness:%.2f\n",soil_humi);
}
else if (ch[0]=='O')
{printf("Open watering\n");
printf("temperature %d.%d\n",ht,lt);
printf("humidness %d.%d\n",hh,lh);
printf("soil humidness:%.2f\n",soil_humi);}
else if (ch[2]=='T')
{
j=((uint16_t)ch[0]-48)*10+((uint16_t)ch[1]-48);
printf("Timing mode\n");
printf("Seting time %d minutesn \n",j);
printf("Start watering in %d minutesn \n",j);
y=j-1;
}
else if (ch[0]=='S')
{printf("Close the servo to stop irrigation.\n");
printf("temperature %d.%d\n",ht,lt);
printf("humidness %d.%d\n",hh,lh);
printf("soil humidness:%.2f\n",soil_humi);}
else
printf("Please select a mode\n");
v=0;
}
}
}
(2)主函数代码
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "bmp.h"
#include "Led.h"
#include "server.h"
#include "usart.h"
#include "dht11.h"
#include "time.h"
#include "key_exti.h"
#include "buzzer.h"
#include "bsp_adc.h"
#include "stdio.h"
extern uint8_t ucTemp,a,h,j,s,v,t,y;
uint8_t x;
extern uint8_t ht,lt,hh,lh;
extern uint8_t ch[20];
uint8_t tempH,tempL,humiH,humiL;
uint8_t humiH2,humiL2;
__IO extern uint16_t ADC_ConvertedValue;
extern float soil_humi;
static void Get_Soil_humi()
{
soil_humi =100*(float)ADC_ConvertedValue/4096;
// printf("soil humidness:%.2f\n",soil_humi);
humiH=(uint8_t)soil_humi;
humiL=(uint16_t)(soil_humi*100)%100;
}
static void Show_Temperature_and_humidity()
{
ht=tempH,lt=tempL,hh=humiH,lh=humiL;
Get_Soil_humi();
OLED_ShowChinese(0,18,4,16,1);//温
OLED_ShowChinese(18,18,5,16,1);//度
OLED_ShowChinese(0,36,6,16,1);//湿
OLED_ShowChinese(18,36,5,16,1);//度
DHT11_Read_Data(&humiH2, &humiL2,&tempH,&tempL);//获取温度和湿度
OLED_ShowString(54,36,".",8,1);//.
OLED_ShowString(54,18,".",8,1);//.
OLED_ShowNum(36,36,humiH,2,16,1);//湿度的整数
OLED_ShowNum(72,36,humiL,2,16,1);//湿度的小数
OLED_ShowNum(36,18,tempH,2,16,1);//温度的整数
OLED_ShowNum(72,18,tempL,2,16,1);//温度的小数
}
/*舵机开启模式*/
void Open_mode(void)
{
h++;
if(h==1)
{
TIM_SetCompare1(TIM1,25);//舵机逆时针转
delay_ms(565);
// Open_worte_Pump();
TIM_SetCompare1(TIM1,15);//舵机停止
delay_ms(50000);
}
OLED_Refresh();
delay_ms(500);
OLED_Clear();
OLED_ShowChinese(0,0,0,16,1);//电
OLED_ShowChinese(18,0,1,16,1);//机
OLED_ShowChinese(36,0,2,16,1);//状
OLED_ShowChinese(54,0,3,16,1);//态
OLED_ShowChinese(72,0,22,16,1);//启
OLED_ShowChinese(90,0,23,16,1);//动
Show_Temperature_and_humidity();
if(tempH<=20| (int)soil_humi>=100)/*温度小于20,土壤湿度大于90时自动关闭,只能固定设置*/
{
j=0;
ch[4]='0';
ch[0]='0';
ch[2]='0';
ch[0]='S';//关闭舵机
}
}
/*停止舵机模式*/
void Sdop_mode(void)
{
j++;
if(j==1)
{
TIM_SetCompare1(TIM1,5);//舵机顺时针转
delay_ms(565);
// Stop_worte_Pump();
TIM_SetCompare1(TIM1,15);//舵机停止
delay_ms(50000);
}
OLED_Refresh();//更新显存到OLED
delay_ms(500);
OLED_Clear();//清屏
OLED_ShowChinese(0,0,0,16,1);//电
OLED_ShowChinese(18,0,1,16,1);//机
OLED_ShowChinese(36,0,2,16,1);//状
OLED_ShowChinese(54,0,3,16,1);//态
OLED_ShowChinese(72,0,24,16,1);//关
OLED_ShowChinese(90,0,25,16,1);//闭
Show_Temperature_and_humidity();
}
/*延时模式*/
void Real_time_mode()
{
u8 g,i=0;
TIM_Cmd(TIM2, ENABLE);
g=60-s;
if(s>=59)
y--;
OLED_Refresh();
delay_ms(500);
OLED_Clear();
OLED_ShowChinese(0,0,7,16,1);//定
OLED_ShowChinese(18,0,8,16,1);//时
OLED_ShowChinese(36,0,9,16,1);//模
OLED_ShowChinese(54,0,10,16,1);//式
OLED_ShowNum(0,18,y,2,16,1);//延时总时间
OLED_ShowString(18,18,":",8,1);//:
OLED_ShowNum(26,18,g,2,16,1);//60s倒计时
OLED_ShowChinese(44,18,15,16,1);//分
OLED_ShowChinese(62,18,16,16,1);//钟
OLED_ShowChinese(80,18,19,16,1);//后
OLED_ShowChinese(0,36,17,16,1);//开
OLED_ShowChinese(18,36,22,16,1);//启
OLED_ShowChinese(36,36,13,16,1);//水
OLED_ShowChinese(54,36,14,16,1);//阀
if(a>=j*60)/*经过延时后开启舵机,开启后有三秒报警,时间通过按键设置*/
{
h=0;
a=0;
s=0;
for(i=0;i<6;i++)//LED闪烁和蜂鸣器响三秒,做开启报警
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
delay_ms(250);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
delay_ms(250);
}
ch[4]='0';
ch[0]='0';
ch[2]='0';
ch[0]='O';//开启舵机
}
}
/*自动开启模式*/
void Automatic_mode()
{
uint8_t i;
OLED_Refresh();
delay_ms(500);
OLED_Clear();
OLED_ShowChinese(0,0,11,16,1);//自
OLED_ShowChinese(18,0,12,16,1);//动
OLED_ShowChinese(36,0,21,16,1);//浇
OLED_ShowChinese(54,0,13,16,1);//水
OLED_ShowChinese(72,0,9,16,1);//模
OLED_ShowChinese(90,0,10,16,1);//式
Show_Temperature_and_humidity();
if(tempH>=t|| soil_humi<=10)
{
h=0;
for(i=0;i<3;i++)//LED闪烁和蜂鸣器响三秒,做高温开启报警
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
delay_ms(250);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
GPIO_SetBits(GPIOA, GPIO_Pin_6);
delay_ms(250);
}
ch[4]='0';
ch[0]='0';
ch[2]='0';
ch[0]='O';//开启舵机
}
}
/*初始选择页面*/
void Init_page()
{
OLED_Refresh();
delay_ms(500);
OLED_Clear();
OLED_ShowChinese(0,0,30,16,1);//请
OLED_ShowChinese(18,0,31,16,1);//选
OLED_ShowChinese(36,0,32,16,1);//择
OLED_ShowChinese(54,0,9,16,1);//模
OLED_ShowChinese(72,0,10,16,1);//式
Show_Temperature_and_humidity();
}
int main(void)
{
USART_Config();
Key_Exti_Config();
// Pump_GPIO_Init();
delay_init();
OLED_Init();
LED_GPIO_Config();
BUZZER_GPIO_Config();
TIM8_PWM_Init();
ADCx_Init();
TIM2_Init(250-1,72-1);
TIM_Cmd(TIM2, DISABLE);
OLED_ColorTurn(0);//0正常显示,1 反色显示
OLED_DisplayTurn(0);//0正常显示 1 屏幕翻转显示
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
while(DHT11_Init()!=0)//DHT11初始化及检测DHT11是否正常
{
OLED_Refresh();
delay_ms(500);
OLED_Clear();
OLED_ShowString(8,16,"error",16,1);//DHT11报错
}
printf("Please select a mode\n");
printf("Input:(temperature and humidity)Auto! Automatic mode\n");
printf("Input:Open! Open watering\n");
printf("Input:(delay time)Time! Timing mode\n");
printf("Input:Stop! Close the servo to stop irrigation.\n");
while(1)
{
if (ch[4]=='A')//自动开启模式 温湿度检测,温度和湿度达到一定时自动打开舵机,温度湿度通过蓝牙输入
{
Automatic_mode();
}
else if (ch[0]=='O')//舵机开启模式 通过蓝牙输入字符开启舵机进行浇水动作
{
Open_mode();
}
else if (ch[2]=='T')//延时开启模式 通过蓝牙输入延时时长并开启此模式后,
{
Real_time_mode();
}
else if (ch[0]=='S') //舵机停止模式 通过蓝牙关闭水龙头
{Sdop_mode();
}
else
Init_page();/*初始选择页面*/
}
}
四、功能演示
1. 蓝牙控制指令集
指令格式 | 功能说明 |
---|---|
Open! | 立即开启浇水 |
Stop! | 立即停止浇水 |
30Time! | 延时30分钟后启动 |
2530Auto! | 自动模式:温度>25℃或湿度<30%时触发 |
2. 操作流程
-
手机通过**微信小程序(蓝牙串口)**发送指令
-
STM32接收指令并执行对应操作
-
OLED显示当前环境数据与系统状态
-
触发浇水时蜂鸣器+LED闪烁提示
-
按键可以直接开启或关闭
五、常见问题解决
Q1:土壤湿度检测不准确
解决方案:
-
采用软件滤波算法(中值平均滤波)
c
复制
#define FILTER_NUM 5 // 采样5次取平均 uint16_t ADC_Filter() { static uint16_t buf[FILTER_NUM]; for(int i=0; i<FILTER_NUM; i++) { buf[i] = ADC_Read(); delay_ms(10); } // 排序后取中间值 bubble_sort(buf, FILTER_NUM); return buf[FILTER_NUM/2]; }
Q2:舵机抖动问题
解决方案:
-
在PWM信号中增加死区控制
-
为舵机单独供电(避免与MCU共地干扰)
六、改进建议
1、增加语音模块,实现声音控制;2增加WiFi模块;3、由于资源有限,这里oled屏幕较小显示信息不多,可用大的lcd屏可以显示更多信息。