简介:本资源为基于STM32的智能婴儿床控制系统设计文档,面向嵌入式开发学习者、电子竞赛参赛者及物联网项目实践者,帮助解决多传感器融合、蓝牙无线通信与手机APP远程控制等综合设计问题。压缩包内仅含1个PDF文件,大小约38.24MB,以图文形式完整呈现项目方案,便于系统阅读与方案复现。目前已有195人学习下载。文档围绕STM32F103RCT6主控,详细讲解声音检测与SG90舵机自动安抚、湿度检测与尿床提示、MQ135空气质量监控、DS18B20温度采集与踢被提醒、HC05蓝牙数据传输及OLED本地显示等模块的硬件选型、电路连接与软件设计思路,并给出HC05从模式配置、阈值判断逻辑与APP交互流程。读者可据此掌握从传感器采集、数据处理到无线反馈的完整开发链路,获取可直接参考的嵌入式项目架构与调试经验,适合课程设计、毕业设计及智能家居类项目借鉴。
1. 从一张婴儿床说起:为什么STM32+蓝牙+APP是当前最务实的方案
家里有新生儿的工程师大概都动过这个念头:市面上带监测功能的婴儿床动辄几千块,功能却未必贴合自己的需求,比如哭声检测灵敏度不可调、温湿度数据只能看不能导出、手机APP还强制注册账号。与其买一台黑盒设备,不如自己搭一套。这个标题指向的正是这样一件事——用STM32做主控,采集床体周围的温湿度、声音、是否尿湿等信号,通过蓝牙把数据推到手机APP上,同时APP能反向下发指令控制床体动作或报警阈值。
选STM32而不是ESP32或树莓派,理由很实际:STM32的ADC、定时器、低功耗模式足够应付这类多传感器轮询场景,标准库和HAL库资料厚,Keil5环境下调试链路成熟,成本也压得住。蓝牙模块常见做法是挂HC-05/06或BLE串口透传模块,走UART,协议简单,手机端用现成的蓝牙串口APP或自己写一个轻量客户端都能跑通。这套组合适合电子类专业做毕业设计、也适合有嵌入式基础的开发者做家用小项目,门槛在于把传感器时序、蓝牙分包和APP解析三件事对齐。
2. STM32主控与传感器采集链路怎么搭
2.1 最小系统与传感器选型
STM32最小系统只需要供电、复位、晶振和BOOT引脚配置,但婴儿床场景对稳定性有额外要求。常见做法是选STM32F103C8T6这类资源够用、封装好焊的型号,主频72MHz,片上ADC用于模拟传感器,多个UART分别接蓝牙和调试串口。传感器方面,温湿度用DHT11或SHT30,前者便宜但时序敏感,后者I2C接口更稳;声音检测用驻极体麦克风加放大电路,或者直接上带数字输出的声音传感器模块;尿湿检测本质是电极间电阻变化,用ADC读分压值即可。
选型时要注意一个反直觉的点:不是传感器精度越高越好。DHT11分辨率只有1℃和1%RH,但对婴儿床来说足够,反而SHT30的I2C时序在长导线连接时更容易受干扰。如果床体到主控的线超过30cm,优先考虑数字接口加屏蔽线,而不是追求高精度模拟传感器。
2.2 用HAL库初始化ADC与定时器轮询
传感器采集的核心是定时触发,不能靠delay死等。下面这段代码用TIM2做1秒周期中断,在中断里置标志位,主循环检测标志后依次读取DHT11和ADC通道。DHT11的时序对微秒级延时敏感,所以单独用一个微秒延时函数,不放在中断里执行。
// tim.c 中配置TIM2为1Hz更新中断 void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 7199; // 72MHz / 7200 = 10kHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 9999; // 10kHz / 10000 = 1Hz htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_Base_Init(&htim2); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); HAL_TIM_Base_Start_IT(&htim2); // 开启更新中断 } // 中断回调中只置标志,不做耗时操作 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { g_sample_flag = 1; } }参数说明:Prescaler和Period共同决定中断频率,公式是72MHz / (Prescaler+1) / (Period+1)。这里设成1Hz,即每秒采集一轮。如果要做哭声检测,采样率需要提高到几十Hz,那就把Period改小,但要注意DHT11两次读取间隔不能小于1秒,所以温湿度和声音要分不同定时器或分时调度。
2.3 蓝牙串口透传的收发缓冲设计
蓝牙模块接在USART1上,波特率常见9600或115200。直接调用HAL_UART_Transmit发数据在低频场景没问题,但接收必须用中断或DMA,否则会丢包。下面是一个环形缓冲的接收实现,把收到的字节存起来,主循环里解析完整帧。
#define RX_BUF_SIZE 128 uint8_t rx_ring[RX_BUF_SIZE]; volatile uint16_t rx_head = 0, rx_tail = 0; // 在USART1中断中调用 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { uint16_t next = (rx_head + 1) % RX_BUF_SIZE; if (next != rx_tail) { // 缓冲未满 rx_ring[rx_head] = g_rx_byte; rx_head = next; } HAL_UART_Receive_IT(&huart1, &g_rx_byte, 1); // 重新武装接收 } } // 主循环中取一帧,约定以0x0D 0x0A结尾 uint8_t parse_frame(uint8_t *out, uint16_t max_len) { uint16_t len = 0; while (rx_tail != rx_head && len < max_len) { uint8_t b = rx_ring[rx_tail]; rx_tail = (rx_tail + 1) % RX_BUF_SIZE; out[len++] = b; if (len >= 2 && out[len-2] == 0x0D && out[len-1] == 0x0A) { return len; // 返回完整帧长度 } } return 0; }逻辑说明:中断里只做入队,避免在中断中解析协议。主循环调用parse_frame,按约定的结束符判断帧尾。参数上,RX_BUF_SIZE要大于单帧最大长度,否则会覆盖;如果APP下发的是变长指令,建议在帧头加长度字段,而不是只靠结束符。
3. 手机APP端:蓝牙连接、数据解析与反向控制
3.1 蓝牙串口APP的快速验证路径
在写自己的APP之前,先用现成的蓝牙串口调试APP验证链路。安卓端常见做法是装一个通用蓝牙串口工具,配对HC-05后打开透传,手动发送十六进制指令,看STM32是否回传数据。这一步能排除硬件接线和波特率问题。验证时注意:HC-05默认配对码是1234,模块的TXD要接STM32的RX,RXD接TX,交叉连接。如果收不到数据,先查波特率是否一致,再查模块是否处于AT模式而非透传模式。
3.2 自定义APP的数据帧格式与解析
自己写APP时,协议设计比UI更重要。推荐用简单的文本帧或二进制帧,二进制帧更省带宽。下面是一个帧格式示例,用Python模拟APP端解析逻辑,实际安卓端用Java/Kotlin或Flutter的蓝牙插件实现同理。
import struct # 帧格式: 0xAA 0x55 | type(1B) | len(1B) | payload | checksum(1B) def parse_packet(data: bytes): if len(data) < 5 or data[0] != 0xAA or data[1] != 0x55: return None ptype = data[2] plen = data[3] if len(data) < 4 + plen + 1: return None payload = data[4:4+plen] checksum = data[4+plen] calc = sum(data[2:4+plen]) & 0xFF if calc != checksum: return None if ptype == 0x01: # 温湿度上报 temp, humi = struct.unpack('<bB', payload[:2]) return {'type': 'env', 'temp': temp, 'humi': humi} elif ptype == 0x02: # 声音强度 level = payload[0] return {'type': 'sound', 'level': level} return {'type': 'unknown', 'raw': payload}参数说明:0xAA 0x55是帧头,用于同步;type区分数据类型;len限制payload长度,防止越界;checksum用简单累加和,够用且计算快。APP端收到数据后按type分发到不同UI组件。反向控制时,APP构造type为0x10的帧,payload里放阈值或开关状态,STM32解析后执行。
3.3 反向控制指令的下发与确认
反向控制最容易出的问题是“发了没反应”。常见原因是STM32端解析帧时没处理粘包,或者APP端发送频率太高导致蓝牙模块缓冲溢出。稳妥做法是每条控制指令都带一个序号,STM32执行后回一个ACK帧,APP收到ACK才更新UI状态。下面是对应的STM32端处理片段。
void handle_control(uint8_t *payload, uint8_t len) { if (len < 2) return; uint8_t cmd = payload[0]; uint8_t seq = payload[1]; switch (cmd) { case 0x01: // 设置温度报警阈值 g_temp_threshold = payload[2]; break; case 0x02: // 开关声音检测 g_sound_enable = payload[2]; break; default: break; } send_ack(seq); // 回ACK,seq原样返回 }逻辑说明:seq用于APP匹配请求和响应,避免UI状态错乱。send_ack构造一个type为0x80的帧发回。如果APP在500ms内没收到ACK,可以重发一次,但不要无限重试,否则蓝牙缓冲会堵。
4. 联调排错:从丢包到APP闪退的常见坑
4.1 蓝牙丢包与波特率不匹配的排查顺序
丢包先查波特率。HC-05在透传模式下波特率必须和STM32的USART配置一致,常见错误是模块被AT指令改过波特率而代码没同步。用示波器或逻辑分析仪抓TXD线,看波形宽度是否对应预期波特率。如果波特率对但仍有丢包,检查供电:蓝牙模块瞬时电流可能超过50mA,如果和STM32共用LDO且滤波不足,会导致模块重启。单独给蓝牙模块加100uF电容通常能解决。
4.2 STM32端HardFault的定位方法
HardFault多半是数组越界或空指针。在Keil5里开启HardFault_Handler的调试,把栈帧里的PC值取出来,用fromelf或map文件反查是哪一行。更快的办法是在HardFault_Handler里死循环前点亮一个LED,确认是硬件异常而非看门狗复位。常见诱因包括:DHT11读取时关中断导致蓝牙接收溢出、环形缓冲的head/tail在多处修改未加临界区保护。
4.3 APP端权限与连接稳定性
安卓APP连蓝牙需要定位权限,这是系统限制,不是bug。如果APP扫描不到设备,先确认定位权限已授予,再确认蓝牙已开启。连接后频繁断开,通常是APP端没有保持GATT连接或蓝牙模块进入了休眠。STM32端可以定期发送心跳帧,APP收到后刷新连接状态。iOS端则要注意后台蓝牙权限配置,否则切到后台就断。
| 现象 | 优先排查 | 解决手段 |
|---|---|---|
| 收不到任何数据 | 波特率、TXD/RXD交叉 | 用串口助手直连模块验证 |
| 数据偶尔乱码 | 供电纹波、地线环路 | 加滤波电容、单点接地 |
| APP闪退 | 解析时数组越界 | 加长度校验、try-catch |
| 控制指令无响应 | 帧校验失败、粘包 | 打印原始字节、加帧头同步 |
5. 进阶技巧:用定时器捕获做哭声频率粗判与低功耗调度
哭声检测如果只靠声音传感器的高低电平,误报率很高,电视声、关门声都会触发。一个成本可控的改进是用STM32的定时器输入捕获功能,测麦克风放大后信号的频率。婴儿哭声主频集中在300-600Hz,用TIM3的通道1做输入捕获,配合简单的过零检测,可以粗判是否落在该频段。
// TIM3输入捕获配置,测量信号周期 void MX_TIM3_Init(void) { TIM_IC_InitTypeDef sConfigIC = {0}; htim3.Instance = TIM3; htim3.Init.Prescaler = 71; // 1MHz计数频率 htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 0xFFFF; HAL_TIM_IC_Init(&htim3); sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); } // 捕获回调中计算频率 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { static uint32_t last = 0; uint32_t now = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); if (last != 0) { uint32_t period = (now > last) ? (now - last) : (0xFFFF - last + now); if (period > 0) { g_freq = 1000000 / period; // 单位Hz } } last = now; }参数说明:Prescaler设71,把72MHz分频到1MHz,每个计数代表1微秒。period是两个上升沿之间的计数值,倒数即频率。实际使用时加一个滑动窗口,连续多次落在300-600Hz才判定为哭声,避免单次误触发。
低功耗方面,如果婴儿床用电池供电,可以在无事件时让STM32进入Stop模式,用RTC闹钟或外部中断唤醒。蓝牙模块也支持休眠,通过AT指令配置后,APP端需要先发唤醒字节再通信。这套调度比单纯降主频更有效,实测待机电流能从十几毫安降到几百微安。最后提醒一点:所有阈值和频率判定参数都应该做成APP可调的,不同月龄的婴儿哭声特征有差异,写死在代码里后期改起来很麻烦。
本文还有配套的精品资源,点击获取