你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【STM32WB5MM-DK | Zephyr】5、STM32WB5MM-DK 驱动 VL53L0CX激光测距

[复制链接]
lugl 发布时间:2026-11-4 13:41

目标:在现有 OLED 工程基础上,实现 VL53L0CXV0DH/1 ToF(Time-of-Flight)传感器测距, 并把距离值实时显示到 OLED 上。

1. 硬件

开发板上板载了VL53L0CXV0DH/1,原理图上编号为U6

image.png

通过开发板原理图的分析,该传感器通过i2c3接入stm32wb5mm,GPIO1经PD9,并有上拉电阻,XSHUT通过10K的电阻上拉到3.3v,即上电即可用。

经整理如下表所示:

image.png

2. 设备树

通过分析zephyr中stm32wb5mm-dk的设备树,i2c3已经是启用了的,在前几期的i2c中我们已经启用。

同时vl53l0x也是配置好了的。

D:\luglZephyrproject\zephyr\boards\st\stm32wb5mm_dk\stm32wb5mm_dk.dts:191:

&i2c3 {
    pinctrl-0 = <&i2c3_scl_pb13 &i2c3_sda_pb11>;
    status = "okay";

    vl53l0x@29 {
        compatible = "st,vl53l0x";
        reg = <0x29>;
        xshut-gpios = <&gpioc 6 GPIO_ACTIVE_LOW>;  /* 与硬件不符,见第 2 步 */
    };
};

但是开发板上的xshut是没有按照官方的设备树节点来连接的,PC6是应用到了触摸那里,因此如果要使用pc6的话,需要进行禁止。

我在boards/stm32wb5mm_dk.overlay里把他删除掉:

&i2c3 {
	vl53l0x@29 {
		/* 硬件上 XSHUT 经 R10 上拉到 3V3,未接 PC6;
		 * 删除上游 dts 中与实际硬件不符的属性 */
		/delete-property/ xshut-gpios;
	};
};

因此我们的boards/stm32wb5mm_dk.overlay源码如下:

#include <zephyr/dt-bindings/sensor/stts22h.h>

/{
	chosen {
		zephyr,display = &oled; /* display API 通过 chosen 找默认屏 */
	};
};

&spi1 {
	status = "okay";
	pinctrl-0 = <&spi1_sck_pa1 &spi1_mosi_pa7>;
	pinctrl-names = "default";
	cs-gpios = <&gpioh 0 GPIO_ACTIVE_LOW>; /* 片选引脚 PH0 */

	oled: ssd1306@0 {
		compatible = "solomon,ssd1306";
		reg = <0>; /* 片选为 0 */
		spi-max-frequency = <10000000>;
		width = <128>;
		height = <64>;
		segment-offset = <0>;
		page-offset = <0>;
		display-offset = <0>;
		multiplex-ratio = <63>;
		segment-remap;
		com-invdir;
		prechargep = <0x22>;
		data-cmd-gpios = <&gpioc 9 GPIO_ACTIVE_HIGH>; /* 数据命令选择引脚 PC9 */
		reset-gpios = <&gpioc 8 GPIO_ACTIVE_LOW>; /* 复位引脚 PC8 */
	};
};

&i2c3 {
	vl53l0x@29 {
		/* 硬件上 XSHUT 经 R10 上拉到 3V3,未接 PC6;
		 * 删除上游 dts 中与实际硬件不符的属性 */
		/delete-property/ xshut-gpios;
	};

	stts22h: stts22h@38 {
		compatible = "st,stts22h";
		reg = <0x38>;                /* 7 位地址 0x38 */
		sampling-rate = <STTS22H_1Hz>; /* 连续转换 1Hz */
	};

};

3. Kconfig配置

编辑 rj.conf,在传感器配置区域加一行:

# I2C 总线与传感器框架
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_STTS22H=y
​
# VL53L0X ToF 测距传感器(板载 U6,I2C3 @ 0x29)
CONFIG_VL53L0X=y

说明:

  • CONFIG_VL53L0X 依赖 DT_HAS_ST_VL53L0X_ENABLED(dts 里有该节点就满足),** 并自动 select I2C 和 select HAS_STLIB(编译 ST 官方 VL53L0X API, **源码在 D:\luglZephyrproject\modules\hal\st\sensor\vl53l0x)

4. 修改src/main.c,加入测距逻辑

Zephyr 的 vl53l0x 驱动是单次轮询模式:每次调用 sensor_sample_fetch()会执行一次完整的测距(ST API 的 VL53L0X_PerformSingleRangingMeasurement,耗时约 30 ms 量级),再用 sensor_channel_get() 取结果。关键 API 行为(与驱动源码 zephyr\drivers\sensor\st\vl53l0x\vl53l0x.c 对应):

image.png

测距状态,0 表示本次测量有效
把距离换算成毫米:mm = val1 * 1000 + val2 / 1000。
注意:自定义通道 SENSOR_CHAN_VL53L0X_RANGE_STATUS 定义在
<zephyr/drivers/sensor/vl53l0x.h>,需要单独包含。
下面是把 OLED + 温度 + 测距 整合后的完整 main.c:

#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/drivers/display.h>
#include <zephyr/display/cfb.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor/vl53l0x.h>

const struct device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
const struct device *temp_dev = DEVICE_DT_GET(DT_NODELABEL(stts22h));
const struct device *tof_dev = DEVICE_DT_GET(DT_INST(0, st_vl53l0x));

static int cmd_hello(const struct shell *shell, size_t argc, char **argv)
{
    shell_print(shell, "Hello from shell demo, stm32wb5mm_dk!");
    return 0;
}

static int cmd_reboot(const struct shell *shell, size_t argc, char **argv)
{
    shell_print(shell, "Rebooting the system...");
    sys_reboot(SYS_REBOOT_COLD);
    return 0;
}

SHELL_CMD_REGISTER(hello, NULL, "Print hello message", cmd_hello);
SHELL_CMD_REGISTER(reboot, NULL, "Reboot the system", cmd_reboot);

int main(void)
{
    printk("OLED + ToF demo application started.\n");

    if (!device_is_ready(dev)) {
        printk("Display device not ready\n");
        return -ENODEV;
    }
    if (!device_is_ready(temp_dev)) {
        printk("Temperature sensor device not ready\n");
        return -ENODEV;
    }
    if (!device_is_ready(tof_dev)) {
        /* 常见原因:CONFIG_VL53L0X 未打开,或 I2C 上 0x29 无应答 */
        printk("VL53L0X device not ready\n");
        return -ENODEV;
    }

    display_set_pixel_format(dev, PIXEL_FORMAT_MONO01);
    display_blanking_off(dev);

    if (cfb_framebuffer_init(dev)) {
        printk("cfb_framebuffer_init failed\n");
        return -EIO;
    }
    cfb_framebuffer_clear(dev, true);

    while (1) {
        struct sensor_value temp_value;
        char buf[32];

        /* --- 温度 --- */
        if (sensor_sample_fetch(temp_dev) == 0) {
            sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp_value);
            snprintf(buf, sizeof(buf), "Temp: %d.%02d C",
                     temp_value.val1, temp_value.val2 / 10000);
        } else {
            snprintf(buf, sizeof(buf), "Temp: error");
        }

        /* 注意第二个参数用 false:只清内存里的帧缓冲,不立刻刷屏,
         * 否则每帧会推两次全屏数据(一次全黑、一次内容),OLED 会明显闪一下 */
        cfb_framebuffer_clear(dev, false);
        cfb_print(dev, "VL53L0CX demo", 0, 0);
        cfb_print(dev, buf, 0, 16);

        /* --- 测距:fetch 触发一次单次测量 --- */
        if (sensor_sample_fetch(tof_dev) == 0) {
            struct sensor_value dist, status;

            sensor_channel_get(tof_dev, SENSOR_CHAN_DISTANCE, &dist);
            sensor_channel_get(tof_dev,
                               (enum sensor_channel)SENSOR_CHAN_VL53L0X_RANGE_STATUS,
                               &status);

            int32_t mm = dist.val1 * 1000 + dist.val2 / 1000;

            if (status.val1 == 0) {
                snprintf(buf, sizeof(buf), "Dist: %d mm", (int)mm);
            } else {
                /* 超量程 / 目标反射率太低时状态非 0,读数不可信 */
                snprintf(buf, sizeof(buf), "Dist: ---- mm");
            }
            printk("range status=%d, distance=%d mm\n",
                   (int)status.val1, (int)mm);
        } else {
            snprintf(buf, sizeof(buf), "Dist: error");
            printk("VL53L0X sample fetch failed\n");
        }

        cfb_print(dev, buf, 0, 32);
        cfb_framebuffer_finalize(dev);

        k_sleep(K_MSEC(500));
    }

    return 0;
}

要点解释:

  • sensor_sample_fetch(tof_dev) 内部第一次调用时会完成 VL53L0X_DataInit 和单次模式配置,之后每次调用就是一次“测一次距”。
  • 测距前先检查 device_is_ready(tof_dev)。如果这里就失败,去第 6 节排查。
  • 循环周期 500 ms 足够;单次测量本身要几十毫秒,别把周期设得太小。
  • 传感器最长量程约 2 m(实际取决于目标反射率和环境光); 超出量程时 RANGE_STATUS != 0,代码里显示 ----。

验证

vl53l0.png

赞 收藏 评论0 发布时间:2026-11-4 13:41

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版