Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发

驱动开发主要位于 Linux 内核层,首先认识一下 BMP180,接着需要修改 DTS,最后才开始编写驱动,编写完驱动我们再来写一个用户层验证程序来获取温度和压力。

一、BMP180 芯片介绍

BMP180 是一款高精度、小体积、超低能的压力传感器,适用于移动电话、PDAs、GPS 导航设备和户外设备。在与仅仅 0.25 米低空噪音快速转换时,BMP180 提供卓越的性能。使用 I2C 接口可以很轻松的跟主控制器系统进行通讯。

BMP180 基于压阻式技术 EMC 稳健性、高精度以及线性长期稳定性,被设计用来直接连接带有 I2C 总线接口的移动设备小型控制器。

BMP180 的 EEPROM 值会补偿气压以及温度值。BMP180 包含压阻式传感器、模拟数字转换器、EEPROM 控制单元和 I2C 接口。

二、硬件特性

Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第1张图片

  • I2C,3.3V
  • 气压值 (16 to 19 bit)
  • 温度值 (16 bit)
  • PCB Dimension(mm): 16 x 16
  • 引脚说明:
名称 描述
SDA I2C SDA
SCL I2C SCL
5V 电源5V
GND

Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第2张图片

三、硬件连接

NanoPC-T4 和以上温度压力传感器模块连接。

温度压力传感器模块 NanoPC-T4
SDA Pin3
SCL Pin5
5V Pin4
GND Pin6

Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第3张图片

以上为示意图,NanoPC-T4 USB 和上图并不一致。

四、修改 DTS

编辑内核目录下的 DTS 文件 arch/arm64/boot/dts/rockchip/rk3399-nanopi4-common.dtsi,在 &i2c2 增加下面的内容。DTS 即 Device Tree Source 设备树源码,Device Tree 是一种描述硬件的数据结构。如此我们就将温度压力传感器模块注册到了内核设备树。

&i2c2 {
	status = "okay";
	bmp180: bmp180@77{
                compatible = "bosch,bmp180";
                reg = <0x77>;
                status = "okay";
    };
};

五、驱动编写

首先要了解如何面向 BMP180 编程。

5.1 BMP180 I2C 从地址

0x77(7bit 有效地址,不包括读写位,Linux I2C 总线驱动内部作读写位处理),DTS 中要和这个地址保持一致。
在这里插入图片描述

5.2 BMP180 校准系数

处理器通过 I2C 读取 BMP180 的温度值和压力值是原始 AD 值,未经过校准补偿。每个 BMP180 器件在出厂时都根据不同特性提供独有的校准系数,存储于内置 EEPROM 中,校准系数共 176 位(22字节)。处理器获得准确的气压值和温度值,首先应读取内置校准系数,将原始 AD 值和校准系数进行换算。BMP180 数据手册提供计算方式。
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第4张图片

5.3 BMP180 压力和温度测量流程

Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第5张图片

5.4 BMP180 寄存器

下面的内存映射显示了操作 BMP180 所需的所有外部可访问数据寄存器。左边的列显示内存地址。中间的列描述了每个寄存器位的内容。位的颜色表示它们是只读的、只读的或可读可写的。内存是易失性的,所以每次开机后都必须重写可写内容。并不是所有的寄存器地址都显示出来了。这些寄存器被保留为博世工厂进一步测试和修整。
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第6张图片

Measurement(寄存器 F4h <4:0>):控制测量。

Sco(寄存器 F4h <5>):开始转换。 在转换过程中,该位的值保持为“1”,在转换完成(数据寄存器已填充)后复位为“0”。

Oss(寄存器 F4h <7:6>):控制压力测量的过采样率(00b:单次,01b:2次,10b:4次,11b:8次)。

软复位(寄存器 E0h):只写寄存器。如果设置为 0xB6,将执行与上电复位相同的序列。

Chip-id(寄存器 D0h):此值固定为 0x55,可用于检查是否通讯正常。

校准系数寄存器
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第7张图片

转换后,数据寄存器可以按任意顺序读出(即 MSB 优先或 LSB 优先) 。

5.5 开始温度和压力测量

下面显示了开始测量温度值 UT 和压力值 UP 的时序图。启动条件后,主机发送设备地址写,寄存器地址和控制寄存器数据。当接收到数据时,BMP180 每 8 个数据位发送一个确认(ACKS)。 主机在最后一个 ACKS 之后发送停止条件。
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第8张图片
控制寄存器的值用于不同的内部过采样设置(OSS)
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第9张图片

5.6 读取 A/D 转换结果或 E2PROM 数据

要读取温度数据字 UT(16位),压力数据字 UP(16至19位)和 E2PROM 数据,请执行以下操作:

启动条件之后,主机发送模块地址写命令和寄存器地址。

寄存器地址选择读取的寄存器:

E2PROM 数据寄存器 0xAA 至 0xBF

温度或压力值 UT或 UP 0xF6(MSB),0xF7(LSB),可选0xF8(XLSB)

然后,主机发送一个重启条件,随后是将被 BMP180(ACKS)确认的模块地址读取。 BMP180 首先发送 8 个 MSB(由主机(ACKM)确认),然后发送 8 个 LSB。 主机发送“不应答”(NACKM),最后发送停止条件。

可选地,为了获得超高分辨率,可以读取地址为 0xF8 的 XLSB 寄存器,以将 16 位字扩展到最多 19 位。
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第10张图片

5.7 驱动开发

kernel/drivers 下新建目录 bmp180。

snake@ubuntu:~/NanoPC-T4/rk3399-android-8.1/kernel/drivers$ mkdir bmp180

然后在 bmp180 目录下新建 bmp180.h bmp180.c Kconfig 和 Makefile 四个文件。

bmp180.h

//
// Created by snake on 7/17/20.
//
#ifndef BMP180_BMP180_H
#define BMP180_BMP180_H

#include 
#include 
#include 

#define BMP180_DEVICE_NODE_NAME  "bmp180"
#define BMP180_DEVICE_PROC_NAME  "bmp180"

#define BMP180_SLAVE_ADDR		        0x77
#define BMP180_REG_ADDR_CHIP_ID			0xD0
#define BMP180_REG_ADDR_RESET			0xE0
#define BMP180_REG_ADDR_CTRL_MEAS		0xF4
#define BMP180_REG_ADDR_AD_MSB			0xF6
#define BMP180_REG_ADDR_AD_LSB			0xF7
#define BMP180_REG_ADDR_AD_XLSB			0xF8
#define BMP180_CAL_ADDR_AC1				0xAA

#define BMP180_REG_VALUE_CTRL_TEMP		    0x2E
#define BMP180_REG_VALUE_CTRL_PRES_OSS0		0x34
#define BMP180_REG_VALUE_CTRL_PRES_OSS1		0x74
#define BMP180_REG_VALUE_CTRL_PRES_OSS2		0xB4
#define BMP180_REG_VALUE_CTRL_PRES_OSS3		0xF4
#define BMP180_REG_VALUE_RESET		        0xB6

#define BMP_RESET _IO('Z', 0)
#define BMP_GET_CHIP_ID	_IOR('Z', 1, char)

struct bmp180_calc_param {
    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_dev {
    struct cdev dev;
    dev_t bmp180_id;
    struct semaphore sem;
    struct class *bmp180_class;
    struct i2c_client *bmp180_i2c_client;
    struct bmp180_calc_param bmp180_calc;
};

#endif //BMP180_BMP180_H

bmp180.c

//
// Created by snake on 7/17/20.
//
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "bmp180.h"

struct bmp180_dev bmp180_device;

static int bmp180_open(struct inode *inode, struct file *filp);
static int bmp180_release(struct inode *inode, struct file *filp);
static ssize_t bmp180_read(struct file *filp, char __user *buf, size_t count, loff_t* f_pos);
static long bmp180_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg);

/*设备文件操作方法表*/
static struct file_operations bmp180_fops = {
        .owner = THIS_MODULE,
        .open  = bmp180_open,
        .read  = bmp180_read,
        .release = bmp180_release,
        .unlocked_ioctl = bmp180_ioctl,
};

static int bmp180_read_regs(struct bmp180_dev *bmp180, uint8_t reg, uint8_t *pdata, int size) {
    int ret = 0;
    struct i2c_msg msg[2];
    struct i2c_client *client = NULL;

    if (size == 0) {
        return 0;
    }

    client = (struct i2c_client *) bmp180->bmp180_i2c_client;
    msg[0].addr = client->addr;
    msg[0].buf = &reg;
    msg[0].len = 1;
    msg[0].flags = 0;

    msg[1].addr = client->addr;
    msg[1].buf = pdata;
    msg[1].len = size;
    msg[1].flags = I2C_M_RD;

    if (i2c_transfer(client->adapter, msg, 2) != 2) {
        ret = -EFAULT;
    }

    return ret;
}

static int bmp180_write_regs(struct bmp180_dev *bmp180, uint8_t reg, uint8_t *pdata, int size) {

    int ret = 0;
    struct i2c_msg msg[1];
    struct i2c_client *client = NULL;
    uint8_t *buf = NULL;

    if (size == 0) {
        return ret;
    }
    buf = kmalloc(size + 1, GFP_KERNEL);
    if (buf == NULL) {
        printk(KERN_ALERT"no more memory\n");
        return -EFAULT;
    }

    buf[0] = reg;
    memcpy(&buf[1], pdata, size);
    client = (struct i2c_client *) bmp180->bmp180_i2c_client;
    msg[0].addr = client->addr;
    msg[0].buf = buf;
    msg[0].len = size + 1;
    msg[0].flags = 0;

    if (i2c_transfer(client->adapter, msg, 1) != 1) {
        ret = -EFAULT;
    }

    kfree(buf);
    return ret;
}

static uint8_t bmp180_read_reg(struct bmp180_dev *bmp180, uint8_t reg) {
    uint8_t data = 0;
    bmp180_read_regs(bmp180, reg, &data, 1);
    return data;
}

static int8_t bmp180_write_reg(struct bmp180_dev *bmp180, uint8_t reg, uint8_t data) {
    return bmp180_write_regs(bmp180, reg, &data, 1);
}

static long bmp180_read_ut(struct bmp180_dev *bmp180) {
    uint8_t buf[2] = {0};
    long data = 0;

    bmp180_write_reg(bmp180, BMP180_REG_ADDR_CTRL_MEAS, BMP180_REG_VALUE_CTRL_TEMP);
    mdelay(5);    /* max conversion time: 4.5ms */
    bmp180_read_regs(bmp180, BMP180_REG_ADDR_AD_MSB, buf, 2);
    data = (buf[0] << 8) | buf[1];

    return data;
}

static long bmp180_read_up(struct bmp180_dev *bmp180) {
    uint8_t buf[2] = {0};
    long data = 0;

    bmp180_write_reg(bmp180, BMP180_REG_ADDR_CTRL_MEAS, BMP180_REG_VALUE_CTRL_PRES_OSS0);
    mdelay(5);
    bmp180_read_regs(bmp180, BMP180_REG_ADDR_AD_MSB, buf, 2);
    data = (buf[0] << 8) | buf[1];

    return data;
}

static int bmp180_open(struct inode *inode, struct file *filp) {
    int ret = 0;
    uint8_t buf[22] = {0};
    filp->private_data = &bmp180_device;

    bmp180_read_regs(&bmp180_device, BMP180_CAL_ADDR_AC1, buf, 22);
    bmp180_device.bmp180_calc.ac1 = (buf[0] << 8) | buf[1];
    bmp180_device.bmp180_calc.ac2 = (buf[2] << 8) | buf[3];
    bmp180_device.bmp180_calc.ac3 = (buf[4] << 8) | buf[5];
    bmp180_device.bmp180_calc.ac4 = (buf[6] << 8) | buf[7];
    bmp180_device.bmp180_calc.ac5 = (buf[8] << 8) | buf[9];
    bmp180_device.bmp180_calc.ac6 = (buf[10] << 8) | buf[11];
    bmp180_device.bmp180_calc.b1 = (buf[12] << 8) | buf[13];
    bmp180_device.bmp180_calc.b2 = (buf[14] << 8) | buf[15];
    bmp180_device.bmp180_calc.mb = (buf[16] << 8) | buf[17];
    bmp180_device.bmp180_calc.mc = (buf[18] << 8) | buf[19];
    bmp180_device.bmp180_calc.md = (buf[20] << 8) | buf[21];

    printk(KERN_INFO"bmp180 opened\n");
    return ret;
}

static int bmp180_release(struct inode *inode, struct file *filp) {
    printk(KERN_INFO"bmp180 released\n");
    return 0;
}

static ssize_t bmp180_read(struct file *filp, char __user *buf, size_t count, loff_t* f_pos) {
    ssize_t ret = 0;
    long x1, x2, b5, b6, x3, b3, p;
    unsigned long b4, b7;
    long bmp180_ut, bmp180_up, temperature = 0, pressure = 0;
    struct bmp180_dev *dev = filp->private_data;

    if(down_interruptible(&(dev->sem))) {
        return -ERESTARTSYS;
    }

    bmp180_ut = bmp180_read_ut(dev);
    bmp180_up = bmp180_read_up(dev);

    //printk(KERN_INFO"ut=%ld\n", bmp180_ut);
    //printk(KERN_INFO"up=%ld\n", bmp180_up);

    /* temperature calc */
    x1 = (((long) bmp180_ut - (long) dev->bmp180_calc.ac6) * (long) dev->bmp180_calc.ac5) >> 15;
    x2 = ((long) dev->bmp180_calc.mc << 11) / (x1 + dev->bmp180_calc.md);
    b5 = x1 + x2;
    temperature = ((b5 + 8) >> 4);
    //printk(KERN_INFO"temperature=%ld\n", temperature);
    /* pressure calc */
    b6 = b5 - 4000;
    x1 = (dev->bmp180_calc.b2 * (b6 * b6) >> 12) >> 11;
    x2 = (dev->bmp180_calc.ac2 * b6) >> 11;
    x3 = x1 + x2;
    b3 = (((((long) dev->bmp180_calc.ac1) * 4 + x3) << 0) + 2) >> 2;

    x1 = (dev->bmp180_calc.ac3 * b6) >> 13;
    x2 = (dev->bmp180_calc.b1 * ((b6 * b6) >> 12)) >> 16;
    x3 = ((x1 + x2) + 2) >> 2;
    b4 = (dev->bmp180_calc.ac4 * (unsigned long) (x3 + 32768)) >> 15;
    b7 = ((unsigned long) (bmp180_up - b3) * (50000 >> 0));

    if (b7 < 0x80000000) {
        p = (b7 << 1) / b4;
    } else {
        p = (b7 / b4) << 1;
    }

    x1 = (p >> 8) * (p >> 8);
    x1 = (x1 * 3038) >> 16;
    x2 = (-7357 * p) >> 16;

    pressure = p + ((x1 + x2 + 3791) >> 4);
    //printk(KERN_INFO"pressure=%ld\n", pressure);

    if(copy_to_user(buf, (const uint8_t*) &temperature, sizeof(temperature))) {
        ret = -EFAULT;
        printk(KERN_ALERT"copy temperature to user failed\n");
        goto out;
    }

    if (copy_to_user(buf + sizeof(temperature), (const uint8_t*) &pressure, sizeof(pressure))) {
        ret = -EFAULT;
        printk(KERN_ALERT"copy pressure to user failed\n");
        goto out;
    }

out:
    up(&(dev->sem));
    return ret;
}

static long bmp180_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    struct bmp180_dev *dev;
    uint8_t chip_id = 0;

    dev = filp->private_data;

    switch(cmd)
    {
        case BMP_RESET:
            bmp180_write_reg(dev, BMP180_REG_ADDR_RESET, BMP180_REG_VALUE_RESET);
            break;
        case BMP_GET_CHIP_ID:
            chip_id = bmp180_read_reg(dev, BMP180_REG_ADDR_CHIP_ID);
            ret = copy_to_user((char __user*) arg, (char __user*) &chip_id, 1);
            break;
    }

    return ret;
}

static int bmp180_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
{
    struct device *dev;
    int ret = -1;
    dev_t id = 0;

    ret = alloc_chrdev_region(&id, 0, 1, BMP180_DEVICE_NODE_NAME);
    if (ret) {
        printk(KERN_ALERT"alloc dev-id failed.\n");
        return ret;
    }

    bmp180_device.bmp180_id = id;
    cdev_init(&bmp180_device.dev, &bmp180_fops);
    ret = cdev_add(&bmp180_device.dev, id, 1);
    if (ret) {
        unregister_chrdev_region(id, 1);
        return ret;
    }

    bmp180_device.bmp180_class = class_create(THIS_MODULE, "bmp180_class");
    if (IS_ERR(bmp180_device.bmp180_class)) {
        printk(KERN_ALERT"class_create failed.\n");
        cdev_del(&bmp180_device.dev);
        ret = -EIO;
        return ret;
    }

    dev = device_create(bmp180_device.bmp180_class, NULL, id, NULL, BMP180_DEVICE_NODE_NAME);
    if (IS_ERR(dev)) {
        return PTR_ERR(dev);
    }

    bmp180_device.bmp180_i2c_client = client;

    sema_init(&(bmp180_device.sem), 1);

    return 0;
}

static int bmp180_remove(struct i2c_client *client) {
    device_destroy(bmp180_device.bmp180_class, bmp180_device.bmp180_id);
    class_destroy(bmp180_device.bmp180_class);
    cdev_del(&bmp180_device.dev);
    unregister_chrdev_region(bmp180_device.bmp180_id, 1);
    return 0;
}

static const struct i2c_device_id bmp180_id[] = {
        {"bmp180", BMP180_SLAVE_ADDR},
        {}
};

MODULE_DEVICE_TABLE(i2c, bmp180_id);

static struct of_device_id of_bmp180_ids[] = {
        {.compatible = "bosch,bmp180"},
        { }
};

static struct i2c_driver bmp180_driver = {
        .driver   = {
                .owner = THIS_MODULE,
                .name = BMP180_DEVICE_NODE_NAME,
                .of_match_table = of_bmp180_ids,
        },
        .id_table = bmp180_id,
        .probe = bmp180_probe,
        .remove = bmp180_remove,
};

static int __init bmp180_init(void) {
    i2c_add_driver(&bmp180_driver);
    return 0;
}

static void __exit bmp180_exit(void) {
    i2c_del_driver(&bmp180_driver);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("BMP180 Driver");
MODULE_AUTHOR("LiuHongWei");

module_init(bmp180_init);
module_exit(bmp180_exit);

Kconfig

config BMP180
       tristate "BMP180 Android Driver"
       default y
       ---help---
         This is the BMP180 android driver.

Makefile

obj-$(CONFIG_BMP180)	+= bmp180.o

接着修改 drivers/Kconfig 文件,在 menu “Device Drivers” 和 endmenu 之间添加一行:

source "drivers/bmp180/Kconfig"

最后修改 drivers/Makefile 文件,最后添加一行:

obj-$(CONFIG_BMP180)            += bmp180/

现在可以编译添加了 BMP180 驱动的内核了。

snake@ubuntu:~/NanoPC-T4/rk3399-android-8.1/kernel/drivers$ ./build-nanopc-t4.sh -K -M

重新烧写镜像,接上串口线,我们就能看到驱动节点 bmp180 了。
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第11张图片
cat bmp180 可以看到内核 Log 输出。

nanopc-t4:/dev # cat bmp180

[ 1397.759774] bmp180 opened
[ 1397.776290] bmp180 released

5.8 写一个应用程序验证驱动

vendor 下新建 bmp180 目录。

snake@ubuntu:~/NanoPC-T4/rk3399-android-8.1/vendor$ mkdir bmp180

添加 bmp180exe.c 和 Android.mk 两个文件。

bmp180exe.c

#include 
#include 
#include 
#include 
#include 

#define BMP_GET_CHIP_ID    _IOR('Z', 1, char)

int main() {
    int fd;
    uint8_t buf[10];
    long temperature = 0;
    long pressure = 0;
    char chip_id = 0;

    fd = open("/dev/bmp180", O_RDWR);
    if (-1 == fd) {
        perror("open /dev/bmp180 failed\n");
        return -1;
    }

    ioctl(fd, BMP_GET_CHIP_ID, &chip_id);
    printf("open bmp180 driver succeed, chip ID:[0x%x]\n", chip_id);
    // Check the communication is working
    if (chip_id == 0x55) {
        for (int i = 0; i < 10; i++) {
            read(fd, buf, 10);
            temperature = *(long *) &buf[0];
            pressure = *(long *) &buf[sizeof(long)];
            printf("Temperature:[%.1f C],Pressure:[%ld Pa]\n", ((float) temperature) / 10, pressure);
            sleep(3);
        }
    }
    close(fd);

    return 0;
}

Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := bmp180exe
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)

然后就可以编译了。

snake@ubuntu:~/NanoPC-T4/rk3399-android-8.1/vendor/bmp180$ mm

snake@ubuntu:~/NanoPC-T4/rk3399-android-8.1/$ ./build-nanopc-t4.sh -M

再次烧写镜像验证。这一次使用 adb 运行程序 ./system/bin/bmp180exe,程序打印了 10 次温度和压力,符合程序的预期效果。后面会封装一层 HAL,写一个 app 查看温度和压力。
Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发_第12张图片

你可能感兴趣的:(NanoPC-T4)