基于立创开发板GD32E230C8T6的HC-SR04超声波测距传感器驱动移植与实战最近在做一个智能小车项目需要用到超声波传感器来避障。手头正好有立创的GD32E230C8T6开发板就想试试看能不能把常见的HC-SR04超声波模块驱动起来。网上资料虽然多但针对GD32这个国产MCU的完整教程还真不好找。折腾了两天总算把驱动调通了测距效果挺稳定。今天我就把整个移植和实战过程整理出来分享给正在学习GD32或者需要用到超声波测距的朋友们。咱们这个教程会手把手带你完成三件事第一理解HC-SR04传感器是怎么工作的第二把驱动代码移植到GD32E230工程里第三配置好定时器和GPIO最终实现精准测距。就算你是嵌入式新手跟着步骤走也能搞定。1. 准备工作认识你的“眼睛”HC-SR04在写代码之前咱们得先搞清楚要驱动的对象——HC-SR04超声波测距模块。这玩意儿就像给单片机装上了一双“眼睛”能“看”到前方物体的距离。1.1 模块长啥样怎么接线HC-SR04模块很小巧就4个引脚VCC接电源正极工作电压是3.3V到5V咱们的开发板有3.3V输出直接接上就行。GND接地接开发板的GND。Trig触发信号输入引脚。这个引脚由我们的单片机控制给它一个高电平脉冲模块就会发射超声波。Echo回响信号输出引脚。模块收到返回的超声波后会通过这个引脚输出一个高电平脉冲脉冲的宽度就代表了距离的远近。模块的探测角度小于15度方向性比较好。测量范围官方说是2厘米到4米实际使用中2-3米内比较可靠。1.2 它到底是怎么测出距离的原理其实不复杂和蝙蝠用回声定位差不多。整个过程就像一次“喊话-听回声”的对话单片机“喊话”咱们通过Trig引脚给模块一个至少10微秒10us的高电平信号。模块一收到这个“开始”命令内部就会发射一束8个40kHz的超声波脉冲。超声波“飞行”这束超声波在空气中向前传播遇到障碍物就会反射回来。模块“听回声”模块自己有个接收器一直在监听。一旦它收到了返回的超声波就会立刻把Echo引脚拉成高电平。单片机“计时”从超声波发射开始Trig信号结束咱们的单片机就得紧盯着Echo引脚。一旦发现Echo变成高电平马上启动一个高精度定时器开始计时。直到Echo引脚变回低电平计时停止。计算距离这个计时得到的时间就是超声波从发射到返回的总时间记为t。声音在空气中的速度大约是340米/秒。那么距离S (速度 * 时间) / 2。为什么要除以2因为时间是超声波“跑了个来回”的时间单程距离就是一半。用公式表示就是距离厘米 (高电平时间 * 34000) / 2。为了计算方便通常把34000/2 17000厘米/秒 再换算一下因为1秒10^6微秒所以17000 / 1000000 0.017厘米/微秒。取个倒数就得到了常用的经验公式距离厘米 ≈ 高电平时间微秒 / 58。这个公式在代码里会直接用到。注意当测量距离超出模块范围比如前面没障碍物时Echo引脚会输出一个约66毫秒的固定宽度高电平然后自动变低。所以我们的程序必须能处理这种情况避免一直傻等。2. 代码移植把轮子装到我们的车上理解了原理接下来就是动手写代码了。原始资料里提供了完整的驱动代码我们需要做的就是把它“搬”到我们自己的GD32工程里并根据自己的硬件连接做点小修改。2.1 建立驱动文件首先在你的工程目录下比如User或BSP文件夹里新建两个文件bsp_ultrasonic.cbsp_ultrasonic.h这两个文件将包含所有超声波驱动的函数和定义。把代码分开到.c和.h文件是好习惯方便管理和重复使用。2.2 编写头文件 (bsp_ultrasonic.h)头文件主要是做声明和定义。最关键的是你要根据自己开发板上的实际接线来修改引脚定义。#ifndef _BSP_ULTRASONIC_H_ #define _BSP_ULTRASONIC_H_ #include gd32e23x.h // 根据你的实际接线修改下面这6行 // 这里示例使用 GPIOA 的第6脚作为Trig第7脚作为Echo #define RCU_TRIG RCU_GPIOA // Trig引脚所在的GPIO端口时钟 #define PORT_TRIG GPIOA // Trig引脚所在的GPIO端口 #define GPIO_TRIG GPIO_PIN_6 // Trig引脚号 #define RCU_ECHO RCU_GPIOA // Echo引脚所在的GPIO端口时钟 #define PORT_ECHO GPIOA // Echo引脚所在的GPIO端口 #define GPIO_ECHO GPIO_PIN_7 // Echo引脚号 // 函数声明 void Ultrasonic_Init(void); // 超声波模块初始化 float Hcsr04GetLength(void); // 启动一次测量并获取距离单位厘米 float Get_distance(void); // 获取上一次测量的距离值 #endif提示务必确认你的开发板上Trig和Echo引脚连接到了哪个GPIO口。如果接的是GPIOB的第0脚和第1脚那么就应该改为GPIO_PIN_0和GPIO_PIN_1端口改为GPIOB时钟改为RCU_GPIOB。2.3 编写源文件 (bsp_ultrasonic.c)这是驱动的核心代码有点长但别怕我拆开一步步讲。你先将下面的完整代码复制到你的bsp_ultrasonic.c文件中。#include bsp_ultrasonic.h #include systick.h // 需要用到毫秒延时函数 delay_1ms 和微秒延时 delay_1us #include stdio.h // 用于printf调试如果不用可以删除 unsigned char msHcCount 0; // 毫秒计数器用于定时器中断 float distance 0; // 存储上一次测量的距离 // 超声波模块初始化函数 void Ultrasonic_Init(void) { // 1. 定义定时器初始化结构体 timer_parameter_struct timer_initpara; // 2. 使能Trig和Echo引脚对应的GPIO端口时钟 rcu_periph_clock_enable(RCU_TRIG); rcu_periph_clock_enable(RCU_ECHO); // 3. 使能定时器5的时钟我们用它来高精度计时 rcu_periph_clock_enable(RCU_TIMER5); /* 配置Trig引脚为输出模式用于触发模块 */ // 设置为推挽输出模式内部上拉 gpio_mode_set(PORT_TRIG, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_TRIG); // 输出选项推挽输出速度50MHz gpio_output_options_set(PORT_TRIG, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_TRIG); // 初始输出低电平 gpio_bit_write(PORT_TRIG, GPIO_TRIG, RESET); /* 配置Echo引脚为输入模式用于接收回响信号 */ // 设置为浮空输入模式因为模块输出就是标准高低电平 gpio_mode_set(PORT_ECHO, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_ECHO); // 初始化引脚电平可省略 gpio_bit_write(PORT_ECHO, GPIO_ECHO, RESET); /* 4. 配置定时器5作为高精度计时器 */ timer_deinit(TIMER5); // 先复位定时器5 // 配置定时器参数 timer_initpara.period 999; // 自动重装载值决定计数周期 timer_initpara.prescaler 71; // 预分频值 timer_initpara.clockdivision TIMER_CKDIV_DIV1; // 时钟分频 timer_initpara.alignedmode TIMER_COUNTER_EDGE; // 边沿对齐模式 timer_initpara.counterdirection TIMER_COUNTER_UP; // 向上计数 // 重复计数器高级定时器才有这里设为0 timer_initpara.repetitioncounter 0; // 初始化定时器5 timer_init(TIMER5, timer_initpara); /* 5. 配置定时器中断 */ // 设置定时器5更新中断的优先级这里设为0可调整 nvic_irq_enable(TIMER5_IRQn, 0); // 使能定时器5的更新中断 timer_interrupt_enable(TIMER5, TIMER_INT_UP); // 6. 使能定时器的主输出和自动重装载预装载按标准流程配置 timer_primary_output_config(TIMER5, ENABLE); timer_auto_reload_shadow_enable(TIMER5); // 注意这里先不使能定时器等需要测量时再开启 // timer_enable(TIMER5); } // 定时器5的中断服务函数 // 这个函数每1ms被调用一次用于累加毫秒数 void TIMER5_IRQHandler(void) { if(timer_interrupt_flag_get(TIMER5, TIMER_INT_UP) ! RESET) { msHcCount; // 毫秒计数器加1 timer_interrupt_flag_clear(TIMER5, TIMER_INT_UP); // 清除中断标志 } } // 内部函数启动定时器 static void OpenTimer(void) { timer_counter_value_config(TIMER5, 0); // 清零计数器 msHcCount 0; // 清零毫秒计数器 timer_enable(TIMER5); // 使能定时器开始计数 } // 内部函数关闭定时器 static void CloseTimer(void) { timer_disable(TIMER5); // 关闭定时器 } // 内部函数获取Echo高电平的持续时间单位微秒 static unsigned int GetEchoTimer(void) { unsigned int time 0; // 计算总时间 毫秒部分 * 1000 微秒部分 time msHcCount * 1000; // msHcCount是毫秒数转换成微秒 time timer_counter_read(TIMER5); // 加上定时器当前的计数值1个计数值1微秒 timer_counter_value_config(TIMER5, 0); // 读取后清零定时器为下次做准备 delay_1ms(10); // 稍作延时让硬件稳定 return time; // 返回时间单位是微秒(us) } // 核心函数执行一次测距返回距离值单位厘米 float Hcsr04GetLength(void) { float length 0; float t 0; float sum 0; unsigned int i 0; // 连续测量5次取平均值提高稳定性 while(i ! 5) { // 1. 发送触发信号给Trig引脚一个20us的高脉冲 gpio_bit_write(PORT_TRIG, GPIO_TRIG, SET); // 拉高 delay_1us(20); // 维持20微秒 gpio_bit_write(PORT_TRIG, GPIO_TRIG, RESET); // 拉低触发完成 // 2. 等待Echo引脚变高回响信号开始 while(gpio_input_bit_get(PORT_ECHO, GPIO_ECHO) 0); // 3. Echo变高立刻启动定时器开始计时 OpenTimer(); i i 1; // 测量次数加1 // 4. 等待Echo引脚变低回响信号结束 while(gpio_input_bit_get(PORT_ECHO, GPIO_ECHO) 1); // 5. Echo变低停止定时器 CloseTimer(); // 6. 获取高电平持续时间并计算单次距离 t GetEchoTimer(); // 得到时间t单位微秒 length (float)t / 58.0f; // 套用公式距离(厘米) 时间(us) / 58 sum length; // 累加 } // 7. 计算5次测量的平均值 length sum / 5.0f; distance length; // 存储到全局变量 // 8. 通过串口打印结果调试用 printf(%f\r\n, length); return length; } // 辅助函数获取上一次测量的距离值 float Get_distance(void) { return distance; }代码关键点解析定时器配置我们用了TIMER5来做高精度计时。代码里prescaler71,period999是怎么来的GD32E230的主频是72MHz定时器时钟也是72MHz。预分频71意味着计数器每72个时钟周期才加1所以计数器频率是72MHz / (711) 1MHz即每计数一次代表1微秒。period999表示计到999即1000微秒后溢出并产生中断我们在中断里记录溢出的次数msHcCount这样就能测量超过1000us的时间了。测量流程Hcsr04GetLength函数完整再现了之前讲的“触发-等待回响-计时”流程。里面那个while循环等待引脚电平变化在嵌入式里叫“轮询”虽然简单但很有效。滤波处理代码里做了5次测量取平均这是为了消除偶然误差让读数更稳定。实际项目中根据环境干扰情况你可能需要调整这个次数。3. 实战应用让代码跑起来驱动写好了最后一步就是在主函数里调用它看看效果。3.1 修改主函数 (main.c)打开你的main.c文件添加头文件并初始化模块。#include gd32e23x.h #include systick.h #include bsp_usart.h // 假设你有串口初始化代码用于printf打印 #include stdio.h #include bsp_ultrasonic.h // 包含我们刚写的超声波驱动头文件 int main(void) { // 系统初始化 systick_config(); // 初始化系统滴答定时器提供delay函数 usart_gpio_config(115200U); // 初始化串口波特率115200用于打印数据 // 超声波模块初始化 Ultrasonic_Init(); printf(Ultrasonic Sensor Test Start!\r\n); while(1) { // 每隔1秒测量并打印一次距离 float current_distance Hcsr04GetLength(); // 执行一次测量 printf(Distance %.2f cm\r\n, current_distance); // 打印距离保留两位小数 delay_1ms(1000); // 延时1秒 } }3.2 连接硬件与上电测试接线按照之前的定义把HC-SR04模块的VCC、GND、Trig、Echo分别接到开发板的3.3V、GND、PA6、PA7。一定要仔细核对接错可能烧坏模块编译下载用你的IDE比如Keil MDK编译整个工程确保没有错误然后下载到GD32E230开发板中。观察结果打开串口调试助手如Putty、SecureCRT设置好波特率115200。你应该能看到终端里每隔一秒打印出一行距离数据。效果展示 在传感器前方约20厘米处放置一个障碍物串口输出的数据应该稳定在20.xx cm附近。移动障碍物数据会随之变化。3.3 可能遇到的问题与调试技巧读数一直是0或者某个极大值首先检查Trig和Echo的引脚定义是否和你的实际接线一致。然后用示波器或者逻辑分析仪看一下Trig引脚有没有发出那个10-20us的脉冲Echo引脚有没有返回高电平。如果没有检查GPIO初始化代码。读数波动很大超声波对测量表面的材质很敏感。光滑坚硬的表面如墙壁反射好读数准柔软、多孔的表面如窗帘会吸收声波导致回波弱甚至测不到。尽量让传感器正对被测物体。测量距离很短确保供电电压足够3.3V以上并且VCC和GND之间并联了一个10uF以上的电容可以稳定电源减少干扰。定时器时间不准检查系统时钟配置确保主频是72MHz。核对定时器预分频和重装载值的计算。移植成功后这个驱动就可以直接用到你的智能小车、避障机器人或者液位检测项目里了。代码中通过全局变量distance保存了最近一次测量的结果其他模块需要距离数据时直接调用Get_distance()函数获取即可非常方便。
基于立创开发板GD32E230C8T6的HC-SR04超声波测距传感器驱动移植与实战
基于立创开发板GD32E230C8T6的HC-SR04超声波测距传感器驱动移植与实战最近在做一个智能小车项目需要用到超声波传感器来避障。手头正好有立创的GD32E230C8T6开发板就想试试看能不能把常见的HC-SR04超声波模块驱动起来。网上资料虽然多但针对GD32这个国产MCU的完整教程还真不好找。折腾了两天总算把驱动调通了测距效果挺稳定。今天我就把整个移植和实战过程整理出来分享给正在学习GD32或者需要用到超声波测距的朋友们。咱们这个教程会手把手带你完成三件事第一理解HC-SR04传感器是怎么工作的第二把驱动代码移植到GD32E230工程里第三配置好定时器和GPIO最终实现精准测距。就算你是嵌入式新手跟着步骤走也能搞定。1. 准备工作认识你的“眼睛”HC-SR04在写代码之前咱们得先搞清楚要驱动的对象——HC-SR04超声波测距模块。这玩意儿就像给单片机装上了一双“眼睛”能“看”到前方物体的距离。1.1 模块长啥样怎么接线HC-SR04模块很小巧就4个引脚VCC接电源正极工作电压是3.3V到5V咱们的开发板有3.3V输出直接接上就行。GND接地接开发板的GND。Trig触发信号输入引脚。这个引脚由我们的单片机控制给它一个高电平脉冲模块就会发射超声波。Echo回响信号输出引脚。模块收到返回的超声波后会通过这个引脚输出一个高电平脉冲脉冲的宽度就代表了距离的远近。模块的探测角度小于15度方向性比较好。测量范围官方说是2厘米到4米实际使用中2-3米内比较可靠。1.2 它到底是怎么测出距离的原理其实不复杂和蝙蝠用回声定位差不多。整个过程就像一次“喊话-听回声”的对话单片机“喊话”咱们通过Trig引脚给模块一个至少10微秒10us的高电平信号。模块一收到这个“开始”命令内部就会发射一束8个40kHz的超声波脉冲。超声波“飞行”这束超声波在空气中向前传播遇到障碍物就会反射回来。模块“听回声”模块自己有个接收器一直在监听。一旦它收到了返回的超声波就会立刻把Echo引脚拉成高电平。单片机“计时”从超声波发射开始Trig信号结束咱们的单片机就得紧盯着Echo引脚。一旦发现Echo变成高电平马上启动一个高精度定时器开始计时。直到Echo引脚变回低电平计时停止。计算距离这个计时得到的时间就是超声波从发射到返回的总时间记为t。声音在空气中的速度大约是340米/秒。那么距离S (速度 * 时间) / 2。为什么要除以2因为时间是超声波“跑了个来回”的时间单程距离就是一半。用公式表示就是距离厘米 (高电平时间 * 34000) / 2。为了计算方便通常把34000/2 17000厘米/秒 再换算一下因为1秒10^6微秒所以17000 / 1000000 0.017厘米/微秒。取个倒数就得到了常用的经验公式距离厘米 ≈ 高电平时间微秒 / 58。这个公式在代码里会直接用到。注意当测量距离超出模块范围比如前面没障碍物时Echo引脚会输出一个约66毫秒的固定宽度高电平然后自动变低。所以我们的程序必须能处理这种情况避免一直傻等。2. 代码移植把轮子装到我们的车上理解了原理接下来就是动手写代码了。原始资料里提供了完整的驱动代码我们需要做的就是把它“搬”到我们自己的GD32工程里并根据自己的硬件连接做点小修改。2.1 建立驱动文件首先在你的工程目录下比如User或BSP文件夹里新建两个文件bsp_ultrasonic.cbsp_ultrasonic.h这两个文件将包含所有超声波驱动的函数和定义。把代码分开到.c和.h文件是好习惯方便管理和重复使用。2.2 编写头文件 (bsp_ultrasonic.h)头文件主要是做声明和定义。最关键的是你要根据自己开发板上的实际接线来修改引脚定义。#ifndef _BSP_ULTRASONIC_H_ #define _BSP_ULTRASONIC_H_ #include gd32e23x.h // 根据你的实际接线修改下面这6行 // 这里示例使用 GPIOA 的第6脚作为Trig第7脚作为Echo #define RCU_TRIG RCU_GPIOA // Trig引脚所在的GPIO端口时钟 #define PORT_TRIG GPIOA // Trig引脚所在的GPIO端口 #define GPIO_TRIG GPIO_PIN_6 // Trig引脚号 #define RCU_ECHO RCU_GPIOA // Echo引脚所在的GPIO端口时钟 #define PORT_ECHO GPIOA // Echo引脚所在的GPIO端口 #define GPIO_ECHO GPIO_PIN_7 // Echo引脚号 // 函数声明 void Ultrasonic_Init(void); // 超声波模块初始化 float Hcsr04GetLength(void); // 启动一次测量并获取距离单位厘米 float Get_distance(void); // 获取上一次测量的距离值 #endif提示务必确认你的开发板上Trig和Echo引脚连接到了哪个GPIO口。如果接的是GPIOB的第0脚和第1脚那么就应该改为GPIO_PIN_0和GPIO_PIN_1端口改为GPIOB时钟改为RCU_GPIOB。2.3 编写源文件 (bsp_ultrasonic.c)这是驱动的核心代码有点长但别怕我拆开一步步讲。你先将下面的完整代码复制到你的bsp_ultrasonic.c文件中。#include bsp_ultrasonic.h #include systick.h // 需要用到毫秒延时函数 delay_1ms 和微秒延时 delay_1us #include stdio.h // 用于printf调试如果不用可以删除 unsigned char msHcCount 0; // 毫秒计数器用于定时器中断 float distance 0; // 存储上一次测量的距离 // 超声波模块初始化函数 void Ultrasonic_Init(void) { // 1. 定义定时器初始化结构体 timer_parameter_struct timer_initpara; // 2. 使能Trig和Echo引脚对应的GPIO端口时钟 rcu_periph_clock_enable(RCU_TRIG); rcu_periph_clock_enable(RCU_ECHO); // 3. 使能定时器5的时钟我们用它来高精度计时 rcu_periph_clock_enable(RCU_TIMER5); /* 配置Trig引脚为输出模式用于触发模块 */ // 设置为推挽输出模式内部上拉 gpio_mode_set(PORT_TRIG, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_TRIG); // 输出选项推挽输出速度50MHz gpio_output_options_set(PORT_TRIG, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_TRIG); // 初始输出低电平 gpio_bit_write(PORT_TRIG, GPIO_TRIG, RESET); /* 配置Echo引脚为输入模式用于接收回响信号 */ // 设置为浮空输入模式因为模块输出就是标准高低电平 gpio_mode_set(PORT_ECHO, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_ECHO); // 初始化引脚电平可省略 gpio_bit_write(PORT_ECHO, GPIO_ECHO, RESET); /* 4. 配置定时器5作为高精度计时器 */ timer_deinit(TIMER5); // 先复位定时器5 // 配置定时器参数 timer_initpara.period 999; // 自动重装载值决定计数周期 timer_initpara.prescaler 71; // 预分频值 timer_initpara.clockdivision TIMER_CKDIV_DIV1; // 时钟分频 timer_initpara.alignedmode TIMER_COUNTER_EDGE; // 边沿对齐模式 timer_initpara.counterdirection TIMER_COUNTER_UP; // 向上计数 // 重复计数器高级定时器才有这里设为0 timer_initpara.repetitioncounter 0; // 初始化定时器5 timer_init(TIMER5, timer_initpara); /* 5. 配置定时器中断 */ // 设置定时器5更新中断的优先级这里设为0可调整 nvic_irq_enable(TIMER5_IRQn, 0); // 使能定时器5的更新中断 timer_interrupt_enable(TIMER5, TIMER_INT_UP); // 6. 使能定时器的主输出和自动重装载预装载按标准流程配置 timer_primary_output_config(TIMER5, ENABLE); timer_auto_reload_shadow_enable(TIMER5); // 注意这里先不使能定时器等需要测量时再开启 // timer_enable(TIMER5); } // 定时器5的中断服务函数 // 这个函数每1ms被调用一次用于累加毫秒数 void TIMER5_IRQHandler(void) { if(timer_interrupt_flag_get(TIMER5, TIMER_INT_UP) ! RESET) { msHcCount; // 毫秒计数器加1 timer_interrupt_flag_clear(TIMER5, TIMER_INT_UP); // 清除中断标志 } } // 内部函数启动定时器 static void OpenTimer(void) { timer_counter_value_config(TIMER5, 0); // 清零计数器 msHcCount 0; // 清零毫秒计数器 timer_enable(TIMER5); // 使能定时器开始计数 } // 内部函数关闭定时器 static void CloseTimer(void) { timer_disable(TIMER5); // 关闭定时器 } // 内部函数获取Echo高电平的持续时间单位微秒 static unsigned int GetEchoTimer(void) { unsigned int time 0; // 计算总时间 毫秒部分 * 1000 微秒部分 time msHcCount * 1000; // msHcCount是毫秒数转换成微秒 time timer_counter_read(TIMER5); // 加上定时器当前的计数值1个计数值1微秒 timer_counter_value_config(TIMER5, 0); // 读取后清零定时器为下次做准备 delay_1ms(10); // 稍作延时让硬件稳定 return time; // 返回时间单位是微秒(us) } // 核心函数执行一次测距返回距离值单位厘米 float Hcsr04GetLength(void) { float length 0; float t 0; float sum 0; unsigned int i 0; // 连续测量5次取平均值提高稳定性 while(i ! 5) { // 1. 发送触发信号给Trig引脚一个20us的高脉冲 gpio_bit_write(PORT_TRIG, GPIO_TRIG, SET); // 拉高 delay_1us(20); // 维持20微秒 gpio_bit_write(PORT_TRIG, GPIO_TRIG, RESET); // 拉低触发完成 // 2. 等待Echo引脚变高回响信号开始 while(gpio_input_bit_get(PORT_ECHO, GPIO_ECHO) 0); // 3. Echo变高立刻启动定时器开始计时 OpenTimer(); i i 1; // 测量次数加1 // 4. 等待Echo引脚变低回响信号结束 while(gpio_input_bit_get(PORT_ECHO, GPIO_ECHO) 1); // 5. Echo变低停止定时器 CloseTimer(); // 6. 获取高电平持续时间并计算单次距离 t GetEchoTimer(); // 得到时间t单位微秒 length (float)t / 58.0f; // 套用公式距离(厘米) 时间(us) / 58 sum length; // 累加 } // 7. 计算5次测量的平均值 length sum / 5.0f; distance length; // 存储到全局变量 // 8. 通过串口打印结果调试用 printf(%f\r\n, length); return length; } // 辅助函数获取上一次测量的距离值 float Get_distance(void) { return distance; }代码关键点解析定时器配置我们用了TIMER5来做高精度计时。代码里prescaler71,period999是怎么来的GD32E230的主频是72MHz定时器时钟也是72MHz。预分频71意味着计数器每72个时钟周期才加1所以计数器频率是72MHz / (711) 1MHz即每计数一次代表1微秒。period999表示计到999即1000微秒后溢出并产生中断我们在中断里记录溢出的次数msHcCount这样就能测量超过1000us的时间了。测量流程Hcsr04GetLength函数完整再现了之前讲的“触发-等待回响-计时”流程。里面那个while循环等待引脚电平变化在嵌入式里叫“轮询”虽然简单但很有效。滤波处理代码里做了5次测量取平均这是为了消除偶然误差让读数更稳定。实际项目中根据环境干扰情况你可能需要调整这个次数。3. 实战应用让代码跑起来驱动写好了最后一步就是在主函数里调用它看看效果。3.1 修改主函数 (main.c)打开你的main.c文件添加头文件并初始化模块。#include gd32e23x.h #include systick.h #include bsp_usart.h // 假设你有串口初始化代码用于printf打印 #include stdio.h #include bsp_ultrasonic.h // 包含我们刚写的超声波驱动头文件 int main(void) { // 系统初始化 systick_config(); // 初始化系统滴答定时器提供delay函数 usart_gpio_config(115200U); // 初始化串口波特率115200用于打印数据 // 超声波模块初始化 Ultrasonic_Init(); printf(Ultrasonic Sensor Test Start!\r\n); while(1) { // 每隔1秒测量并打印一次距离 float current_distance Hcsr04GetLength(); // 执行一次测量 printf(Distance %.2f cm\r\n, current_distance); // 打印距离保留两位小数 delay_1ms(1000); // 延时1秒 } }3.2 连接硬件与上电测试接线按照之前的定义把HC-SR04模块的VCC、GND、Trig、Echo分别接到开发板的3.3V、GND、PA6、PA7。一定要仔细核对接错可能烧坏模块编译下载用你的IDE比如Keil MDK编译整个工程确保没有错误然后下载到GD32E230开发板中。观察结果打开串口调试助手如Putty、SecureCRT设置好波特率115200。你应该能看到终端里每隔一秒打印出一行距离数据。效果展示 在传感器前方约20厘米处放置一个障碍物串口输出的数据应该稳定在20.xx cm附近。移动障碍物数据会随之变化。3.3 可能遇到的问题与调试技巧读数一直是0或者某个极大值首先检查Trig和Echo的引脚定义是否和你的实际接线一致。然后用示波器或者逻辑分析仪看一下Trig引脚有没有发出那个10-20us的脉冲Echo引脚有没有返回高电平。如果没有检查GPIO初始化代码。读数波动很大超声波对测量表面的材质很敏感。光滑坚硬的表面如墙壁反射好读数准柔软、多孔的表面如窗帘会吸收声波导致回波弱甚至测不到。尽量让传感器正对被测物体。测量距离很短确保供电电压足够3.3V以上并且VCC和GND之间并联了一个10uF以上的电容可以稳定电源减少干扰。定时器时间不准检查系统时钟配置确保主频是72MHz。核对定时器预分频和重装载值的计算。移植成功后这个驱动就可以直接用到你的智能小车、避障机器人或者液位检测项目里了。代码中通过全局变量distance保存了最近一次测量的结果其他模块需要距离数据时直接调用Get_distance()函数获取即可非常方便。