基于STM32的智能窗帘:语音控制、光线感应与防盗报警实战
给家里做一套能听懂话的窗帘听起来有点折腾但其实方案并不复杂。我用的核心是STM32F103C8T6这块几块钱的单片机把语音识别、光线感应、防盗监测三件事全干完了。窗帘自动开合的逻辑很简单语音模块收到“打开窗帘”的指令电机就带着挂钩往一边跑光线传感器检测到光照低于阈值窗帘也会自己打开。防盗功能则靠人体红外和干簧管双重确认布防状态下检测到异常就拉响蜂鸣器。这套系统适合三类人想做课设或毕设的学生、想给家里升级成智能窗帘的爱好者、以及单纯想练手多任务状态机的嵌入式入门者。下面我会把硬件接线、代码框架和调试过程完整展开所有清单都可以直接照抄。1. 先想明白这个项目要解决哪些问题1.1 三大核心需求的实际使用场景做项目之前别急着买模块先把需求拆清楚。这个窗帘系统要解决的三个问题分别是怎么用语音控制、怎么利用光线自动调节、怎么实现防盗提醒。语音识别这块最直接的场景就是人躺在床上不想下地。以前晚上躺下发现窗帘没拉只能再爬起来现在说一句“关闭窗帘”电机就会自动运行。对家里老人来说语音控制也比手机App更友好不用学习操作界面。但要强调的是这个语音识别必须做到“说人话就能懂”不是按遥控器上的按键所以需要一套完整的指令词表打开窗帘、关闭窗帘、窗帘半开、暂停、布防、撤防这些都要能准确识别出来。光线这个需求更巧妙。很多人以为只要“光线强就关窗帘光线暗就开窗帘”实际上住过一楼的房子就知道白天阳光直射进屋气温猛升这时候窗帘应该自动关闭遮阳到了傍晚天色暗下来窗帘又应该自动打开让室内透透气。所以光线控制的核心不是“强和弱”两个档位而是要给光照度设定一个阈值区间再配合延时判断避免云层飘过时窗帘反复开关。防盗需求则来自真实的生活痛点。出差几天不在家最担心的就是有人从窗户进来。窗帘系统本身就是一个窗口侧的设备天然适合承担安防职责。我把人体红外传感器和门磁干簧管都接上了布防状态下如果有人破窗而入、触动窗帘轨道或者翻进阳台系统会立刻拉响蜂鸣器LED灯也会同步闪烁示警。1.2 方案选型为什么主控选STM32、语音选离线模块主控选择上我盯上的是STM32F103C8T6。这颗芯片在元件市场只要几块钱却集成了Cortex-M3内核、72MHz主频、多个定时器、串口、I2C和中断控制器。对于同时挂载语音模块、光照传感器、电机驱动、报警模块的智能窗帘来说资源绰绰有余。相比之下用51系列单片机也能做但I2C要软件模拟多路PWM和高精度ADC要自己折腾外设库也不如STM32的HAL库顺手开发和调试成本明显更高。“要不要用ESP8266走物联网方案”这个问题我也认真想过。联网方案确实能实现手机远程控制但窗帘语音识别如果依赖云端识别就必须保证家庭WiFi稳定断网时语音指令会完全卡死隐私也让人不踏实。现在市面上有不少离线语音识别模块它们自带声学前端和本地指令库把“唤醒词检测—指令匹配—结果输出”整个流程在模块内部完成然后通过UART串口把识别结果发给主控。这样做的优势很直接响应速度快100毫秒级出结果不依赖网络不会因为路由器重启而失灵功耗也低更适合嵌入式环境。光线传感器我选了BH1750数字光照模块而不是光敏电阻。光敏电阻方案需要自己搭分压电路、做ADC采样、再查表折算成光照度误差还不小BH1750内部集成光敏二极管和放大电路直接通过I2C输出0到65535的数值单位就是勒克斯线性度好模块价格也就比光敏电阻贵一两块钱。花这点小钱买回一个标准化的光照数据校准省下的时间完全值得。防盗传感器组合用的是PIR人体热释电传感器加干簧管。PIR负责检测移动人体干簧管负责检测窗户/窗帘轨道的开合状态两个信号同时参与逻辑判断能大幅降低误报率。单独用PIR家里宠物走过也会触发单独用干簧管只要窗户没被动过就不会报警。让两种传感器互为备份这才是可靠的安全防护。1.3 系统数据流与状态划分整个系统的数据流可以这样理解语音模块和光线传感器是“输入源”它们的信号汇入STM32主控主控根据当前工作模式决定是否让电机执行动作防盗传感器同样把信号送给主控但它的判定结果会走另一条报警逻辑只影响蜂鸣器和警示灯。我为系统划分了三种工作模式手动模式、自动模式和布防模式。手动模式下只响应语音指令和按键光线传感器不会干预电机自动模式下语音指令仍然有效光照阈值判断会自动开合窗帘布防模式则偏重防盗语音模块只响应“撤防”指令PIR和干簧管开始实时监测。三种模式用一句话总结就是手动“听人话”自动“看天色”布防“盯门口”。2. 硬件选型与电路搭建2.1 元器件清单与选购建议这套系统的硬件清单不复杂下面按功能模块分类列出来主控STM32F103C8T6最小系统板蓝色pill板那个就行记得带一个ST-Link下载器或串口烧录器语音模块UART接口的离线语音识别模块支持自定义唤醒词和指令词常见方案有LD3320类或基于通用语音芯片的模块光照模块BH1750数字光照传感器模块I2C接口自带电平转换3.3V/5V都能用电机驱动TB6612FNG双路电机驱动模块比L298N体积小、压降小适合驱动小型直流减速电机执行电机带减速箱的直流电动机额定电压6V/12V均可扭矩需要能带动窗帘轨道上的滑块红外传感器HC-SR501人体热释电模块工作电压5V输出3.3V高电平灵敏度可调干簧管常开型门磁传感器模块与磁铁配套使用贴在窗帘轨道固定端报警输出有源蜂鸣器模块加一个LED灯蜂鸣器低电平触发LED高电平点亮电源模块12V/2A直流适配器、MP1584降压模块12V转5V、AMS1117-3.3V降压模块选购时有个容易忽略的细节减速电机一定要买带安装支架和编码盘的吗不用对智能窗帘来说开度控制靠时间估算就够了编码器反而是增加复杂度的东西。但要留意电机额定转速最好选60到100转/分钟的减速电机扭矩足够速度也不会快得吓人。2.2 接线表与供电设计接线是这个项目里最容易抓狂的部分。我踩过最大的坑就是不同模块的电平标准不统一。STM32的GPIO容忍5V但PB6/PB7的I2C如果接5V模块长期运行会有隐患HC-SR501输出高电平是3.3V可以直接进GPIO但如果语音模块是5V供电它的串口TX输出电平就可能接近5V必须做分压处理。完整的接线关系见下表模块模块引脚接STM32引脚说明语音模块VCC5V若模块支持3.3V也可接3.3V语音模块GNDGND与主控共地语音模块TXPA10(USART1_RX)模块发出的识别结果必须分压电平语音模块RXPA9(USART1_TX)需要给模块发送查询指令BH1750SCLPB6接4.7k上拉到3.3VBH1750SDAPB7接4.7k上拉到3.3VTB6612PWMAPA0(TIM2_CH1)输出PWM控制电机转速TB6612AIN1PA1电机方向控制TB6612AIN2PA2电机方向控制TB6612VM12V电机电源TB6612VCC5V逻辑电源TB6612GNDGND与主控共地HC-SR501OUTPB5下降沿触发设置单次触发模式干簧管OUTPB4加内部上拉常开型蜂鸣器I/OPB3低电平触发串电阻保护LED正极PB8高电平点亮供电设计方面我建议从12V适配器开始逐级降压。12V进TB6612的VM作为电机电源5V给语音模块、HC-SR501和逻辑电路供电再通过AMS1117把5V降至3.3V给STM32和BH1750。注意两个降压模块的地线必须统一接到主控板的GND上不然串口通信、I2C读取都会出现诡异的数据跳变。电机启动瞬间电流很大所以5V和3.3V的降压模块输入输出两端都要并一个大电容470uF以上否则电压跌落会导致单片机复位。2.3 上电检查与信号完整性注意事项接线完成后别急着烧程序先做几个静态检查。用万用表蜂鸣档量一下电源和地是不是短路再看BOOT0引脚是否接地正常运行时必须接地否则进不了程序。接下来给语音模块、HC-SR501单独上电。HC-SR501上有两个旋钮电位器一个调节灵敏度一个调节延时时间。我把延时调到最小约5秒灵敏度调到中间偏小避免安装在窗帘旁边时被窗外飞过的鸟触发。语音模块需要先通过配套上位机软件烧录一次指令词表比如“打开窗帘”“关闭窗帘”“布防”“撤防”烧录时注意波特率要和模块手册一致常见的是9600或115200。信号完整性方面我遇到过两个很典型的坑。第一个是BH1750的I2C总线偶尔读到255后来查到是模块上的上拉电阻阻值和我主控板上的上拉电阻并联后等效电阻太小导致信号沿过缓。解决办法是把主控板内部上拉关掉只保留模块上的上拉。第二个是电机启动瞬间语音模块会触发一次误识别输出一串乱码到串口。这是因为电机大电流造成了地线电位波动把语音模块的数据线干扰了。解决办法是把电机电源线换成双绞线并在电机电源两端接一个104瓷片电容吸收换向火花。3. 软件设计从初始化到核心逻辑3.1 CubeMX配置与工程骨架软件部分我选择用STM32CubeMX生成初始化代码再用HAL库写业务逻辑。先配置好这些外设RCC开启HSE外部晶振时钟树拉满到72MHzUSART1波特率115200用于语音模块I2C1标准模式100kHz用于BH1750TIM2_CH1输出PWM频率1kHz用于电机调速GPIO的PB3、PB4、PB5、PB8全部设为输入/输出模式其中PB4和PB5开启内部上拉。初始化代码的骨架大概是下面这个样子我摘录了关键部分int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); MX_TIM2_Init(); // 需要在用户代码块里补充的变量 uint8_t mode MODE_MANUAL; // 手动/自动/布防 uint8_t curtain_state CUR_CLOSED; // 窗帘状态 uint16_t light_lux 0; while (1) { light_lux BH1750_ReadLux(); handle_voice_command(); // 串口解析语音指令 handle_light_auto(); // 光线自动控制 handle_guard_mode(); // 防盗检测 HAL_Delay(200); } }主循环不要写得太大定时器、中断、串口接收才是关键。业务逻辑按“感知—决策—执行”三步走先读取传感器数据再根据模式做决策最后只对电机和蜂鸣器输出。我把光线采集放在主循环里每200毫秒读一次语音识别结果用串口空闲中断接收存环形缓冲区防盗检测则放在外部中断回调里只做标志位置位具体判断留在主循环处理。3.2 光线采集与滤波先看清环境再说BH1750的读取过程是先向模块发送测量命令0x10连续高分辨率模式等待180毫秒采样再连续读取两个字节的光照值。HAL库的直接I2C发送接收函数就能搞定关键是读到的值要除以1.2倍修正系数才是真实的勒克斯数。我封装了一个函数float BH1750_ReadLux(void) { uint8_t cmd 0x10; uint8_t buf[2]; HAL_I2C_Master_Transmit(hi2c1, 0x46, cmd, 1, 100); HAL_Delay(180); HAL_I2C_Master_Receive(hi2c1, 0x47, buf, 2, 100); uint16_t raw ((uint16_t)buf[0] 8) | buf[1]; return (float)raw / 1.2f; }只读一次原始值不够用光照传感器很容易被瞬时阴影干扰。我用了“中位平均滤波法”连续采样5次去掉最大值和最小值剩下的3个值取平均。这个滤波放在主循环调用处实现简单有效实测在窗帘飘动、人路过等场景下光照值不会出现大幅跳变。光线控制的阈值不能拍脑袋定。我拿手机上的光照计App做了一次环境标定白天太阳直射窗台时约8000到12000勒克斯阴天室内约200到400勒克斯傍晚17点室内约50勒克斯夜晚开灯约100到150勒克斯。根据这个数据我把“强光关窗帘”的阈值设为2000勒克斯“暗光开窗帘”的阈值设为100勒克斯。两个阈值之间留出回差区间防止窗帘在临界点来回震荡。3.3 语音指令识别与协议解析语音模块烧录好指令词后每次识别成功都会从串口发出一帧数据。不同模块的协议不一样最常见的是帧头加命令字的格式比如// 假设协议帧格式0xAA 0x55 0x01 0x02 0x00 0xFE // 其中byte[3]是命令号byte[4]是保留字节 typedef struct { uint8_t frame_header1; uint8_t frame_header2; uint8_t group_id; uint8_t cmd_id; uint8_t reserved; uint8_t frame_end; } VoiceFrame_t;解析的核心是状态机。我在串口空闲中断里把一帧数据收进缓冲区然后在主循环中校验帧头、帧尾和命令字。如果校验失败就丢弃不执行任何操作校验成功再把cmd_id映射成窗帘动作。注意一个细节串口中断接收数据时要防止数据覆盖最好用带指针的环形缓冲区或者直接在空闲中断里计数收满固定长度再处理。映射关系我做成一张表命令1对应“打开窗帘”命令2对应“关闭窗帘”命令3对应“窗帘半开”命令4对应“暂停”命令5对应“布防”命令6对应“撤防”。其中“布防”和“撤防”比较特殊它们不只控制电机还要切换系统的工作模式所以我把模式切换单独放在语音命令处理函数里并在切换成功时给蜂鸣器一个短促提示音。3.4 电机控制怎么判断窗帘全开全关电机控制是PWM加方向引脚组合。TB6612的AIN1/AIN2控制方向PWMA输入PWM调速。我设置了两个固定速度正常开合用80%占空比接近限位时降到30%。速度突变会让窗帘滑车猛地一冲所以我加了一个简单的梯形加速减速逻辑每次调整占空比不超过5%20毫秒更新一次这样启停都很平稳。窗帘开度判断采用“时间估算限位开关”双保险。窗帘从完全关闭到完全打开我实测需要约18秒所以“半开”就是电机正转9秒后停止。但只要装上物理限位开关在导轨两端各装一个微动开关时间估算再多也不会撞到导轨端头。程序里的判断逻辑是如果限位开关被触发立即停止电机并清零方向标志。void Curtain_Move(uint8_t direction, uint8_t target_percent) { // direction: 1打开, 0关闭 // target_percent: 0~100 uint32_t total_time (uint32_t)(18.0f * (float)target_percent / 100.0f * 1000); uint32_t elapsed 0; HAL_GPIO_WritePin(GPIOA, AIN1_Pin, direction); HAL_GPIO_WritePin(GPIOA, AIN2_Pin, !direction); while (elapsed total_time) { // 如果触发限位立即停 if (IsLimitTriggered()) { break; } __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, PWM_Period * 0.8); HAL_Delay(20); elapsed 20; } __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 0); }实际运行中这个时间估算方法够用了因为窗帘开合不需要毫米级精度。但有个前提电机要带减速箱转速要稳定。如果电机负载变化很大比如窗帘拉绳很紧时间估算会失真建议加一个霍尔测速或电流检测来判断堵转。不过我最终没加因为我在导轨滑轮上涂了硅脂手动拉动窗帘很顺滑电机负载恒定时间估算的误差基本可忽略。3.5 防盗检测布防状态机与双重确认防盗逻辑是这套系统里最有意思的部分。布防状态不是简单检测PIR和干簧管而是用了一个小型状态机避免误报。先定义状态IDLE待机、ARMED布防、ALARM报警。在ARMED状态下如果PIR先触发我不立即报警而是启动一个30秒的“确认窗口”同时检查干簧管是否翻转。如果PIR和干簧管在确认窗口内都满足条件才进入ALARM状态。这个设计挡掉了大多数误报窗帘被风吹动会让PIR误判但干簧管没有动作不会报警干簧管因为门缝松动误触发但PIR没有人影同样不报警。typedef enum { IDLE, ARMED, ALARM } GuardState_t; void Handle_Guard(void) { static GuardState_t state IDLE; static uint32_t confirm_start 0; switch (state) { case IDLE: if (cur_mode MODE_GUARD) state ARMED; break; case ARMED: if (cur_mode ! MODE_GUARD) { state IDLE; break; } if (PIR_Read() ACTIVE) { confirm_start HAL_GetTick(); HAL_Delay(50); if (PIR_Read() ACTIVE Reed_Read() OPEN) { state ALARM; BEEP_ON(); LED_ON(); } } break; case ALARM: if (voice_cmd_recognized) // 只有语音“撤防”可以解除 { BEEP_OFF(); LED_OFF(); state IDLE; cur_mode MODE_MANUAL; } break; } }这个状态机看起来简单但执行逻辑上有个细节很关键布防状态下语音模块只能识别“撤防”一条指令“打开窗帘”等指令会被主控直接丢弃。这样才能保证有人半夜喊“打开窗帘”门也锁不住。报警解除后系统默认回到手动模式车主需要重新语音“布防”才能再次进入安防状态避免误以为还在布防而放松警惕。4. 调试验证与常见问题4.1 最容易踩的5个坑调试是这项目里最耗时的一环我把踩过的坑整理成清单每条都是我实打实调试过的场景。第一串口数据乱码。原因基本是波特率不匹配或者地线不共地。我一开始只给语音模块接了VCC和TX忘了接GND发过来的数据全是乱码。把GND接好之后瞬间恢复正常。第二电机启动时单片机重启。这是电源跌落问题我用12V适配器直接给开发板供电一启动电机电压就崩了。后来改成前文说的分级降压、加大电容问题解决。第三BH1750读不到数据。排查发现I2C总线上有两个上拉电阻并联阻值太小信号被拉平关断内部上拉后正常。第四PIR误报。装在窗帘导轨旁边空调吹风导致窗帘飘动红外数据反复触发。后来调整了PIR的朝向移动到窗户侧边框误报率降到了零。第五窗帘接近全开时电机咔咔响却走不动。这是时间估算超出物理行程限位开关接线没做对我一开始把限位开关接成了常闭程序里判断逻辑正好相反。统一改成常开触发后正常。4.2 常见问题速查表下面这张表浓缩了我遇到的典型问题和对应解法可以直接拿去对照排查现象可能原因排查步骤解决建议语音说指令没反应语音模块没烧录词表检查串口是否收到数据重新烧录指令词表确认波特率语音识别出乱码模块与主控未共地万用表量GND导通连接公共地线BH1750返回255I2C上拉电阻异常示波器看时钟和数据线关内部上拉保留模块上拉光照值波动大采样间隔太短连续打印10次原始值加中位平均滤波电机不动逻辑电源没接看电机驱动指示灯恢复5V逻辑电源电机抖动不转PWM频率太低示波器量波形把PWM频率提高到1kHz以上窗帘撞导轨限位开关失效手动按下限位测试换成常开微动开关PIR频繁误报位置太靠近窗帘观察触发角度移动传感器并调灵敏度进入报警无法解除语音模块不能识别确认模块词表程序里只放行“撤防”指令整机重启电源跌落量IC电压纹波加储能电容和共地汇总4.3 调试方法与工具建议调试这套系统我的必用工具只有三个串口助手、万用表、逻辑分析仪。串口助手是最核心的我在主循环里用printf重定向到USART2每200毫秒把当前模式、光照值、语音命令号和电机状态打出来。调试时看着串口输出调逻辑比瞎猜定位快得多。比如PIR误报我就是靠串口上连续的“PIR1”记录发现它每隔几秒就自己触发一次才意识到是位置问题。万用表主要查电源和接线。我习惯在关键节点飞线引出测试点3.3V、5V、VM、GND上电后挨个量电压确认没压降异常再运行程序。逻辑分析仪用来抓I2C时序适合BH1750读不到数据的疑难杂症。实际经验告诉我这种8通道、24MHz采样率的廉价逻辑分析仪就够用了没必要用几千块的高端示波器去调一个窗帘项目。还有一个建议把代码里的每个关键分支都加一个串口打印宏比如语音命令到来时打印“CMD: OPEN”进入报警时打印“ALARM: TRIGGERED”。这样不仅调试阶段方便后期跑真机时把日志接出来就能复盘整个系统的决策链路。日志越详细越不用靠猜。我个人在实际操作中最满意的一点是给整机做了个小“接单测试”把窗帘导轨立在书桌旁边给语音模块喊了三十多次指令统计识别通过率最终稳定在95%以上。这种带着统计的系统验证比“感觉能用”靠谱得多。我强烈建议无论你是课设还是给家里装都把这个测试环节加上一句话一句话地喊、一个光照值一个光照值地调把所有边界情况摸透了系统才能算真正的可用。