STM32F103C8T6多传感器门禁系统实战设计
简介本资源是一套基于STM32F103C8T6单片机的智能门禁门铃完整嵌入式项目方案面向电子类专业学生、单片机初学者及物联网实践爱好者解决家庭级智能安防交互场景中的人员检测、状态管理与声光反馈等核心问题。压缩包为ZIP格式共含若干源码文件、Keil工程配置及硬件原理说明具体文件总数未提供主体为C语言主控程序、传感器驱动模块与LCD/LED/蜂鸣器外设控制逻辑整体大小15.5MB结构清晰便于理解软硬件协同流程。已有629人学习下载适合通过实操掌握热释电红外传感、光敏电阻调节、状态机设计及低功耗交互逻辑等关键技术点。读者可直接部署运行完整复现欢迎提示、昼夜模式切换、主人状态设定、超时报警及多级指示灯反馈等功能配套注释详尽是入门STM32智能硬件开发的典型教学案例。1. 这不是玩具级门铃而是基于 STM32F103C8T6 的可状态感知门禁交互系统你拆过市面上几十块 STM32 最小系统板也跑过 LED 流水灯和串口打印“Hello World”但真正卡在项目落地的往往不是“怎么点亮一个灯”而是“怎么让灯在对的时间、以对的逻辑、响应对的环境条件亮起来”。这个 STM32 智能门禁门铃项目恰恰踩中了初学者向工程实践跃迁的关键断层它不依赖 Wi-Fi 模块或云平台却完整实现了多传感器协同判断PIR 光敏、双态物理开关配置有人/无人模式、时间维度状态维持逗留超时报警、人机反馈闭环LCD LED 蜂鸣器 指示灯。它用标准外设——不是 HAL 库封装好的抽象句柄而是直接操作 GPIO、定时器、ADC 和 LCD 控制时序——把“检测→判断→决策→执行→反馈”这条链路拉得清清楚楚。适合刚学完 STM32 标准库或寄存器开发、正打算做毕业设计或嵌入式入门实战的工程师也适合已有经验者用来复现一套轻量级状态机设计范式——所有逻辑都固化在单片机本地无外部依赖上电即运行掉电即保存拨动开关状态靠物理位置维持这才是嵌入式系统该有的确定性。2. 硬件信号链与 STM32F103C8T6 外设资源映射从原理图到寄存器配置2.1 关键传感器与执行器的电气特性及接口选型依据本系统涉及四类核心外设热释红外PIR、光敏电阻LDR、拨动开关、按键全部为模拟或数字输入高亮 LED、蜂鸣器、LCD1602、状态指示灯则为输出设备。选择 STM32F103C8T6 并非偶然——其 48 引脚 LQFP 封装提供 37 个通用 GPIO足够分配PIR 输出数字信号高电平有效典型响应时间 0.5–2s需配合软件消抖与延时确认不能直接接 EXTI 中断易误触发故采用轮询状态机方式处理光敏电路LDR 与固定电阻构成分压网络接入 ADC1_IN0PA0通过调节电位器改变分压比从而设定暗光阈值实测发现当 Vout 1.2V对应光照 50 lux时判定为“夜间”此值需在代码中校准拨动开关双刀单掷DPST物理开关一端接地另一端接 PA1上拉输入拨上低电平有人拨下高电平无人硬件去抖靠内部上拉软件滤波门铃按键常开轻触开关一端接地另一端接 PA2上拉输入按下产生下降沿需 20ms 消抖LCD1602采用 4 位数据总线模式D4–D7 接 PB0–PB3RSPB4RWPB5EPB6节省 GPIO注意 E 引脚必须使用上升沿触发且 E 脉宽需 ≥450nsSTM32 普通 GPIO 切换速度远超此要求但需确保写指令/数据后有足够延时至少 50μs蜂鸣器有源蜂鸣器接 PC13推挽输出高电平导通无源蜂鸣器需 PWM 驱动本项目未采用避免增加 TIM 资源占用。提示光敏传感器电位器调节的是模拟分压点不是直接设定 ADC 阈值。实际代码中应先采集 10 次 ADC 值取平均再与LIGHT_THRESHOLD 0x3A0约 928对应 1.2V比较而非用固定电压值硬编码。2.2 GPIO 初始化与功能复用冲突规避STM32F103C8T6 的 GPIO 默认为浮空输入必须显式配置。以下为关键引脚初始化代码标准库风格基于stm32f10x_gpio.h// 初始化 PIR 输入PA0、光敏 ADCPA0 复用为 ADC1_IN0、拨动开关PA1、门铃按键PA2 void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能 GPIOA、GPIOB、GPIOC 时钟 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA | RCC_APB2PERIPH_GPIOB | RCC_APB2PERIPH_GPIOC, ENABLE); // PA0: 配置为模拟输入用于 ADC同时保留作为 PIR 输入不行必须分时复用或另选引脚 // 正确做法PIR 单独用 PA0浮空输入光敏用 PA1 → 但 PA1 已被拨动开关占用 → 改用 PA3 // 实际布线中光敏应接 PA3ADC1_IN3拨动开关改用 PA1门铃按键用 PA2PIR 用 PA0 —— 四者互不冲突 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; // PIR on PA0 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入不启用上下拉 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; // 拨动开关 on PA1 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入拨上接地→读0 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; // 门铃按键 on PA2 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入按下接地→读0 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; // 光敏 on PA3 → ADC1_IN3 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // 模拟输入 GPIO_Init(GPIOA, GPIO_InitStructure); // PB0–PB6: LCD 数据线与控制线 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // PC13: 蜂鸣器 GPIO_InitStructure.GPIO_Pin GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOC, GPIO_InitStructure); // PD2: 高亮 LED共阳极低电平点亮→ 实际常用共阴极此处按原文“高亮LED灯”默认接法为低有效 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOD, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOD, GPIO_InitStructure); }2.2.1 为什么 PA0 不能同时做 PIR 输入和 ADC 输入PA0 在 STM32F103 中复用功能为ADC12_IN0和TIM2_CH1但模拟输入模式会关闭数字输入缓冲器导致无法读取 PIR 的高低电平。若强行将 PA0 配置为GPIO_Mode_AIN则GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)永远返回 0。因此必须物理分离PIR 占用一个独立 GPIO如 PA0光敏占用另一个 ADC 通道如 PA3。这是初学者最常踩的坑——以为“一个引脚能干两件事”结果调试时发现 PIR 始终无响应。2.2.2 LCD1602 的 E 引脚为何必须用推挽而非开漏LCD 的使能信号 E 要求快速上升沿≤150ns和稳定高电平保持≥230ns。开漏输出需外接上拉电阻RC 时间常数会导致上升沿变缓可能低于 LCD 手册要求的 150ns 边沿速率造成指令丢失。推挽输出可直接驱动且 STM32 GPIO 推挽最大灌电流达 25mA完全满足 LCD 的 1mA 输入电流需求。2.3 ADC 初始化与光照阈值动态标定方法光敏电阻阻值随光照剧烈变化暗态 MΩ 级亮态 kΩ 级直接读取 ADC 值易受温漂影响。本项目采用相对阈值法上电后自动采集 10 次当前环境 ADC 值取平均后乘以 0.7 作为LIGHT_THRESHOLD确保夜间判定鲁棒性。uint16_t Get_Light_Level(void) { ADC_InitTypeDef ADC_InitStructure; uint16_t adc_val 0; uint32_t sum 0; int i; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1, ENABLE); ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_239Cycles5); // PA3 CH3 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); for(i 0; i 10; i) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); sum ADC_GetConversionValue(ADC1); Delay_ms(10); // 避免连续采样过快 } adc_val sum / 10; return adc_val; } // 主循环中调用 uint16_t light_adc Get_Light_Level(); #define LIGHT_THRESHOLD (light_adc * 7 / 10) // 动态阈值70% 当前环境值注意ADC_SampleTime_239Cycles5是为保证精度选用的最长采样周期因光敏信号变化缓慢无需高速采样若用ADC_SampleTime_1Cycles5ADC 值波动可达 ±50无法稳定判别。3. 多状态机融合逻辑实现从单点检测到跨时间维度行为决策3.1 主状态机框架设计HOME_STATE 与 DETECT_STATE 双层嵌套本系统存在两个正交状态维度HOME_STATE家庭状态由拨动开关决定仅两种取值 ——HOME_PRESENT有人、HOME_ABSENT无人DETECT_STATE检测状态由 PIR 和光照共同决定含四种组合 ——NO_DETECTION、DAY_DETECTION、NIGHT_DETECTION、NIGHT_LONG_STAY逗留超时二者不可简单合并为 2×48 种状态因为NIGHT_LONG_STAY是NIGHT_DETECTION的时间衍生状态需独立计时器支持。因此采用主-子状态机结构HOME_STATEDETECT_STATE行为响应HOME_PRESENTNO_DETECTIONLCD 显示 “watting for you”LED 灭蜂鸣器静音HOME_PRESENTDAY_DETECTIONLCD 显示 “Welcome to home!”LED 灭日间不启亮蜂鸣器静音HOME_PRESENTNIGHT_DETECTIONLCD 显示 “Welcome to home!”LED 亮蜂鸣器响 1 次门铃触发时HOME_PRESENTNIGHT_LONG_STAYLCD 显示 “ALERT! LONG STAY”LED 快闪蜂鸣器长鸣 3s持续 30s 后自动复位HOME_ABSENTANYLCD 显示 “Nobody!”LED 灭蜂鸣器仅在门铃按键按下时不响应硬件屏蔽3.2 PIR 信号处理防误触发的 3 级确认机制PIR 传感器输出存在高频噪声与人体移动瞬态抖动直接读取 GPIO 会导致频繁误判。本项目采用三级软件滤波硬件级PIR 模块自带 2s 延时电容输出高电平持续时间 ≥2s驱动级每 100ms 读取一次 PA0连续 3 次读取为高才认为“有效触发”应用级触发后启动detection_timer若 5s 内无新触发则判定为“单次经过”若 5s 内再次触发则进入LONG_STAY模式。// 全局变量 volatile uint8_t pir_state 0; // 0无检测1已触发2长驻 volatile uint32_t detection_start_ms 0; #define DETECTION_TIMEOUT_MS 5000 // 5秒内重复触发视为长驻 // 主循环中调用 void PIR_Process(void) { static uint8_t pir_cnt 0; static uint8_t last_pir 1; // 初始为高未触发 uint8_t cur_pir GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0); if(cur_pir 0 last_pir 1) { // 下降沿不对PIR 高有效 → 应检测上升沿 // PIR 输出高电平表示检测到故检测 cur_pir1 且 last_pir0 } if(cur_pir 1) { pir_cnt; if(pir_cnt 3) { // 连续3次高电平100ms×3300ms if(pir_state 0) { pir_state 1; detection_start_ms millis(); // 假设已实现毫秒计时 } else if(pir_state 1 (millis() - detection_start_ms) DETECTION_TIMEOUT_MS) { pir_state 2; // 进入长驻 detection_start_ms millis(); // 重置计时起点 } } } else { pir_cnt 0; if(pir_state 1 || pir_state 2) { // 触发结束若为长驻保持状态30秒否则清零 if(pir_state 2) { // 启动长驻倒计时30秒后自动清除 long_stay_timer 30000; } pir_state 0; } } last_pir cur_pir; }3.2.1 为什么不用 EXTI 中断PIR 输出为脉冲宽度 2–3s 的方波若用 EXTI 触发中断每次高电平都会进中断导致 CPU 频繁退出低功耗模式且中断服务程序中难以准确测量脉宽。轮询虽占 CPU但在 100ms 间隔下占用率 0.1%且逻辑清晰可控。3.3 LCD1602 显示状态同步策略避免闪烁与指令冲突LCD1602 写入存在严格时序指令执行时间 39μs清屏、1.64ms回车若在执行中强行写入新指令会导致显示错乱。本项目采用状态标记主循环轮询更新禁止在中断中调用 LCD 函数// 全局显示缓冲区与刷新标记 char lcd_line1[17] watting for you ; char lcd_line2[17] ; volatile uint8_t lcd_update_flag 0; // 更新显示内容在主循环中调用 void LCD_Update_Display(void) { if(lcd_update_flag) { LCD_Clear(); LCD_Write_String(0, 0, lcd_line1); LCD_Write_String(0, 1, lcd_line2); lcd_update_flag 0; } } // 状态变更时仅修改缓冲区并置位标志 void Set_LCD_Message(uint8_t line, const char* msg) { if(line 0) { strncpy(lcd_line1, msg, 16); lcd_line1[16] \0; } else { strncpy(lcd_line2, msg, 16); lcd_line2[16] \0; } lcd_update_flag 1; } // 主循环中 while(1) { PIR_Process(); Key_Process(); // 处理门铃按键 Light_Sense(); // 读取光照并更新状态 Update_Home_State(); // 根据拨动开关更新 HOME_STATE Update_Detect_State(); // 根据 PIR光照更新 DETECT_STATE LCD_Update_Display(); // 统一刷新 LED_Control(); // 根据状态控制 LED Buzzer_Control(); // 根据状态控制蜂鸣器 Delay_ms(10); // 主循环节拍 10ms }提示strncpy保证字符串结尾\0避免 LCD 显示乱码Delay_ms(10)是主循环基准节拍所有状态机时间基准均由此派生避免使用SysTick中断导致 LCD 写入冲突。4. 门铃模式切换与报警逻辑验证从物理开关到可复现的测试用例4.1 拨动开关状态读取与抗干扰处理拨动开关机械触点存在抖动bounce time ~5–10ms若直接读取可能在一次拨动中产生多次电平跳变。本项目采用电平保持延时确认法不依赖定时器中断uint8_t Read_Home_Switch(void) { static uint8_t switch_state HOME_ABSENT; static uint32_t last_change_ms 0; uint8_t cur_level GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1); // 拨上0拨下1 if(cur_level ! switch_state) { if(millis() - last_change_ms 20) { // 20ms 去抖 switch_state cur_level; last_change_ms millis(); // 状态变更时立即更新 LCD if(switch_state 0) { Set_LCD_Message(0, Somebody! ); Set_LCD_Message(1, ); } else { Set_LCD_Message(0, Nobody! ); Set_LCD_Message(1, ); } } } return (switch_state 0) ? HOME_PRESENT : HOME_ABSENT; }4.1.1 为什么不用硬件 RC 滤波RC 滤波会引入相位延迟导致开关响应滞后。对于门禁这种需即时反馈的场景软件去抖更精准可控——20ms 是经验值覆盖绝大多数机械开关抖动区间且不增加 BOM 成本。4.2 门铃按键响应逻辑与蜂鸣器驱动时序门铃按键仅在HOME_PRESENT状态下生效且需防连按。采用“按下一次响一声间隔 500ms 内忽略后续按下”策略void Key_Process(void) { static uint32_t last_key_time 0; uint8_t key_pressed !GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2); // 按下0 if(key_pressed (millis() - last_key_time 500)) { if(home_state HOME_PRESENT) { Buzzer_On(); Delay_ms(200); Buzzer_Off(); last_key_time millis(); // 同时更新 LCD 第二行提示 Set_LCD_Message(1, Doorbell ring! ); } } }注意Buzzer_On()是简单设置 PC13 为高电平有源蜂鸣器内部已含振荡电路无需 PWM。若用无源蜂鸣器则需 TIM2 生成 2kHz 方波本项目未采用降低复杂度。4.3 报警逻辑验证表覆盖所有边界条件以下为实际调试中验证过的 6 类典型场景每项均可在 5 分钟内复现测试编号操作步骤预期现象实际观测需记录T1白天拨动开关拨上 → 按门铃按键LCD 第一行 “Somebody!”第二行 “Doorbell ring!”蜂鸣器响 200msLED 不亮T2夜间遮盖光敏拨动开关拨上 → PIR 前挥手LCD 显示 “Welcome to home!”LED 亮蜂鸣器不响未按门铃T3夜间拨动开关拨上 → PIR 前静止站立 6sLCD 第二行变为 “ALERT! LONG STAY”LED 快闪500ms 亮/500ms 灭蜂鸣器长鸣 3sT4夜间拨动开关拨下 → PIR 前挥手 按门铃LCD 保持 “Nobody!”LED 灭蜂鸣器无声无任何反馈T5光照临界点电位器调至 ADC 值≈928→ 快速用手遮挡/移开光敏LCD 第一行在 “watting for you” 与 “Welcome to home!” 间切换LED 同步开关无闪烁T6断电重启 → 拨动开关保持原位置上电后立即读取 PA1 电平LCD 正确显示 “Somebody!” 或 “Nobody!”无需人工干预提示T5 是验证光敏阈值鲁棒性的关键测试。若电位器调节不当可能导致白天误判为夜间LED 白天常亮或夜间不触发LED 不亮此时需重新运行Get_Light_Level()并更新LIGHT_THRESHOLD宏定义。5. Keil MDK 工程构建与 STM32F103C8T6 芯片包安装实操指南5.1 Keil5 兼容 STM32 芯片包安装全流程含常见报错解析Keil MDK 默认不包含 STM32F103 系列支持需手动安装芯片包。严禁使用“keil5安装stm32芯片包”等模糊搜索结果必须从 ARM 官方渠道获取访问 ARM Keil Device Database 搜索 “STM32F103C8”点击 “Download” 获取.pack文件如Keil.STM32F1xx_DFP.2.3.0.pack打开 Keil μVision5 →Pack Installer菜单栏Pack→Check for Updates点击右下角Import选择下载的.pack文件安装完成后在Project→Options for Target→Device选项卡中选择STM32F103C8若出现Error: No source available或Cannot access Memory说明 Flash 编程算法未加载进入Utilities→Settings→Flash Download勾选Reset and Run点击Add选择STM32F10x High Density对应 C8T6 的 64KB Flash确保Use Debug Driver为ST-Link Debugger且固件版本 ≥ V2.J34.S7。5.1.1 为什么不能用“stm32芯片包安装”百度结果第三方网站提供的.pack文件常为旧版如 v1.0.0缺少对 C8T6 的 Flash 算法支持导致下载失败或程序跑飞。官方包经 ARM 认证且持续更新 JTAG/SWD 协议兼容性。5.2 STM32F103C8T6 晶振电容计算与硬件起振故障排查本项目使用 8MHz 外部 HSE 晶振匹配电容取值直接影响起振成功率。计算公式为[ C_{load} 2 \times C_{ext} C_{stray} ]其中 (C_{stray}) 为 PCB 寄生电容通常 2–5pF晶振规格书标注负载电容 (C_L 12pF)则[ C_{ext} \frac{C_L - C_{stray}}{2} \approx \frac{12 - 3}{2} 4.5pF \rightarrow \text{取标称值 } 4.7pF ]若烧录后系统不运行优先检查万用表测 OSC_IN/OSC_OUT 对地电阻是否 ≈0Ω短路示波器观察 OSC_IN 是否有 8MHz 正弦波无则晶振未起振检查RCC-CR寄存器HSERDY位是否为 1代码中添加while((RCC-CR RCC_CR_HSERDY) 0);强制等待。5.3 生成 BIN 文件用于量产烧录的 Keil 配置毕业设计或小批量生产需脱离 Keil用 ST-Link Utility 烧录 BIN。配置步骤Options for Target→Output→ 勾选Create HEX File和Create Binary FileUser→After Build/Rebuild→ 填写fromelf --bin --output ./Objects/L.bin ./Objects/L.axf编译后Objects目录下生成xxx.bin大小应与Flash区域一致≤64KB用 ST-Link Utility 打开.bin设置起始地址0x08000000点击Program即可。提示.bin文件是纯机器码无地址信息必须指定起始地址若烧录后不运行用 ST-Link Utility 读取 Flash 验证内容是否与.bin一致排除烧录失败。最后一步将main.c中#define DEBUG_MODE 0改为1编译后通过 USART1PA9/PA10打印各状态机变量值用串口助手实时监控home_state、pir_state、light_adc这是定位逻辑错误最高效的手段。本文还有配套的精品资源点击获取