程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

龙芯2k0300 - 走马观碑组VL53L0X驱动移植

在《龙芯2k0300 - 久久派开发环境搭建及内核升级》中我们介绍了如何将久久派默认带有的PMON固件以及内核linux 4.19升级为ubootlinux 6.12版本。当我们的板子执行了升级操作后,我们的系统环境已经搭建完成了,接下来的任务就是移植驱动程序和编写应用程序。其中涉及的模块有:

  • VL53L0X测距模块;
  • 1.8SPI TFT屏幕(驱动芯片ST7735);
  • MPU6050陀螺仪;

这里我们参考的资料主要来源以下两个仓库:

一、开源驱动

1.1 创建项目目录

/opt/2k0300目录下创建子目录loongson_2k300_lib

zhengyang@ubuntu:/opt/2k0300$ mkdir loongson_2k300_lib
zhengyang@ubuntu:/opt/2k0300$ cd loongson_2k300_lib

创建驱动子目录:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ mkdir driver

创建测试用例子目录:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ mkdir example

创建走马观碑项目子目录:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ mkdir car_project

1.2 拷贝驱动

这里我们将《WwuSama/21届智能车走马观碑开源仓库》开源的linux 6.9源码下载下来,需要注意的是仓库中已经将linux内核源码移除了,但是其QQ交流群依然提供了内核源码,这里我下载了linux-6.9-WuwuSama-99pi.tar.gz,其驱动位于drivers/wuwu_drivers/

zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.9-WuwuSama-99pi$ ll drivers/wuwu_drivers/
-rw-rw-r--   1 zhengyang zhengyang  7109 12月 17 16:42 imc20602.h
-rw-rw-r--   1 zhengyang zhengyang  4144 12月 17 16:42 wuwu_brushless.c
-rw-rw-r--   1 zhengyang zhengyang  3601 12月 17 16:42 wuwu_buzzer.c
-rw-rw-r--   1 zhengyang zhengyang  4134 12月 17 16:42 wuwu_fans.c
-rw-rw-r--   1 zhengyang zhengyang  9198 12月 17 16:42 wuwu_icm42688.c
-rw-rw-r--   1 zhengyang zhengyang  5306 12月 17 16:42 wuwu_icm42688.h
-rw-rw-r--   1 zhengyang zhengyang 11666 12月 17 16:42 wuwu_imc20602.c
-rw-rw-r--   1 zhengyang zhengyang 13770 12月 17 16:42 wuwu_motor.c
-rw-rw-r--   1 zhengyang zhengyang  1488 12月 17 16:42 wuwu_motor.h
-rw-rw-r--   1 zhengyang zhengyang  4416 12月 17 16:42 wuwu_servo.c
-rw-rw-r--   1 zhengyang zhengyang 30204 12月 17 16:42 wuwu_vl53l0x.c
-rw-rw-r--   1 zhengyang zhengyang   877 12月 17 16:42 wuwu_vl53l0x.h

我们将这些驱动拷贝到loongson_2k300_lib/driver目录,供我们参考使用;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver$ cp /opt/2k0300/build-2k0300/workspace/linux-6.9-WuwuSama-99pi/drivers/wuwu_drivers/* ./

1.3 准备工作

先在内核源码目录执行如下命令,生成 Module.symvers

zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make loongson_2k300_defconfig V=1
# 生成Module.symvers的命令
zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make  modules_prepare -j$(nproc)
# 如果没有Module.symvers才需要执行下面这条指令
zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ cp vmlinux.symvers Module.symvers

这一步会生成完整的 Module.symvers,其中记录了内核导出的所有符号信息,这个后面编译驱动需要使用到。

二、VL53L0X设备驱动

如果对I2C驱动源码移植感兴趣,可参考:

2.1 准备阶段

2.1.1 项目结构

接下来我们创建子目录i2c_vl53l0x_driver

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver$ mkdir i2c_vl53l0x_driver

拷贝源码:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver$ cp ../wuwu_vl53l0x.* ./
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver$ ll

-rw-rw-r-- 1 zhengyang zhengyang 30204  3月 23 21:25 wuwu_vl53l0x.c
-rw-rw-r-- 1 zhengyang zhengyang   877  3月 23 21:25 wuwu_vl53l0x.h

wuwu_vl53l0x.c代码内容较多,这里我们对其进行拆分,并进行适当调整,最终目录结构如下:

i2c_vl53l0x_driver/
├── inc/
│   └── vl53l0x.h         # 公共头文件(宏、结构体定义、ioctl 命令等)
├── src/
│   ├── vl53l0x_core.c    # 核心层:传感器初始化、测距算法
│   ├── vl53l0x_dev.c     # 设备层:字符设备接口
│   └── vl53l0x_hw.c      # 硬件层:I2C 读写操作
├── vl53l0x_driver.c      # 驱动层:I2C 驱动注册、probe/remove
└── Makefile

注意:龙邱科技也提供了驱动源码: i2c_vl53l0x_driver,只不过这个是基于linux 4.19的,如果移植这个改动的相对较多。

此外内核源码也内置了VL53L0X驱动,位于drivers/iio/proximity/vl53l0x-i2c.c,其基于IIO框架实现,如果有兴趣可自行阅读。

2.1.2 VL53L0X简介

VL53L0X是一款由ST(意法半导体)生产的红外传感器,通常用于测量物体与传感器之间的距离。

VL53L0XST推出的第二代FlightSense技术的飞行时间传感器,与传统的测距传感器不同,它使用时飞行时间(Time-of-FlightTOF)测量原理,可以实现精确、快速的非接触式测量。无论目标颜色和反射率如何,都可以进行距离测量,抗干扰能力更强。

VL53L0X激光测距模块的原理是利用激光脉冲的飞行时间来测量距离。该模块通过发射一束激光脉冲,并测量该激光脉冲从发射到被接收的时间来计算出物体与传感器之间的距离。

更多相关资料参考:《STM32传感器模块编程实践(九) VL53L0X激光红外测距传感器简介及驱动源码》。

2.1.3 配置内核

进入内核配置界面:

zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ cd ~
zhengyang@ubuntu:~$ cd /opt/2k0300/build-2k0300/workspace/linux-6.12

zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make menuconfig

依次进入以下菜单:

Device Drivers → 
    I2C support → 
        I2C Hardware Bus support → 
            <*> Loongson fast speed I2C adapter    # 龙芯 2K0300 I2C控制器驱动
        <*>   I2C device interface    

默认会生成配置:

CONFIG_I2C_LSFS=y

实际上.config中这个已经默认配置了,我们不用再次配置。

2.2 vl53l0x.h

inc为头文件目录,在inc创建vl53l0x.h文件;

/*
 * VL53L0X Linux I2C Driver
 * Copyright (C) 2026 zhengyang
 *
 * Based on Seekfree TC264 Library: https://gitee.com/seekfree/TC264_Library
 * SPDX-License-Identifier: GPL-2.0-only
 */

#ifndef _VL53L0X_H_
#define _VL53L0X_H_

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/ioctl.h>

/* ========================= 设备信息 ========================= */
#define VL53L0X_COUNT           1
#define VL53L0X_NAME            "vl53l0x_tof"

/* ========================= ioctl 命令 ========================= */
#define VL53L0X_IOCTL_MAGIC     't'
#define VL53L0X_GET_DATA        _IOR(VL53L0X_IOCTL_MAGIC, 1, uint16_t)

/* ========================= 硬件常量 ========================= */
#define VL53L0X_TIMEOUT_COUNT   10

/* ========================= 辅助宏 ========================= */
#define decode_vcsel_period(reg_val)            (((reg_val) + 1) << 1)
#define calc_macro_period(vcsel_period_pclks)   ((((uint32_t)2304 * (vcsel_period_pclks) * 1655) + 500) / 1000)

/* ========================= 枚举定义 ========================= */
typedef enum {
    VL53L0X_VCSEL_PERIOD_PER_RANGE,
    VL53L0X_VCSEL_PERIOD_FINAL_RANGE,
} vl53l0x_vcsel_period_type_enum;

/* ========================= 结构体定义 ========================= */
typedef struct {
    uint8_t tcc;
    uint8_t msrc;
    uint8_t dss;
    uint8_t pre_range;
    uint8_t final_range;
} vl53l0x_sequence_enables_step_struct;

typedef struct {
    uint16_t pre_range_vcsel_period_pclks;
    uint16_t final_range_vcsel_period_pclks;

    uint16_t msrc_dss_tcc_mclks;
    uint16_t pre_range_mclks;
    uint16_t final_range_mclks;
    uint32_t msrc_dss_tcc_us;
    uint32_t pre_range_us;
    uint32_t final_range_us;
} vl53l0x_sequence_timeout_step_struct;

/* 内部设备结构(所有模块共用) */
struct vl53l0x_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;

    void *private_data;          /* 指向 i2c_client */
    struct mutex lock;
    uint16_t vl53l0x_distance_mm;
};

/* ========================= 全局变量声明 ========================= */
extern struct vl53l0x_dev vl53l0x;

/* ========================= 硬件层函数声明 ========================= */
int vl53l0x_read_regs(struct vl53l0x_dev *dev, u8 reg, void *val, int len);
int vl53l0x_write_regs(struct vl53l0x_dev *dev, u8 reg, u8 *buf, u8 len);
void vl53l0x_writeByte(u8 reg, u8 Byte);
void vl53l0x_writeBuf(u8 reg, u8 *buf, u8 len);
void vl53l0x_readByte(u8 reg, u8 *byte);
void vl53l0x_readBuf(u8 reg, u8 *buf, u8 len);

/* ========================= 核心层函数声明 ========================= */
void vl53l0x_set_signal_rate_limit(uint16_t limit_mcps);
uint8_t vl53l0x_get_spad_info(uint8_t *index, uint8_t *type_is_aperture);
void vl53l0x_get_sequence_step_enables(vl53l0x_sequence_enables_step_struct *enables);
uint8_t vl53l0x_get_vcsel_pulse_period(vl53l0x_vcsel_period_type_enum type);
uint32_t vl53l0x_timeout_mclks_to_microseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks);
uint16_t vl53l0x_decode_timeout(uint16_t reg_val);
void vl53l0x_get_sequence_step_timeouts(const vl53l0x_sequence_enables_step_struct *enables,
                                        vl53l0x_sequence_timeout_step_struct *timeouts);
uint32_t vl53l0x_get_measurement_timing_budget(void);
uint16_t vl53l0x_encode_timeout(uint16_t timeout_mclks);
uint32_t vl53l0x_timeout_microseconds_to_mclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks);
uint8_t vl53l0x_set_measurement_timing_budget(uint32_t budget_us);
uint8_t vl53l0x_perform_single_ref_calibration(uint8_t vhv_init_byte);
int vl53l0x_init(void);
void vl53l0x_get_distance(void);

/* ========================= 设备层函数声明 ========================= */
int vl53l0x_cdev_init(struct i2c_client *client);
void vl53l0x_cdev_destroy(void);

#endif /* _VL53L0X_H_ */

2.3 src

src为源码库所在目录,这块代码主要包含以下几个功能模块:

功能模块 说明
I2C 通信封装 提供了 vl53l0x_read_regs / vl53l0x_write_regs 底层函数,基于 Linux I2C 框架完成与传感器的寄存器读写。
传感器初始化 按照 VL53L0X 数据手册规定的时序,完成芯片的启动、SPAD 校准、时序预算设置等一系列初始化操作(vl53l0x_init)。
测距触发与读取 通过 vl53l0x_get_distance 启动一次测量(写入寄存器 0x0B 触发),并读取结果寄存器获取距离值(毫米)。
用户空间接口 注册为字符设备,用户程序通过 open 打开设备时自动完成初始化;通过自定义 ioctl 命令 VL53L0X_GET_DATA 获取最新距离值。
2.3.1 vl53l0x_hw.c

vl53l0x_hw.c: 硬件层,提供I2C读写函数;

#include "vl53l0x.h"

/* I2C 读写函数(硬件层) */
int vl53l0x_read_regs(struct vl53l0x_dev *dev, u8 reg, void *val, int len)
{
    struct i2c_client *client = (struct i2c_client *)dev->private_data;

    struct i2c_msg msg[] = {
        {
            .addr = client->addr,
            .flags = 0,
            .len = 1,
            .buf = &reg,
        },
        {
            .addr = client->addr,
            .flags = I2C_M_RD,
            .len = len,
            .buf = val,
        },
    };

    return i2c_transfer(client->adapter, msg, 2);
}

int vl53l0x_write_regs(struct vl53l0x_dev *dev, u8 reg, u8 *buf, u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client = (struct i2c_client *)dev->private_data;

    b[0] = reg;
    memcpy(&b[1], buf, len);

    msg.addr = client->addr;
    msg.flags = 0;
    msg.len = len + 1;
    msg.buf = b;

    return i2c_transfer(client->adapter, &msg, 1);
}

/* 单字节/多字节封装 */
void vl53l0x_writeByte(u8 reg, u8 Byte)
{
    vl53l0x_write_regs(&vl53l0x, reg, &Byte, 1);
}

void vl53l0x_writeBuf(u8 reg, u8 *buf, u8 len)
{
    vl53l0x_write_regs(&vl53l0x, reg, buf, len);
}

void vl53l0x_readByte(u8 reg, u8 *byte)
{
    vl53l0x_read_regs(&vl53l0x, reg, byte, 1);
}

void vl53l0x_readBuf(u8 reg, u8 *buf, u8 len)
{
    vl53l0x_read_regs(&vl53l0x, reg, buf, len);
}
2.3.2 vl53l0x_core.c

vl53l0x_core.c: 核心层,实现传感器初始化、距离读取等算法,调用硬件层;

点击查看详情
#include "vl53l0x.h"

/* 全局设备结构实例(所有模块共享) */
struct vl53l0x_dev vl53l0x;

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     设置返回信号速率限制 该值单位为 MCPS (百万次每秒)
// 参数说明     limit_mcps      设置的最小速率
// 返回参数     void
// 使用示例     vl53l0x_set_signal_rate_limit(32);
// 备注信息     这个速率表示从目标反射并被设备检测到的信号的振幅
//              设置此限制可以确定传感器报告有效读数所需的最小测量值
//              设置一个较低的限制可以增加传感器的测量范围
//              但似乎也增加了 <由于来自目标以外的物体的不需要的反射导致> 得到不准确读数的可能性
//              默认为 32 MCPS 可预设范围为 0 - 65534
//-------------------------------------------------------------------------------------------------------------------
void vl53l0x_set_signal_rate_limit(uint16_t limit_mcps_fixed)
{
    uint8_t data_buffer[2];

    data_buffer[0] = (limit_mcps_fixed >> 8) & 0xFF;
    data_buffer[1] = limit_mcps_fixed & 0xFF;

    vl53l0x_write_regs(&vl53l0x, 0x44, data_buffer, 2);
}


//-------------------------------------------------------------------------------------------------------------------
// 函数简介     获取设备 SPAD 信息
// 参数说明     index           索引
// 参数说明     type            类型值
// 返回参数     uint8_t           是否成功 0-成功 1-失败
// 使用示例     vl53l0x_get_spad_info(index, type_is_aperture);
// 备注信息
//-------------------------------------------------------------------------------------------------------------------
uint8_t vl53l0x_get_spad_info(uint8_t *index, uint8_t *type_is_aperture)
{
    uint8_t tmp = 0;
    uint8_t return_state = 0;
    volatile uint16_t loop_count = 0;

    do {
        vl53l0x_writeByte(0x80, 0x01);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x00, 0x00);

        vl53l0x_writeByte(0xFF, 0x06);
        vl53l0x_readByte(0x83, &tmp);
        vl53l0x_writeByte(0x83, tmp | 0x04);
        vl53l0x_writeByte(0xFF, 0x07);
        vl53l0x_writeByte(0x81, 0x01);

        vl53l0x_writeByte(0x80, 0x01);

        vl53l0x_writeByte(0x94, 0x6b);
        vl53l0x_writeByte(0x83, 0x00);

        tmp = 0x00;
        while (0x00 == tmp || 0xFF == tmp) {
            msleep(1);
            vl53l0x_readByte(0x83, &tmp);
            if (VL53L0X_TIMEOUT_COUNT < loop_count++) {
                return_state = 1;
                break;
            }
        }
        if (return_state)
            break;

        vl53l0x_writeByte(0x83, 0x01);
        vl53l0x_readByte(0x92, &tmp);

        *index = tmp & 0x7f;
        *type_is_aperture = (tmp >> 7) & 0x01;

        vl53l0x_writeByte(0x81, 0x00);
        vl53l0x_writeByte(0xFF, 0x06);
        vl53l0x_readByte(0x83, &tmp);
        vl53l0x_writeByte(0x83, tmp);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x00, 0x01);

        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x80, 0x00);
    } while (0);

    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     获取序列步骤使能设置
// 参数说明     enables         序列使能步骤结构体
// 返回参数     void
// 使用示例     vl53l0x_get_sequence_step_enables(enables);
// 备注信息
//-------------------------------------------------------------------------------------------------------------------
void vl53l0x_get_sequence_step_enables(vl53l0x_sequence_enables_step_struct *enables)
{
    uint8_t sequence_config = 0;
    vl53l0x_readByte(0x01, &sequence_config);

    enables->tcc          = (sequence_config >> 4) & 0x1;
    enables->dss          = (sequence_config >> 3) & 0x1;
    enables->msrc         = (sequence_config >> 2) & 0x1;
    enables->pre_range    = (sequence_config >> 6) & 0x1;
    enables->final_range  = (sequence_config >> 7) & 0x1;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     获取脉冲周期
// 参数说明     type            预量程类型
// 返回参数     uint8_t           返回的周期值
// 使用示例     vl53l0x_get_vcsel_pulse_period(VL53L0X_VCSEL_PERIOD_PER_RANGE);
// 备注信息     在 PCLKs 中获取给定周期类型的 VCSEL 脉冲周期
//-------------------------------------------------------------------------------------------------------------------
uint8_t vl53l0x_get_vcsel_pulse_period(vl53l0x_vcsel_period_type_enum type)
{
    uint8_t data_buffer = 0;
    if (VL53L0X_VCSEL_PERIOD_PER_RANGE == type) {
        vl53l0x_readByte(0x50, &data_buffer);
        data_buffer = decode_vcsel_period(data_buffer);
    } else if (VL53L0X_VCSEL_PERIOD_FINAL_RANGE == type) {
        vl53l0x_readByte(0x70, &data_buffer);
        data_buffer = decode_vcsel_period(data_buffer);
    } else {
        data_buffer = 255;
    }
    return data_buffer;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     将超时数值从 MCLKs 转换到对应的 ms
// 参数说明     timeout_period_mclks    超时周期 MCLKs
// 参数说明     vcsel_period_pclks      PCLK 值
// 返回参数     uint32_t                  返回超时数值
// 使用示例     vl53l0x_timeout_mclks_to_microseconds(timeout_period_mclks, vcsel_period_pclks);
// 备注信息     将序列步骤超时从具有给定 VCSEL 周期的 MCLK (以 PCLK 为单位)转换为微秒
//-------------------------------------------------------------------------------------------------------------------
uint32_t vl53l0x_timeout_mclks_to_microseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks)
{
    uint32_t macro_period_ns = calc_macro_period(vcsel_period_pclks);
    return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns / 2)) / 1000;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     对超时数值进行解码
// 参数说明     reg_val         超时时长 寄存器值
// 返回参数     uint16_t          返回超时数值
// 使用示例     vl53l0x_decode_timeout(reg_val);
// 备注信息     从寄存器值解码 MCLK 中的序列步骤超时
//-------------------------------------------------------------------------------------------------------------------
uint16_t vl53l0x_decode_timeout(uint16_t reg_val)
{
    return (uint16_t)((reg_val & 0x00FF) << ((reg_val & 0xFF00) >> 8)) + 1;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     获取序列步骤超时设置
// 参数说明     enables         序列使能步骤结构体
// 参数说明     timeouts        序列超时步骤结构体
// 返回参数     void
// 使用示例     vl53l0x_get_sequence_step_timeouts(enables, timeouts);
// 备注信息     获取所有超时而不仅仅是请求的超时 并且还存储中间值
//-------------------------------------------------------------------------------------------------------------------
void vl53l0x_get_sequence_step_timeouts(const vl53l0x_sequence_enables_step_struct *enables,
                                        vl53l0x_sequence_timeout_step_struct *timeouts)
{
    uint8_t reg_buffer[2];
    uint16_t reg16_buffer = 0;

    timeouts->pre_range_vcsel_period_pclks = vl53l0x_get_vcsel_pulse_period(VL53L0X_VCSEL_PERIOD_PER_RANGE);

    vl53l0x_readByte(0x46, reg_buffer);
    timeouts->msrc_dss_tcc_mclks = reg_buffer[0] + 1;
    timeouts->msrc_dss_tcc_us = vl53l0x_timeout_mclks_to_microseconds(timeouts->msrc_dss_tcc_mclks,
                                                                       (uint8_t)timeouts->pre_range_vcsel_period_pclks);

    vl53l0x_readBuf(0x51, reg_buffer, 2);
    reg16_buffer = ((uint16_t)reg_buffer[0] << 8) | reg_buffer[1];
    timeouts->pre_range_mclks = vl53l0x_decode_timeout(reg16_buffer);
    timeouts->pre_range_us = vl53l0x_timeout_mclks_to_microseconds(timeouts->pre_range_mclks,
                                                                   (uint8_t)timeouts->pre_range_vcsel_period_pclks);

    timeouts->final_range_vcsel_period_pclks = vl53l0x_get_vcsel_pulse_period(VL53L0X_VCSEL_PERIOD_FINAL_RANGE);

    vl53l0x_readBuf(0x71, reg_buffer, 2);
    reg16_buffer = ((uint16_t)reg_buffer[0] << 8) | reg_buffer[1];
    timeouts->final_range_mclks = vl53l0x_decode_timeout(reg16_buffer);

    if (enables->pre_range) {
        timeouts->final_range_mclks -= timeouts->pre_range_mclks;
    }

    timeouts->final_range_us = vl53l0x_timeout_mclks_to_microseconds(timeouts->final_range_mclks,
                                                                     (uint8_t)timeouts->final_range_vcsel_period_pclks);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     获取测量定时预算 (以微秒为单位)
// 参数说明     void
// 返回参数     uint32_t          已设定的测量允许的时间
// 使用示例     vl53l0x_get_measurement_timing_budget();
// 备注信息
//-------------------------------------------------------------------------------------------------------------------
uint32_t vl53l0x_get_measurement_timing_budget(void)
{
    vl53l0x_sequence_enables_step_struct enables;
    vl53l0x_sequence_timeout_step_struct timeouts;

    uint32_t budget_us = 1910 + 960;

    vl53l0x_get_sequence_step_enables(&enables);
    vl53l0x_get_sequence_step_timeouts(&enables, &timeouts);

    if (enables.tcc)
        budget_us += (timeouts.msrc_dss_tcc_us + 590);
    if (enables.dss)
        budget_us += 2 * (timeouts.msrc_dss_tcc_us + 690);
    else if (enables.msrc)
        budget_us += (timeouts.msrc_dss_tcc_us + 660);
    if (enables.pre_range)
        budget_us += (timeouts.pre_range_us + 660);
    if (enables.final_range)
        budget_us += (timeouts.final_range_us + 550);

    return budget_us;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     对超时数值进行编码
// 参数说明     timeout_mclks   超时时长 -MCLKs 值
// 返回参数     uint16_t          返回编码值
// 使用示例     vl53l0x_encode_timeout(timeout_mclks);
// 备注信息     在 MCLK 中对超时的序列步骤超时寄存器值进行编码
//-------------------------------------------------------------------------------------------------------------------
uint16_t vl53l0x_encode_timeout (uint16_t timeout_mclks)
{
    uint32_t ls_byte = 0;
    uint16_t ms_byte = 0;
    uint16_t return_data = 0;

    if(0 < timeout_mclks)
    {
        // 格式: (LSByte * 2 ^ MSByte) + 1
        ls_byte = timeout_mclks - 1;
        while(0 < (ls_byte & 0xFFFFFF00))
        {
            ls_byte >>= 1;
            ms_byte++;
        }
        return_data = (ms_byte << 8) | ((uint16_t)ls_byte & 0xFF);
    }
    return return_data;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     将超时数值从 ms 转换到对应的 MCLKs
// 参数说明     timeout_period_us   超时周期 微秒单位
// 参数说明     vcsel_period_pclks  PCLK 值
// 返回参数     uint32_t              返回超时数值
// 使用示例     vl53l0x_timeout_microseconds_to_mclks(timeout_period_us, vcsel_period_pclks);
// 备注信息     将序列步骤超时从微秒转换为具有给定 VCSEL 周期的 MCLK (以 PCLK 为单位)
//-------------------------------------------------------------------------------------------------------------------
uint32_t vl53l0x_timeout_microseconds_to_mclks (uint32_t timeout_period_us, uint8_t vcsel_period_pclks)
{
    uint32_t macro_period_ns = calc_macro_period(vcsel_period_pclks);

    return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     设置测量定时预算 (以微秒为单位)
// 参数说明     budget_us       设定的测量允许的时间
// 返回参数     uint8_t           操作结果 0-成功 1-失败
// 使用示例     vl53l0x_set_measurement_timing_budget(measurement_timing_budget_us);
// 备注信息     这是一次测量允许的时间
//              即在测距序列的子步骤之间分配时间预算
//              更长的时间预算允许更精确的测量
//              增加一个N倍的预算可以减少一个sqrt(N)倍的范围测量标准偏差
//              默认为33毫秒 最小值为20 ms
//-------------------------------------------------------------------------------------------------------------------
uint8_t vl53l0x_set_measurement_timing_budget (uint32_t budget_us)
{
    uint8_t return_state = 0;
    uint8_t data_buffer[3];
    uint16_t data = 0;

    vl53l0x_sequence_enables_step_struct enables;
    vl53l0x_sequence_timeout_step_struct timeouts;

    do
    {
        if(20000 > budget_us)
        {
            return_state = 1;
            break;
        }

        uint32_t used_budget_us = 1320 + 960;
        vl53l0x_get_sequence_step_enables(&enables);
        vl53l0x_get_sequence_step_timeouts(&enables, &timeouts);

        if (enables.tcc)
        {
            used_budget_us += (timeouts.msrc_dss_tcc_us + 590);
        }

        if (enables.dss)
        {
            used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + 690);
        }
        else if (enables.msrc)
        {
            used_budget_us += (timeouts.msrc_dss_tcc_us + 660);
        }

        if (enables.pre_range)
        {
            used_budget_us += (timeouts.pre_range_us + 660);
        }

        if (enables.final_range)
        {
            // 请注意 最终范围超时由计时预算和序列中所有其他超时的总和决定
            // 如果没有空间用于最终范围超时 则将设置错误
            // 否则 剩余时间将应用于最终范围
            used_budget_us += 550;
            if (used_budget_us > budget_us)
            {
                // 请求的超时太大
                return_state = 1;
                break;
            }

            // 对于最终超时范围 必须添加预量程范围超时
            // 为此 最终超时和预量程超时必须以宏周期 MClks 表示
            // 因为它们具有不同的 VCSEL 周期
            uint32_t final_range_timeout_us = budget_us - used_budget_us;
            uint16_t final_range_timeout_mclks =
            (uint16_t)vl53l0x_timeout_microseconds_to_mclks(final_range_timeout_us,
                     (uint8_t)timeouts.final_range_vcsel_period_pclks);

            if (enables.pre_range)
            {
                final_range_timeout_mclks += timeouts.pre_range_mclks;
            }

            data = vl53l0x_encode_timeout(final_range_timeout_mclks);
            data_buffer[0] = 0x71;
            data_buffer[1] = ((data >> 8) & 0xFF);
            data_buffer[2] = (data & 0xFF);
            vl53l0x_writeBuf(data_buffer[0], &data_buffer[1], 2);
        }
    }while(0);
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     执行单次参考校准
// 参数说明     vhv_init_byte   预设校准值
// 返回参数     uint8_t           操作是否成功 0-成功 1-失败
// 使用示例     vl53l0x_get_vcsel_pulse_period(VL53L0X_VCSEL_PERIOD_PER_RANGE);
// 备注信息     在 PCLKs 中获取给定周期类型的 VCSEL 脉冲周期
//-------------------------------------------------------------------------------------------------------------------
uint8_t vl53l0x_perform_single_ref_calibration (uint8_t vhv_init_byte)
{
    uint8_t return_state = 0;
    uint8_t data_buffer = 0;
    volatile uint16_t loop_count = 0;
    do
    {
        vl53l0x_writeByte(0x00, 0x01 | vhv_init_byte);
        vl53l0x_readByte(0x46, &data_buffer);
        while(0 == (data_buffer & 0x07))
        {
            msleep(1);
            vl53l0x_readByte(0x46, &data_buffer);
            if(VL53L0X_TIMEOUT_COUNT < loop_count ++)
            {
                return_state = 1;
                break;
            }
        }
        if(return_state)
        {
            break;
        }
        vl53l0x_writeByte(0x0B, 0x01);
        vl53l0x_writeByte(0x00, 0x00);
    }while(0);

    return return_state;
}

int vl53l0x_init(void)
{
    uint8_t stop_variable = 0;
    uint8_t reg_data_buffer = 0;
    uint8_t return_state = 0;
    uint32_t measurement_timing_budget_us;

    uint8_t data_buffer[7] = {0};
    uint8_t ref_spad_map[6];
    int i = 0;

    do
    {
        vl53l0x_readByte(0x89, &reg_data_buffer);
        vl53l0x_writeByte(0x89, reg_data_buffer | 0x01);

        vl53l0x_writeByte(0x88, 0x00);

        vl53l0x_writeByte(0x80, 0x01);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x00, 0x00);

        vl53l0x_readByte(0x91, &stop_variable);

        vl53l0x_writeByte(0x00, 0x01);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x80, 0x00);

        vl53l0x_readByte(0x60, &reg_data_buffer);
        vl53l0x_writeByte(0x60, reg_data_buffer | 0x12);

        vl53l0x_set_signal_rate_limit(32);
        vl53l0x_writeByte(0x01, 0xFF);


        if(vl53l0x_get_spad_info(&data_buffer[0], &data_buffer[1]))
        {
            return_state = 1;
            printk("init error\r\n");
            break;
        }

        vl53l0x_read_regs(&vl53l0x, 0xB0, ref_spad_map, 6);

        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x4F, 0x00);
        vl53l0x_writeByte(0x4E, 0x2C);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x86, 0xB4);

        data_buffer[2] = data_buffer[1] ? 12 : 0;

        for(i = 0; 48 > i; i ++)
        {
            if(i < data_buffer[2] || data_buffer[3] == data_buffer[0])
            {
                // 此位低于应启用的第一个位
                // 或者 (eference_spad_count) 位已启用
                // 因此此位为零
                ref_spad_map[i / 8] &= ~(1 << (i % 8));
            }
            else if((ref_spad_map[i / 8] >> (i % 8)) & 0x1)
            {
                data_buffer[3] ++;
            }
        }

        data_buffer[0] = 0xB0;
        for(i = 1; 7 > i; i ++)
        {
            data_buffer[1] = ref_spad_map[i - 1];
        }
        vl53l0x_write_regs(&vl53l0x, 0xB0, &data_buffer[1], 6);

        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x00, 0x00);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x09, 0x00);
        vl53l0x_writeByte(0x10, 0x00);
        vl53l0x_writeByte(0x11, 0x00);
        vl53l0x_writeByte(0x24, 0x01);
        vl53l0x_writeByte(0x25, 0xFF);
        vl53l0x_writeByte(0x75, 0x00);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x4E, 0x2C);
        vl53l0x_writeByte(0x48, 0x00);
        vl53l0x_writeByte(0x30, 0x20);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x30, 0x09);
        vl53l0x_writeByte(0x54, 0x00);
        vl53l0x_writeByte(0x31, 0x04);
        vl53l0x_writeByte(0x32, 0x03);
        vl53l0x_writeByte(0x40, 0x83);
        vl53l0x_writeByte(0x46, 0x25);
        vl53l0x_writeByte(0x60, 0x00);
        vl53l0x_writeByte(0x27, 0x00);
        vl53l0x_writeByte(0x50, 0x06);
        vl53l0x_writeByte(0x51, 0x00);
        vl53l0x_writeByte(0x52, 0x96);
        vl53l0x_writeByte(0x56, 0x08);
        vl53l0x_writeByte(0x57, 0x30);
        vl53l0x_writeByte(0x61, 0x00);
        vl53l0x_writeByte(0x62, 0x00);
        vl53l0x_writeByte(0x64, 0x00);
        vl53l0x_writeByte(0x65, 0x00);
        vl53l0x_writeByte(0x66, 0xA0);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x22, 0x32);
        vl53l0x_writeByte(0x47, 0x14);
        vl53l0x_writeByte(0x49, 0xFF);
        vl53l0x_writeByte(0x4A, 0x00);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x7A, 0x0A);
        vl53l0x_writeByte(0x7B, 0x00);
        vl53l0x_writeByte(0x78, 0x21);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x23, 0x34);
        vl53l0x_writeByte(0x42, 0x00);
        vl53l0x_writeByte(0x44, 0xFF);
        vl53l0x_writeByte(0x45, 0x26);
        vl53l0x_writeByte(0x46, 0x05);
        vl53l0x_writeByte(0x40, 0x40);
        vl53l0x_writeByte(0x0E, 0x06);
        vl53l0x_writeByte(0x20, 0x1A);
        vl53l0x_writeByte(0x43, 0x40);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x34, 0x03);
        vl53l0x_writeByte(0x35, 0x44);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x31, 0x04);
        vl53l0x_writeByte(0x4B, 0x09);
        vl53l0x_writeByte(0x4C, 0x05);
        vl53l0x_writeByte(0x4D, 0x04);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x44, 0x00);
        vl53l0x_writeByte(0x45, 0x20);
        vl53l0x_writeByte(0x47, 0x08);
        vl53l0x_writeByte(0x48, 0x28);
        vl53l0x_writeByte(0x67, 0x00);
        vl53l0x_writeByte(0x70, 0x04);
        vl53l0x_writeByte(0x71, 0x01);
        vl53l0x_writeByte(0x72, 0xFE);
        vl53l0x_writeByte(0x76, 0x00);
        vl53l0x_writeByte(0x77, 0x00);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x0D, 0x01);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x80, 0x01);
        vl53l0x_writeByte(0x01, 0xF8);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x8E, 0x01);
        vl53l0x_writeByte(0x00, 0x01);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x80, 0x00);

        vl53l0x_writeByte(0x0A, 0x04);
        vl53l0x_readByte(0x84, &reg_data_buffer);
        vl53l0x_writeByte(0x84, reg_data_buffer & ~0x10);
        vl53l0x_writeByte(0x0B, 0x01);

        measurement_timing_budget_us  = vl53l0x_get_measurement_timing_budget();

        vl53l0x_writeByte(0x01, 0xE8);
        vl53l0x_set_measurement_timing_budget(measurement_timing_budget_us);    // 重新计算时序预算

        vl53l0x_writeByte(0x01, 0x01);
        if(vl53l0x_perform_single_ref_calibration(0x40))
        {
            return_state = 1;
            break;
        }
        vl53l0x_writeByte(0x01, 0x02);
        if(vl53l0x_perform_single_ref_calibration(0x00))
        {
            return_state = 1;
            break;
        }

        vl53l0x_writeByte(0x01, 0xE8);

        msleep(100);

        vl53l0x_writeByte(0x80, 0x01);
        vl53l0x_writeByte(0xFF, 0x01);
        vl53l0x_writeByte(0x00, 0x00);
        vl53l0x_writeByte(0x91, stop_variable);
        vl53l0x_writeByte(0x00, 0x01);
        vl53l0x_writeByte(0xFF, 0x00);
        vl53l0x_writeByte(0x80, 0x00);

        vl53l0x_writeByte(0x00, 0x02);

    }while(0);

    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     返回以毫米为单位的范围读数
// 参数说明     void
// 返回参数     void
// 使用示例     vl53l0x_get_distance();
// 备注信息     在开始单次射程测量后也调用此函数
//-------------------------------------------------------------------------------------------------------------------
void vl53l0x_get_distance (void)
{
    uint8_t reg_databuffer[3];

    vl53l0x_readByte(0x13, reg_databuffer);
    if((reg_databuffer[0] & 0x07) != 0)
    {
        // 假设线性度校正增益为默认值 1000 且未启用分数范围
        vl53l0x_readBuf(0x14 + 10, reg_databuffer, 2);
        vl53l0x.vl53l0x_distance_mm = ((uint16_t)reg_databuffer[0] << 8);
        vl53l0x.vl53l0x_distance_mm |= reg_databuffer[1];

        vl53l0x_writeByte(0x0B, 0x01);
    }
    if(reg_databuffer[0] & 0x10)
    {
        vl53l0x_readBuf(0x14 + 10, reg_databuffer, 2);
        vl53l0x_writeByte(0x0B, 0x01);
    }
}
2.3.3 vl53l0x_dev.c

vl53l0x_dev.c: 设备接口层,实现字符设备操作,调用核心层获取数据;

#include "vl53l0x.h"

/* 字符设备操作函数 */
static int vl53l0x_open(struct inode *inode, struct file *file)
{
    if (vl53l0x_init()) {
        return -EACCES;
    }
    file->private_data = &vl53l0x;
    return 0;
}

static int vl53l0x_release(struct inode *inode, struct file *file)
{
    return 0;
}

static long vl53l0x_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    struct vl53l0x_dev *dev = file->private_data;
    void __user *argp = (void __user *)arg;

    mutex_lock(&dev->lock);
    switch (cmd) {
    case VL53L0X_GET_DATA:
        vl53l0x_get_distance();
        if (copy_to_user(argp, &dev->vl53l0x_distance_mm, sizeof(dev->vl53l0x_distance_mm)))
            ret = -EFAULT;
        break;
    default:
        ret = -ENOTTY;
        break;
    }
    mutex_unlock(&dev->lock);
    return ret;
}

/* vl53l0x操作集 */
static const struct file_operations vl53l0x_fops = {
    .owner = THIS_MODULE,
    .open = vl53l0x_open,
    .release = vl53l0x_release,
    .unlocked_ioctl = vl53l0x_unlocked_ioctl,
};

/* 字符设备注册(供 I2C 驱动调用) */
int vl53l0x_cdev_init(struct i2c_client *client)
{
    int ret;

    /* 动态分配字符设备编号: (major,0) */
    ret = alloc_chrdev_region(&vl53l0x.devid, 0, VL53L0X_COUNT, VL53L0X_NAME);
    if (ret < 0) {
        dev_err(&client->dev, "alloc_chrdev_region error\n");
        return ret;
    }

    vl53l0x.cdev.owner = THIS_MODULE;
    
    /* 字符设备初始化以及注册 */
    cdev_init(&vl53l0x.cdev, &vl53l0x_fops);
    ret = cdev_add(&vl53l0x.cdev, vl53l0x.devid, VL53L0X_COUNT);
    if (ret < 0) {
        dev_err(&client->dev, "cdev_add error\n");
        unregister_chrdev_region(vl53l0x.devid, VL53L0X_COUNT);
        return ret;
    }

    /* 创建类,它会在sys目录下创建/sys/class/vl53l0x_tof这个类  */
    vl53l0x.class = class_create(VL53L0X_NAME);
    if (IS_ERR(vl53l0x.class)) {
        dev_err(&client->dev, "class_create error\n");
        ret = PTR_ERR(vl53l0x.class);
        cdev_del(&vl53l0x.cdev);
        unregister_chrdev_region(vl53l0x.devid, VL53L0X_COUNT);
        return ret;
    }

     /* 在/sys/class/vl53l0x_tof下创建vl53l0x_tof设备,然后mdev通过这个自动创建/dev/vl53l0x_tof这个设备节点 */
    vl53l0x.device = device_create(vl53l0x.class, NULL, vl53l0x.devid, NULL, VL53L0X_NAME);
    if (IS_ERR(vl53l0x.device)) {
        dev_err(&client->dev, "device_create error\n");
        ret = PTR_ERR(vl53l0x.device);
        class_destroy(vl53l0x.class);
        cdev_del(&vl53l0x.cdev);
        unregister_chrdev_region(vl53l0x.devid, VL53L0X_COUNT);
        return ret;
    }

    mutex_init(&vl53l0x.lock);
    
    /* 保存当前i2c_client */
    vl53l0x.private_data = client;
    vl53l0x.vl53l0x_distance_mm = 8192;

    return 0;
}

/* 字符设备销毁(供 I2C 驱动调用) */
void vl53l0x_cdev_destroy(void)
{
    device_destroy(vl53l0x.class, vl53l0x.devid);
    class_destroy(vl53l0x.class);
    cdev_del(&vl53l0x.cdev);
    unregister_chrdev_region(vl53l0x.devid, VL53L0X_COUNT);
}

2.4 vl53l0x_driver.c

vl53l0x_driver.c: I2C驱动层,实现probe/remove,创建设备节点;

#include "vl53l0x.h"

// 当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中
static int vl53l0x_probe(struct i2c_client *client)
{
    int ret;

    ret = vl53l0x_cdev_init(client);
    if (ret) {
        dev_err(&client->dev, "Failed to init char device\n");
        return ret;
    }

    return 0;
}

/* 设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中 */
static void vl53l0x_remove(struct i2c_client *client)
{
    vl53l0x_cdev_destroy();
    dev_err(&client->dev, "remove vl53l0x success\n");
}

/* i2c设备id列表(设备树) */
static const struct of_device_id vl53l0x_of_match[] = {
    { .compatible = "icar,vl53l0x" },
    {},
};

/* i2c设备id列表(非设备树) */
static const struct i2c_device_id vl53l0x_id[] = {
    { "icar,vl53l0x", 0 },
    {},
};

/* i2c驱动 */
static struct i2c_driver vl53l0x_driver = {
    .probe = vl53l0x_probe,
    .remove = vl53l0x_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "vl53l0x",
        .of_match_table = of_match_ptr(vl53l0x_of_match),
    },
    .id_table = vl53l0x_id,
};

/* i2c驱动入口函数 */
module_i2c_driver(vl53l0x_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhengyang");

通过 of_match_table 与设备树中 compatible = "icar,vl53l0x" 的节点匹配,实现驱动与硬件自动绑定。

2.5 Makefile

创建Makefile文件:

# 内核源码路径
KERNELDIR ?= /opt/2k0300/build-2k0300/workspace/linux-6.12
# 当前驱动目录
PWD := $(shell pwd)
# 交叉编译工具链
CROSS_COMPILE := loongarch64-linux-gnu-
# 架构
ARCH := loongarch

# 所需文件夹
BUILD_DIR := build
INCLUDE:=inc
KO_DIR:=ko
SRC_DIR:=src

EXTRA_CFLAGS +=  -I$(src)/$(INCLUDE)

# 编译目标
obj-m := vl53l0x.o
vl53l0x-y := vl53l0x_driver.o \
             src/vl53l0x_hw.o \
             src/vl53l0x_core.o \
             src/vl53l0x_dev.o

# 编译规则
ll: prepare compile move_files

# 提前创建目录
prepare:
        @mkdir -p $(BUILD_DIR) $(KO_DIR)
        @echo "📂 \033[32m已创建目录\033[0m"

compile:
        @echo "📂 开始编译驱动模块"
        # 关键:指定内核路径并传递正确的编译参数
        make -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules

# 移动文件
move_files:
        # 移动中间文件(.o/.mod.o/.mod.c/.cmd等)到build目录
        @find . -type f \
                -not -path "./$(BUILD_DIR)/*" -not -path "./$(KO_DIR)/*" \
                \( -name '*.o' -o -name '*.mod.o' -o -name '*.mod.c' -o -name '.*.cmd' -o -name 'modules.order' -o -name 'Module.symvers' \) \
                ! -name '*.ko' -exec mv -t $(BUILD_DIR)/ {} +
        # 移动 ko 模块到 ko 目录
        @find . -type f \
                -not -path "./$(BUILD_DIR)/*" -not -path "./$(KO_DIR)/*" \
                -name '*.ko' -exec mv -t $(KO_DIR)/ {} +

clean:
        @echo "🧹 清理编译产物"
        make -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
        rm -rf *.ko *.o *.mod.o *.mod.c *.symvers *.order .*.cmd .tmp_versions build ko

2.6 新增设备节点

这里我们将VL53L0X测距模块接到i2c0接口,因此需要适当调整设备树。

2.6.1 i2c0

i2c0节点定义在arch/loongarch/boot/dts/loongson-2k0300.dtsi

i2c0: i2c0@0x16108000 {
        compatible = "loongson,lsfs-i2c";
        reg = <0 0x16108000 0 0x1000>;
        #address-cells = <1>;
        #size-cells = <0>;
        interrupt-parent = <&liointc0>;
        interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
        /* 定义引脚状态 */
        pinctrl-0 = <&i2c0_pins>;
        pinctrl-names = "default";
        clock-frequency = <200000000>;
        status = "disabled";
};

其中:

  • i2c0::标签,用于在设备树中其他地方通过&i2c0引用这个节点,方便添加属性或修改状态;
  • i2c0@0x16108000:节点名,格式为 设备名@寄存器基地址。这里i2c0是功能名,0x16108000是该I2C控制器的物理寄存器基地址。
  • compatible:驱动匹配字符串。内核通过该属性寻找能驱动此设备的驱动程序;
  • reg:寄存器地址范围。格式为 <地址高位 地址低位 长度高位 长度低位>,由于龙芯采用64位寻址,这里用两个32位数表示64位地址;
  • #address-cells#size-cells:定义该节点下子节点(即挂载的I2C从设备)的地址和长度格式。
    • #address-cells = <1>:子节点的 reg 属性中,地址部分占用132位单元。对于I2C设备,这通常是7位从设备地址;
    • #size-cells = <0>:子节点的 reg 属性中没有长度字段(因为I2C设备地址不涉及地址范围);
  • interrupt-parent:指定该设备的中断路由到哪个中断控制器。&liointc0 是龙芯2K0300内部的中断控制器节点(即龙芯I/O中断控制器)的标签;
  • interrupts:描述中断线的具体信息;
    • <4>:硬件中断编号(对应I2C0控制器在中断控制器中的编号);
    • IRQ_TYPE_LEVEL_HIGH:中断触发类型,这里定义为高电平触发。该宏在 <dt-bindings/interrupt-controller/irq.h> 中定义。
  • pinctrl-0:指定设备使用的第一组引脚配置,&i2c0_pins 是另一个设备树节点的标签,该节点描述了I2C0SCLSDA引脚应复用为I2C功能,并可能包含上拉等电气属性;
  • pinctrl-names:为引脚的配置状态命名,与 pinctrl-0 对应。"default" 是默认状态,驱动在probe时会自动应用 pinctrl-0 的配置,引脚配置设置为&i2c0_pins
  • clock-frequencyI2C控制器的输入时钟频率(单位Hz);
  • status:设备状态,此时处于禁用状态。

更多有关设备树相关的内容可以参考:《linux设备树-基础介绍》。

2.6.2 vl53l0x@29

修改arch/loongarch/boot/dts/ls2k300_99pi.dtsi

// 在根节点或合适位置(通常是 &soc 内)引用并启用 I2C0
&i2c0 {
    status = "okay";

    // VL53L0X 传感器
    vl53l0x@29 {
        compatible = "icar,vl53l0x";
        reg = <0x29>;
    };
};

通过 &i2c0 引用这个节点,将 status 改为 "okay",同时添加了I2C从设备节点。

其中vl53l0x@29设备节点下的compatible名称要和i2c_vl53l0x_driver.c驱动中的vl53l0x_driver.driver.of_match_table里的名称匹配。

static const struct i2c_device_id vl53l0x_id[] = {
    {"icar,vl53l0x", 0},
    {},
};

static struct i2c_driver vl53l0x_driver = {
    .probe = vl53l0x_probe,
    .remove = vl53l0x_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "vl53l0x",
        .of_match_table = of_match_ptr(vl53l0x_of_match),
    },
    .id_table = vl53l0x_id,
};

这样在适配器注册的时候会遍历i2c设备树节点下的所有子设备节点,从而实现注册该控制器下的所有I2C从设备,具体可以参考《I2C适配器注册》源码部分。

2.6.3 i2c0_pins

i2c0_pins定义在arch/loongarch/boot/dts/loongson-2k0300.dtsi

pinmux: pinmux@16000490 {
	......
	i2c0_pins: pinmux_G48_G49_as_i2c0 {
    	    pinctrl-single,bits = <0xc 0x0000000f 0x0000000f>;
	};
	......
}

这个设备树节点是使用pinctrl-single驱动来配置引脚复用功能的典型写法,这行代码会在寄存器0x1600049c的低4位写入 0xff,而高28位保持不变,用于将芯片的GPIO48GPIO49这两个引脚设置为I2C0功能。

注:pinctrl-single 是一个通用的引脚控制驱动,适用于那些引脚复用寄存器是“单寄存器位域”的芯片。当SoC没有提供复杂的pinctrl框架时,可以直接用这种方式“裸写”寄存器。

2.7 编译驱动

执行make命令编译驱动:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver$ make
📂 已创建目录
📂 开始编译驱动模块
# 关键:指定内核路径并传递正确的编译参数
make -C /opt/2k0300/build-2k0300/workspace/linux-6.12 ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- M=/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver modules
make[1]: 进入目录“/opt/2k0300/build-2k0300/workspace/linux-6.12”
  CC [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/vl53l0x_driver.o
  CC [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/src/vl53l0x_hw.o
  CC [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/src/vl53l0x_core.o
  CC [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/src/vl53l0x_dev.o
  LD [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/vl53l0x.o
  MODPOST /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/Module.symvers
  CC [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/vl53l0x.mod.o
  CC [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/.module-common.o
  LD [M]  /opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/vl53l0x.ko
make[1]: 离开目录“/opt/2k0300/build-2k0300/workspace/linux-6.12”
# 移动中间文件(.o/.mod.o/.mod.c/.cmd等)到build目录
# 移动 ko 模块到 ko 目录

查看驱动:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver$ ll
drwxrwxr-x 2 zhengyang zhengyang 4096  3月 24 11:24 build/
drwxrwxr-x 2 zhengyang zhengyang 4096  3月 24 11:23 inc/
drwxrwxr-x 2 zhengyang zhengyang 4096  3月 24 11:24 ko/
-rw-rw-r-- 1 zhengyang zhengyang 1551  3月 24 11:15 Makefile
drwxrwxr-x 2 zhengyang zhengyang 4096  3月 24 11:24 src/
-rw-rw-r-- 1 zhengyang zhengyang  946  3月 24 11:24 vl53l0x_driver.c
-rw-rw-r-- 1 zhengyang zhengyang  299  3月 24 11:24 vl53l0x.mod

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver$ ll ko/
-rw-rw-r-- 1 zhengyang zhengyang 30680  3月 24 11:24 vl53l0x.ko

三、应用程序

接下来我们在example目录下创建子目录vl53l0x_app

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example$ mkdir vl53l0x_app

目录结构如下:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/vl53l0x_app$ tree .
.
├── main.c
└── Makefile

3.1 main.c

基于你的VL53L0X驱动(字符设备 /dev/vl53l0x_tofioctl命令 VL53L0X_GET_DATA),下面编写一个简单的测试应用程序,用于读取距离并打印;

/*
 * 测试 VL53L0X 驱动,通过 ioctl 读取距离值并打印
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>

/* 与驱动中一致的 ioctl 命令定义 */
#define VL53L0X_IOCTL_MAGIC     't'
#define VL53L0X_GET_DATA        _IOR(VL53L0X_IOCTL_MAGIC, 1, uint16_t)

/* 设备节点路径 */
#define DEVICE_PATH             "/dev/vl53l0x_tof"

int main(int argc, char **argv)
{
    int fd;
    uint16_t distance_mm;
    int ret;

    /* 1. 打开设备 */
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    printf("Opened %s successfully.\n", DEVICE_PATH);

    /* 2. 循环读取距离值 */
    while (1) {
        ret = ioctl(fd, VL53L0X_GET_DATA, &distance_mm);
        if (ret < 0) {
            perror("ioctl");
            break;
        }

        printf("Distance: %d mm\n", distance_mm);

        /* 控制打印频率,避免过快,可调节 */
        usleep(200000);  // 200ms
    }

    close(fd);
    return 0;
}

3.2 Makefile

all:
	loongarch64-linux-gnu-gcc -o main main.c
clean:
	rm -rf *.o main

3.3 编译应用程序

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/vl53l0x_app$ make
loongarch64-linux-gnu-gcc -o main main.c
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/vl53l0x_app$ ll
-rwxrwxr-x 1 zhengyang zhengyang 20680  3月 24 14:26 main*
-rw-rw-r-- 1 zhengyang zhengyang  1070  3月 24 14:23 main.c
-rw-rw-r-- 1 zhengyang zhengyang    71  3月 24 14:24 Makefile

四、测试

4.1 烧录设备树

有关设备树的编译和烧录分别参考:

4.2 安装驱动

久久派开发板执行如下命令:

root@buildroot:~$ scp zhengyang@172.23.34.186:/opt/2k0300/loongson_2k300_lib/driver/i2c_vl53l0x_driver/ko/vl53l0x.ko ./

root@buildroot:~$ insmod vl53l0x.ko

查看设备节点文件:

root@buildroot:~$ ls /dev/vl53l0x_tof -l

root@buildroot:~$ ls /sys/devices/platform/i2c.0/i2c-0/ -l

root@buildroot:~$ ls /sys/devices/platform/i2c.0/i2c-0/0-0028/ -l

root@buildroot:~$ ls /sys/bus/i2c/devices -l

root@buildroot:~$ ls /sys/class/vl53l0x_tof -l

4.3 应用程序测试

久久派开发板执行如下命令:

root@buildroot:~$ scp zhengyang@172.23.34.186:/opt/2k0300/loongson_2k300_lib/example/vl53l0x_app/main ./
root@buildroot:~$ ./main

观察输出,每隔约200ms打印一次距离(单位:毫米),按 Ctrl+C 退出。

参考文章

[1] Linux内核分析——基于LoongArch架构(Linux-6.0).pdf

[2] 第一卷-pmon软件使用手册.pdf

[3] 第二卷-pinctrldts开发手册.pdf

[4] 第三卷-2k300内核开发手册.pdf

[5] 第四卷-文件系统开发手册.pdf

[6] 第五卷-qt相关解决方案.pdf

[7] LS2K0300久久派_V1.1板卡使用手册v1.2_20240705.pdf

[8] 龙芯2K300_301软件开源库

[9] 21届智能车走马观碑开源仓库

posted @ 2026-03-24 12:47  大奥特曼打小怪兽  阅读(76)  评论(0)    收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步