这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【PocketBeagle2】七,I2C子系统-BMP180驱动

共1条 1/1 1 跳转至

【PocketBeagle2】七,I2C子系统-BMP180驱动

菜鸟
2025-11-03 09:33:29     打赏

【PocketBeagle2】七,I2C子系统-BMP180驱动

BMP180 是博世(Bosch)公司推出的一款高精度数字气压与温度传感器,属于低功耗、小体积的环境监测传感器。它通过测量大气压间接计算海拔高度,并同时提供温度数据,广泛应用于气象站、无人机、移动设备、室内导航等领域。

我们只需要关注这一部分内容就可以了

image-20250809120004277image.png

image-20250809122015994image.png

本次实验我们选择i2c2接口与BMP180相连。实物连接图如下:

image-20251102171451401image.png

一,BMP180设备树插件

设备树插件(Device Tree Overlay,简称DTO)是Linux内核(通常在4.4版本及以上引入)中一种强大的动态设备树扩展机制。可以把它理解成能给正在运行的系统“打补丁”,允许在系统运行时动态地添加、修改或删除设备树中的节点和属性,而无需重新编译和烧写整个设备树。

1.设备树插件源码

/dts-v1/;
/plugin/;

#include <dt-bindings/leds/common.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>

/*
* Helper to show loaded overlays under: /proc/device-tree/chosen/overlays/
*/
&{/chosen} {
overlays {
 kone-pocketbeagle2.kernel = __TIMESTAMP__;
};
};
&main_i2c2 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;

mpu6050@68{
 status = "okay";
 compatible = "kone,i2c_mpu6050";
 reg = <0x68>;
};
BMP180@77{
 status = "okay";
 compatible = "kone,BMP180";
 reg = <0x77>;
};
   AHT30@38{
       status = "okay";
       compatible = "kone,AHT30";
       reg=<0x38>;
   };
};

2.编译设备树插件

设置正确的环境变量

export PATH="/home/mxz/pocketbeagle_2/sdk/gcc-11.5.0-nolibc/aarch64-linux/bin:$PATH"
# 设置架构和交叉编译前缀
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-

makefile中添加新增的设备树

nano /home/mxz/pocketbeagle_2/sdk/linux/arch/arm64/boot/dts/ti/Makefile
dtb-$(CONFIG_ARCH_K3) += kone-pocketbeagle2.dtbo

编译

# 进入内核源码目录
cd /home/mxz/pocketbeagle_2/sdk/linux

# 编译所有设备树
make dtbs

3.加载设备树插件

  1. 将生成的 kone-pocketbeagle2.dtbo  copy到/boot/firmware/overlays/下

  2. 更新引导配置

    sudo nano /boot/firmware/extlinux/extlinux.conf
    label Linux
       kernel /Image.gz
       fdtdir /
       append console=ttyS2,115200n8 earlycon=ns16550a,mmio32,0x02860000 root=/dev/mmcblk1p2 ro rootfstype=ext4 rootwait net.ifnames=0
       fdtoverlays /overlays/kone-pocketbeagle2.dtbo
  3. 验证

    # 重启
    sudo reboot

    确认设备树覆盖层加载成功

    # 查看覆盖层时间戳(确认编译时间)
    cat /sys/firmware/devicetree/base/chosen/overlays/kone-pocketbeagle2.kernel

    # 查看所有加载的覆盖层
    ls /sys/firmware/devicetree/base/chosen/overlays/

    查看 I2C2 总线

    ls /sys/bus/i2c2/devices/

二,BMP180驱动

  1. 驱动源码

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kdev_t.h>
    #include <linux/cdev.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/i2c.h>
    #include <linux/types.h>
    #include <linux/errno.h>       // 错误码定义
    #include <linux/of.h>
    #include <asm/io.h>
    #include <linux/device.h>
    #include <linux/delay.h>
    #include <linux/platform_device.h>
    #include <linux/slab.h>     // 内存分配
    #include <linux/uaccess.h>  // copy_to_user头文件
    
    // ======================================define=========================================
    #define DEV_NAME        "bmp180"
    #define BMP180_ADDR     0x77
    
    // BMP180寄存器定义
    #define BMP180_REG_CALIB_START      0xAA    // 校准数据起始地址
    #define BMP180_REG_CTRL_MEAS        0xF4    // 控制测量寄存器
    #define BMP180_RRG_OUT_MSB          0xF6    // 输出数据MSB寄存器
    
    // 控制测量命令
    #define BMP180_CMD_TEMP             0x2E    // 温度测量命令
    #define BMP180_CMD_PRESS_OSS0       0x34    // 压力测量命令(OSS=0)
    
    // 海拔计算相关常量
    #define SEA_LEVEL_PRESSURE          101325  // 海平面标准大气压 (Pa)
    #define ALTITUDE_FACTOR             44330000 // 用于海拔计算的常数
    
    // ============================== 全局变量 =============================
    
    
    
    static dev_t bmp180_dev_num;                // 设备号
    static struct cdev bmp180_cdev;             // 字符设备结构
    struct device *bmp180_device;               // 设备结构
    struct class *bmp180_class;                 // 设备类
    static struct i2c_client *bmp180_client = NULL;    // I2C客户端
    
    // BMP180校准数据结构
    struct bmp180_calib_data {
        short AC1;
        short AC2;
        short AC3;
        unsigned short AC4;
        unsigned short AC5;
        unsigned short AC6;
        short B1;
        short B2;
        short MB;
        short MC;
        short MD;
    };
     struct bmp180_result {
            long temperature; // 温度(0.1°C)
            long pressure;    // 压力(Pa)
        } ;
    static struct bmp180_result    result ;
    static struct bmp180_calib_data calib; // 存储校准数据
    static uint8_t OSS = 0;
    
    // ============================== I2C通信函数 ==========================
    /**
     * i2c_write_bmp180 - 向BMP180写入数据
     * @client: I2C客户端
     * @data: 要写入的数据
     * @len: 数据长度
     * 
     * 返回值: 0成功,负数表示错误
     */
     static int i2c_write_bmp180(struct i2c_client*client,uint8_t *data,uint8_t len)
     {
        int ret;
        struct i2c_msg msg;
        if(!client)
        {
            printk(KERN_ERR "BMP180: NULL client pointer\n");
            return -EINVAL;
        }
        msg.addr = client->addr;
        msg.flags = 0;               //write
        msg.buf = data;
        msg.len = len;
    
        ret = i2c_transfer(client->adapter,&msg,1);
        if(ret != 1)
        {
            printk(KERN_ERR "bmp180 write is error\n");
            return -EIO;
        }
        return 0;
     }
     /**
     * i2c_read_bmp180 - 从BMP180读取数据
     * @client: I2C客户端
     * @reg: 要读取的寄存器地址
     * @data: 存储读取数据的缓冲区
     * @len: 要读取的数据长度
     * 
     * 返回值: 0成功,负数表示错误
     */
     static int i2c_read_bmp180(struct i2c_client *client,uint8_t reg,uint8_t *data,uint8_t len)
     {
        int ret;
        struct i2c_msg msg[2];
    
        if(!client)
        {
            printk(KERN_ERR "BMP180 : NULL client pointer\n");  //无效参数
            return -EINVAL;
        }
    
        msg[0].addr = client->addr;
        msg[0].flags = 0;
        msg[0].buf = &reg;
        msg[0].len = 1;
    
        msg[1].addr = client->addr;
        msg[1].flags = I2C_M_RD;
        msg[1].buf = data;
        msg[1].len = len;
    
        ret = i2c_transfer(client->adapter,msg,2);
        if(ret != 2)
        {
            printk(KERN_ERR "bmp180 read is error\n");
            return -EIO;            //(输入输出错误)
        }
        return 0;
     }
    
     // ============================== 设备操作函数 =========================
    /**
     * bmp180_open - 打开设备
     * @inode: inode结构
     * @file: file结构
     * 
     * 返回值: 0成功,负数表示错误
     */
     static int bmp180_open(struct inode*inode,struct file*file)
     {
        printk(KERN_INFO"BMP180 device is open\n");
        return 0;
     }
    
     
    /**
     * bmp180_read - 读取设备数据
     * @file: file结构
     * @buf: 用户空间缓冲区
     * @size: 请求读取的大小
     * @off: 文件偏移
     * 
     * 返回值: 实际读取的字节数,负数表示错误
     */
     static ssize_t bmp180_read(struct file*file,char __user*buf,size_t size,loff_t*off)
     {
        uint8_t cmd[2];
        uint8_t data[3];
        long ut = 0;    // 未补偿的温度值
        long up = 0;    // 未补偿的压力值
        long x1, x2, b5;
        long p;
        int ret;
        // ====== 1. 读取温度 ======
        cmd[0] = BMP180_REG_CTRL_MEAS;
        cmd[1] = BMP180_CMD_TEMP;
        ret = i2c_write_bmp180(bmp180_client,cmd,2);
        if(ret < 0)
        {
            printk(KERN_ERR "BMP180: Failed to start temperature measurement\n");
            return ret;
        }
        // 等待温度测量完成(最大4.5ms)
        msleep(5);
        // 读取温度数据(2字节)
        ret = i2c_read_bmp180(bmp180_client,BMP180_RRG_OUT_MSB,data,2);
        if(ret < 0)
        {
            printk(KERN_ERR "BMP180: Failed to read temperature data\n");
            return ret;
        }
        ut = (data[0] << 8) | data[1];
    
        // ====== 2. 读取压力 ======
        cmd[0] = BMP180_REG_CTRL_MEAS;
        cmd[1] = BMP180_CMD_PRESS_OSS0 + (OSS << 6);
        ret = i2c_write_bmp180(bmp180_client,cmd,2);
        if (ret < 0) {
            printk(KERN_ERR "BMP180: Failed to start pressure measurement\n");
            return ret;
        }
        msleep(5);
        ret = i2c_read_bmp180(bmp180_client,BMP180_RRG_OUT_MSB,data,3);
        if (ret < 0) {
            printk(KERN_ERR "BMP180: Failed to read pressure data\n");
            return ret;
        }
        up = ((data[0] << 16) | (data[1] << 8) | data[2]) >> (8 - OSS); 
    
        // ====== 3. 温度补偿计算 ======
        x1 = ((ut - calib.AC6) * calib.AC5) >> 15;
        x2 = (calib.MC << 11) / (x1 + calib.MD);
        b5 = x1 + x2;
        long temperature = (b5 + 8) >> 4;
    
        // ====== 4. 气压补偿计算 ======
        long b6 = b5 - 4000;
    
        x1 = (calib.B2 * ((b6 * b6)>>12)) >> 11;
        x2 = (calib.AC2 * b6) >> 11;
        long x3 = x1 + x2;
        long b3 = (((calib.AC1 * 4 + x3) << OSS) + 2) / 4;
    
        x1 = (calib.AC3 * b6) >> 13;
        x2 = (calib.B1 * ((b6 * b6) >> 12)) >> 16;
        x3 = ((x1 + x2) + 2) >> 2;
        unsigned long b4 = (calib.AC4 * (unsigned long)(x3 + 32768)) >> 15;
    
        unsigned long b7 = ((unsigned long)up - b3) * (50000 >> OSS);
        if(b7 < 0x80000000){
            p = (b7 * 2) / b4;
        }else{
            p = (b7 / b4) * 2;
        }
        x1 = (p >> 8) * (p >> 8);
        x1 = (x1 * 3038) >> 16;
        x2 = (-7357 * p) >> 16;
        long pressure = p + ((x1 + x2 + 3791) >> 4);
    
        // ====== 5. 准备返回数据 ======
        // 创建包含温度、气压和海拔高度的数据结构
       
        result.temperature = temperature;
        result.pressure  = pressure;
    
        printk(KERN_DEBUG "BMP180: Raw T=%ld, P=%ld  Calculated T=%ld.%ld°C, P=%ldPa, \n",
               ut, up, temperature/10, temperature%10, pressure );
        // ====== 6. 复制到用户空间 ======       
        if(copy_to_user(buf,&result,sizeof(result))){
            printk(KERN_ERR "BMP180:copy_to_user is error \n");
            return -EFAULT;
        }
        return sizeof(result);
     }
     /**
     * bmp180_release - 关闭设备
     * @inode: inode结构
     * @file: file结构
     * 
     * 返回值: 0
     */
    static int bmp180_release(struct inode*inode,struct file*file)
    {
        printk(KERN_INFO"bmp180 device is error\n");
        return 0;
    }
    // 文件操作结构体
    static struct file_operations fops = {
        .owner = THIS_MODULE,
        .open = bmp180_open,
        .read = bmp180_read,
        .release = bmp180_release ,
    };
    
    // ============================== 校准数据读取 =========================
    /**
     * read_calibration_data - 读取校准数据
     * 
     * 返回值: 0成功,负数表示错误
     */
     int read_calibration_data(void)
     {
        uint8_t calib_data[22];
        int ret;
    
        ret = i2c_read_bmp180(bmp180_client,BMP180_REG_CALIB_START,calib_data,sizeof(calib_data));
        if(ret < 0)
        {
            printk(KERN_ERR "BMP180 read calibration data is error\n");
            return ret;
        }
        calib.AC1 = (calib_data[0] << 8) | calib_data[1];
        calib.AC2 = (calib_data[2] << 8) | calib_data[3];
        calib.AC3 = (calib_data[4] << 8) | calib_data[5];
        calib.AC4 = (calib_data[6] << 8) | calib_data[7];
        calib.AC5 = (calib_data[8] << 8) | calib_data[9];
        calib.AC6 = (calib_data[10] << 8) | calib_data[11];
        calib.B1 = (calib_data[12] << 8) | calib_data[13];
        calib.B2 = (calib_data[14] << 8) | calib_data[15];
        calib.MB = (calib_data[16] << 8) | calib_data[17];
        calib.MC = (calib_data[18] << 8) | calib_data[19];
        calib.MD = (calib_data[20] << 8) | calib_data[21];
        
        // 打印校准数据用于调试
        printk(KERN_INFO "BMP180 Calibration Data:\n");
        printk(KERN_INFO "AC1=%d, AC2=%d, AC3=%d\n", calib.AC1, calib.AC2, calib.AC3);
        printk(KERN_INFO "AC4=%u, AC5=%u, AC6=%u\n", calib.AC4, calib.AC5, calib.AC6);
        printk(KERN_INFO "B1=%d, B2=%d, MB=%d\n", calib.B1, calib.B2, calib.MB);
        printk(KERN_INFO "MC=%d, MD=%d\n", calib.MC, calib.MD);
    
        ret = i2c_read_bmp180(bmp180_client,BMP180_REG_CTRL_MEAS,&OSS,1);
        if(ret < 0)
        {
            printk(KERN_ERR "BMP180 read OSS data is error\n");
            return ret;
        }
        OSS >>= 6;
        printk(KERN_INFO "OSS=%d\n",OSS);
        return 0;
    }
    
    // ============================== 驱动探测和移除 =======================
    /**
     * bmp180_probe - 驱动探测函数
     * @client: I2C客户端
     * @id: 设备ID
     * 
     * 返回值: 0成功,负数表示错误
     */
     static int bmp180_probe(struct i2c_client*client)
     {
        int ret;
        printk(KERN_INFO "BMP180 probe started for device at 0x%02X\n", client->addr);
        // 1. 分配设备号
        ret = alloc_chrdev_region(&bmp180_dev_num,0,1,DEV_NAME);
        if(ret < 0)
        {
            printk(KERN_ERR "BMP180 : alloc_chrdev_region is errot\n");
            return ret;
        }
        // 2. 初始化字符设备
        bmp180_cdev.owner = THIS_MODULE;
        cdev_init(&bmp180_cdev,&fops);
        // 3. 添加字符设备
        ret = cdev_add(&bmp180_cdev,bmp180_dev_num,1);
        if(ret < 0)
        {
            printk(KERN_ERR"BMP180:cdev_add is error\n");
            goto free_dev_num;
        }
        // 4. 创建设备类
        bmp180_class =  class_create(DEV_NAME);
        // 5. 创建设备节点
        bmp180_device = device_create(bmp180_class,NULL,bmp180_dev_num,NULL,DEV_NAME);
        // 6. 保存I2C客户端
        bmp180_client = client;
        // 7. 读取校准数据
        ret = read_calibration_data();
        if(ret < 0)
        {
            printk(KERN_ERR "BMP180: Failed to read calibration data\n");
            goto destroy_device;
        }
        printk(KERN_INFO "BMP180 driver probed successfully\n");
        return 0;
    destroy_device:
        device_destroy(bmp180_class,bmp180_dev_num);
        class_destroy(bmp180_class);
        cdev_del(&bmp180_cdev);
    free_dev_num:
        unregister_chrdev_region(bmp180_dev_num,1);
        return ret;
     }
     /**
     * bmp180_remove - 驱动移除函数
     * @client: I2C客户端
     */
     static void bmp180_remove(struct i2c_client*client)
     {
        device_destroy(bmp180_class,bmp180_dev_num);
        class_destroy(bmp180_class);
        cdev_del(&bmp180_cdev);
        unregister_chrdev_region(bmp180_dev_num,1);
        printk(KERN_INFO"bmp180 is remove\n");
     }
    
     // ============================== 设备ID和设备树匹配表 =================
     static const struct i2c_device_id bmp180_device_id[] = {
        {"kone,bmp180"},
        {},
     };
    
     static const struct of_device_id bmp180_of_match_table[] = {
        {.compatible = "kone,bmp180"},
        {},
     };
    // ============================== I2C驱动结构 =========================
     static struct i2c_driver bmp180_driver = {
        .probe = bmp180_probe,
        .remove = bmp180_remove,
        .id_table = bmp180_device_id,
        .driver = {
            .name = "bmp180",
            .owner = THIS_MODULE,
            .of_match_table = bmp180_of_match_table,
        },
     };
    
     static int __init bmp180_init(void)
     {
        i2c_add_driver(&bmp180_driver);
        printk(KERN_INFO"bmp180 init \n");
        return 0;
     }
     static void __exit bmp180_exit(void)
     {
        i2c_del_driver(&bmp180_driver);
        printk(KERN_INFO"bnp180 exit \n");
     }
    
     module_init(bmp180_init);
     module_exit(bmp180_exit);
     MODULE_AUTHOR("kone");
     MODULE_LICENSE("GPL v2");
  2. Makefile

    #export PATH="/home/mxz/pocketbeagle_2/sdk/gcc-11.5.0-nolibc/aarch64-linux/bin:$PATH"
    # 确保系统工具目录在 PATH 的最前面
    export PATH="/bin:/usr/bin:/home/mxz/pocketbeagle_2/sdk/gcc-11.5.0-nolibc/aarch64-linux/bin:$PATH"
    # 设置架构和交叉编译前缀
    export ARCH=arm64
    export CROSS_COMPILE=aarch64-linux-
    obj-m:=bmp180.o
    
    KDIR:=/home/mxz/pocketbeagle_2/sdk/linux
    PWD:=$(shell pwd)
    
    all:
     make -C $(KDIR) M=$(PWD) modules
    clean:
     make -C $(KDIR) M=$(PWD) clean


  3. 测试app

    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <math.h>
    
    // ICAO 标准大气参数
    #define ICAO_SEA_LEVEL_PRESSURE 101325.0  // 海平面标准气压 (Pa)
    #define ICAO_SCALE_HEIGHT 44330.0         // 尺度高度 (米)
    #define ICAO_EXPONENT 0.190263            // 指数系数 (1/5.255 ≈ 0.190263)
    
    // 与驱动中定义相同的结果结构
    struct bmp180_result {
        long temperature; // 温度(0.1°C)
        long pressure;    // 压力(Pa)
    };
    /**
     * @brief 使用ICAO简化公式计算海拔高度    altitude = 44330 * [1 - (P/P0)^(1/5.255)] 
     * @param pressure 当前气压 (Pa)
     * @param sea_level_pressure 海平面气压 (Pa),如果为0则使用标准值
     * @return 海拔高度 (米)
     */
    double calculate_altitude(double pressure, double sea_level_pressure)
    {
        if(pressure < 0)    return -1;
        // 确保气压值合理
        if (pressure > sea_level_pressure) {
            pressure = sea_level_pressure; // 气压不可能高于海平面气压
        }
        // 计算气压比
        double pressure_ratio = pressure / sea_level_pressure;
        // 计算海拔
        double altitude = ICAO_SCALE_HEIGHT * (1.0 - pow(pressure_ratio,ICAO_EXPONENT) );
        return altitude;
    }
    int main() {
        int fd;
        struct bmp180_result result;
        
        // 打开设备
        fd = open("/dev/bmp180", O_RDONLY);
        if (fd < 0) {
            perror("Failed to open device");
            return -1;
        }
        
        // 读取数据
        if (read(fd, &result, sizeof(result)) != sizeof(result)) {
            perror("Failed to read data");
            close(fd);
            return -1;
        }
        
        // 计算真实值
        float temperature = result.temperature / 10.0;
        float pressure = result.pressure / 100.0; // 转换为hPa
        double altitude = calculate_altitude((double)pressure,ICAO_SEA_LEVEL_PRESSURE);
        
        // 打印结果
        printf("Temperature: %.1f°C\n", temperature);
        printf("Pressure: %.2f hPa\n", pressure);
        printf("Altitude: %.2f meters\n", altitude);
        
        // 关闭设备
        close(fd);
        return 0;
    }

三,演示

将生成的bmp180.ko和app.c   copy到开发板上

insmod bmp180.ko
gcc app.c -lm
./a.out /dev/bmp180

image.png

image-20251102172135430

可以看出温度和气压的数据还不错,但是海拔就不准了,毕竟的使用的是ICAO简化公式计算海拔高度。



共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]