这次我们来看一个基于STM32的智能家居控制系统,重点是蓝牙通信版本。这个项目不是复杂的云端方案,而是面向本地化、低成本、可定制的硬件实现。核心思路是用STM32微控制器作为主控,通过蓝牙模块(如HC-05)与手机APP通信,实现对家居设备(如灯光、风扇、窗帘)的无线控制,并利用OLED屏幕显示系统状态。对于嵌入式开发者、电子爱好者或物联网初学者来说,这是一个非常经典的练手项目,能完整串联起MCU编程、外设驱动、无线通信和上位机交互的全流程。
最值得关注的点在于其硬件门槛低、方案成熟。主控STM32F103C8T6(俗称“蓝桥杯”或“最小系统板”)价格亲民,蓝牙模块HC-05和0.96寸OLED屏也都是常见模块,整套硬件成本可以控制在百元以内。开发环境上,无论是使用Keil MDK、STM32CubeIDE还是VSCode+PlatformIO,都有丰富的社区支持。本文将带你从零开始,完成一个具备基础设备控制、状态反馈和手机遥控功能的智能家居原型系统。我们会重点拆解硬件选型、蓝牙通信协议设计、OLED驱动显示以及一个简易Android控制端的实现思路,让你不仅能复现,更能理解其设计原理并进行二次开发。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 主控芯片 | STM32F103C8T6 (Cortex-M3内核,72MHz,64KB Flash,20KB RAM),性价比高,资源足够。 |
| 无线通信 | 蓝牙2.0+EDR模块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。
- 杜邦线:若干。
软件清单:
- 集成开发环境(IDE):
- Keil MDK-ARM:需安装STM32F1的Device Family Pack。经典,但部分版本需授权。
- STM32CubeIDE:ST官方免费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.3V
- OLED GND->GND
- OLED SCL->PB6(STM32的I2C1_SCL)
- OLED SDA->PB7(STM32的I2C1_SDA)
2. 蓝牙模块 HC-05
- HC-05 VCC->5V(注意:部分HC-05模块兼容3.3V电平,但5V供电更稳定)
- HC-05 GND->GND
- HC-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 TTL,HC-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调试)。 - 时钟(RCC):
High Speed Clock (HSE)选择Crystal/Ceramic Resonator。- 在
Clock Configuration标签页,将系统时钟(SYSCLK)通过PLL倍频至72MHz。
- 串口1(USART1):用于连接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标签页设置工程名、路径、IDE(STM32CubeIDE)。 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_OFF,LED应熄灭,显示更新。 - 同样方法测试
FAN_ON和FAN_OFF,听继电器是否有“咔嗒”吸合声。
- 在APP发送框输入
- 状态查询测试:发送
GET_STATUS,APP应收到类似“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-05 | 1. 模块未上电或损坏。 2. 模块处于AT命令模式(LED慢闪)。 3. 模块已与其他设备配对并连接。 | 1. 检查模块电源指示灯是否常亮。 2. 观察模块LED:快闪(等待连接),慢闪(AT模式)。 3. 尝试给模块重新上电。 | 1. 确保5V/3.3V供电稳定。 2. 若在AT模式,需断开KEY/EN引脚与VCC的连接,重新上电进入透传模式。 3. 长按模块按键进入AT模式,用USB转TTL发送 AT+ROLE=0\r\n和AT+RESET\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并显示固定内容,第二步打通蓝牙串口收发单个字符,第三步再集成完整的命令控制逻辑,这样能有效隔离问题。