
简介基于STM32的儿童误锁车内远程报警系统源码包面向嵌入式方向学生、软硬件开发者与汽车安全监控项目人员可解决儿童被困车内报警不及时、环境无法远程感知等痛点适合课程设计、毕业设计与工程落地。资源共322个文件以C/H源码、Keil工程配置、库文件及PDF文档为主另含安卓APK、原理图与PCB文件约116MB结构清晰可直接导入Keil和AD查看修改。已有643人学习下载系统划分车辆状态检测、车内情况采集、执行模块三部分集成GPS、MPU6050、霍尔开关、DS18B20、SGP30、微波雷达与振动传感器判断行驶/车门状态监控温度、CO2与后排人体活动。异常时摄像头抓拍后座画面通过MQTT上传服务器蜂鸣器示警并向车主APP推送报警信息车主可远程控制车窗通风形成感知、通信、远程处置完整链路。源码和设计文档完整关键代码注释清晰涵盖传感器驱动、GPRS/MQTT通信与移动端联动可直接复现或二次开发。1. 儿童滞留车内为什么 STM32 远程报警比手机蓝牙方案更值得搭车内温度在太阳暴晒下十分钟就能升到 50℃ 以上而真正致命的往往不是空调失效而是车主下车后把儿童单独留在后排。手机蓝牙方案只在家长靠近车辆时提醒一旦人走远就失去作用纯云端方案又依赖车机联网。用 STM32 单片机做独立报警系统的核心思路是自带电池、独立检测、通过 GSM 模块主动拨打电话或发送短信不依赖手机和车机网络。这套“基于STM32的儿童误锁车内远程报警系统”要解决的就是把传感器检测、温度判定、远程通知三段链路在单片机上闭环。前置问题只有一个前端的“有人”判定和“高温”判定到底怎么做才不会误报。这篇内容从传感器选型讲到 AT 指令链再到低功耗收尾把一套能在 Keil5 上编译、烧录进常见 STM32F103 的源码级做法讲清楚适合正在做毕业设计、车载安全类硬件验证和想学状态机编码的嵌入式工程师。2. 报警系统先定架构传感器选型、GSM 串口与状态机2.1 车内人体检测PIR 热释电红外传感器的局限与替代检测“后排有人”最常用的是热释电红外传感器型号通常是 HC-SR501。它靠检测人体红外辐射的变化来触发静态坐着的儿童反而容易漏判因为 PIR 本质上是“移动检测器”。所以报警系统不会只依赖 PIR而是让 PIR 负责“有人活动”的触发再由温度传感器判断“车厢处于危险高温”。常见传感器选型如下表传感器输出类型优点缺点本系统的角色HC-SR501 PIR数字电平便宜、响应快对静止人体可能漏检触发检测激活系统进入确认流程DS18B20单总线温度精度±0.5℃耐温需要上拉电阻和时序判断是否达到报警温度微波雷达/毫米波数字/模拟能感知微动成本高、调试复杂可做升级替代不放入最小系统选型理由很直接STM32F103C8T6 的 GPIO 足够多每个传感器都只需要一根信号线PIR 输出直接进 GPIODS18B20 用一根线接上拉电阻即可。需要注意的是 PIR 模块上的两个电位器一个调灵敏度一个调延时输出这两组参数在量产前必须固定好否则误报率会高得不可控。接下来给出 GPIO 初始化代码我用的是标准外设库风格的写法HAL 库用户对应替换引脚即可。void BSP_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); // PIR_OUT 接到 PA0浮空输入读取传感器输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // DS18B20 数据线 PB1先配成开漏输出用于后续单总线时序 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // 蜂鸣器 PB2 推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOB, GPIO_InitStructure); }这段代码把三件事一次性做完PA0 作为 PIR 输入PB1 专门留给 DS18B20因为它需要反复切换输入输出开漏模式配合外部 4.7kΩ 上拉最稳定PB2 驱动蜂鸣器。开漏输出不是给 DS18B20 供电而是因为单总线协议要求主机释放总线让从设备拉低开漏才能实现“线与”。2.2 远程报警通道STM32 与 SIM800C 的串口接线和 AT 指令前置远程报警模块我选用 SIM800C。它的好处是支持 2G 网络国内大部分地区还在覆盖拨号和短信都稳定。STM32 只需要用 USART2 和它通信波特率 9600 是 SIM800C 的默认值。接线方式很固定但容易踩坑的是交叉接线和电源共地STM32 引脚SIM800C 引脚说明PA2 (USART2_TX)RXD单片机发送给模块PA3 (USART2_RX)TXD模块发给单片机GNDGND必须共地否则电平参考点不一致3.3V不接模块电源VCC 单独接 4V 电源不能用开发板 3.3V 驱动GSM 模块峰值电流可能到 2A直接用 STM32 开发板 3.3V 供电会让电压跌落导致重启。我一般用 4V/2A 的 DC-DC 降压模块单独供电并在线路上并联一个 1000μF 电容吸收瞬态电流。初始化模块时单片机要先通过串口发 AT 指令等模块返回 OK。常见做法是开机后延时 5 秒再发命令因为模块开机搜索网络需要时间。AT OK ATE0 OK ATCPIN? CPIN: READY这里ATE0关闭回显让后续解析响应更干净ATCPIN?用来确认 SIM 卡已就绪。如果长时间卡在CME ERROR先检查 SIM 卡座接触和模块天线是否接好。2.3 防误报状态机不要触发一次就发短信最容易犯的错误是 PIR 一拉高就打电话结果停车时旁边有人走动、后排安全带晃动都会误报。我采用三步确认状态机PIR 连续检测到人体活动达到 3 次DS18B20 温度超过 40℃两个条件同时满足并维持 30 秒。状态机用枚举变量表示不依赖实时操作系统主循环里轮询判断即可。为了不阻塞主循环30 秒延时用定时器计数而不是HAL_Delay(30000)。typedef enum { MONITORING, // 监控PIR 未触发 CONFIRMING, // 确认PIR 已触发等待温度和时间确认 ALARMING, // 报警满足条件发短信并本地蜂鸣 CLEARED // 解除家长通过按键撤防 } AlarmState; void Alarm_Task(void) { static AlarmState state MONITORING; static uint8_t pir_count 0; static uint16_t confirm_ticks 0; switch (state) { case MONITORING: if (PIR_Read() ACTIVE) { pir_count; if (pir_count 3) { state CONFIRMING; confirm_ticks 0; } } else { pir_count 0; } break; case CONFIRMING: if (Temperature_Read() TEMP_ALARM_THRESHOLD) { confirm_ticks; if (confirm_ticks 300) { // 30 秒10ms 一个 tick state ALARMING; } } else { state MONITORING; pir_count 0; } break; case ALARMING: // 防止重复发送只有第一次进入时发短信 GSM_SendSMS(PHONE_NUMBER, Child left in car, temperature too high!); Buzzer_On(); state CLEARED; break; case CLEARED: if (Button_Read() PRESSED) { Buzzer_Off(); state MONITORING; pir_count 0; } break; } }这里的300对应的 tick 周期必须和定时器配置一致。我习惯让定时器中断每 10ms 调用一次Alarm_Task()所以 30 秒就是 3000 个 tick上面代码里写 300 是示意实际需要根据自己的 tick 周期换算否则报警时间会差 10 倍。这个状态机的核心价值是把“检测”和“报警”解耦PIR 只是入口温度才是最终判决条件。3. 源码核心模块定时器巡检、DS18B20 读温和 AT 指令联动的实现前面把架构定了接下来要写能让系统真正跑起来的代码。这一章从定时器、单总线时序到短信发送全部可以直接搬进工程。3.1 用定时器做 10ms 周期巡检避免传感器阻塞主逻辑很多入门项目会在主循环里反复读传感器但 DS18B20 的单总线转换时间长达 750ms如果主循环每次都等它转换完整个报警判断就失效了。正确的做法是定时器中断驱动调度主循环只处理状态机。我用 TIM2 产生 10ms 中断配置如下void TIM2_Init(void) { TIM_TimeBaseInitTypeDef TIM_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_InitStructure.TIM_Period 719; // 自动重装载值与预分频共同决定中断周期 TIM_InitStructure.TIM_Prescaler 999; // 72MHz / (9991) 72kHz TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE); }计算逻辑STM32F103 系统时钟 72MHz预分频 1000 倍之后变成 72kHz自动重装载值设 719所以计数 720 次后产生一次更新中断中断频率正好是 100Hz周期 10ms。如果换到 48MHz 的芯片预分频和重装载值都要跟着调整不能把这段代码原样拷过去。中断服务函数里只做一个动作void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); Alarm_Task(); // 每 10ms 执行一次状态机 } }这里强调中断服务函数里不要做耗时操作比如发短信、读温度。Alarm_Task 本身只是状态跳转真正的 GSM 发送靠标志位放到主循环去执行这样不会卡死定时器。有兴趣的读者可以进一步把状态机改成查表方式但现在这样已经够稳。3.2 DS18B20 单总线读取防止 CRC 校验和卡死DS18B20 读出原始数据只是一个温度寄存器的低字节和高字节但单总线对时序要求极严。初始化时主机必须拉低总线 480μs 以上然后释放等从设备拉低 60~240μs。我写了一个简化但完整的读取函数uint8_t DS18B20_Reset(void) { uint8_t presence; GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_RESET); // 拉低 delay_us(500); GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_SET); // 释放 delay_us(60); presence GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1); delay_us(420); return presence; // 0 表示存在1 表示未检测到 }读取过程中要不断切换 GPIO 模式这也是前面把引脚配成开漏输出的原因。关于这个函数经常出现两个问题一是延时函数用普通循环实现在不同编译器优化等级下时间差异巨大二是温度转换后必须等待 750ms 才能读寄存器否则读到的是上一次数据。温度读取完成后合并 16 位数据并转换成摄氏度int16_t DS18B20_ReadTemperature(void) { uint8_t low, high; int16_t raw; low DS18B20_ReadByte(); high DS18B20_ReadByte(); raw (high 8) | low; if (raw 0xF800) { // 高字节低 5 位全为 1 表示负温度 raw ~raw 1; return (int16_t)(raw * 0.0625) * -1; } return (int16_t)(raw * 0.0625); }参数说明里最重要的值是0.0625因为 DS18B20 的 12 位分辨率对应每一位是 0.0625℃。如果不需要那么高精度可以在配置寄存器里改成 9 位分辨率但转换时间会缩短到 94ms适合频繁采样场景。我保留 12 位因为报警系统每 10 秒才读一次温度750ms 的转换时间完全够用。3.3 GSM 短信发送AT 指令的串口透传实现短信发送是远程报警的最后一个环节。发送普通文本短信需要先设置短信格式为文本模式再输入接收号码最后发送内容。STM32 和 SIM800C 的串口通信是透明的单片机只需要按时序把字节流写进 USART2。uint8_t GSM_SendSMS(char *phone, char *text) { char cmd[64]; USART_SendString(ATCMGF1\r\n); // 文本模式 Delay_Ms(200); sprintf(cmd, ATCMGS\%s\\r\n, phone); USART_SendString(cmd); Delay_Ms(200); USART_SendString(text); USART_SendString(\r\n\x1A); // CtrlZ 表示结束 Delay_Ms(200); return GSM_WaitResponse(CMGS); // 等待模块返回成功标志 }注意\x1A是一个不可见字符如果直接在字符串里写\r\n后面加一个普通\0模块会把短信内容截断导致发送失败。还要注意号码格式国内号码直接写 11 位即可给 SIM800C 发送时不需要加 86 前缀如果加了反而可能被运营商拒收。GSM_WaitResponse会阻塞等待串口数据通常等待 15 秒仍然没有CMGS就返回超时。发短信这个动作属于耗时操作必须放到主循环中执行不能用定时器中断里的 Alarm_Task 直接调用。我一般用全局标志位gsm_send_flagAlarm_Task 置位主循环查询后执行发送。3.4 本地蜂鸣器与按键撤防GPIO 边沿检测的细节远程报警之外本地蜂鸣器可以在危险发生时提醒路过行人。蜂鸣器驱动不复杂但直接接在 GPIO 上驱动能力不够我使用三极管放大PB2 输出高电平导通蜂鸣器。按键撤防要处理抖动void Key_Scan(void) { static uint8_t last_state 0; uint8_t now GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1); if (last_state 0 now 1) { // 上升沿触发启动 20ms 计时去抖 if (Debounce_Check()) { System_SetAlarmState(MONITORING); } } last_state now; }如果按键直接接到 GND按下时读 0释放时读 1实际代码里的触发边沿要根据硬件接线调整。我这个示例用的是上拉按键按下为 1所以检测0到1的上升沿。去抖最好用定时器计数不要用HAL_Delay(20)阻塞否则状态机这段时间不会运行。4. 从源码到跑通Keil5 工程配置、AT 指令联调和 5 个常见坑很多人拿到源码后第一个卡点是工程配置而不是算法逻辑。STM32 开发和纯软件不一样芯片型号、烧录方式、调试端口都可能成为拦路虎。这里按我的调试顺序把关键步骤说出来。4.1 用 Keil5 安装 STM32 芯片包并选择对应型号Keil5 不像 Keil4 那样内置所有芯片支持新建工程时如果 Device 列表里找不到 STM32F103C8说明没有安装对应的器件包。在 Keil5 的 Pack Installer 中搜索STM32F1xx_DFP安装后重启即可看到。器件型号我建议选STM32F103C8而不是带HD或者ZE的型号。C8T6 是 64KB Flash、20KB RAM 跑这个项目绰绰有余。选错成大容量型号后烧录时地址偏移会导致程序飞了。开源社区里大量“最小系统板”用的就是 C8T6网上常说的“STM32 蓝色药丸”板就是它。代码编译后会生成.hex文件。烧录方式有 JTAG 和串口 ISP 两种调试时用 ST-Link 最方便量产时可以用串口 ISP 刷 bootloader。如果烧录时提示Internal command error先检查 SWDIO、SWCLK 两根线以及目标板是否独立供电。4.2 串口打印 AT 响应先跑通用串口助手再进单片机流程GSM 模块联调最怕单片机代码和模块问题混在一起。我一般先把 SIM800C 接 USB-TTL 转串口在电脑上用串口助手逐条发 AT 指令等所有指令都通了再让 STM32 接管这个通信链路。这样能排除掉“单片机波特率配置错误”和“模块假死”两个变量。如果发现 STM32 发送 AT 后模块不回复先检查串口打印接收到的原始字节。常见原因是 USART2 没有开启中断或者中断里读寄存器后没有清除标志位。一个很隐蔽的坑是SIM800C 的串口电平是 2.8V往上接 5V 的单片机可能出现识别异常最好用串口配套的电平转换电路或者直接用 3.3V 版的 STM32。F103 输入引脚虽然标注容忍 5V但保险起见我还是建议加电阻分压。4.3 电源跌落导致重启GSM 模块瞬间电流的应对方法前面提过 SIM800C 峰值电流接近 2A。很多 DIY 用的是 USB 供电爆发电流一上来USB 口的电压降到 3.3V 以下STM32 直接复位GSM 模块也跟着重启形成“重启循环”。解决思路是能量缓冲。我常在模块电源输入端并联一个低 ESR 的 1000μF 电解电容再并联一个 0.1μF 陶瓷电容吸收高频纹波。如果系统用锂电池供电还要在电池输出加一个 3A 自恢复保险丝防止电流过大保护板误动作。注意电容不能太大否则上电瞬间充电电流会反过来把电源拉垮。下面是一个电源检查表适合烧录前测试测试点正常现象异常排查空载时模块 VCC4.0V 左右降压模块调压不对拨号时模块 VCC不低于 3.6V 且无跌闪加大电容检查供电功率STM32 VDD3.3V 稳定检查 LDO 输入不要和 GSM 共用 LDO串口 TX/RX 电平高电平接近 STM32 VDD检查是否有交叉接反4.4 禁用 JTAG 释放 3 个 GPIO把 PB3/PB4/PA15 让给传感器STM32F103 的部分引脚默认被 JTAG 调试端口占用包括 PA15、PB3、PB4。很多新手把按键或温度传感器接到 PB4结果怎么拉都不起作用。解决办法是在 GPIO 初始化前禁用 JTAG保留 SWD。GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);这条语句之后 PB3、PB4、PA15 才会变成普通 GPIO但需要注意启用后 JTAG 会失效ST-Link 的 SWD 接口仍然可用。如果使用的是 ST-Link可以放心禁用如果使用的是以 JTAG 模式连接的 J-Link禁用了会导致无法下载程序。写完这句后再次烧录需要按住板子的复位键点击下载再松开。4.5 高温误报温度传感器放在副驾座椅下方而非仪表台DS18B20 放错位置会直接让整个系统失去意义。放仪表台时太阳一晒温度就到 60℃而实际后排儿童身处的空间可能才 35℃。我一般把传感器放在后排座椅坐垫和靠背的夹缝处靠近儿童胸口高度。这个位置不被阳光直接照射测出来的是热辐射和空气循环后的结果。系统启动后先用 5 分钟读取环境基线温度后续报警阈值用“基线10℃”的方式计算而不是写死 40℃。夏天凌晨和中午的基线温度能差 20℃固定阈值必然误报或漏报。5. 进阶玩法把原型改成低功耗方案并验证防误报逻辑前面的系统一直上电运行耗电大头有两个STM32 运行模式的几十毫安以及 GSM 模块空闲时平均十几毫安。改成低功耗方案核心是让 STM32 进入 STOP 模式用 RTC 定时唤醒配合外部事件做传感器读取。先看 STM32 的睡眠模式差异模式电流典型值唤醒源对 GSM 的控制Run约 20mA无可持续通信Sleep约 5mA任意中断不太适合唤醒慢STOP约 20μARTC、外部中断唤醒后需要数秒重新初始化STANDBY约 2μA复位、RTC唤醒后程序从头跑状态无法保持我建议用 STOP 模式因为 PIR 引脚可以用外部中断唤醒而不是干等 RTC。温度检测每 30 秒一次在这 30 秒里系统大多数时候睡在 STOP 中唤醒后读取传感器和 GSM 状态如果没有异常再睡回去。void Enter_StopMode(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // 关闭 USART2 的接收中断避免串口噪声唤醒 USART_ITConfig(USART2, USART_IT_RXNE, DISABLE); // 进入 STOP 模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); SystemInit(); // 唤醒后重新配置时钟 }注意SystemInit()一定要调用否则唤醒后 STM32 继续用 HSI 的 8MHz 运行所有外设的波特率、定时器周期全部跑偏。唤醒后的 10ms 内不要去操作 DS18B20因为单总线仍然认为主设备处于复位状态先发一次复位脉冲重新建链。最后聊聊防误报验证。我常用的方法是在后备箱放一个 100W 白炽灯模拟暴晒每隔 5 分钟记录一次温度和报警状态连续 8 小时没有误报才算通过。还要在副驾座椅放一个温水袋模拟儿童体温确认 PIR 在静态情况下不会误触发但温度条件达到后依然能报警。经过这一轮测试后把状态机里的 PIR 触发次数从 3 次改成 5 次能进一步过滤掉路面颠簸带来的抖动而不影响误报率。实际测试中我发现把报警确认时间从 30 秒延长到 45 秒对误报率没有明显改善反而会让真实求助晚 15 秒所以最终取 30 秒作为默认值。这个值受传感器安装位置影响较大上车实测时可以烧录一个调试版本把状态机的 tick 值通过串口打到上位机再根据真实环境下 PIR 的误触发分布去标定。本文还有配套的精品资源点击获取