
这次我们来看一个基于STM32的智能家居控制系统重点是蓝牙通信版本。这个项目不是复杂的云端方案而是面向本地化、低成本、可定制的硬件实现。核心思路是用STM32微控制器作为主控通过蓝牙模块如HC-05与手机APP通信实现对家居设备如灯光、风扇、窗帘的无线控制并利用OLED屏幕显示系统状态。对于嵌入式开发者、电子爱好者或物联网初学者来说这是一个非常经典的练手项目能完整串联起MCU编程、外设驱动、无线通信和上位机交互的全流程。最值得关注的点在于其硬件门槛低、方案成熟。主控STM32F103C8T6俗称“蓝桥杯”或“最小系统板”价格亲民蓝牙模块HC-05和0.96寸OLED屏也都是常见模块整套硬件成本可以控制在百元以内。开发环境上无论是使用Keil MDK、STM32CubeIDE还是VSCodePlatformIO都有丰富的社区支持。本文将带你从零开始完成一个具备基础设备控制、状态反馈和手机遥控功能的智能家居原型系统。我们会重点拆解硬件选型、蓝牙通信协议设计、OLED驱动显示以及一个简易Android控制端的实现思路让你不仅能复现更能理解其设计原理并进行二次开发。1. 核心能力速览能力项说明主控芯片STM32F103C8T6 (Cortex-M3内核72MHz64KB Flash20KB RAM)性价比高资源足够。无线通信蓝牙2.0EDR模块HC-05主从一体支持串口透传通信距离约10米视环境而定。显示模块0.96寸OLED显示屏SSD1306驱动I2C或SPI接口用于显示设备状态、连接信息、菜单等。控制对象可扩展控制LED灯、继电器接风扇、灯带、步进电机模拟窗帘、温湿度传感器(DHT11)等。上位机控制通过Android手机APP可使用MIT App Inventor、Android Studio或串口调试助手发送指令。开发环境Keil uVision5 / STM32CubeIDE / VSCode PlatformIO推荐使用HAL库或标准库。供电方式USB供电或5V/3.3V外部电源整体功耗较低。适合场景嵌入式学习、课程设计、毕业设计、智能家居原型验证、物联网入门实践。2. 适用场景与使用边界这个基于STM32的蓝牙智能家居设计主要适合以下几类人群和场景嵌入式与物联网初学者希望通过一个完整的项目学习STM32外设GPIO、定时器、串口、I2C的使用、中断处理以及模块驱动编程。高校学生用于单片机原理、嵌入式系统、物联网技术等课程的课程设计或毕业设计具备完整的“感知-控制-通信-显示”环节。电子爱好者/创客想要搭建一个可定制、成本可控的智能家居演示模型控制自己工作台的灯光、风扇等。原型验证在产品开发前期快速验证蓝牙控制逻辑和硬件方案的可行性。使用边界与注意事项通信距离与稳定性HC-05属于经典蓝牙通信距离有限通常10米内且穿墙能力较弱。不适合大户型全屋覆盖更适合单个房间内的控制。安全性本项目实现的蓝牙通信一般为简单串口透传未集成高强度加密。切勿用于控制门锁、安防摄像头等涉及人身财产安全的关键设备仅适用于灯光、风扇等非关键电器。并发与多设备STM32F103处理能力和内存有限HC-05通常为点对点连接。本设计适合一对一控制一个手机控制一个主控难以直接扩展为同时连接多个手机或控制端。功耗考虑虽然STM32和HC-05功耗不高但若采用电池供电且需常年待机需深入优化MCU的低功耗模式Stop、Sleep和蓝牙模块的休眠机制。合规性使用蓝牙模块需符合所在地区的无线电发射设备管理规定。用于学习和小范围演示无碍若作为产品商用需进行相关的型号核准认证。3. 环境准备与前置条件在开始编写代码和连接电路之前需要准备好软硬件环境。硬件清单主控板STM32F103C8T6最小系统板 1块。蓝牙模块HC-05主从一体蓝牙串口模块 1个。注意区分引脚VCC(5V/3.3V)、GND、TXD、RXD、STATE、EN。显示模块0.96寸OLED显示屏SSD1306驱动1个建议购买I2C接口的接线更简单。执行器件LED灯及限流电阻用于指示。5V继电器模块用于控制220V电器注意高压安全。SG90舵机模拟窗帘、开关。DHT11温湿度传感器可选用于环境感知。电源Micro USB线或5V/1A电源适配器。下载调试器ST-Link V2或DAP-Link。杜邦线若干。软件清单集成开发环境IDEKeil MDK-ARM需安装STM32F1的Device Family Pack。经典但部分版本需授权。STM32CubeIDEST官方免费IDE集成CubeMX图形化配置生成HAL库代码推荐新手。VSCode PlatformIO轻量插件丰富适合喜欢现代编辑器的开发者。STM32CubeMX用于图形化配置引脚、时钟、外设生成初始化代码。如果使用STM32CubeIDE此功能已内置。串口调试助手如XCOM、SSCOM、Putty等用于调试蓝牙通信数据。手机APP可预先准备一个蓝牙串口调试APP如“蓝牙串口”用于测试。后续可自己开发简易控制APP。知识前置条件了解C语言基础。了解STM32 GPIO、USART、I2C等基本外设的概念。会使用IDE创建工程、编译、下载和调试。4. 硬件连接与电路设计正确的硬件连接是项目成功的第一步。以下是核心模块的连接示意图以STM32F103C8T6和常用引脚为例1. OLED显示屏 (I2C接口)OLED VCC-3.3VOLED GND-GNDOLED SCL-PB6(STM32的I2C1_SCL)OLED SDA-PB7(STM32的I2C1_SDA)2. 蓝牙模块 HC-05HC-05 VCC-5V(注意部分HC-05模块兼容3.3V电平但5V供电更稳定)HC-05 GND-GNDHC-05 TXD-PA10(STM32的USART1_RX)// HC-05发送STM32接收HC-05 RXD-PA9(STM32的USART1_TX)// HC-05接收STM32发送STATE和EN引脚本例暂不连接3. 控制输出以LED和继电器为例LED 正极-PB0(通过一个220Ω电阻)LED 负极-GND继电器 IN-PB1(控制信号引脚)继电器 VCC-5V继电器 GND-GND继电器 COM/NO/NC接被控电器如台灯操作220V强电务必断电接线注意安全电路设计要点电源去耦在STM32的VDD和GND之间靠近芯片引脚处放置一个0.1uF和一个10uF的电容以提高电源稳定性。蓝牙模块电平确保STM32的串口引脚与HC-05的通信电平匹配。STM32引脚为3.3V TTLHC-05的RXD/TXD为3.3V/5V TTL兼容直接连接一般问题不大若担心可加电平转换电路。继电器隔离继电器控制端IN与STM32 GPIO之间可以增加一个光耦或三极管如S8050进行隔离以保护MCU。5. 软件设计与代码实现我们将使用STM32CubeIDE配合HAL库进行开发。流程是CubeMX配置 - 生成代码 - 添加业务逻辑。5.1 使用STM32CubeMX进行工程配置新建工程选择MCU型号STM32F103C8Tx。系统核心SYS在SYS-Debug中选择Serial Wire(SWD接口用于ST-Link调试)。时钟RCCHigh Speed Clock (HSE)选择Crystal/Ceramic Resonator。在Clock Configuration标签页将系统时钟SYSCLK通过PLL倍频至72MHz。串口1USART1用于连接HC-05蓝牙模块。模式选择Asynchronous(异步)。配置参数波特率9600(HC-05默认)字长8 Bits停止位1校验None流控制None。使能串口全局中断NVIC Settings-USART1 global interrupt勾选Enabled。I2C1用于连接OLED。模式选择I2C。保持默认参数标准模式100kHz。GPIO将PB0、PB1等用于控制LED、继电器的引脚设置为GPIO_Output。生成代码在Project Manager标签页设置工程名、路径、IDESTM32CubeIDE。Code Generator中勾选Generate peripheral initialization as a pair of ‘.c/.h’ files。点击GENERATE CODE。5.2 添加OLED驱动代码生成的工程中需要自行添加OLED的驱动文件通常为oled.c和oled.h以及字库文件。将驱动文件放入Src和Inc文件夹。在main.c中包含头文件并初始化OLED/* Private includes ----------------------------------------------------------*/ #include oled.h #include string.h // 用于字符串操作 /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); /* USER CODE BEGIN 2 */ OLED_Init(); // 初始化OLED OLED_Clear(); // 清屏 OLED_ShowString(0, 0, Smart Home, 16); // 第0行显示标题 OLED_ShowString(0, 2, BT:Disconnected, 16); // 第2行显示蓝牙状态 OLED_ShowString(0, 4, Light: OFF, 16); // 第4行显示灯光状态 OLED_ShowString(0, 6, Fan: OFF, 16); // 第6行显示风扇状态 /* USER CODE END 2 */ }5.3 实现蓝牙数据接收与解析蓝牙数据通过串口中断接收。我们使用一个缓冲区来存储接收到的命令。在main.c的/* USER CODE BEGIN PV */区域定义变量/* Private variables ---------------------------------------------------------*/ uint8_t uart_rx_buf[64]; // 接收缓冲区 uint8_t uart_rx_len 0; // 接收数据长度 uint8_t cmd_ready 0; // 命令就绪标志重写串口接收中断回调函数在main.c末尾的/* USER CODE BEGIN 4 */区域/* USER CODE BEGIN 4 */ // 重写HAL库的串口接收回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { uint8_t rx_byte; HAL_UART_Receive_IT(huart1, rx_byte, 1); // 重新开启中断接收单个字节 if(rx_byte \n || uart_rx_len sizeof(uart_rx_buf)-1) // 以换行符或缓冲区满作为命令结束 { uart_rx_buf[uart_rx_len] \0; // 添加字符串结束符 cmd_ready 1; // 设置命令就绪标志 uart_rx_len 0; // 重置长度 } else { uart_rx_buf[uart_rx_len] rx_byte; // 存储字节 } } } /* USER CODE END 4 */在main函数的初始化部分开启串口接收中断/* USER CODE BEGIN 2 */ OLED_Init(); // ... 其他初始化 uint8_t rx_temp; HAL_UART_Receive_IT(huart1, rx_temp, 1); // 启动串口中断接收 /* USER CODE END 2 */5.4 主循环逻辑与命令处理在main函数的while (1)循环中检查命令标志并解析执行。/* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if(cmd_ready) // 如果收到新命令 { cmd_ready 0; // 清除标志 // 解析并执行命令 if(strcmp((char*)uart_rx_buf, LED_ON) 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // PB0置高LED亮 OLED_ShowString(0, 4, Light: ON , 16); // 更新OLED显示 HAL_UART_Transmit(huart1, (uint8_t*)Light ON OK\n, 12, 100); // 回复手机 } else if(strcmp((char*)uart_rx_buf, LED_OFF) 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // PB0置低LED灭 OLED_ShowString(0, 4, Light: OFF, 16); HAL_UART_Transmit(huart1, (uint8_t*)Light OFF OK\n, 13, 100); } else if(strcmp((char*)uart_rx_buf, FAN_ON) 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // PB1置高继电器吸合 OLED_ShowString(0, 6, Fan: ON , 16); HAL_UART_Transmit(huart1, (uint8_t*)Fan ON OK\n, 10, 100); } else if(strcmp((char*)uart_rx_buf, FAN_OFF) 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // PB1置低继电器断开 OLED_ShowString(0, 6, Fan: OFF , 16); HAL_UART_Transmit(huart1, (uint8_t*)Fan OFF OK\n, 11, 100); } else if(strcmp((char*)uart_rx_buf, GET_STATUS) 0) { // 查询所有状态并返回 char status_msg[64]; sprintf(status_msg, Light:%s Fan:%s\n, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)?ON:OFF, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)?ON:OFF); HAL_UART_Transmit(huart1, (uint8_t*)status_msg, strlen(status_msg), 100); } else { HAL_UART_Transmit(huart1, (uint8_t*)Unknown CMD\n, 12, 100); // 未知命令 } OLED_Refresh(); // 刷新OLED显示 } HAL_Delay(10); // 短暂延时防止CPU空转 } /* USER CODE END 3 */6. 手机控制端实现简易方案对于快速验证可以使用现成的“蓝牙串口”APP。在APP中配对HC-05默认密码1234或0000连接后在发送框输入LED_ON、LED_OFF等指令并发送即可控制STM32。如需自制简易Android APP以MIT App Inventor 2为例设计界面放置ListPicker用于选择蓝牙设备、Button连接、断开、发送命令、Label显示状态。逻辑设计点击ListPicker调用BluetoothClient1.AddressesAndNames获取设备列表并选择。点击连接按钮调用BluetoothClient1.Connect。点击“开灯”按钮调用BluetoothClient1.SendText发送字符串LED_ON\n注意加换行符。在BluetoothClient1.BytesReceived事件中读取返回数据并显示。7. 功能测试与效果验证完成软硬件搭建后按以下步骤进行系统测试上电与OLED显示给系统上电。OLED屏幕应点亮并显示“Smart Home”、“BT:Disconnected”等初始化信息。蓝牙配对手机打开蓝牙搜索附近设备找到名为“HC-05”的设备或你自定义的名称输入配对码默认1234进行配对。串口连接测试打开手机蓝牙串口APP连接HC-05。连接成功后OLED屏幕上的“BT:Disconnected”应变为“BT:Connected”。指令控制测试在APP发送框输入LED_ON并发送。观察板载LED或外接LED是否点亮同时OLED上“Light: OFF”应变为“Light: ON”APP可能收到回复“Light ON OK”。发送LED_OFFLED应熄灭显示更新。同样方法测试FAN_ON和FAN_OFF听继电器是否有“咔嗒”吸合声。状态查询测试发送GET_STATUSAPP应收到类似“Light:OFF Fan:ON”的字符串反馈当前所有设备状态。稳定性与距离测试拿着手机在房间内移动观察在哪些位置会出现控制延迟或断开连接测试实际有效控制距离。判断成功的标准手机APP能成功连接蓝牙模块。发送指令后对应的执行器件LED、继电器动作与指令一致。OLED屏幕能实时更新设备状态和蓝牙连接状态。在一定距离内如5米控制响应迅速延迟小于1秒。8. 资源占用与性能观察本项目资源占用主要集中在STM32F103C8T6上可以通过IDE的编译信息和分析工具进行观察。程序存储空间Flash在Keil或CubeIDE编译后查看Build Output窗口。一个包含HAL库、OLED驱动、蓝牙通信和基础逻辑的程序通常占用30KB - 50KB的Flash空间远小于STM32F103C8T6的64KB容量留有充足空间扩展功能如增加菜单、更多设备控制逻辑。运行内存RAM同样在编译信息中查看。全局变量、栈和堆的占用通常在10KB以内也小于芯片的20KB RAM。CPU利用率本项目主循环中仅有简单的命令解析和GPIO控制大部分时间在HAL_Delay(10)中空转CPU利用率极低。如果未来加入复杂的传感器数据滤波算法或图形菜单需要关注循环执行时间。通信性能串口波特率为9600bps对于“LED_ON”这样的短指令约8字节传输时间约8ms完全满足手动控制的需求。如果需要传输大量数据如图片、长文本应考虑提高波特率如115200。功耗观察使用万用表电流档串联测量系统总电流。在待机状态蓝牙连接所有负载关闭整机电流通常在30mA - 50mA主要来自STM32、HC-05和OLED。当继电器吸合带动大功率负载时电流会显著增加这部分是负载电流而非控制板本身。9. 常见问题与排查方法在开发过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案OLED不显示1. 电源接反或未接。2. I2C地址错误通常0x78或0x7A。3. I2C引脚PB6/PB7配置错误或被复用。4. 初始化时序或驱动代码有误。1. 检查VCC/GND连接。2. 用逻辑分析仪或I2C扫描程序检查地址。3. 检查CubeMX中I2C1配置。4. 检查OLED_Init()函数。1. 确保3.3V供电。2. 修改驱动中的I2C从机地址宏定义。3. 确认引脚配置为I2C模式且未与其他功能冲突。4. 参考可靠的SSD1306驱动代码。手机搜不到HC-051. 模块未上电或损坏。2. 模块处于AT命令模式LED慢闪。3. 模块已与其他设备配对并连接。1. 检查模块电源指示灯是否常亮。2. 观察模块LED快闪等待连接慢闪AT模式。3. 尝试给模块重新上电。1. 确保5V/3.3V供电稳定。2. 若在AT模式需断开KEY/EN引脚与VCC的连接重新上电进入透传模式。3. 长按模块按键进入AT模式用USB转TTL发送ATROLE0\r\n和ATRESET\r\n恢复从机模式。蓝牙已连接但无法控制1. 串口引脚TX/RX接反。2. STM32串口配置错误波特率、数据位等。3. 命令格式不对如缺少换行符。4. 程序未开启串口接收中断。1. 检查接线HC-05.TXD - STM32.RX (PA10)。2. 用串口调试助手连接STM32的USART1_TX(PA9)引脚看是否有调试信息输出。3. 在手机APP发送指令后加\n。4. 检查代码中是否调用了HAL_UART_Receive_IT。1. 纠正TX/RX交叉连接。2. 确保CubeMX中USART1配置与HC-05一致通常9600,8,N,1。3. 在指令末尾添加换行符或在代码中修改结束符判断逻辑。4. 在main函数初始化部分启动接收中断。控制指令执行混乱1. 串口接收缓冲区溢出或数据覆盖。2. 命令解析逻辑有误字符串比较错误。3. 中断处理时间过长导致数据丢失。1. 在串口中断回调中打印接收到的每个字符进行调试。2. 检查strcmp比较的字符串是否完全匹配包括大小写。3. 简化中断服务函数只做数据搬运在主循环解析。1. 增大接收缓冲区并做好边界检查。2. 使用strncmp限定比较长度或统一指令大小写。3. 遵循“中断快进快出”原则在主循环处理复杂逻辑。继电器有声音但负载不工作1. 继电器输出端COM/NO接线错误。2. 负载功率过大或损坏。3. 继电器模块供电不足。1. 用万用表测量继电器吸合时COM和NO端子是否导通。2. 检查负载如灯泡本身是否完好。3. 检查给继电器模块供电的5V电源是否稳定、电流足够。1. 正确接线市电火线接COM负载线接NO。2. 更换负载或确保负载在继电器额定功率内。3. 使用独立的5V/2A电源为继电器模块供电。10. 项目优化与扩展方向完成基础功能后可以从以下几个方面进行优化和扩展让项目更实用、更健壮增加本地控制接入按键或旋转编码器实现OLED菜单交互在无手机时也能本地控制设备。协议优化定义更严谨的通信协议帧例如加入帧头、帧尾、校验和提高通信可靠性。例如[0xAA][CMD][DATA][CHECKSUM][0x55]。低功耗优化在无操作时让STM32进入Stop或Sleep模式并配置HC-05进入低功耗模式大幅降低待机电流适合电池供电场景。增加传感器反馈接入DHT11温湿度传感器、光敏电阻等将环境数据实时显示在OLED上并可根据阈值自动控制设备如温度过高自动开风扇。多设备与场景扩展GPIO或使用IO扩展芯片控制更多设备。实现“场景”功能如发送一条“HOME”指令自动执行“开灯、关窗帘、开空调”等一系列动作。更换无线方案若需要更远距离、更强穿墙能力或组网可将HC-05替换为Wi-Fi模块如ESP-01S让STM32连接家庭路由器通过MQTT协议与手机APP或云平台通信。上位机软件除了手机APP还可以使用Qt、C#或Python开发电脑端的上位机软件通过蓝牙或USB转串口与STM32通信实现更复杂的监控和配置界面。这个基于STM32和蓝牙的智能家居设计核心价值在于提供了一个清晰、完整、可触摸的物联网技术落地范例。它没有复杂的云服务却涵盖了嵌入式开发的核心环节。最容易踩的坑是蓝牙模块的接线、模式配置以及串口通信的数据处理。建议在第一步成功点亮OLED并显示固定内容第二步打通蓝牙串口收发单个字符第三步再集成完整的命令控制逻辑这样能有效隔离问题。