驱动开发主要位于 Linux 内核层,首先认识一下 BMP180,接着需要修改 DTS,最后才开始编写驱动,编写完驱动我们再来写一个用户层验证程序来获取温度和压力。
BMP180 是一款高精度、小体积、超低能的压力传感器,适用于移动电话、PDAs、GPS 导航设备和户外设备。在与仅仅 0.25 米低空噪音快速转换时,BMP180 提供卓越的性能。使用 I2C 接口可以很轻松的跟主控制器系统进行通讯。
BMP180 基于压阻式技术 EMC 稳健性、高精度以及线性长期稳定性,被设计用来直接连接带有 I2C 总线接口的移动设备小型控制器。
BMP180 的 EEPROM 值会补偿气压以及温度值。BMP180 包含压阻式传感器、模拟数字转换器、EEPROM 控制单元和 I2C 接口。
名称 | 描述 |
---|---|
SDA | I2C SDA |
SCL | I2C SCL |
5V | 电源5V |
GND | 地 |
NanoPC-T4 和以上温度压力传感器模块连接。
温度压力传感器模块 | NanoPC-T4 |
---|---|
SDA | Pin3 |
SCL | Pin5 |
5V | Pin4 |
GND | Pin6 |
以上为示意图,NanoPC-T4 USB 和上图并不一致。
编辑内核目录下的 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 编程。
0x77(7bit 有效地址,不包括读写位,Linux I2C 总线驱动内部作读写位处理),DTS 中要和这个地址保持一致。
处理器通过 I2C 读取 BMP180 的温度值和压力值是原始 AD 值,未经过校准补偿。每个 BMP180 器件在出厂时都根据不同特性提供独有的校准系数,存储于内置 EEPROM 中,校准系数共 176 位(22字节)。处理器获得准确的气压值和温度值,首先应读取内置校准系数,将原始 AD 值和校准系数进行换算。BMP180 数据手册提供计算方式。
下面的内存映射显示了操作 BMP180 所需的所有外部可访问数据寄存器。左边的列显示内存地址。中间的列描述了每个寄存器位的内容。位的颜色表示它们是只读的、只读的或可读可写的。内存是易失性的,所以每次开机后都必须重写可写内容。并不是所有的寄存器地址都显示出来了。这些寄存器被保留为博世工厂进一步测试和修整。
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,可用于检查是否通讯正常。
转换后,数据寄存器可以按任意顺序读出(即 MSB 优先或 LSB 优先) 。
下面显示了开始测量温度值 UT 和压力值 UP 的时序图。启动条件后,主机发送设备地址写,寄存器地址和控制寄存器数据。当接收到数据时,BMP180 每 8 个数据位发送一个确认(ACKS)。 主机在最后一个 ACKS 之后发送停止条件。
控制寄存器的值用于不同的内部过采样设置(OSS)
要读取温度数据字 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 位。
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 = ®
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 了。
cat bmp180 可以看到内核 Log 输出。
nanopc-t4:/dev # cat bmp180
[ 1397.759774] bmp180 opened
[ 1397.776290] bmp180 released
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 查看温度和压力。