当前位置: 首页 > news >正文

从竞赛题到实战项目:手把手教你用STM32和超声波模块DIY一个智能测距仪(附完整代码)

从竞赛题到实战项目:手把手教你用STM32和超声波模块DIY一个智能测距仪(附完整代码)

在电子设计竞赛中,我们常常会遇到各种功能模块的编程题目,比如超声波测距、LCD显示、按键控制等。这些题目虽然能考察选手的基础能力,但往往缺乏实际应用场景的连贯性。本文将带你把这些零散的技术点整合起来,打造一个真正实用的智能测距仪。

这个项目特别适合刚接触嵌入式开发的初学者,或者对电子DIY感兴趣的爱好者。我们将使用STM32F103C8T6(俗称"蓝莓派")作为主控,搭配常见的HC-SR04超声波模块和12864液晶屏,构建一个功能完善的测距设备。不同于简单的实验Demo,这个项目会教你如何设计完整的代码架构,处理实际应用中的各种问题。

1. 项目规划与硬件选型

1.1 核心功能设计

一个实用的测距仪需要具备以下基本功能:

  • 实时距离测量与显示
  • 历史数据记录(最远/最近值)
  • 用户交互界面
  • 数据校准功能

在此基础上,我们可以考虑添加一些扩展功能:

  • 通过蓝牙模块上传数据到手机
  • 设置报警阈值
  • 数据记录与导出

1.2 硬件清单与连接

主控芯片:STM32F103C8T6开发板(性价比高,资源丰富)测距模块:HC-SR04超声波传感器(测量范围2cm-400cm)显示模块:12864液晶屏(带中文字库)其他组件

  • 按键 x4(功能控制)
  • LED指示灯 x2(状态显示)
  • 蜂鸣器(报警提示)
  • HC-05蓝牙模块(可选)

接线示意图:

模块STM32引脚说明
HC-SR04 TrigPB9触发信号输出
HC-SR04 EchoPF8回波信号输入
12864 SCLPB6I2C时钟线
12864 SDAPB7I2C数据线
按键1PA0模式切换
按键2PA1数据记录

2. 超声波测距模块的实现

2.1 工作原理与驱动代码

HC-SR04模块通过发送40kHz的超声波脉冲并接收回波来测量距离。计算公式为: 距离(cm) = (回波高电平时间 × 声速340m/s) / 2

以下是关键的初始化代码:

// 超声波模块初始化 void Ultrasonic_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 使能GPIO和AFIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOF|RCC_APB2Periph_AFIO, ENABLE); // 配置Trig引脚为推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); // 配置Echo引脚为浮空输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOF, &GPIO_InitStruct); // 配置外部中断 GPIO_EXTILineConfig(GPIO_PortSourceGPIOF, GPIO_PinSource8); EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line = EXTI_Line8; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); // 配置NVIC NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); }

2.2 测量流程优化

在实际应用中,我们需要考虑以下问题:

  1. 多次测量取平均值提高精度
  2. 添加超时处理防止卡死
  3. 温度补偿(声速随温度变化)

改进后的测量函数:

float Get_Distance(void) { float sum = 0; uint8_t valid_count = 0; for(int i=0; i<5; i++) { // 发送触发信号 GPIO_SetBits(GPIOB, GPIO_Pin_9); delay_us(20); GPIO_ResetBits(GPIOB, GPIO_Pin_9); // 等待回波信号 uint32_t timeout = 0; while(GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_8)==0) { if(++timeout > 10000) return -1; // 超时返回错误 delay_us(1); } // 测量高电平时间 uint32_t start = TIM2->CNT; while(GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_8)); uint32_t duration = TIM2->CNT - start; float distance = duration * 0.017; // 340m/s ÷ 2 ÷ 10000 if(distance > 2 && distance < 400) { // 有效范围判断 sum += distance; valid_count++; } delay_ms(50); // 两次测量间隔 } return valid_count>0 ? sum/valid_count : -1; }

提示:实际环境中超声波可能受到多种干扰,建议在代码中添加滤波算法,如中值滤波或卡尔曼滤波。

3. 用户界面设计与实现

3.1 12864液晶屏驱动

12864液晶屏可以通过I2C或并口驱动。这里我们使用常见的I2C方式,接线更简单:

// 初始化I2C void I2C_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; I2C_InitTypeDef I2C_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 配置PB6(SCL), PB7(SDA) GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x00; I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStruct.I2C_ClockSpeed = 100000; // 100kHz I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); }

3.2 界面布局与状态管理

一个良好的用户界面应该包含:

  • 实时测量数据显示区
  • 历史记录查看区
  • 系统状态指示区
  • 操作提示区

我们使用状态机模式来管理界面:

typedef enum { MODE_MEASURE, // 测量模式 MODE_HISTORY, // 历史记录查看 MODE_SETTING, // 系统设置 MODE_CALIBRATE // 校准模式 } SystemMode; void Update_Display(SystemMode mode) { LCD_Clear(); switch(mode) { case MODE_MEASURE: LCD_ShowString(0, 0, "当前距离:"); LCD_ShowFloat(60, 0, current_distance, 1); LCD_ShowString(120, 0, "cm"); LCD_ShowString(0, 2, "最大值:"); LCD_ShowFloat(60, 2, max_distance, 1); LCD_ShowString(0, 3, "最小值:"); LCD_ShowFloat(60, 3, min_distance, 1); break; case MODE_HISTORY: // 历史记录显示逻辑 break; // 其他模式显示... } // 显示公共元素 LCD_ShowString(90, 3, "BAT:80%"); }

4. 数据存储与蓝牙传输

4.1 EEPROM数据存储

为了防止断电数据丢失,我们需要将关键数据保存到EEPROM中。STM32内部没有真正的EEPROM,但可以用Flash模拟:

#define EEPROM_START_ADDR 0x0800F000 void EE_WriteFloat(uint32_t addr, float data) { uint32_t data_tmp = *(uint32_t*)&data; FLASH_Unlock(); FLASH_ErasePage(EEPROM_START_ADDR); FLASH_ProgramWord(EEPROM_START_ADDR + addr, data_tmp); FLASH_Lock(); } float EE_ReadFloat(uint32_t addr) { uint32_t data_tmp = *(uint32_t*)(EEPROM_START_ADDR + addr); return *(float*)&data_tmp; }

4.2 蓝牙模块集成

HC-05蓝牙模块可以通过串口与STM32通信,实现数据无线传输:

void USART1_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置PA9(TX), PA10(RX) GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStruct); USART_InitStruct.USART_BaudRate = baudrate; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStruct); USART_Cmd(USART1, ENABLE); } void Bluetooth_SendData(float distance) { char buffer[32]; sprintf(buffer, "DIST:%.1fcm\n", distance); for(int i=0; buffer[i]!='\0'; i++) { USART_SendData(USART1, buffer[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET); } }

5. 系统整合与调试技巧

5.1 主程序架构

一个好的嵌入式程序应该采用模块化设计,主循环保持简洁:

int main(void) { // 硬件初始化 System_Init(); Ultrasonic_Init(); LCD_Init(); Key_Init(); USART1_Init(9600); // 从EEPROM加载历史数据 max_distance = EE_ReadFloat(0); min_distance = EE_ReadFloat(4); while(1) { // 1. 读取按键 Key_Scan(); // 2. 测量距离 current_distance = Get_Distance(); // 3. 更新历史记录 if(current_distance > max_distance) { max_distance = current_distance; EE_WriteFloat(0, max_distance); } if(current_distance < min_distance) { min_distance = current_distance; EE_WriteFloat(4, min_distance); } // 4. 更新显示 Update_Display(current_mode); // 5. 蓝牙传输 if(bluetooth_enable) { Bluetooth_SendData(current_distance); } delay_ms(100); } }

5.2 常见问题排查

在实际制作过程中,你可能会遇到以下问题:

  1. 超声波模块无响应

    • 检查Trig和Echo接线是否正确
    • 确保供电电压在5V左右
    • 测量Trig信号是否正常发出(可用示波器观察)
  2. LCD显示异常

    • 确认I2C地址是否正确(通常0x3F或0x27)
    • 检查对比度调节电位器
    • 确保初始化序列完整
  3. 蓝牙连接不稳定

    • 检查波特率设置是否匹配
    • 确保模块进入AT模式时波特率为38400
    • 避免强电磁干扰环境

注意:调试时建议先单独测试每个模块,确认正常工作后再进行系统集成。使用逻辑分析仪或示波器可以大大简化调试过程。

http://www.jsqmd.com/news/754971/

相关文章:

  • 量子优化问题(QUBO)在路径规划中的应用与优化
  • 多模态语音识别:MoME框架提升复杂场景准确率
  • 用Multisim仿真带你玩转方波三角波发生器:从滞回比较器到ICL8038的保姆级教程
  • 告别Linux依赖!手把手教你用PowerShell在Windows下实现watch命令监控GPU状态
  • 避开这些坑!用STM32U5做IoT项目时,传感器选型和低功耗配置的实战心得
  • Pravega客户端开发完全指南:从基础API到高级特性
  • 对话系统开发:mirrors/unsloth/llama-3-8b-bnb-4bit聊天模板最佳实践
  • PCL 计算外接圆的半径【2026最新版】
  • 为OpenClaw构建私有搜索后端:基于SearXNG的桥接方案
  • 别再只会mvn package了!Maven打包插件实战:jar、shade、assembly到底怎么选?
  • 量子纠错码与逻辑门实现技术解析
  • 3步搞定Unity游戏实时翻译:XUnity.AutoTranslator完整指南
  • Onyx框架深度解析:高性能TypeScript Web开发实践
  • 本地部署开源AI对话应用LLMChat:从架构到实战的完整指南
  • Windows打印管理自动化:PowerShell脚本与WMI技术实战指南
  • Ollama网格搜索工具:自动化超参数调优与提示工程实践
  • 从激光笔到工业切割:一文看懂不同激光器(CO2/YAG/半导体)怎么选
  • Translumo终极指南:5分钟掌握免费开源实时屏幕翻译神器
  • 如何利用Real Toxicity Prompts改进你的语言模型:降低毒性输出的10个技巧
  • 别急着删文件!用 apt-key 和 add-apt-repository 科学管理 Ubuntu 软件源,告别 NO_PUBKEY
  • 2026年4月比较好的滚轮轴承厂家口碑推荐,凸轮轴承/平面滚针轴承/滚轮轴承/复合滚轮轴承,滚轮轴承源头厂家哪家可靠 - 品牌推荐师
  • 【信号处理】基于扩展的卡尔曼滤波器和无气体的卡尔曼滤波器对窄带信号的时变频率估计附matlab代码
  • 如何配置 mkdocstrings:从基础设置到高级选项详解
  • Oh My Zsh与低代码平台:加速应用开发流程的终极指南
  • PCL common模块应用实例【2026最新版】
  • 深度学习模型低比特量化技术实践与优化
  • Node.js 中 async await 与 Generator 函数实现异步的区别对比
  • Java集成OpenAI API:kousen/OpenAIClient增强库实战指南
  • 投资3000亿,日本汽车转向下一个与中国相当的市场,新的希望?
  • OrchardKit:现代Web应用UI组件库的设计哲学与工程实践