STM32项目实战:基于FreeRTOS的多任务智能家居控制系统

文章目录

    • 一、项目概述
      • [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个核心任务,优先级从高到低依次为:

  1. 按键扫描任务(最高优先级):响应手动操作,避免按键事件丢失
  2. 串口通信任务:向上位机发送数据/接收控制指令
  3. 环境数据采集任务:采集温湿度、光照数据
  4. 设备控制任务:根据采集数据/按键指令控制外设
  5. 数据处理任务:对采集的环境数据进行滤波/阈值判断
  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 电路注意事项

  1. 按键模块需添加10K上拉电阻(或使用STM32内部上拉)
  2. 继电器模块需添加续流二极管,防止反向电动势损坏单片机
  3. DHT11数据引脚需添加4.7K上拉电阻
  4. BH1750需外接3.3V电源,避免5V电压损坏传感器
  5. 所有外设GND需与STM32共地

四、工程搭建步骤

4.1 新建Keil工程

  1. 打开Keil MDK-ARM V5.38,点击Project -> New μVision Project,命名为SmartHome_FreeRTOS,选择保存路径
  2. 选择芯片型号:STMicroelectronics -> STM32F1 Series -> STM32F103 -> STM32F103ZET6
  3. 弹出Run-Time Environment窗口,取消默认勾选,点击OK
  4. 手动添加固件库文件:
    • 新建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 工程配置

  1. 点击魔法棒图标 -> Target,设置:
    • 晶振频率:8MHz
    • 堆栈大小:Heap Size=0x2000,Stack Size=0x1000
  2. 点击Output,勾选Create HEX File
  3. 点击Editor,设置编码格式为UTF-8
  4. 点击Include Paths,添加所有文件夹的路径(Libraries、FreeRTOS、User等)
  5. 点击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 编译工程

  1. 点击Keil中的Build按钮(或按F7),编译整个工程
  2. 检查编译输出窗口,确保无错误(0 Errors)、无警告(0 Warnings)
  3. 编译成功后,会在工程目录下生成SmartHome_FreeRTOS.hex文件

6.2 下载程序

  1. 连接STM32开发板到电脑(通过J-Link/ST-Link下载器)
  2. 打开ST-Link Utility软件,点击Connect连接开发板
  3. 点击Program & Verify,选择生成的SmartHome_FreeRTOS.hex文件
  4. 点击Start,等待程序下载完成

七、测试与调试

7.1 硬件测试

  1. 给开发板上电,观察各模块电源指示灯是否正常
  2. 按下按键1/2/3,观察对应的继电器是否吸合/断开
  3. 用手遮挡BH1750传感器,观察灯光是否自动开启
  4. 用手触摸DHT11传感器,观察温度升高后空调是否自动开启

7.2 串口调试

  1. 打开串口调试助手(如SSCOM),配置:
    • 波特率:115200
    • 数据位:8
    • 停止位:1
    • 校验位:无
    • 流控:无
  2. 连接CH340串口模块到电脑,打开串口
  3. 观察串口输出的环境数据和设备状态是否正确

八、常见问题解决

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函数中波特率配置正确

总结

  1. 本项目基于STM32F103ZET6和FreeRTOS实现了多任务智能家居控制系统,核心包含环境数据采集、自动控制、按键交互、串口通信四大模块,通过FreeRTOS的任务调度和消息队列实现模块间解耦。
  2. 代码实现遵循模块化设计思想,每个功能模块对应独立的源文件,零基础小白可按照文件分类逐步实现,关键代码均添加详细注释,便于理解和调试。
  3. 系统具备手动控制和自动控制双重模式,可通过按键手动切换设备状态,也可根据环境数据(温湿度、光照)自动调整设备状态,具备实际落地应用的价值。
相关推荐
✎ ﹏梦醒͜ღ҉繁华落℘2 小时前
单片机基础知识 -- 大端模式 与 小端模式
单片机·嵌入式硬件
雾削木2 小时前
STM32 基于外部时钟源的 PWM 测量
stm32·单片机·嵌入式硬件
qq_411262422 小时前
esp的深度睡眠关机功耗很高,一般软件方面应该查哪里?
单片机·嵌入式硬件
San_a dreamer fish2 小时前
STM32开发入门(二):
stm32·单片机·嵌入式硬件
冒险家KL3 小时前
STM32 ISP自动下载探索及官方STM32CubeProgrammer实现自动下载
stm32·嵌入式硬件·isp
IpdataCloud3 小时前
智能家居设备上线IP归属分析:从设备发现到区域功能适配
网络协议·tcp/ip·智能家居
Wave8453 小时前
智能家居安防系统
stm32·单片机·智能家居
鄭郑3 小时前
STM32学习笔记--SPI初始化与数据收发(01)
笔记·stm32·学习
姓刘的哦4 小时前
STM32控制直流有刷电机
单片机·嵌入式硬件