ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32图书馆空位检测:多传感器采集、定时器去抖与联网上报

STM32图书馆空位检测:多传感器采集、定时器去抖与联网上报 简介这是一份围绕基于STM32的图书馆空位检测系统展开的完整技术文档面向嵌入式开发、物联网与机器视觉方向的学习者及毕业设计开发者帮助其掌握从硬件选型到算法部署的全流程实现思路。资源为单个PDF文件约43.67MB章节层级清晰便于按模块分期查阅。文中详细介绍了设备端与服务器端的协同方案以STM32F103RCT6为主控配合ESP8266无线模块与1.44寸SPI液晶屏实时显示座位占用与人流信息服务器端借助OpenCV与YOLO目标检测算法处理摄像头画面判断人员数量与座位状态再经网络下发至设备端刷新界面。图像采集、图像处理、数据传输与显示控制四大模块的职责划分以及系统框架图、原理图、硬件实物图、通信协议与核心代码均有对应章节并附项目描述与常见问答方便读者理解结构、复现实验并整理答辩材料。已有132人学习参考。1. 图书馆空位检测系统为什么不是「装一排红外」就完事图书馆座位密集相邻座位间隔小红外对射一旦装歪隔壁的人伸手就能触发靠窗位置下午光照变化大红外接收管容易饱和还有书包占座、人短暂离开、传感器表面落灰。空位检测系统真正要输出的是「这个座位当前能不能坐」不是「传感器有没有被挡」。STM32 在这里的价值不是算力而是把多路 IO 扫描、定时器节拍、状态去抖、串口/以太网上报做成一条稳定流水线。常见方案用 STM32F103 做 32 到 64 路座位终端再经 RS485 或 WiFi 汇聚到上位机。适合做 stm32项目 的毕业设计、图书馆智能化改造也适合需要低功耗、多路数字量采集的物联网终端。2. STM32 选型与最小系统从 keil5创建stm32工程步骤 到引脚分配2.1 图书馆空位检测系统的 IO 与定时器预算先算座位规模。100 个座位若每个座位用 1 路红外需要 100 路输入若用超声波每路要 1 个触发和 1 个回波。STM32F103C8T6 只有 37 个 GPIO不够直连。常见做法是分区每 16 个座位放一片 74HC165 并入串出或者用 74HC4051 八选一模拟开关扫描。STM32 只负责 3 到 4 根控制线和 1 根数据线。选型时不要只看 IO 数量定时器数量决定能不能同时做多路超声捕获。F103 有 4 个定时器其中 TIM1 高级TIM2/3/4 通用。如果要接 8 路超声用输入捕获会紧张可以用外部中断加定时器计数或者分时触发同一时刻只让一路超声工作。型号主频FlashGPIO通用定时器UART适合座位规模STM32F103C8T672MHz64KB373316 到 32 路带扩展芯片STM32F103ZET672MHz512KB1128564 到 128 路可直连STM32F407VET6168MHz512KB82126多路超声、以太网、看板F103C8T6 便宜、资料多适合 stm32教程 和毕业设计F407 适合带以太网和较多超声的现场。表格里 GPIO 是可用 IO实际还要扣掉晶振、下载口、串口。如果只是做演示系统F103C8T6 加 74HC165 足够如果图书馆要求每座位独立指示灯和蜂鸣器就要留出输出扩展。2.2 keil5创建stm32工程步骤与标准库/HAL 的选择标准库资料多寄存器层面清晰适合 stm32项目 和毕业设计HAL 配 CubeMX 生成快但库层厚排查时序问题要看底层。keil5安装stm32芯片包 后新建工程选芯片加启动文件、时钟配置、GPIO 驱动。下面是一个标准库 GPIO 初始化片段用于读取 74HC165 的串行数据。#include stm32f10x.h // 初始化 PA0 为上拉输入接 74HC165 串行输出 void HC165_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能 GPIOA 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); }逻辑说明74HC165 空闲时串行输出为高所以用上拉输入有座位遮挡时变为低。参数说明GPIO_Mode_IPU是上拉输入适合开关或光耦输出GPIO_Speed_50MHz在输入模式下影响小统一写 50MHz 方便复制。若传感器是 5V 输出PA0 不能直连要加光耦或电平转换STM32 的 IO 耐压不是 5V。2.3 引脚分配与 stm32禁用jtag 释放 PB3/PB4/PA15图书馆空位检测系统常见引脚分配如下先列表再接线避免焊完才发现引脚冲突。引脚功能连接对象备注PA0数据输入74HC165 Q7上拉输入PA1时钟74HC165 CP推挽输出PA2锁存74HC165 PL推挽输出PA9/PA10UART1上位机/RS485115200 8N1PB6/PB7I2C1OLED 或 EEPROM4.7k 上拉PC13状态灯LED低电平点亮PB3/PB4/PA15扩展 IO蜂鸣器、指示灯需关闭 JTAGPB3、PB4、PA15 默认是 JTAG 引脚直接配 GPIO 不生效。需要在初始化里关闭 JTAG保留 SWD 下载。// 使能 AFIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 关闭 JTAG保留 SWD释放 PA15/PB3/PB4 作为普通 IO GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); // 之后可把 PB3 配成推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOB, GPIO_InitStructure);逻辑说明GPIO_Remap_SWJ_JTAGDisable只关 JTAG不影响 SWD下载和仿真仍可用。参数说明RCC_APB2Periph_AFIO必须使能否则重映射配置无效。提示关闭 JTAG 后仿真器设置里要选 SWD 模式不要选 JTAG否则连不上。3. 空位检测传感器接入红外对射、超声、压力薄膜与 ToF 的 STM32 驱动3.1 四种空位检测方案对比与安装位置图书馆空位检测常用红外对射、超声、压力薄膜、ToF 四种。红外对射装桌沿两侧遮挡判定成本低但怕光照和灰尘超声装桌下或桌面测距判定怕多径和温度压力薄膜装坐垫测压力怕长期形变ToF 装桌下测距精度高但价格高。实际项目里常见组合是红外加压力薄膜红外判断有没有人经过压力判断有没有坐下两个条件同时满足才输出「有人」。这样书包占座、人短暂离开可以设不同超时。方案检测量安装位置响应速度常见坑红外对射遮挡桌沿两侧毫秒级强光、灰尘、隔壁触发超声波距离桌下或桌面几十毫秒多径反射、温度漂移压力薄膜压力坐垫百毫秒级久坐形变、零点漂移ToF距离桌下毫秒级成本高、玻璃反射选型先问三个问题座位间距多少、有没有阳光直射、上位机多久要一次数据。间距小于 60cm红外容易串扰优先压力或 ToF阳光直射红外要加调制或换超声上位机只要 10 秒一次状态扫描周期不用太快。3.2 stm32按键模块电路设计 思路接红外对射与薄膜开关红外对射输出通常是集电极开路或高低电平薄膜开关是机械触点。参考 stm32按键模块电路设计上拉 10k并联 100nF 电容光耦隔离。上拉保证空闲高电平电容滤高频干扰光耦隔离 5V 和 3.3V。代码里读取 GPIO 即可。// 读取 PA0 上的座位状态低电平表示遮挡 uint8_t Seat_ReadRaw(uint16_t pin) { if (GPIO_ReadInputDataBit(GPIOA, pin) Bit_RESET) { return 1; // 有人或遮挡 } return 0; // 无人 }逻辑说明GPIO_ReadInputDataBit返回Bit_SET或Bit_RESET这里把低电平映射为「有人」。参数说明上拉电阻 10k 与 100nF 构成约 1ms 时间常数可以滤掉按键抖动和部分工频干扰。注意如果传感器是 5V 供电光耦输出侧上拉到 3.3V不要直接把 5V 信号接进 STM32。3.3 超声波 HC-SR04 用 stm32定时器捕获测频率/回波超声波适合检测桌面到人体或椅面的距离。HC-SR04 的 Trig 给 10us 高电平Echo 输出高电平时间与距离成正比。用 stm32定时器 输入捕获测回波高电平时间比delay轮询稳。下面以 TIM2 通道 1 为例。// TIM2 输入捕获初始化1MHz 计数1us 分辨率 void Ultrasonic_Capture_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD; // 下拉输入等待回波高电平 GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 72MHz/72 1MHz TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0; TIM_ICInit(TIM2, TIM_ICInitStructure); TIM_Cmd(TIM2, ENABLE); }逻辑说明预分频 72-1 后定时器每 1us 计数一次。捕获上升沿得到回波起点切换下降沿得到回波终点差值就是回波时间。距离计算distance_cm time_us * 0.034 / 2因为声速约 340m/s往返除以 2。参数说明TIM_Prescaler由系统时钟决定F103 主频 72MHz 时写 71若主频不同要重算。注意回波超时约 30ms超过就判无效避免中断卡死。3.4 多路扩展74HC4051 与 74HC595 扫描座位多时不能每个传感器占一个 IO。74HC4051 是八选一模拟开关A/B/C 三根地址线选通道公共端接 STM32 的 ADC 或输入。74HC595 是串入并出用来驱动座位指示灯。扫描逻辑每 1ms 切一个通道8 路 8ms 扫完。// 74HC4051 地址线选择通道 0 到 7 void Mux_Select(uint8_t ch) { // A 接 PA1B 接 PA2C 接 PA3 GPIO_WriteBit(GPIOA, GPIO_Pin_1, (ch 0x01) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_2, (ch 0x02) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_3, (ch 0x04) ? Bit_SET : Bit_RESET); }逻辑说明ch的低三位分别对应 A/B/CGPIO_WriteBit直接写引脚。参数说明切换通道后要等 10us 再采样模拟开关有建立时间。如果接的是数字量可以用 74HC165 代替 4051读取更直接。提示多路扫描时模拟地和数字地要分开走线否则相邻通道串扰会让空位状态跳变。4. STM32 定时器轮询与去抖把「有人/空位」判定做稳定4.1 用 stm32定时器 建立 10ms 扫描节拍空位检测不能在主循环里裸跑否则串口一发数据、OLED 一刷新扫描周期就飘。常见做法是用 stm32定时器 建 10ms 节拍中断里只置标志主循环处理扫描和判定。下面用 TIM3 做 10ms 中断。// TIM3 初始化72MHz 下 10ms 中断 void TIM3_10ms_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period 100 - 1; // 100 个计数 TIM_TimeBaseStructure.TIM_Prescaler 7200 - 1; // 72MHz/7200 10kHz TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); NVIC_InitStructure.NVIC_IRQChannel TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM3, ENABLE); } // 中断服务函数 volatile uint8_t g_scan_flag 0; void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); g_scan_flag 1; // 只置标志不做耗时操作 } }逻辑说明预分频 7200-1 得到 10kHz重装载 100-1 得到 10ms。中断里只置g_scan_flag主循环检测到标志再扫描避免中断里做串口发送或 OLED 刷新。参数说明TIM_Prescaler和TIM_Period根据主频调整F407 主频 168MHz 时要重算。提示中断优先级不要设太高否则会影响串口接收。4.2 状态机去抖连续 N 次一致才切换状态原始传感器值会抖必须用状态机。每个座位维护raw_cnt和stable_state。连续 3 次读到有人才把稳定状态切成有人连续 5 次读到无人才切成空位。这样短时遮挡和瞬间丢波不会让看板跳变。typedef struct { uint8_t raw_cnt; // 连续一致计数 uint8_t stable_state; // 0 空位1 有人 } SeatState; void Seat_Debounce(SeatState *seat, uint8_t raw) { if (raw seat-stable_state) { seat-raw_cnt 0; // 状态一致清计数 return; } seat-raw_cnt; if (raw 1 seat-raw_cnt 3) { seat-stable_state 1; // 连续 3 次有人确认有人 seat-raw_cnt 0; } else if (raw 0 seat-raw_cnt 5) { seat-stable_state 0; // 连续 5 次无人确认空位 seat-raw_cnt 0; } }逻辑说明只有原始值与稳定状态不同才计数连续达到阈值才切换。参数说明有人阈值 3 次无人阈值 5 次按 10ms 扫描周期算有人确认约 30ms无人确认约 50ms。如果人离开后要快速释放座位可以把无人阈值降到 3如果怕书包占座误判可以升到 10。注意阈值不要设成 1 次否则看板会频繁刷新。4.3 stm32延时函数delay卡死 的排查与替代stm32延时函数delay卡死常见于三种情况SysTick 被其他库占用、在中断里调用delay_ms、等待标志时没有超时。图书馆空位检测系统里如果主循环用delay_ms(1000)等串口扫描周期会变成 1 秒空位状态严重滞后。替代方案是定时器节拍加状态机所有等待都带超时。现象可能原因处理程序停在delay_msSysTick 配置被改检查SysTick_Config或用 TIM 做延时中断里卡死在中断中调用延时中断只置标志延时放主循环串口等待无响应没等 TXE/TC 或超时加超时计数超时复位扫描周期忽长忽短主循环有阻塞操作用 10ms 节拍驱动状态机排查时先看while循环有没有超时退出再看中断优先级。提示调试时可以在超时分支翻转一个 IO用示波器看周期比打印日志快。4.4 阈值参数表与空位判定表不同传感器阈值不同最好集中放一张表现场标定只改表。下面给出常见参数。传感器有人条件空位条件去抖次数超时红外对射低电平高电平持续3 次无压力薄膜ADC 1500ADC 8005 次2 秒超声波距离 40cm距离 60cm3 次30msToF距离 50cm距离 70cm3 次50ms红外只有 0/1压力薄膜有模拟量中间带 800 到 1500 保持原状态避免临界跳动。超声波和 ToF 的 40/60、50/70 是回差防止距离在阈值附近来回切。提示参数表可以放在config.h编译前统一改不要散落在各个.c文件。5. 空位数据上报stm32串口调试、ESP8266 与 stm32配置以太网5.1 UART 帧格式与 stm32串口调试 要点STM32 把座位状态打包成帧经 UART 发给 RS485 或无线模块。帧格式要带地址和校验否则多座位数据容易串。常见帧结构如下。字节含义示例0帧头0xAA1终端地址0x012座位号0x0A3状态0x01 有人0x00 空位4校验前四字节异或5帧尾0x55发送函数只发稳定状态变化不要每 10ms 都发。// 发送一帧座位状态data 为前四字节 void Uart_SendSeatFrame(uint8_t addr, uint8_t seat, uint8_t state) { uint8_t buf[6]; buf[0] 0xAA; buf[1] addr; buf[2] seat; buf[3] state; buf[4] buf[0] ^ buf[1] ^ buf[2] ^ buf[3]; buf[5] 0x55; for (uint8_t i 0; i 6; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, buf[i]); } }逻辑说明buf[4]是异或校验接收端重算一遍即可判断帧是否完整。参数说明波特率常用 1152008 位数据无校验1 位停止。stm32串口调试 时先用串口助手确认帧头帧尾再看状态位。注意RS485 收发切换要留 1 个字节时间否则最后一字节发不出去。5.2 esp8266与stm32连接原理图 与 AT 指令上报没有网线时用 ESP8266 做 WiFi 上报。连接思路STM32 的 UART2 TX 接 ESP8266 RXRX 接 TX共地3.3V 供电。ESP8266 峰值电流约 300mA不能直接用 STM32 的 3.3V 引脚供电要单独 LDO。AT 指令流程如下。# ESP8266 常用 AT 指令通过 STM32 UART2 发送 ATCWMODE1 # 设置为 Station 模式 ATCWJAPlibrary_ap,12345678 # 连接图书馆 AP ATCIPSTARTTCP,192.168.1.100,8080 # 连接上位机 TCP 服务 ATCIPSEND6 # 准备发送 6 字节 AA010A014455 # 自定义帧十六进制逻辑说明先设模式再连 AP再连 TCP 服务最后发数据。参数说明ATCIPSEND后面的数字是字节数发送十六进制帧时要注意模块是否处于透传模式。常见坑是 AT 指令回显没关STM32 解析时把回显当数据。提示初始化时发ATE0关回显每条指令后等OK超时 3 秒就重试。5.3 stm32配置以太网 与 stm32 http库 上报如果图书馆有网口用 STM32F407 加 LAN8720 或 W5500。stm32配置以太网 要初始化 MAC、PHY、DMA 描述符再用 LwIP 或 HTTP 库发 POST。报文用 JSON体积小上位机好解析。// 组装 HTTP POST 报文发送到上位机 char http_buf[128]; sprintf(http_buf, POST /api/seat HTTP/1.1\r\n Host: 192.168.1.100\r\n Content-Type: application/json\r\n Content-Length: 27\r\n\r\n {\seat\:10,\status\:1}); // 通过 TCP 发送 http_buf TCP_Send((uint8_t *)http_buf, strlen(http_buf));逻辑说明Content-Length必须等于 JSON 体的实际长度否则服务器会等或截断。参数说明Host填上位机 IP 或域名端口默认 80。stm32 http库 内存占用不大但 JSON 字段名不要拼太长。注意以太网布线要远离强电RJ45 变压器中心抽头按参考设计接。5.4 上位机看板与数据库字段上位机收到帧后解析、落库、刷新看板。数据库表字段要能支持按楼层、区域、时间查询。字段类型说明idBIGINT自增主键seat_idVARCHAR(16)座位编号如 3F-A-012statusTINYINT0 空位1 有人update_timeDATETIME状态变更时间terminal_addrTINYINT终端地址-- 创建座位状态表 CREATE TABLE seat_status ( id BIGINT AUTO_INCREMENT PRIMARY KEY, seat_id VARCHAR(16) NOT NULL, status TINYINT NOT NULL DEFAULT 0, update_time DATETIME NOT NULL, terminal_addr TINYINT NOT NULL, INDEX idx_seat_time (seat_id, update_time) );逻辑说明seat_id用字符串能表达楼层和区域update_time用于查询历史空位率。参数说明只在状态变化时插入不要每秒插一条否则一周数据量很大。提示看板查询用seat_id加时间索引不要全表扫。6. 现场部署进阶用 stm32内部32khz做RTC 做分时空位统计与误报验证6.1 RTC 分时统计与低功耗轮询图书馆空位检测系统不只要实时状态还要知道哪个时段空位多。用 stm32内部32khz做RTC每小时统计一次各区域空位率数据存 EEPROM 或 Flash。RTC 配置用 LSI 或 LSELSI 精度差但省晶振适合分钟级统计要准就用外部 32.768kHz 晶振。低功耗轮询时STM32 进停止模式RTC 闹钟每 2 秒唤醒一次扫描电池供电的座位终端可以撑更久。// RTC 闹钟唤醒配置每 2 秒唤醒一次 void RTC_Alarm_Init(void) { RTC_AlarmTypeDef RTC_AlarmStructure; EXTI_InitTypeDef EXTI_InitStructure; // 允许 RTC 闹钟中断 RTC_ITConfig(RTC_IT_ALR, ENABLE); RTC_AlarmStructure.RTC_AlarmTime.RTC_H12 RTC_H12_AM; RTC_AlarmStructure.RTC_AlarmTime.RTC_Seconds 2; RTC_AlarmStructure.RTC_AlarmMask RTC_AlarmMask_DateWeekDay; RTC_SetAlarm(RTC_Alarm_2, RTC_AlarmStructure); // 配置 EXTI 线 17 连接 RTC 闹钟 EXTI_ClearITPendingBit(EXTI_Line17); EXTI_InitStructure.EXTI_Line EXTI_Line17; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); }逻辑说明RTC 闹钟到点后触发 EXTI 17唤醒停止模式。参数说明RTC_AlarmMask_DateWeekDay屏蔽日期只按秒匹配适合周期性唤醒。注意停止模式唤醒后要重新配置时钟串口和定时器要等时钟稳定再初始化。6.2 误报验证与现场标定现场误报大多来自安装和阈值。先跑一周原始数据把每个座位的传感器值、判定结果、时间戳记录到 SD 卡或上位机再对照监控回放标定。下面列出常见误报与处理。误报现象可能原因标定方法空位显示有人红外被阳光直射加遮光罩调低接收灵敏度有人显示空位压力薄膜零点漂移重新采集空载 ADC更新阈值状态来回跳去抖次数太少有人 3 次改 5 次无人 5 次改 8 次相邻座位联动超声多径或红外串扰分时触发错开扫描时隙夜间大量误报虫爬或灰尘加防护罩定期清洁标定完成后把阈值写回 Flash下次上电直接加载。本文还有配套的精品资源点击获取
返回列表