供自己备忘;
linux 下有自带的 bmp280 驱动,实际测试数据抖动不理想;
于是自己重写一个 bmp280 驱动,实际测试数据依旧抖动,不理想;
考虑使用 SPL06 来测试看看效果;
1. 参考资料:
BMP280气压传感器详解(STM32)_哔哩哔哩_bilibili
2. 简单调试
计划是每秒读取 100 次,实际是按下图框出的配置配置的;
内核版本为:4.9.84
2.1 阅读手册
通过阅读数据手册,可以获取如下信息:
-
存在 3 种模式
-
休眠模式:不进行采样
-
正常模式:循环采样
-
强制模式:采样一次后进入休眠模式
-
-
上电后,自动进入休眠模式
-
推荐数据连续读取
-
使用补偿参数计算实际压力和温度
-
连续读取时,数据不会错误;非连续读取时数据会有混淆情况(读取的时候更新采样数据)
-
MSB:高字节(高 8 位),表示寄存器地址的前 8 位。
-
LSB:低字节(低 8 位),表示寄存器地址的后 8 位。
-
可以写的寄存器一共只有三个
-
BMP280_REG_CONFIG 0xF5
-
BMP280_REG_CTRL_MEAS 0xF4
-
BMP280_REG_RESET 0xE0
-
2.2 dts 配置如下
0x76 是 7 位 i2c 地址,对应芯片 SDO 脚是接地的;
2.3 Makefile 文件如下
内核路径修改为自己的,交叉编译器也是;前提是内核编译过,配置好交叉编译器。
KERN_DIR = /opt/liangtao/sigmastar/js230-IKAYAKI_DLM00V017_SSD222D/kernelall:make -C $(KERN_DIR) M=`pwd` modulesarm-linux-gnueabihf-gcc bmp280_read.c -o bmp280_read -lm#cp i2c_bmp280.ko bmp280_read /opt/liangtao/output/clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm bmp280_readobj-m += i2c_bmp280.o
2.4 驱动代码修改如下
从之前的博客 mpu6050 拷贝而来;
主要的点如下:
-
bmp280_init 初始化 bmp280
-
复位后延时 2ms
-
温度 2 倍过采样
-
气压 16 倍过采样
-
正常模式
-
STANDBY TIME 为 0.5ms
-
IIR 为 16 倍
-
读取校验数据
-
-
定时器 30ms 触发工作队列进行数据读取
代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>// 宏定义
/* BMP280 specific registers */
#define BMP280_REG_TEMP_XLSB 0xFC
#define BMP280_REG_TEMP_LSB 0xFB
#define BMP280_REG_TEMP_MSB 0xFA
#define BMP280_REG_PRESS_XLSB 0xF9
#define BMP280_REG_PRESS_LSB 0xF8
#define BMP280_REG_PRESS_MSB 0xF7/* Helper mask to truncate excess 4 bits on pressure and temp readings */
#define BMP280_MEAS_TRIM_MASK GENMASK(24, 4)#define BMP280_REG_CONFIG 0xF5
#define BMP280_REG_CTRL_MEAS 0xF4
#define BMP280_REG_STATUS 0xF3
#define BMP280_REG_RESET 0xE0
#define BMP280_REG_ID 0xD0#define BMP280_REG_COMP_TEMP_START 0x88
#define BMP280_COMP_TEMP_REG_COUNT 6#define BMP280_REG_COMP_PRESS_START 0x8E
#define BMP280_COMP_PRESS_REG_COUNT 18#define BMP280_COMP_H5_MASK GENMASK(15, 4)#define BMP280_CONTIGUOUS_CALIB_REGS (BMP280_COMP_TEMP_REG_COUNT + \BMP280_COMP_PRESS_REG_COUNT)#define BMP280_STANDBY_T_MASK GENMASK(7, 5)
#define BMP280_STANDBY_T_500US 0
#define BMP280_STANDBY_T_62500US 1
#define BMP280_STANDBY_T_125MS 2
#define BMP280_STANDBY_T_250MS 3
#define BMP280_STANDBY_T_500MS 4
#define BMP280_STANDBY_T_1000MS 5
#define BMP280_STANDBY_T_2000MS 6
#define BMP280_STANDBY_T_4000MS 7#define BMP280_FILTER_MASK GENMASK(4, 2)
#define BMP280_FILTER_OFF 0
#define BMP280_FILTER_2X 1
#define BMP280_FILTER_4X 2
#define BMP280_FILTER_8X 3
#define BMP280_FILTER_16X 4#define BMP280_OSRS_TEMP_MASK GENMASK(7, 5)
#define BMP280_OSRS_TEMP_SKIP 0
#define BMP280_OSRS_TEMP_1X 1
#define BMP280_OSRS_TEMP_2X 2
#define BMP280_OSRS_TEMP_4X 3
#define BMP280_OSRS_TEMP_8X 4
#define BMP280_OSRS_TEMP_16X 5#define BMP280_OSRS_PRESS_MASK GENMASK(4, 2)
#define BMP280_OSRS_PRESS_SKIP 0
#define BMP280_OSRS_PRESS_1X 1
#define BMP280_OSRS_PRESS_2X 2
#define BMP280_OSRS_PRESS_4X 3
#define BMP280_OSRS_PRESS_8X 4
#define BMP280_OSRS_PRESS_16X 5#define BMP280_MODE_MASK GENMASK(1, 0)
#define BMP280_MODE_SLEEP 0
#define BMP280_MODE_FORCED 1
#define BMP280_MODE_NORMAL 3/* BMP280 register skipped special values */
#define BMP280_TEMP_SKIPPED 0x80000
#define BMP280_PRESS_SKIPPED 0x80000#define DEV_NAME "i2c_bmp280"
#define DEV_CNT (1)struct bmp280_calib
{u16 T1; /* calibration T1 data */s16 T2; /* calibration T2 data */s16 T3; /* calibration T3 data */u16 P1; /* calibration P1 data */s16 P2; /* calibration P2 data */s16 P3; /* calibration P3 data */s16 P4; /* calibration P4 data */s16 P5; /* calibration P5 data */s16 P6; /* calibration P6 data */s16 P7; /* calibration P7 data */s16 P8; /* calibration P8 data */s16 P9; /* calibration P9 data */s32 t_fine; /* calibration t_fine data */
};
static struct bmp280_calib bmp280_calib_data;static dev_t bmp280_devno; // 定义字符设备的设备号
static struct cdev bmp280_chr_dev; // 定义字符设备结构体 chr_dev
struct class *class_bmp280; // 保存创建的类
struct device *device_bmp280; // 保存创建的设备
struct device_node *bmp280_device_node; // bmp280 的设备树节点结构体
struct i2c_client *bmp280_client = NULL; // 保存 bmp280 设备对应的 i2c_client 结构体,匹配成功后由 .prob 函数带回。// 定时器,定时读取 bmp280 的数据
static struct timer_list t;
// 用于定时器是否使能
static char is_timer_active;
static unsigned char bmp280_result[8];// 工作队列,用于执行耗时的 i2c 操作
static struct workqueue_struct *bmp280_wq;
static struct work_struct bmp280_work;// 一个互斥锁,对读取数据进行保护
static DEFINE_MUTEX(bmp280_mutex);static u32 __get_unaligned_be24(const u8 *p)
{return p[0] << 16 | p[1] << 8 | p[2];
}static u32 get_unaligned_be24(const void *p)
{return __get_unaligned_be24(p);
}/* 通过 i2c 向 bmp280 写入数据* bmp280_client:bmp280 的 i2c_client 结构体。* address, 数据要写入的地址,* data, 要写入的数据* 返回值,错误,-1。成功,0 */
static int i2c_write_bmp280(struct i2c_client *bmp280_client, u8 address, u8 data)
{int error = 0;u8 write_data[2];struct i2c_msg send_msg; // 要发送的数据结构体/* 设置要发送的数据 */write_data[0] = address;write_data[1] = data;/* 发送 iic 要写入的地址 reg */send_msg.addr = bmp280_client->addr; // bmp280 在 iic 总线上的地址send_msg.flags = 0; // 标记为发送数据send_msg.buf = write_data; // 写入的首地址send_msg.len = 2; // reg 长度/* 执行发送 */error = i2c_transfer(bmp280_client->adapter, &send_msg, 1);if (error != 1) {printk("i2c_write_bmp280 error %d\n", error);return -1;}return 0;
}/* 通过 i2c 向 bmp280 写入数据* bmp280_client:bmp280 的 i2c_client 结构体。* address, 要读取的地址,* data,保存读取得到的数据* length,读长度* 返回值,错误,-1。成功,0*/
static int i2c_read_bmp280(struct i2c_client *bmp280_client, u8 address, void *data, u32 length)
{int error = 0;u8 address_data = address;struct i2c_msg bmp280_msg[2];/* 设置读取位置 msg */bmp280_msg[0].addr = bmp280_client->addr; // bmp280 在 iic 总线上的地址bmp280_msg[0].flags = 0; // 标记为发送数据bmp280_msg[0].buf = &address_data; // 写入的首地址bmp280_msg[0].len = 1; // 写入长度/* 设置读取位置 msg */bmp280_msg[1].addr = bmp280_client->addr; // bmp280 在 iic 总线上的地址bmp280_msg[1].flags = I2C_M_RD; // 标记为读取数据bmp280_msg[1].buf = data; // 读取得到的数据保存位置bmp280_msg[1].len = length; // 读取长度error = i2c_transfer(bmp280_client->adapter, bmp280_msg, 2);if (error != 2) {printk("i2c_read_bmp280 error %d\n", error);return -1;}return 0;
}/** Returns temperature in DegC, resolution is 0.01 DegC. Output value of* "5123" equals 51.23 DegC. t_fine carries fine temperature as global* value.** Taken from datasheet, Section 3.11.3, "Compensation formula".*/
static s32 bmp280_compensate_temp(s32 adc_temp)
{struct bmp280_calib *calib = &bmp280_calib_data;s32 var1, var2;var1 = (((adc_temp >> 3) - ((s32)calib->T1 << 1)) *((s32)calib->T2)) >> 11;var2 = (((((adc_temp >> 4) - ((s32)calib->T1)) *((adc_temp >> 4) - ((s32)calib->T1))) >> 12) *((s32)calib->T3)) >> 14;calib->t_fine = var1 + var2;return (calib->t_fine * 5 + 128) >> 8;
}/** Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24* integer bits and 8 fractional bits). Output value of "24674867"* represents 24674867/256 = 96386.2 Pa = 963.862 hPa** Taken from datasheet, Section 3.11.3, "Compensation formula".*/
static u32 bmp280_compensate_press(s32 adc_press)
{struct bmp280_calib *calib = &bmp280_calib_data;s64 var1, var2, p;var1 = ((s64)calib->t_fine) - 128000;var2 = var1 * var1 * (s64)calib->P6;var2 += (var1 * (s64)calib->P5) << 17;var2 += ((s64)calib->P4) << 35;var1 = ((var1 * var1 * (s64)calib->P3) >> 8) +((var1 * (s64)calib->P2) << 12);var1 = ((((s64)1) << 47) + var1) * ((s64)calib->P1) >> 33;if (var1 == 0)return 0;p = ((((s64)1048576 - adc_press) << 31) - var2) * 3125;p = div64_s64(p, var1);var1 = (((s64)calib->P9) * (p >> 13) * (p >> 13)) >> 25;var2 = ((s64)(calib->P8) * p) >> 19;p = ((p + var1 + var2) >> 8) + (((s64)calib->P7) << 4);return (u32)p;
}static void bmp280_work_func(struct work_struct *work)
{u8 buf[6];int ret;s32 adc, comp_temp;u32 comp_press;// 读取原始的气压、温度数据ret = i2c_read_bmp280(bmp280_client, BMP280_REG_PRESS_MSB, buf, sizeof(buf));if (ret < 0) {printk("i2c_read_bmp280 error\n");return;}adc = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&buf[3]));if (adc == BMP280_TEMP_SKIPPED) {/* reading was skipped */printk("reading temperature skipped\n");return;}comp_temp = bmp280_compensate_temp(adc);adc = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&buf[0]));if (adc == BMP280_PRESS_SKIPPED) {/* reading was skipped */printk("reading pressure skipped\n");return;}comp_press = bmp280_compensate_press(adc);mutex_lock(&bmp280_mutex);memcpy(bmp280_result, &comp_temp, sizeof(comp_temp));memcpy(&bmp280_result[4], &comp_press, sizeof(comp_press));mutex_unlock(&bmp280_mutex);//comp_press = comp_press / 256;//printk("temperature = %d, pressure = %d\n", comp_temp, comp_press);
}static void bmp280_timer_func(unsigned long data)
{if (is_timer_active) {mod_timer(&t, jiffies + msecs_to_jiffies(30));}// 只在任务未在队列中时才添加新任务if (!work_pending(&bmp280_work)) {queue_work(bmp280_wq, &bmp280_work);}
}/* 初始化 bmp280* 返回值,成功,返回 0。失败,返回 -1*/
static int bmp280_init(void)
{int err = 0;unsigned char buf[1];/* 配置 bmp280 */// 设置 BMP280_RST_REG 寄存器值为 0xB6,触发复位err += i2c_write_bmp280(bmp280_client, BMP280_REG_RESET, 0xB6);// 等待复位完成msleep(2);// 读取 BMP280_ID_REG 寄存器,判断是否连接成功err += i2c_read_bmp280(bmp280_client, BMP280_REG_ID, &buf[0], 1);if (buf[0] != 0x58) {printk("bmp280_init id error, id 0x%x, err %d\n", buf[0], err);return -1;}// 配置 BMP280_REG_CTRL_MEAS 寄存器; 配置温度过采样,配置气压过采样,配置供电模式buf[0] = FIELD_PREP(BMP280_OSRS_TEMP_MASK, BMP280_OSRS_TEMP_2X) | FIELD_PREP(BMP280_OSRS_PRESS_MASK, BMP280_OSRS_PRESS_16X) | FIELD_PREP(BMP280_MODE_MASK, BMP280_MODE_NORMAL);err += i2c_write_bmp280(bmp280_client, BMP280_REG_CTRL_MEAS, buf[0]);// 配置 IIR 滤波 BMP280_STANDBY_T_MASKbuf[0] = FIELD_PREP(BMP280_STANDBY_T_MASK, BMP280_STANDBY_T_500US) |FIELD_PREP(BMP280_FILTER_MASK, BMP280_FILTER_16X);err += i2c_write_bmp280(bmp280_client, BMP280_REG_CONFIG, buf[0]);/** Some chips have calibration parameters "programmed into the devices'* non-volatile memory during production". Let's read them out at probe* time once. They will not change.*/// 读取校准参数err += i2c_read_bmp280(bmp280_client, BMP280_REG_COMP_TEMP_START, (void *)&bmp280_calib_data, BMP280_CONTIGUOUS_CALIB_REGS);if (err < 0) {printk("bmp280_init error\n");return -1;}return 0;
}static int bmp280_open(struct inode *inode, struct file *filp)
{is_timer_active = 1;mod_timer(&t, jiffies + msecs_to_jiffies(1));return 0;
}static ssize_t bmp280_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{int error;unsigned char buffer[8]; // 保存 bmp280 转换得到的原始数据mutex_lock(&bmp280_mutex);memcpy(buffer, bmp280_result, sizeof(bmp280_result));mutex_unlock(&bmp280_mutex);error = copy_to_user(buf, buffer, sizeof(buffer));if (error != 0) {printk("copy_to_user error!");return -1;}return sizeof(buffer);
}static int bmp280_release(struct inode *inode, struct file *filp)
{is_timer_active = 0;return 0;
}static struct file_operations bmp280_chr_dev_fops =
{.owner = THIS_MODULE,.open = bmp280_open,.read = bmp280_read,.release = bmp280_release,
};static int bmp280_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret;printk("bmp280_probe, HZ = %d\n", HZ);bmp280_client = client;/* 初始化 bmp280 */ret = bmp280_init();if (ret < 0) {printk("fail to init bmp280\n");goto alloc_err;}// 采用动态分配的方式,获取设备编号,次设备号为 0,// 设备名称为 i2c_bmp280,可通过命令 cat /proc/devices 查看// DEV_CNT 为 1,当前只申请一个设备编号ret = alloc_chrdev_region(&bmp280_devno, 0, DEV_CNT, DEV_NAME);if (ret < 0) {printk("fail to alloc bmp280_devno\n");goto alloc_err;}// 关联字符设备结构体 cdev 与文件操作结构体 file_operationsbmp280_chr_dev.owner = THIS_MODULE;cdev_init(&bmp280_chr_dev, &bmp280_chr_dev_fops);// 添加设备至 cdev_map 散列表中ret = cdev_add(&bmp280_chr_dev, bmp280_devno, DEV_CNT);if (ret < 0) {printk("fail to add cdev\n");goto add_err;}/* 创建类 */class_bmp280 = class_create(THIS_MODULE, DEV_NAME);/* 创建设备 DEV_NAME 指定设备名 */device_bmp280 = device_create(class_bmp280, NULL, bmp280_devno, NULL, DEV_NAME);/* 初始化工作队列 */bmp280_wq = create_singlethread_workqueue("bmp280_wq");INIT_WORK(&bmp280_work, bmp280_work_func);/* 创建一个定时器,开始以 100hz 的频率来采样 */setup_timer(&t, bmp280_timer_func, 0);return 0;add_err:// 添加设备失败时,需要注销设备号unregister_chrdev_region(bmp280_devno, DEV_CNT);printk("bmp280_probe error! \n");
alloc_err:return -1;
}static int bmp280_remove(struct i2c_client *client)
{device_destroy(class_bmp280, bmp280_devno); // 清除设备class_destroy(class_bmp280); // 清除类cdev_del(&bmp280_chr_dev); // 清除设备号unregister_chrdev_region(bmp280_devno, DEV_CNT); // 取消注册字符设备del_timer_sync(&t); // 删除定时器flush_workqueue(bmp280_wq); // 确保所有任务完成destroy_workqueue(bmp280_wq); // 销毁工作队列return 0;
}static const struct i2c_device_id bmp280_device_id[] = {{"bosch,bmp280", 0},{/* sentinel */}
};static const struct of_device_id bmp280_of_match_table[] = {{.compatible = "bosch,bmp280"},{/* sentinel */}
};struct i2c_driver bmp280_driver = {.probe = bmp280_probe,.remove = bmp280_remove,.id_table = bmp280_device_id,.driver = {.name = "invensense,bmp280",.owner = THIS_MODULE,.of_match_table = bmp280_of_match_table,},
};static int __init bmp280_driver_init(void)
{int ret;printk("bmp280_driver_init\n");ret = i2c_add_driver(&bmp280_driver);return ret;
}static void __exit bmp280_driver_exit(void)
{printk("bmp280_driver_exit\n");i2c_del_driver(&bmp280_driver);
}module_init(bmp280_driver_init);
module_exit(bmp280_driver_exit);MODULE_LICENSE("GPL");
2.5 应用代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <math.h>void print_timestamp()
{struct timeval tv;gettimeofday(&tv, NULL);printf("[%ld.%06ld] ", tv.tv_sec, tv.tv_usec);
}int main(int argc, char *argv[])
{int error;unsigned char receive_data[8]; //保存收到的 bmp280 转换结果数据,先为温度,后为气压int temp;unsigned int press;float real_temp;float real_press;/*打开文件*/int fd = open("/dev/i2c_bmp280", O_RDWR);if (fd < 0) {printf("open file : %s failed !\n", argv[0]);return -1;}usleep(200000);while (1) {/* 读取数据 */print_timestamp();error = read(fd, receive_data, sizeof(receive_data));if (error != 8) {printf("read file error! \n");close(fd);break;}print_timestamp();/* 打印数据 */memcpy(&temp, receive_data, sizeof(temp));memcpy(&press, &receive_data[4], sizeof(press));real_temp = temp / 100.0;real_press = press / 256.0;printf("temp %f, press %f\n", real_temp, real_press);usleep(10000);}/*关闭文件*/error = close(fd);if (error < 0) {printf("close file error! \n");}return 0;
}