1. 实现方案
本任务使用三条独立链路:
HC-SR04 TRIG/ECHO --GPIO--> MCXA153 --LPUART0/MCU-Link--> 电脑 | +--LPI2C0--> SSD1306 OLED
HC-SR04 不是串口传感器。MCU 给 TRIG 输出至少 10 us 的高电平,模块在 ECHO 输出与声波往返时间成正比的高电平脉冲。Zephyr 内置 hc_sr04 驱动完成 GPIO 配置、边沿测量和距离换算;应用通过 sensor_sample_fetch() 和 sensor_channel_get() 获取距离。
2. 硬件连接
| OLED VCC | 3V3 | 不建议接 5 V |
| OLED GND | GND | 共地 |
| OLED SDA | Arduino D14 / P1_8 | LPI2C0 SDA |
| OLED SCL | Arduino D15 / P1_9 | LPI2C0 SCL |
| HC-SR04 VCC | 5 V | 按模块规格供电 |
| HC-SR04 GND | GND | 与 MCU 共地 |
| HC-SR04 TRIG | Arduino D2 / P2_4 | MCU 输出 |
| HC-SR04 ECHO | Arduino D7 / P3_1 | MCU 输出 |
电脑通过 USB 连接开发板的 J15,J15 对应 MCU-Link 虚拟串口,不需要再占用 Arduino UART 引脚。串口终端设置为 115200, 8 data bits, no parity, 1 stop bit。
3. 设备树配置
应用中的 boards/frdm_mcxa153.overlay 完成两项声明:
在 lpi2c0 下声明 solomon,ssd1306,地址为 0x3c,分辨率为 128x64。
声明 hc-sr04 节点,TRIG 为 gpio2[4],ECHO 为 gpio3[1]。
如果 OLED 扫描到的地址是 0x3d,把 reg = <0x3c>; 改为 reg = <0x3d>;。如果屏幕控制器是 SH1106,应将 compatible 改为 Zephyr 支持的 SH1106 绑定,并按实际分辨率调整参数。
4. Kconfig 配置
prj.conf 打开了:
CONFIG_SENSOR、CONFIG_HC_SR04:传感器 API 和 HC-SR04 驱动
CONFIG_I2C、CONFIG_DISPLAY:I2C 和显示框架
CONFIG_CHARACTER_FRAMEBUFFER:在单色 OLED 上显示字符
CONFIG_CONSOLE、CONFIG_UART_CONSOLE、CONFIG_PRINTK:向电脑发送串口日志
5. 构建、烧录和观察输出
构建和烧录可直接使用工程现有的 Zephyr 工作区配置,本文重点放在应用代码本身。烧录后打开 J15 对应的 MCU-Link 虚拟串口(115200、8N1),按下 RESET 即可观察输出:
Ultrasonic OLED demo started SW2 toggles the display unit: cm / m distance_raw=234 mm distance_filtered=234 mm range=cm
6. 代码实现分析
应用主体位于 src/main.c,代码按照“设备初始化—采样—数据处理—输出显示—周期等待”的流程运行。
6.1 通过设备树获取外设
#define SENSOR_NODE DT_NODELABEL(hcsr04) #define DISPLAY_NODE DT_CHOSEN(zephyr_display) #define BUTTON_NODE DT_ALIAS(sw0) static const struct device *const g_sensor = DEVICE_DT_GET(SENSOR_NODE); static const struct device *const g_display = DEVICE_DT_GET(DISPLAY_NODE); static const struct gpio_dt_spec g_button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios);
代码不直接写死 GPIO 寄存器,而是通过设备树节点获得设备对象:
hcsr04 对应 overlay 中的 HC-SR04 节点,驱动负责 TRIG 输出和 ECHO 脉宽测量;
zephyr_display 指向 SSD1306 OLED;
sw0 是板卡 DTS 中的 SW2 按键别名。
device_is_ready() 在运行时检查驱动是否初始化成功,避免在设备不可用时继续访问硬件。
6.2 HC-SR04 测距调用
int result = sensor_sample_fetch(g_sensor);
if (result == 0) {
result = sensor_channel_get(g_sensor,
SENSOR_CHAN_DISTANCE,
&distance);
}sensor_sample_fetch() 触发一次测量。Zephyr 的 HC-SR04 驱动会输出约 10 us 的 TRIG 脉冲,然后通过 ECHO 的上升沿和下降沿计算高电平时间。sensor_channel_get() 将结果写入 struct sensor_value。
sensor_value 使用整数部分 val1 和微单位部分 val2 表示,因此程序将其转换为毫米:
int32_t raw_mm = (int32_t)distance.val1 * 1000 + distance.val2 / 1000;
串口保留毫米单位,便于判断传感器的原始输出和定位接线问题。
6.3 五点滑动平均滤波
#define FILTER_WINDOW 5 static int32_t g_filter_values[FILTER_WINDOW];
moving_average_mm() 使用环形数组保存最近 5 次有效采样。每次写入后遍历当前有效样本求平均:
filtered = (sample[n] + sample[n-1] + ... + sample[n-4]) / N
刚启动时 N 小于 5,只对已经采集到的样本求平均,因此不会因为数组尚未填满而引入额外的零值。滤波后的数值用于 OLED 显示,同时串口同时输出 raw 和 filtered 便于对比滤波效果。
6.4 OLED 初始化和刷新
display_init() 依次完成以下工作:
检查 SSD1306 设备状态;
设置单色像素格式;
初始化 Zephyr Character Framebuffer(CFB);
选择设备支持的字体;
清屏并关闭显示 blanking。
每次测量后,display_distance() 先清除上一帧,再按固定坐标绘制四行内容:
cfb_framebuffer_clear(g_display, false); cfb_print(g_display, "Distance:", 0, 0); cfb_print(g_display, distance_line, 0, 16); cfb_print(g_display, range_line, 0, 32); cfb_print(g_display, "Filter: AVG5", 0, 48); cfb_framebuffer_finalize(g_display);
短量程模式直接将毫米转换为一位小数的厘米,例如 234 mm 显示为 23.4 cm;长量程模式显示为米并保留三位小数,例如 234 mm 显示为 0.234 m。超过当前模式的最大值时显示 OUT OF RANGE。
6.5 SW2 按键中断和消抖
static volatile bool g_long_range;
SW2 配置为 GPIO 中断,按键有效沿触发 button_pressed()。回调通过 k_uptime_get_32() 记录时间,只有相邻两次触发间隔达到 250 ms 才切换 g_long_range,从而抑制机械按键抖动。
该变量只改变显示单位和显示量程,不会改变 HC-SR04 的硬件测量范围:
false:显示 0-100 cm;
true:显示 0-4 m。
6.6 主循环和异常处理
主循环每 250 ms 执行一次测距。测量成功时依次完成单位转换、滑动平均、串口打印和 OLED 刷新;驱动返回错误时打印错误码,并在 OLED 上显示 Sensor error,不会把错误数据当成 0 cm。
整体数据流如下:
TRIG/ECHO -> sensor_sample_fetch() -> sensor_channel_get() -> 毫米换算 -> AVG5 滤波 -> UART 输出 + OLED 显示 -> 延时 250 ms 后重复
串口数据查看:

屏幕显示信息:

我要赚赏金
