文章目录
-
- 一、项目概述
-
- [1.1 硬件选型](#1.1 硬件选型)
- [1.2 软件环境](#1.2 软件环境)
- 二、系统架构设计
-
- [2.1 任务划分](#2.1 任务划分)
- [2.2 系统流程图](#2.2 系统流程图)
- 三、硬件电路设计
-
- [3.1 核心电路连接](#3.1 核心电路连接)
- [3.2 电路注意事项](#3.2 电路注意事项)
- 四、工程搭建步骤
-
- [4.1 新建Keil工程](#4.1 新建Keil工程)
- [4.2 工程配置](#4.2 工程配置)
- 五、代码实现
-
- [5.1 核心头文件(文件名:SmartHome_Config.h)](#5.1 核心头文件(文件名:SmartHome_Config.h))
- [5.2 系统初始化文件(文件名:System_Init.c)](#5.2 系统初始化文件(文件名:System_Init.c))
- [5.3 DHT11驱动文件(文件名:DHT11_Driver.c)](#5.3 DHT11驱动文件(文件名:DHT11_Driver.c))
- [5.4 BH1750驱动文件(文件名:BH1750_Driver.c)](#5.4 BH1750驱动文件(文件名:BH1750_Driver.c))
- [5.5 按键处理文件(文件名:Key_Process.c)](#5.5 按键处理文件(文件名:Key_Process.c))
- [5.6 数据采集任务文件(文件名:DataCollect_Task.c)](#5.6 数据采集任务文件(文件名:DataCollect_Task.c))
- [5.7 数据处理任务文件(文件名:DataProcess_Task.c)](#5.7 数据处理任务文件(文件名:DataProcess_Task.c))
- [5.8 设备控制任务文件(文件名:DeviceCtrl_Task.c)](#5.8 设备控制任务文件(文件名:DeviceCtrl_Task.c))
- [5.9 串口通信任务文件(文件名:Usart_Task.c)](#5.9 串口通信任务文件(文件名:Usart_Task.c))
- [5.10 主函数文件(文件名:main.c)](#5.10 主函数文件(文件名:main.c))
- 六、编译与下载
-
- [6.1 编译工程](#6.1 编译工程)
- [6.2 下载程序](#6.2 下载程序)
- 七、测试与调试
-
- [7.1 硬件测试](#7.1 硬件测试)
- [7.2 串口调试](#7.2 串口调试)
- 八、常见问题解决
-
- [8.1 编译错误](#8.1 编译错误)
- [8.2 硬件问题](#8.2 硬件问题)
- [8.3 功能问题](#8.3 功能问题)
- 总结
一、项目概述
本项目基于STM32F103ZET6单片机,结合FreeRTOS实时操作系统实现多任务智能家居控制系统。系统包含环境监测(温湿度、光照)、设备控制(灯光、空调、窗帘)、按键交互、串口通信等核心功能,通过FreeRTOS的任务管理机制实现各功能模块的并行运行,满足智能家居场景下多设备协同工作的需求。
1.1 硬件选型
| 模块 | 型号 | 功能 |
|---|---|---|
| 主控芯片 | STM32F103ZET6 | 核心控制单元 |
| 温湿度传感器 | DHT11 | 采集环境温湿度数据 |
| 光照传感器 | BH1750 | 采集环境光照强度 |
| 继电器模块 | 5V四路继电器 | 控制灯光、空调、窗帘等外设 |
| 按键模块 | 独立按键×3 | 手动控制设备/模式切换 |
| 串口模块 | CH340 | 与上位机通信,输出调试信息 |
| 电源模块 | 5V/3.3V | 为各模块供电 |
1.2 软件环境
- 开发工具:Keil MDK-ARM V5.38
- 固件库:STM32F10x_StdPeriph_Lib_V3.5.0
- FreeRTOS版本:FreeRTOS V10.4.3
- 编译器:ARMCC V5.06
二、系统架构设计
2.1 任务划分
整个系统基于FreeRTOS划分为6个核心任务,优先级从高到低依次为:
- 按键扫描任务(最高优先级):响应手动操作,避免按键事件丢失
- 串口通信任务:向上位机发送数据/接收控制指令
- 环境数据采集任务:采集温湿度、光照数据
- 设备控制任务:根据采集数据/按键指令控制外设
- 数据处理任务:对采集的环境数据进行滤波/阈值判断
- 空闲任务(FreeRTOS自带):低优先级,仅在无其他任务运行时执行
2.2 系统流程图
系统初始化
FreeRTOS初始化
创建各任务
启动任务调度器
任务调度
按键扫描任务
串口通信任务
环境数据采集任务
数据处理任务
设备控制任务
检测按键事件
更新控制指令
读取DHT11数据
读取BH1750数据
数据滤波/阈值判断
生成设备控制信号
控制继电器输出
向上位机发送状态数据
三、硬件电路设计
3.1 核心电路连接
| STM32引脚 | 外设模块 | 连接引脚 |
|---|---|---|
| PA0 | 按键1 | 一端接PA0,一端接地 |
| PA1 | 按键2 | 一端接PA1,一端接地 |
| PA2 | 按键3 | 一端接PA2,一端接地 |
| PB0 | DHT11 | 数据引脚 |
| PB1 | BH1750-SDA | SDA引脚 |
| PB2 | BH1750-SCL | SCL引脚 |
| PC0 | 继电器1(灯光) | 控制引脚 |
| PC1 | 继电器2(空调) | 控制引脚 |
| PC2 | 继电器3(窗帘) | 控制引脚 |
| PA9 | 串口TX | CH340-RX |
| PA10 | 串口RX | CH340-TX |
3.2 电路注意事项
- 按键模块需添加10K上拉电阻(或使用STM32内部上拉)
- 继电器模块需添加续流二极管,防止反向电动势损坏单片机
- DHT11数据引脚需添加4.7K上拉电阻
- BH1750需外接3.3V电源,避免5V电压损坏传感器
- 所有外设GND需与STM32共地
四、工程搭建步骤
4.1 新建Keil工程
- 打开Keil MDK-ARM V5.38,点击
Project -> New μVision Project,命名为SmartHome_FreeRTOS,选择保存路径 - 选择芯片型号:
STMicroelectronics -> STM32F1 Series -> STM32F103 -> STM32F103ZET6 - 弹出
Run-Time Environment窗口,取消默认勾选,点击OK - 手动添加固件库文件:
- 新建
Libraries文件夹,放入STM32F10x标准库的核心文件(stm32f10x_core.c、stm32f10x_periph.c等) - 新建
FreeRTOS文件夹,放入FreeRTOS核心文件(croutine.c、event_groups.c、list.c、queue.c、tasks.c、timers.c等) - 新建
User文件夹,用于存放用户代码
- 新建
4.2 工程配置
- 点击
魔法棒图标 -> Target,设置:晶振频率:8MHz堆栈大小:Heap Size=0x2000,Stack Size=0x1000
- 点击
Output,勾选Create HEX File - 点击
Editor,设置编码格式为UTF-8 - 点击
Include Paths,添加所有文件夹的路径(Libraries、FreeRTOS、User等) - 点击
Define,添加宏定义:STM32F10X_HD,USE_STDPERIPH_DRIVER,USE_FULL_ASSERT
五、代码实现
5.1 核心头文件(文件名:SmartHome_Config.h)
c
#ifndef __SMARTHOME_CONFIG_H
#define __SMARTHOME_CONFIG_H
// 硬件相关配置
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
// FreeRTOS配置
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
// 任务优先级配置
#define KEY_TASK_PRIO 5 // 按键任务优先级(最高)
#define USART_TASK_PRIO 4 // 串口任务优先级
#define DATA_COLLECT_TASK_PRIO 3 // 数据采集任务优先级
#define DATA_PROCESS_TASK_PRIO 2 // 数据处理任务优先级
#define DEVICE_CTRL_TASK_PRIO 1 // 设备控制任务优先级
// 任务堆栈大小
#define KEY_TASK_STACK_SIZE 128
#define USART_TASK_STACK_SIZE 256
#define DATA_COLLECT_STACK_SIZE 256
#define DATA_PROCESS_STACK_SIZE 128
#define DEVICE_CTRL_STACK_SIZE 128
// 引脚定义
#define KEY1_PIN GPIO_Pin_0
#define KEY1_PORT GPIOA
#define KEY2_PIN GPIO_Pin_1
#define KEY2_PORT GPIOA
#define KEY3_PIN GPIO_Pin_2
#define KEY3_PORT GPIOA
#define DHT11_PIN GPIO_Pin_0
#define DHT11_PORT GPIOB
#define BH1750_SDA_PIN GPIO_Pin_1
#define BH1750_SDA_PORT GPIOB
#define BH1750_SCL_PIN GPIO_Pin_2
#define BH1750_SCL_PORT GPIOB
#define LIGHT_PIN GPIO_Pin_0
#define LIGHT_PORT GPIOC
#define AIR_PIN GPIO_Pin_1
#define AIR_PORT GPIOC
#define CURTAIN_PIN GPIO_Pin_2
#define CURTAIN_PORT GPIOC
// 数据结构体定义
typedef struct
{
float temperature; // 温度(℃)
float humidity; // 湿度(%)
uint16_t light; // 光照强度(lux)
}EnvData_t;
typedef struct
{
uint8_t light_ctrl; // 灯光控制:0-关闭,1-开启
uint8_t air_ctrl; // 空调控制:0-关闭,1-开启
uint8_t curtain_ctrl;// 窗帘控制:0-关闭,1-开启
}DeviceCtrl_t;
// 全局变量声明
extern EnvData_t g_EnvData; // 环境数据
extern DeviceCtrl_t g_DeviceCtrl; // 设备控制指令
extern QueueHandle_t xEnvDataQueue; // 环境数据队列
extern QueueHandle_t xDeviceCtrlQueue;// 设备控制队列
// 函数声明
void System_Init(void); // 系统初始化
void Key_Task(void *pvParameters); // 按键任务
void Usart_Task(void *pvParameters);// 串口任务
void DataCollect_Task(void *pvParameters);// 数据采集任务
void DataProcess_Task(void *pvParameters);// 数据处理任务
void DeviceCtrl_Task(void *pvParameters); // 设备控制任务
// 外设驱动函数声明
void DHT11_ReadData(EnvData_t *pEnvData); // DHT11读取函数
void BH1750_ReadData(EnvData_t *pEnvData); // BH1750读取函数
void Key_Scan(DeviceCtrl_t *pDeviceCtrl); // 按键扫描函数
void Relay_Control(DeviceCtrl_t *pDeviceCtrl); // 继电器控制函数
void Usart_SendEnvData(EnvData_t *pEnvData); // 发送环境数据
void Usart_SendDeviceStatus(DeviceCtrl_t *pDeviceCtrl); // 发送设备状态
#endif
5.2 系统初始化文件(文件名:System_Init.c)
c
#include "SmartHome_Config.h"
// 全局变量定义
EnvData_t g_EnvData = {0};
DeviceCtrl_t g_DeviceCtrl = {0};
QueueHandle_t xEnvDataQueue = NULL;
QueueHandle_t xDeviceCtrlQueue = NULL;
/**
* @brief 系统时钟初始化
* @param 无
* @retval 无
*/
void SystemClock_Init(void)
{
// 启用外部高速时钟
RCC_HSEConfig(RCC_HSE_ON);
// 等待HSE稳定
while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
// 设置PLL倍频因子:HSE=8MHz, PLL=72MHz
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
// 启用PLL
RCC_PLLCmd(ENABLE);
// 等待PLL稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 设置AHB分频因子
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// 设置APB1分频因子
RCC_PCLK1Config(RCC_HCLK_Div2);
// 设置APB2分频因子
RCC_PCLK2Config(RCC_HCLK_Div1);
// 设置FLASH等待周期
FLASH_SetLatency(FLASH_Latency_2);
// 切换系统时钟到PLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 等待系统时钟切换完成
while(RCC_GetSYSCLKSource() != 0x08);
}
/**
* @brief GPIO初始化
* @param 无
* @retval 无
*/
void GPIO_Init_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 启用GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
// 按键引脚初始化(上拉输入)
GPIO_InitStructure.GPIO_Pin = KEY1_PIN | KEY2_PIN | KEY3_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY1_PORT, &GPIO_InitStructure);
// DHT11引脚初始化(推挽输出+上拉)
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
GPIO_SetBits(DHT11_PORT, DHT11_PIN); // 初始置高
// BH1750引脚初始化(开漏输出)
GPIO_InitStructure.GPIO_Pin = BH1750_SDA_PIN | BH1750_SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BH1750_SDA_PORT, &GPIO_InitStructure);
// 继电器引脚初始化(推挽输出)
GPIO_InitStructure.GPIO_Pin = LIGHT_PIN | AIR_PIN | CURTAIN_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LIGHT_PORT, &GPIO_InitStructure);
// 初始关闭所有继电器
GPIO_ResetBits(LIGHT_PORT, LIGHT_PIN);
GPIO_ResetBits(AIR_PORT, AIR_PIN);
GPIO_ResetBits(CURTAIN_PORT, CURTAIN_PIN);
}
/**
* @brief 串口初始化(USART1,115200bps)
* @param 无
* @retval 无
*/
void USART1_Init_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 启用USART1和GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// USART1_TX (PA9) 初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART1_RX (PA10) 初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART1配置
USART_InitStructure.USART_BaudRate = 115200; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
USART_Init(USART1, &USART_InitStructure);
// 启用USART1
USART_Cmd(USART1, ENABLE);
}
/**
* @brief 串口发送字符
* @param ch: 要发送的字符
* @retval 无
*/
void USART1_SendChar(uint8_t ch)
{
// 等待发送缓冲区为空
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
// 发送字符
USART_SendData(USART1, ch);
}
/**
* @brief 串口发送字符串
* @param pStr: 字符串指针
* @retval 无
*/
void USART1_SendString(uint8_t *pStr)
{
while(*pStr != '\0')
{
USART1_SendChar(*pStr);
pStr++;
}
}
/**
* @brief 系统总初始化
* @param 无
* @retval 无
*/
void System_Init(void)
{
// 初始化系统时钟
SystemClock_Init();
// 初始化GPIO
GPIO_Init_Config();
// 初始化串口
USART1_Init_Config();
// 创建队列
// 环境数据队列:长度5,每个元素大小为EnvData_t
xEnvDataQueue = xQueueCreate(5, sizeof(EnvData_t));
// 设备控制队列:长度5,每个元素大小为DeviceCtrl_t
xDeviceCtrlQueue = xQueueCreate(5, sizeof(DeviceCtrl_t));
// 打印初始化完成信息
USART1_SendString((uint8_t*)"System Init OK!\r\n");
}
5.3 DHT11驱动文件(文件名:DHT11_Driver.c)
c
#include "SmartHome_Config.h"
/**
* @brief 微秒级延时函数
* @param us: 延时微秒数
* @retval 无
*/
void DHT11_Delay_us(uint32_t us)
{
uint32_t i;
for(i=0; i<us*8; i++); // 适配72MHz系统时钟
}
/**
* @brief DHT11开始信号
* @param 无
* @retval 无
*/
void DHT11_Start(void)
{
// 设置为输出模式
GPIO_SetBits(DHT11_PORT, DHT11_PIN);
DHT11_Delay_us(2);
// 拉低总线至少18ms
GPIO_ResetBits(DHT11_PORT, DHT11_PIN);
DHT11_Delay_us(18000);
// 拉高总线20-40us
GPIO_SetBits(DHT11_PORT, DHT11_PIN);
DHT11_Delay_us(30);
}
/**
* @brief 等待DHT11响应
* @param 无
* @retval 0-成功,1-失败
*/
uint8_t DHT11_Wait_Ack(void)
{
uint32_t timeout = 0;
// 等待DHT11拉低总线
while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET)
{
timeout++;
DHT11_Delay_us(1);
if(timeout > 100) return 1; // 超时
}
timeout = 0;
// 等待DHT11拉高总线
while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == RESET)
{
timeout++;
DHT11_Delay_us(1);
if(timeout > 100) return 1; // 超时
}
return 0;
}
/**
* @brief 从DHT11读取一个字节
* @param 无
* @retval 读取的字节数据
*/
uint8_t DHT11_Read_Byte(void)
{
uint8_t i, data = 0;
for(i=0; i<8; i++)
{
// 等待低电平结束
while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == RESET);
// 延时40us,判断高电平时长
DHT11_Delay_us(40);
// 初始化数据位为0
data <<= 1;
// 如果高电平持续时间大于40us,数据位为1
if(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET)
{
data |= 0x01;
// 等待高电平结束
while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET);
}
}
return data;
}
/**
* @brief 读取DHT11温湿度数据
* @param pEnvData: 环境数据结构体指针
* @retval 0-成功,1-失败
*/
void DHT11_ReadData(EnvData_t *pEnvData)
{
uint8_t buf[5];
uint8_t i;
// 发送开始信号
DHT11_Start();
// 等待响应
if(DHT11_Wait_Ack() == 0)
{
// 读取40位数据
for(i=0; i<5; i++)
{
buf[i] = DHT11_Read_Byte();
}
// 校验和验证
if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
// 湿度整数部分
pEnvData->humidity = buf[0];
// 湿度小数部分(DHT11小数部分为0)
pEnvData->humidity += buf[1] / 10.0;
// 温度整数部分
pEnvData->temperature = buf[2];
// 温度小数部分
pEnvData->temperature += buf[3] / 10.0;
}
}
else
{
// 读取失败,赋值为0
pEnvData->humidity = 0;
pEnvData->temperature = 0;
}
}
5.4 BH1750驱动文件(文件名:BH1750_Driver.c)
c
#include "SmartHome_Config.h"
/**
* @brief I2C起始信号
* @param 无
* @retval 无
*/
void BH1750_I2C_Start(void)
{
// SDA先置高
GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// SDA拉低
GPIO_ResetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
DHT11_Delay_us(2);
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
}
/**
* @brief I2C停止信号
* @param 无
* @retval 无
*/
void BH1750_I2C_Stop(void)
{
// SDA拉低
GPIO_ResetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// SDA置高
GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
DHT11_Delay_us(2);
}
/**
* @brief I2C发送应答信号
* @param ack: 0-应答,1-非应答
* @retval 无
*/
void BH1750_I2C_SendAck(uint8_t ack)
{
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
if(ack)
{
// 非应答:SDA置高
GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
}
else
{
// 应答:SDA拉低
GPIO_ResetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
}
// SCL置高
GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
}
/**
* @brief I2C接收应答信号
* @param 无
* @retval 0-应答,1-非应答
*/
uint8_t BH1750_I2C_RecvAck(void)
{
uint8_t ack = 1;
// SDA置高(释放总线)
GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
DHT11_Delay_us(2);
// SCL置高
GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// 读取SDA电平
if(GPIO_ReadInputDataBit(BH1750_SDA_PORT, BH1750_SDA_PIN) == RESET)
{
ack = 0; // 应答
}
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
return ack;
}
/**
* @brief I2C发送一个字节
* @param data: 要发送的字节
* @retval 无
*/
void BH1750_I2C_SendByte(uint8_t data)
{
uint8_t i;
for(i=0; i<8; i++)
{
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// 发送最高位
if(data & 0x80)
{
GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
}
else
{
GPIO_ResetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
}
// SCL置高
GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// 左移一位
data <<= 1;
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
}
// 接收应答
BH1750_I2C_RecvAck();
}
/**
* @brief I2C接收一个字节
* @param 无
* @retval 接收的字节
*/
uint8_t BH1750_I2C_RecvByte(void)
{
uint8_t i, data = 0;
// SDA置高(释放总线)
GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN);
for(i=0; i<8; i++)
{
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// SCL置高
GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
// 读取一位
data <<= 1;
if(GPIO_ReadInputDataBit(BH1750_SDA_PORT, BH1750_SDA_PIN) == SET)
{
data |= 0x01;
}
// SCL拉低
GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN);
DHT11_Delay_us(2);
}
return data;
}
/**
* @brief 向BH1750写入指令
* @param cmd: 指令
* @retval 无
*/
void BH1750_WriteCmd(uint8_t cmd)
{
// 发送起始信号
BH1750_I2C_Start();
// 发送设备地址(写)
BH1750_I2C_SendByte(0x46); // BH1750地址:01000110
// 发送指令
BH1750_I2C_SendByte(cmd);
// 发送停止信号
BH1750_I2C_Stop();
}
/**
* @brief 读取BH1750数据
* @param pEnvData: 环境数据结构体指针
* @retval 无
*/
void BH1750_ReadData(EnvData_t *pEnvData)
{
uint8_t data_high, data_low;
uint16_t light_value;
// 发送连续高分辨率模式指令
BH1750_WriteCmd(0x10);
// 等待测量完成(120ms)
vTaskDelay(120);
// 发送起始信号
BH1750_I2C_Start();
// 发送设备地址(读)
BH1750_I2C_SendByte(0x47);
// 读取高字节
data_high = BH1750_I2C_RecvByte();
// 发送应答
BH1750_I2C_SendAck(0);
// 读取低字节
data_low = BH1750_I2C_RecvByte();
// 发送非应答
BH1750_I2C_SendAck(1);
// 发送停止信号
BH1750_I2C_Stop();
// 计算光照强度(lux)
light_value = (data_high << 8) | data_low;
pEnvData->light = light_value / 1.2;
}
5.5 按键处理文件(文件名:Key_Process.c)
c
#include "SmartHome_Config.h"
/**
* @brief 按键扫描函数
* @param pDeviceCtrl: 设备控制结构体指针
* @retval 无
*/
void Key_Scan(DeviceCtrl_t *pDeviceCtrl)
{
// 按键1:灯光控制(按下切换状态)
if(GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == RESET)
{
vTaskDelay(20); // 消抖
if(GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == RESET)
{
// 切换灯光状态
pDeviceCtrl->light_ctrl = !pDeviceCtrl->light_ctrl;
// 等待按键释放
while(GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == RESET);
}
}
// 按键2:空调控制(按下切换状态)
if(GPIO_ReadInputDataBit(KEY2_PORT, KEY2_PIN) == RESET)
{
vTaskDelay(20); // 消抖
if(GPIO_ReadInputDataBit(KEY2_PORT, KEY2_PIN) == RESET)
{
// 切换空调状态
pDeviceCtrl->air_ctrl = !pDeviceCtrl->air_ctrl;
// 等待按键释放
while(GPIO_ReadInputDataBit(KEY2_PORT, KEY2_PIN) == RESET);
}
}
// 按键3:窗帘控制(按下切换状态)
if(GPIO_ReadInputDataBit(KEY3_PORT, KEY3_PIN) == RESET)
{
vTaskDelay(20); // 消抖
if(GPIO_ReadInputDataBit(KEY3_PORT, KEY3_PIN) == RESET)
{
// 切换窗帘状态
pDeviceCtrl->curtain_ctrl = !pDeviceCtrl->curtain_ctrl;
// 等待按键释放
while(GPIO_ReadInputDataBit(KEY3_PORT, KEY3_PIN) == RESET);
}
}
}
/**
* @brief 按键任务函数
* @param pvParameters: 任务参数
* @retval 无
*/
void Key_Task(void *pvParameters)
{
DeviceCtrl_t device_ctrl;
// 初始化设备控制结构体
device_ctrl.light_ctrl = 0;
device_ctrl.air_ctrl = 0;
device_ctrl.curtain_ctrl = 0;
while(1)
{
// 按键扫描
Key_Scan(&device_ctrl);
// 将控制指令发送到队列
xQueueSend(xDeviceCtrlQueue, &device_ctrl, 0);
// 更新全局变量
g_DeviceCtrl = device_ctrl;
// 任务延时(10ms)
vTaskDelay(10);
}
}
5.6 数据采集任务文件(文件名:DataCollect_Task.c)
c
#include "SmartHome_Config.h"
/**
* @brief 数据采集任务函数
* @param pvParameters: 任务参数
* @retval 无
*/
void DataCollect_Task(void *pvParameters)
{
EnvData_t env_data;
// 初始化环境数据结构体
env_data.temperature = 0;
env_data.humidity = 0;
env_data.light = 0;
while(1)
{
// 读取DHT11温湿度数据
DHT11_ReadData(&env_data);
// 读取BH1750光照数据
BH1750_ReadData(&env_data);
// 将环境数据发送到队列
xQueueSend(xEnvDataQueue, &env_data, 0);
// 更新全局变量
g_EnvData = env_data;
// 任务延时(1000ms,每秒采集一次)
vTaskDelay(1000);
}
}
5.7 数据处理任务文件(文件名:DataProcess_Task.c)
c
#include "SmartHome_Config.h"
// 滤波缓冲区(保存最近5次采集的数据)
#define FILTER_BUFFER_SIZE 5
EnvData_t g_EnvDataBuffer[FILTER_BUFFER_SIZE];
uint8_t g_BufferIndex = 0;
/**
* @brief 数据滤波函数(均值滤波)
* @param pEnvData: 滤波后的环境数据
* @retval 无
*/
void DataFilter(EnvData_t *pEnvData)
{
float temp_sum = 0;
float humi_sum = 0;
uint32_t light_sum = 0;
uint8_t i;
// 计算平均值
for(i=0; i<FILTER_BUFFER_SIZE; i++)
{
temp_sum += g_EnvDataBuffer[i].temperature;
humi_sum += g_EnvDataBuffer[i].humidity;
light_sum += g_EnvDataBuffer[i].light;
}
// 赋值滤波后的数据
pEnvData->temperature = temp_sum / FILTER_BUFFER_SIZE;
pEnvData->humidity = humi_sum / FILTER_BUFFER_SIZE;
pEnvData->light = light_sum / FILTER_BUFFER_SIZE;
}
/**
* @brief 自动控制逻辑处理
* @param pEnvData: 环境数据
* @param pDeviceCtrl: 设备控制指令
* @retval 无
*/
void AutoControlProcess(EnvData_t *pEnvData, DeviceCtrl_t *pDeviceCtrl)
{
// 光照强度低于100lux,自动开灯
if(pEnvData->light < 100)
{
pDeviceCtrl->light_ctrl = 1;
}
// 光照强度高于500lux,自动关灯
else if(pEnvData->light > 500)
{
pDeviceCtrl->light_ctrl = 0;
}
// 温度高于28℃,自动开空调
if(pEnvData->temperature > 28)
{
pDeviceCtrl->air_ctrl = 1;
}
// 温度低于25℃,自动关空调
else if(pEnvData->temperature < 25)
{
pDeviceCtrl->air_ctrl = 0;
}
// 光照强度高于800lux,自动关窗帘
if(pEnvData->light > 800)
{
pDeviceCtrl->curtain_ctrl = 1;
}
// 光照强度低于300lux,自动开窗帘
else if(pEnvData->light < 300)
{
pDeviceCtrl->curtain_ctrl = 0;
}
}
/**
* @brief 数据处理任务函数
* @param pvParameters: 任务参数
* @retval 无
*/
void DataProcess_Task(void *pvParameters)
{
EnvData_t env_data, filtered_data;
DeviceCtrl_t device_ctrl;
// 初始化缓冲区
memset(g_EnvDataBuffer, 0, sizeof(g_EnvDataBuffer));
while(1)
{
// 从队列读取环境数据
if(xQueueReceive(xEnvDataQueue, &env_data, portMAX_DELAY) == pdTRUE)
{
// 将新数据存入缓冲区
g_EnvDataBuffer[g_BufferIndex] = env_data;
g_BufferIndex = (g_BufferIndex + 1) % FILTER_BUFFER_SIZE;
// 数据滤波
DataFilter(&filtered_data);
// 读取当前设备控制指令
xQueueReceive(xDeviceCtrlQueue, &device_ctrl, 0);
// 自动控制逻辑处理
AutoControlProcess(&filtered_data, &device_ctrl);
// 将处理后的控制指令写回队列
xQueueOverwrite(xDeviceCtrlQueue, &device_ctrl);
}
// 任务延时(100ms)
vTaskDelay(100);
}
}
5.8 设备控制任务文件(文件名:DeviceCtrl_Task.c)
c
#include "SmartHome_Config.h"
/**
* @brief 继电器控制函数
* @param pDeviceCtrl: 设备控制指令
* @retval 无
*/
void Relay_Control(DeviceCtrl_t *pDeviceCtrl)
{
// 灯光控制
if(pDeviceCtrl->light_ctrl == 1)
{
GPIO_SetBits(LIGHT_PORT, LIGHT_PIN); // 开灯
}
else
{
GPIO_ResetBits(LIGHT_PORT, LIGHT_PIN); // 关灯
}
// 空调控制
if(pDeviceCtrl->air_ctrl == 1)
{
GPIO_SetBits(AIR_PORT, AIR_PIN); // 开空调
}
else
{
GPIO_ResetBits(AIR_PORT, AIR_PIN); // 关空调
}
// 窗帘控制
if(pDeviceCtrl->curtain_ctrl == 1)
{
GPIO_SetBits(CURTAIN_PORT, CURTAIN_PIN); // 关窗帘
}
else
{
GPIO_ResetBits(CURTAIN_PORT, CURTAIN_PIN); // 开窗帘
}
}
/**
* @brief 设备控制任务函数
* @param pvParameters: 任务参数
* @retval 无
*/
void DeviceCtrl_Task(void *pvParameters)
{
DeviceCtrl_t device_ctrl;
// 初始化设备控制结构体
device_ctrl.light_ctrl = 0;
device_ctrl.air_ctrl = 0;
device_ctrl.curtain_ctrl = 0;
while(1)
{
// 从队列读取设备控制指令
if(xQueueReceive(xDeviceCtrlQueue, &device_ctrl, portMAX_DELAY) == pdTRUE)
{
// 控制继电器
Relay_Control(&device_ctrl);
// 更新全局变量
g_DeviceCtrl = device_ctrl;
}
// 任务延时(50ms)
vTaskDelay(50);
}
}
5.9 串口通信任务文件(文件名:Usart_Task.c)
c
#include "SmartHome_Config.h"
/**
* @brief 发送环境数据到上位机
* @param pEnvData: 环境数据
* @retval 无
*/
void Usart_SendEnvData(EnvData_t *pEnvData)
{
char buf[100];
// 格式化数据
sprintf(buf, "Environment Data: Temp=%.1f℃, Humi=%.1f%%, Light=%d lux\r\n",
pEnvData->temperature, pEnvData->humidity, pEnvData->light);
// 发送数据
USART1_SendString((uint8_t*)buf);
}
/**
* @brief 发送设备状态到上位机
* @param pDeviceCtrl: 设备控制指令
* @retval 无
*/
void Usart_SendDeviceStatus(DeviceCtrl_t *pDeviceCtrl)
{
char buf[100];
// 格式化数据
sprintf(buf, "Device Status: Light=%s, Air=%s, Curtain=%s\r\n",
pDeviceCtrl->light_ctrl ? "ON" : "OFF",
pDeviceCtrl->air_ctrl ? "ON" : "OFF",
pDeviceCtrl->curtain_ctrl ? "CLOSE" : "OPEN");
// 发送数据
USART1_SendString((uint8_t*)buf);
}
/**
* @brief 串口任务函数
* @param pvParameters: 任务参数
* @retval 无
*/
void Usart_Task(void *pvParameters)
{
while(1)
{
// 发送环境数据
Usart_SendEnvData(&g_EnvData);
// 发送设备状态
Usart_SendDeviceStatus(&g_DeviceCtrl);
// 任务延时(1000ms,每秒发送一次)
vTaskDelay(1000);
}
}
5.10 主函数文件(文件名:main.c)
c
#include "SmartHome_Config.h"
/**
* @brief 主函数
* @param 无
* @retval 0
*/
int main(void)
{
// 系统初始化
System_Init();
// 创建任务
xTaskCreate(Key_Task, "Key_Task", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIO, NULL);
xTaskCreate(Usart_Task, "Usart_Task", USART_TASK_STACK_SIZE, NULL, USART_TASK_PRIO, NULL);
xTaskCreate(DataCollect_Task, "DataCollect_Task", DATA_COLLECT_STACK_SIZE, NULL, DATA_COLLECT_TASK_PRIO, NULL);
xTaskCreate(DataProcess_Task, "DataProcess_Task", DATA_PROCESS_STACK_SIZE, NULL, DATA_PROCESS_TASK_PRIO, NULL);
xTaskCreate(DeviceCtrl_Task, "DeviceCtrl_Task", DEVICE_CTRL_STACK_SIZE, NULL, DEVICE_CTRL_TASK_PRIO, NULL);
// 启动任务调度器
vTaskStartScheduler();
// 如果程序运行到这里,说明任务调度器启动失败
while(1)
{
}
}
/**
* @brief 空闲钩子函数(可选)
* @param 无
* @retval 无
*/
void vApplicationIdleHook(void)
{
// 空闲任务执行的代码
}
/**
* @brief 内存分配失败钩子函数
* @param 无
* @retval 无
*/
void vApplicationMallocFailedHook(void)
{
// 内存分配失败时执行的代码
while(1)
{
}
}
六、编译与下载
6.1 编译工程
- 点击Keil中的
Build按钮(或按F7),编译整个工程 - 检查编译输出窗口,确保无错误(0 Errors)、无警告(0 Warnings)
- 编译成功后,会在工程目录下生成
SmartHome_FreeRTOS.hex文件
6.2 下载程序
- 连接STM32开发板到电脑(通过J-Link/ST-Link下载器)
- 打开ST-Link Utility软件,点击
Connect连接开发板 - 点击
Program & Verify,选择生成的SmartHome_FreeRTOS.hex文件 - 点击
Start,等待程序下载完成
七、测试与调试
7.1 硬件测试
- 给开发板上电,观察各模块电源指示灯是否正常
- 按下按键1/2/3,观察对应的继电器是否吸合/断开
- 用手遮挡BH1750传感器,观察灯光是否自动开启
- 用手触摸DHT11传感器,观察温度升高后空调是否自动开启
7.2 串口调试
- 打开串口调试助手(如SSCOM),配置:
- 波特率:115200
- 数据位:8
- 停止位:1
- 校验位:无
- 流控:无
- 连接CH340串口模块到电脑,打开串口
- 观察串口输出的环境数据和设备状态是否正确
八、常见问题解决
8.1 编译错误
- 问题:提示FreeRTOS头文件找不到
解决:检查工程的Include Paths是否添加了FreeRTOS文件夹路径 - 问题:提示堆栈溢出
解决:增大对应任务的堆栈大小(在SmartHome_Config.h中修改)
8.2 硬件问题
- 问题:DHT11读取数据为0
解决:检查接线是否正确,确保数据引脚添加了4.7K上拉电阻 - 问题:BH1750无数据输出
解决:检查I2C引脚接线,确保使用开漏输出模式,外接上拉电阻 - 问题:继电器不动作
解决:检查继电器控制引脚电平是否正确,确保继电器模块供电正常
8.3 功能问题
- 问题:自动控制逻辑不生效
解决:检查DataProcess_Task.c中的阈值设置是否合理,调整光照/温度阈值 - 问题:串口无数据输出
解决:检查串口引脚接线,确保USART1_Init_Config函数中波特率配置正确
总结
- 本项目基于STM32F103ZET6和FreeRTOS实现了多任务智能家居控制系统,核心包含环境数据采集、自动控制、按键交互、串口通信四大模块,通过FreeRTOS的任务调度和消息队列实现模块间解耦。
- 代码实现遵循模块化设计思想,每个功能模块对应独立的源文件,零基础小白可按照文件分类逐步实现,关键代码均添加详细注释,便于理解和调试。
- 系统具备手动控制和自动控制双重模式,可通过按键手动切换设备状态,也可根据环境数据(温湿度、光照)自动调整设备状态,具备实际落地应用的价值。