资讯动态

基于STM32的智能导盲杖设计:定时器输入捕获与分级报警系统

发布时间:2026/9/15 4:03:39 来源:尧图企业网站定制
简介一套基于STM32F103ZET6的智能导盲杖完整毕业设计项目采用Keil裸机开发面向电子、嵌入式方向的本科生及开发者。项目集成SIM900A通信、GPS定位、语音合成、超声波避障、水滴检测、光敏电阻感应等多类传感器并配备高功率LED、蜂鸣器、按键及锂电池电源管理模块能够实现环境感知、定位求助与语音交互等典型功能。压缩包共1138个文件以C语言源码397个c、326个h为主体辅以汇编启动文件、Keil/IAR工程配置uvproj/ewp及编译链接脚本整体体积27.17MB结构完整可直接导入工程学习。这份项目包已有719人学习下载适合作为毕业设计参考可帮助读者快速理解各模块电路接口、驱动逻辑及Keil工程组织方式并基于完整工程进行二次开发或功能扩展。1. 为什么智能导盲杖的核心不是传感器而是 STM32 这套调度逻辑传统导盲杖只能靠物理接触判断一米以内的地面情况对正前方悬空的招牌、雨天积水和夜间来车完全没有感知。加装超声波、雨水传感器和振动反馈之后这个设备就从「一根杆子」变成了需要多传感器轮流采集、分级告警、还要控制功耗的嵌入式系统。真正决定它好不好用的不是传感器有多贵而是 STM32 能否用定时器、中断和状态机把「采集到反馈」的延迟稳定下来。超声波回波脉宽要精确到微秒蜂鸣器和马达强度要靠 PWM 连续调节OLED 刷新又不能拖慢主循环这些恰好是 STM32 定时器外设最擅长的事。下面按硬件选型、CubeMX 定时器配置、分级报警和整机联调四步展开适合作为课程设计和毕业设计原型也适合做辅助设备的第一版工程样例。2. 硬件选型与引脚规划先把 STM32 的外设资源定死2.1 主控为什么选 STM32F103C8T6做这种外设不超过十个的辅助设备F103C8T6 一直是性价比最高的选择。看 STM32 系统架构就明白72 MHz Cortex-M3 配合 DMA跑 20 Hz 超声波测距和 5 Hz 反馈刷新绰绰有余64 KB Flash 放 HAL 库加业务代码也够用。对比 ESP32蓝牙和 Wi-Fi 用不上反而要处理射频功耗对比 51硬件 I2C 和输入捕获都要拿 GPIO 模拟超声波脉宽测量的精度会打折扣。选 F103 的另一层原因是资料密度。从标准库新建工程到 CubeMX 生成 HAL 工程开发环境配错的坑基本都有现成答案团队里换人接手也快。真遇到查不到的问题ST-Link 连上后用 ST-Link Utility 读寄存器状态比对着数据手册猜效率高得多。2.2 外设挂载清单一张可以直接照抄的引脚分配表智能导盲杖的传感器和执行器一般是前向超声波、雨水检测、蜂鸣器、振动马达、OLED 和一个功能按键。按下表分配引脚可以避开 PA13/PA14/PA15、PB3/PB4 这几个默认 JTAG 引脚避免出现「程序烧一次后就下载不了」的问题。在 CubeMX 的 SYS 配置里 Debug 选 Serial Wire相当于禁用 JTAG同时保留 SWD 下载能力。如果后续要加 GPS 模块USART1 建议提前留出来。外设引脚工作模式说明超声波 TrigPA0GPIO 输出拉高 10 µs 触发测距超声波 EchoPA1TIM2_CH2 输入捕获测量回波脉宽雨水传感器 AOPA4ADC 输入阈值 1.2 V 判定水渍蜂鸣器PA6TIM3_CH1 PWM2 kHz 方波振动马达PA7TIM3_CH2 PWM占空比控制强度OLED SCL/SDAPB8/PB9硬件 I2C1需接 4.7 kΩ 上拉功能按键PB12GPIO 输入内部上拉低电平触发电池电压PB0ADC1_IN8分压后采样这张表对应 20 脚封装的 F103C8T6引脚功能在 CubeMX Pinout 视图里逐根核对过。做 PCB 时把超声波模块放在杖体前方约 15 cm 高度朝前上方倾斜 10°既能探测正前方障碍物又不会把地面当目标。OLED 固定在杖柄位置方便使用者余光查看。2.3 供电方案与电池电压采样整机功耗大头在超声波模块和 OLED正常工作时电流约 60 mA用两节 18650 串联再降压到 3.3 V 比较稳。为了知道剩余电量电池电压要先分压再进 ADC。2.3.1 分压比的计算两个常见错法补一个具体计算18650 满电 4.2 V两节串联就是 8.4 V。ADC 引脚最高能承受 3.3 V所以分压比要大于 2.5。我习惯用 30 kΩ 和 10 kΩ把 8.4 V 分到 2.1 V留出 1.2 V 的给波动余量防止电池电压异常时烧掉 ADC 引脚。最常见的错误是用两个 10 kΩ 对半分8.4 V 变成 4.2 V直接超限。/* ADC 采集电池电压ADC1_IN8 对应 PB0 */ uint16_t adc_read_battery(void) { HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { return HAL_ADC_GetValue(hadc1); } return 0; } /* 12 位 ADCVREF3.3V分压比(3010)/104.0 */ float battery_voltage(uint16_t adc_value) { float v_adc adc_value * 3.3f / 4095.0f; return v_adc * 4.0f 0.3f; /* 0.3 补偿负载压降 */ }这段代码先启动 ADC 并等待转换完成超时时间取 10 ms避免采集失败时主循环被卡死。battery_voltage 里乘的 4.0 是分压比(3010)/10 从电阻取值推导出来加 0.3 是经验补偿值因为 18650 在负载下端电压低于开路电压每块板子的补偿系数要按万用表实测值标定一次。电量按 3.0 V 到 4.2 V 映射到 0% 到 100%低于 3.3 V 进入待机关闭 OLED、超声波和 PWM只保留按键外部中断作为唤醒源。3. 给智能导盲杖生成 CubeMX 工程用定时器输入捕获做超声波测距3.1 CubeMX 里的关键配置项HAL 库工程比标准库少写很多寄存器代码但 CubeMX 配置要按顺序来。先配时钟外部晶振 8 MHzPLL 倍频到 72 MHzAPB1 定时器时钟确认是 72 MHz 而不是 36 MHz。这一项直接影响测距结果配错了计数频率变成 500 kHz读回来的脉宽会整体偏大一倍。然后配置 TIM2 通道 2 为输入捕获直接模式关键参数如下表配置项设定值作用PLL SourceHSE 8 MHz外部晶振作为时钟源SYSCLK72 MHz主频APB1 Timer Clock72 MHz定时器计数频率基准TIM2 CH2Input Capture directEcho 上升/下降沿捕获TIM2 PSC71计数频率 72MHz/(711)TIM2 ARR0xFFFF16 位计数上限Trig PA0GPIO Output默认低电平PSC 取 71 时计数频率正好 1 MHz一个计数值对应 1 µs读回的 CNT 差值就是脉宽微秒数省去单位换算。GPIO 里 Echo 脚要复用为 TIM2_CH2 并开启内部上拉。如果用 Keil5 打开生成的工程先确认已安装 F1 系列芯片包否则编译会报找不到目标芯片。注意APB1 定时器时钟是 36 MHz 还是 72 MHz取决于时钟树里 APB1 分频系数是否大于 1。HAL 初始化后用 HAL_RCC_GetPCLK1Freq() 打印确认不要凭印象。这里有个必须处理的电平问题HC-SR04 的 Echo 输出是 5 V 电平虽然多数板子 5 V 灌进 3.3 V 引脚不会立刻烧但长期运行有风险。稳妥做法是在 Echo 和 PA1 之间用 3.3 kΩ 和 6.8 kΩ 分压把电平降到 3.3 V 以下。GPIO 的电平转换和复用配置江科大 stm32 的入门教程讲得很细值得照着敲一遍再改自己的工程。3.2 输入捕获实现为什么不用 HAL_Delay网上很多超声波例程用 HAL_GPIO_ReadPin 加循环等待回波逻辑简单但有两个问题等待 Echo 拉高的过程中主循环被阻塞OLED 刷新、按键扫描全停中断优先级复杂时循环读引脚容易漏掉窄脉冲。用定时器输入捕获硬件在电平跳变瞬间锁存计数值CPU 只需要在中断里做减法主循环可以继续干别的。3.2.1 回波计时与计数器回绕处理volatile uint32_t capture_rise 0; volatile uint32_t capture_fall 0; volatile uint8_t capture_done 0; /* 触发脉冲高电平保持 10 us */ void ultrasonic_trigger(void) { HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); capture_done 0; } /* 输入捕获中断回调先记录上升沿再记录下降沿 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2 htim-Channel HAL_TIM_ACTIVE_CHANNEL_2) { if (capture_rise 0) { capture_rise HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); } else { capture_fall HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); capture_done 1; } } }用 capture_rise 是否为零来区分上升沿和下降沿。首次进入中断记录上升沿计数值第二次记录下降沿并置位 capture_done主循环轮询到 capture_done 后计算脉宽uint32_t echo_width_us(void) { if (!capture_done) return 0; if (capture_fall capture_rise) return capture_fall - capture_rise; return (0xFFFF - capture_rise) capture_fall; /* 16 位回绕 */ } float distance_cm(uint32_t width_us) { return width_us * 0.034f / 2.0f; /* 声速 340m/s往返除 2 */ }1 MHz 计数频率下脉宽微秒数就是计数值差值。16 位计数器最大计 65535 µs对应约 11 m 的往返声程数值上够用但 HC-SR04 标称有效测量范围只有 4 m超过这个值的数据应该直接丢弃否则会把多径反射当成真实障碍物。距离公式里 0.034 是声速的厘米/微秒除以 2 是因为回波走的是往返路程。测距频率控制在 20 Hz也就是 50 ms 触发一次给超声波模块留足余振消散时间。3.3 测距值滤波中值滤波怎么处理跳变超声波最烦的是偶发毛刺行人衣服吸音、马达 PWM 干扰、或者旁边金属栏杆多次反射。最省事的办法是连续采 3 次取中值注意每次采样间隔要留 60 ms否则上一次回波没结束就触发下一次会读到假回波。uint16_t distance_filtered(void) { uint16_t samples[3]; for (int i 0; i 3; i) { samples[i] (uint16_t)distance_cm(echo_width_us()); HAL_Delay(60); } for (int i 0; i 3; i) /* 冒泡取中值 */ for (int j i 1; j 3; j) if (samples[i] samples[j]) { uint16_t t samples[i]; samples[i] samples[j]; samples[j] t; } return samples[1]; }三次采样加两次 60 ms 延时整个流程约 180 ms反馈刷新率约 5 Hz对导盲场景足够。需要更快的响应就把 3 次改成 2 次取平均间隔缩到 40 ms。要理解中值滤波只处理毛刺不处理真实缓慢逼近的障碍物障碍物匀速靠近时读数本身平缓滤波只会引入滞后。所以我在工程里加了一层判断相邻两次原始读数差超过 20 cm 才启用中值滤波差值小时直接用当前值兼顾平滑和响应速度。4. 智能导盲杖的分级报警逻辑PWM 蜂鸣器、振动马达和 OLED 一起工作4.1 报警状态如何划分导盲杖的报警不能只有「响」和「不响」两档。距离 150 cm 以内就频繁告警会让使用者疲劳等障碍物到 30 cm 才响又来不及避让。我按三档划分大于 150 cm 为安全区不响60–150 cm 为注意区低频短鸣加弱振动小于 60 cm 为危险区连续急促鸣叫加马达全速振动。阈值参考人正常步速 1.2 m/s 和反应时间约 0.5 s60 cm 正好是半秒的反应余量。测试阶段用 USART2 以 115200 波特率把实时距离打到串口监视器边推障碍物边调整阈值。状态距离范围蜂鸣器振动马达OLED 提示SAFE 150 cm关闭关闭正常行走WARN60–150 cm1 Hz 间歇短鸣30% 占空比前方注意DANGER 60 cm连续鸣叫100% 占空比立即停止4.1.1 迟滞切换避免状态抖动阈值边界是最容易出 bug 的地方。距离在 60 cm 附近抖动时状态会在 WARN 和 DANGER 之间来回跳蜂鸣器像继电器一样乱响。解决办法是加迟滞进入 DANGER 需要距离小于 55 cm离开 DANGER 需要距离大于 70 cm两个阈值错开 15 cm实测能有效消除边界抖动。迟滞判断放在主循环状态机里做不要放在中断里避免在中断里做浮点比较和状态翻转。4.2 PWM 驱动蜂鸣器和马达蜂鸣器分有源和无源两种。有源蜂鸣器给高电平就响但不能调音调无源蜂鸣器需要特定频率方波音色可以区分告警等级。推荐无源蜂鸣器接 TIM3_CH1用 PWM 输出 2 kHz 方波振动马达接 TIM3_CH2占空比控制强度。驱动电机时注意用三极管或 MOS 管做开关GPIO 直接带不动马达而且马达两端要反并联一个续流二极管否则关闭瞬间的反电动势可能打坏引脚。void alarm_set_level(uint8_t level) { switch (level) { case ALARM_SAFE: HAL_TIM_PWM_Stop(htim3, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, 0); break; case ALARM_WARN: HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, 300); /* 30% */ break; case ALARM_DANGER: HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, 1000); /* 100% */ break; } }PWM 频率由 TIM3 的 ARR 决定占空比由比较寄存器 CCR 决定。ARR 设为 1000 时CCR300 就是 30% 占空比。WARN 的 1 Hz 间歇短鸣不在中断里翻转而是在主循环状态机里每 500 ms 切换一次蜂鸣器使能避免在中断里做耗时操作。DANGER 状态还要叠加超时判断连续 2 s 距离都小于 60 cm认为不是临时遮挡把报警强度直接拉满。4.3 OLED 显示与按键切换模式OLED 用 0.96 寸 I2C 屏挂 PB8/PB9 的硬件 I2C1。CubeMX 里 I2C 速度选 400 kHz屏幕默认 7 位地址 0x3C。屏幕不亮先量 SCL/SDA 的上拉电阻很多裸屏模组为了兼容 5 V 系统没有上拉板子上要补 4.7 kΩ。按键模块电路设计上功能键接 PB12另一端接地开启内部上拉按下为低电平用外部中断而不是轮询这样待机模式下按键依然可以唤醒系统。中断回调里只翻转模式标志和刷新屏幕不做超声波触发这类耗时操作。void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin KEY_Pin) { static uint8_t night_mode 0; night_mode !night_mode; oled_show_mode(night_mode ? NIGHT : DAY); alarm_refresh(); /* 立即按新模式输出一轮反馈 */ } }这段回调用一个 static 变量做模式翻转白天模式振动弱、蜂鸣声响夜间模式反过来避免吵到同住的人。外部中断优先级在 NVIC 里设为低于 TIM2 捕获中断避免按键中断打断回波边沿捕获。OLED 刷新频率压到 2 Hz128×64 全屏刷新一次约 30 ms刷新太快会占住 I2C 总线拖慢主循环里其他任务的调度。5. 智能导盲杖整机联调先验证状态机收敛再读实测数据全部外设接起来后最容易出问题的不是传感器本身而是状态跳变。main 循环里我用 switch-case 状态机把「测距 → 滤波 → 定级 → 输出反馈」串起来并且规定只有连续 3 次进入同一状态才切换报警等级。这个要求配合上一章的迟滞阈值比单纯改阈值更有效能同时压掉高频抖动和瞬时毛刺。实机测试时按 20 cm 步进从 20 cm 推到 180 cm记录 OLED 状态和蜂鸣器行为验证三件事第一状态切换边界与设定阈值一致误差不超过 2 cm第二障碍物从侧面快速扫过时不触发报警超声波主瓣较窄恰好利用这一点第三户外阳光直射下测距漂移小于 2 cmHC-SR04 收发管在室外可用。以下是一组室内走廊实测数据杖体手持、超声波模块离地 25 cm实际距离 (cm)回波读值 (cm)报警状态实际表现2020.4DANGER连续鸣叫6061.2DANGER→WARN 边界迟滞生效8079.1WARN1 Hz 短鸣150152.3WARN→SAFE 边界迟滞生效180181.5SAFE静音如果读回值和实际卷尺测量差距超过 5%先查 TIM2 的 PSC。最常见的低级错误是把 PSC 设成 71-1计数频率从 1 MHz 变成约 1.16 MHz脉宽和微秒数对不上测出来的距离时近时远。我把「PSC1」的换算关系直接写成注释放在定时器初始化旁边重新烧录后读数才和卷尺一致。最后一项验证是低功耗进入待机前关闭 OLED、超声波和 PWM仅保留按键外部中断唤醒3.7 V 电池下实测待机电流约 12 µA符合 F103 停机模式的典型值把这个电流值写进板子出厂测试项后续改版直接对比。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价