供自己备忘;
1. 参考资料:
博客:
https://blog.csdn.net/CAI____NIAO/article/details/117654468
代码:
https://github.com/liuheng135/drivers/blob/master/i2c/device/spl06.c#L11
2. 简单调试
计划是每秒读取 100 次;内核版本为:4.9.84;
2.1 阅读手册
通过阅读数据手册,可以获取如下信息:
-
气压精度比 BMP280 高;
-
传感器有三种模式,上电后进入待机模式
-
待机模式,寄存器可以访问,但不采样;
-
命令模式,采样一次后进入待机模式;
-
后台模式,连续采样;存在 FIFO 可以存储 32 个采样结果;
-
-
可以写的寄存器如下:
-
PRS_CFG
-
TMP_CFG
-
CFG_REG
-
MEAS_CFG
-
RESET
-
-
TMP_CFG 寄存器中,bit7 位:Note: Please use the external sensor setting.
-
CFG_REG 寄存器中,bit2、bit3 位:Note: Must be set to '1' when the oversampling rate is >8 times.
-
复位后,建议延时 50ms(自己实测);不然校验参数读取有问题;
2.2 dts 配置如下
-
7 位 i2c 地址为 0x77,SDO 引脚上拉;
2.3 Makefile 文件如下
内核路径修改为自己的,交叉编译器也是;前提是内核编译过,配置好交叉编译器。
KERN_DIR = /opt/liangtao/sigmastar/js230-IKAYAKI_DLM00V017_SSD222D/kernel
all:
make -C $(KERN_DIR) M=`pwd` modules
arm-linux-gnueabihf-gcc spl06_read.c -o spl06_read -lm
#cp i2c_spl06.ko spl06_read /opt/liangtao/output/
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm spl06_read
obj-m += i2c_spl06.o
2.4 驱动代码修改如下
从之前的博客 bmp280 拷贝而来;
主要的点如下:
- spl06_init 初始化 spl06-001
- 复位后延时 50ms
- 读取传感器 id,判断通信是否 ok
- 读取传感器校验参数,数据形式都是以补码的形式存储,所以 12、20、24 位数据时需要手动转正负;
- 配置气压采样频率为 32,过采样为 16
- 配置温度采样频率为 4,过采样为 4
- 配置为背景模式,气压与温度都采样
- 定时器 30ms 触发工作队列进行数据读取
- 系统的 HZ 配置为 1000
- 将数据传输给 app,第一次读取的为校准参数,后面读取的是原始未校准数据;(内核不便做浮点计算)
代码如下:
#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>
// 宏定义
#define CONTINUOUS_P_AND_T 3
#define PRESSURE_SENSOR 0
#define TEMPERATURE_SENSOR 1
#define SPL06_REG_PSR 0x00
#define SPL06_REG_TMP 0x03
#define SPL06_REG_PRS_CFG 0x06
#define SPL06_REG_TMP_CFG 0x07
#define SPL06_REG_MEAS_CFG 0x08
#define SPL06_REG_CFG 0x09
#define SPL06_REG_INT_STS 0x0A
#define SPL06_REG_FIFO_STS 0x0B
#define SPL06_REG_RESET 0x0C
#define SPL06_REG_ID 0x0D
#define SPL06_REG_COEF 0x10
#define DEV_NAME "i2c_spl06"
#define DEV_CNT (1)
struct spl06_calib
{
s16 c0;
s16 c1;
s32 c00;
s32 c10;
s16 c01;
s16 c11;
s16 c20;
s16 c21;
s16 c30;
s32 kP;
s32 kT;
};
static struct spl06_calib spl06_calib_data;
static dev_t spl06_devno; // 定义字符设备的设备号
static struct cdev spl06_chr_dev; // 定义字符设备结构体 chr_dev
static struct class *class_spl06; // 保存创建的类
static struct device *device_spl06; // 保存创建的设备
static struct i2c_client *spl06_client = NULL; // 保存 spl06 设备对应的 i2c_client 结构体,匹配成功后由 .prob 函数带回。
// 定时器,定时读取 spl06 的数据
static struct timer_list t;
// 用于定时器是否使能
static char is_timer_active;
static unsigned char spl06_result[8];
// 工作队列,用于执行耗时的 i2c 操作
static struct workqueue_struct *spl06_wq;
static struct work_struct spl06_work;
// 一个互斥锁,对读取数据进行保护
static DEFINE_MUTEX(spl06_mutex);
static s16 sign_extend_12bit(u16 value) {
return (value & 0x0800) ? (value | 0xF000) : value;
}
static s32 sign_extend_20bit(u32 value) {
return (value & 0x80000) ? (value | 0xFFF00000) : value;
}
static s32 sign_extend_24bit(u32 value) {
return (value & 0x800000) ? (value | 0xFF000000) : value;
}
/* 通过 i2c 向 spl06 写入数据
* spl06_client:spl06 的 i2c_client 结构体。
* address, 数据要写入的地址,
* data, 要写入的数据
* 返回值,错误,-1。成功,0
*/
static int i2c_write_spl06(struct i2c_client *spl06_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 = spl06_client->addr; // spl06 在 iic 总线上的地址
send_msg.flags = 0; // 标记为发送数据
send_msg.buf = write_data; // 写入的首地址
send_msg.len = 2; // reg 长度
/* 执行发送 */
error = i2c_transfer(spl06_client->adapter, &send_msg, 1);
if (error != 1) {
printk("i2c_write_spl06 error %d\n", error);
return -1;
}
return 0;
}
/* 通过 i2c 向 spl06 写入数据
* spl06_client:spl06 的 i2c_client 结构体。
* address, 要读取的地址,
* data,保存读取得到的数据
* length,读长度
* 返回值,错误,-1。成功,0
*/
static int i2c_read_spl06(struct i2c_client *spl06_client, u8 address, void *data, u32 length)
{
int error = 0;
u8 address_data = address;
struct i2c_msg spl06_msg[2];
/* 设置读取位置 msg */
spl06_msg[0].addr = spl06_client->addr; // spl06 在 iic 总线上的地址
spl06_msg[0].flags = 0; // 标记为发送数据
spl06_msg[0].buf = &address_data; // 写入的首地址
spl06_msg[0].len = 1; // 写入长度
/* 设置读取位置 msg */
spl06_msg[1].addr = spl06_client->addr; // spl06 在 iic 总线上的地址
spl06_msg[1].flags = I2C_M_RD; // 标记为读取数据
spl06_msg[1].buf = data; // 读取得到的数据保存位置
spl06_msg[1].len = length; // 读取长度
error = i2c_transfer(spl06_client->adapter, spl06_msg, 2);
if (error != 2) {
printk("i2c_read_spl06 error %d\n", error);
return -1;
}
return 0;
}
static void spl06_work_func(struct work_struct *work)
{
u8 buf[6];
s32 raw_press, raw_temp;
int ret;
// 读取原始的气压、温度数据
ret = i2c_read_spl06(spl06_client, SPL06_REG_PSR, buf, sizeof(buf));
if (ret < 0) {
printk("i2c_read_spl06 error\n");
return;
}
raw_press = (buf[0] << 16) | (buf[1] << 8) | buf[2];
raw_press = sign_extend_24bit((u32)raw_press);
raw_temp = (buf[3] << 16) | (buf[4] << 8) | buf[5];
raw_temp = sign_extend_24bit((u32)raw_temp);
mutex_lock(&spl06_mutex);
memcpy(spl06_result, &raw_press, sizeof(raw_press));
memcpy(&spl06_result[4], &raw_temp, sizeof(raw_temp));
mutex_unlock(&spl06_mutex);
}
static void spl06_timer_func(unsigned long data)
{
if (is_timer_active) {
mod_timer(&t, jiffies + msecs_to_jiffies(30));
}
// 只在任务未在队列中时才添加新任务
if (!work_pending(&spl06_work)) {
queue_work(spl06_wq, &spl06_work);
}
}
static int spl06_rate_set(unsigned char i_sensor, unsigned char smpl_rate, unsigned char over_smpl)
{
unsigned char reg = 0, val = 0;
int kPkT = 0, err = 0;
switch (smpl_rate) {
case 2:
val = 1;
break;
case 4:
val = 2;
break;
case 8:
val = 3;
break;
case 16:
val = 4;
break;
case 32:
val = 5;
break;
case 64:
val = 6;
break;
case 128:
val = 7;
break;
case 1:
default:
break;
}
reg = val << 4;
switch(over_smpl){
case 2:
val = 1;
kPkT = 1572864;
break;
case 4:
val = 2;
kPkT = 3670016;
break;
case 8:
val = 3;
kPkT = 7864320;
break;
case 16:
val = 4;
kPkT = 253952;
break;
case 32:
val = 5;
kPkT = 516096;
break;
case 64:
val = 6;
kPkT = 1040384;
break;
case 128:
val = 7;
kPkT = 2088960;
break;
case 1:
default:
val = 0;
kPkT = 524288;
break;
}
reg |= val;
if (i_sensor == PRESSURE_SENSOR) {
spl06_calib_data.kP = kPkT;
err += i2c_write_spl06(spl06_client, SPL06_REG_PRS_CFG, reg);
err += i2c_read_spl06(spl06_client, SPL06_REG_CFG, ®, 1);
if (over_smpl > 8) {
err += i2c_write_spl06(spl06_client, SPL06_REG_CFG, reg | 0x04);
} else {
err += i2c_write_spl06(spl06_client, SPL06_REG_CFG, reg & (~0x04));
}
} else if (i_sensor == TEMPERATURE_SENSOR) {
spl06_calib_data.kT = kPkT;
err += i2c_write_spl06(spl06_client, SPL06_REG_TMP_CFG, reg | 0x80); // Using mems temperature
err += i2c_read_spl06(spl06_client, SPL06_REG_CFG, ®, 1);
if (over_smpl > 8) {
err += i2c_write_spl06(spl06_client, SPL06_REG_CFG, reg | 0x08);
} else {
err += i2c_write_spl06(spl06_client, SPL06_REG_CFG, reg & (~0x08));
}
} else {
printk("spl06_rate_set error, i_sensor %d\n", i_sensor);
err = -1;
}
return err;
}
/* 初始化 spl06
* 返回值,成功,返回 0。失败,返回 -1
*/
static int spl06_init(void)
{
s32 err = 0;
u8 buf[18];
struct spl06_calib *p;
p = &spl06_calib_data;
/* 配置 spl06 */
#if 1
// 设置 SPL06_REG_RST 寄存器值为 0x89,触发复位
err += i2c_write_spl06(spl06_client, SPL06_REG_RESET, 0x89);
// 等待复位完成
msleep(50);
#endif
// 读取 SPL06_REG_ID 寄存器,判断是否连接成功
err += i2c_read_spl06(spl06_client, SPL06_REG_ID, &buf[0], 1);
if (buf[0] != 0x10) {
printk("spl06_init id error, id 0x%x, err %d\n", buf[0], err);
return -1;
}
// 读取校准参数
err += i2c_read_spl06(spl06_client, SPL06_REG_COEF, &buf[0], sizeof(buf));
// c0 c1 是 12 为补码,带正负
p->c0 = (buf[0] << 4) + (buf[1] >> 4);
p->c0 = sign_extend_12bit((u16)p->c0);
p->c1 = ((buf[1] & 0x0F) << 8) + buf[2];
p->c1 = sign_extend_12bit((u16)p->c1);
// c00 c10 是 20 位补码,带正负
p->c00 = (buf[3] << 12) | (buf[4] << 4) | (buf[5] >> 4);
p->c00 = sign_extend_20bit((u32)p->c00);
p->c10 = ((buf[5] & 0x0F) << 16) | (buf[6] << 8) | buf[7];
p->c10 = sign_extend_20bit((u32)p->c10);
p->c01 = (s16)((buf[8] << 8) | buf[9]);
p->c11 = (s16)((buf[10] << 8) | buf[11]);
p->c20 = (s16)((buf[12] << 8) | buf[13]);
p->c21 = (s16)((buf[14] << 8) | buf[15]);
p->c30 = (s16)((buf[16] << 8) | buf[17]);
// 配置气压采样次数,过采样次数
err += spl06_rate_set(PRESSURE_SENSOR, 32, 16);
// 配置温度采样次数,过采样次数
err += spl06_rate_set(TEMPERATURE_SENSOR, 4, 4);
//printk("c0: %d, c1: %d, c00: %d, c10: %d, c01: %d, c11: %d, c20: %d, c21: %d, c30: %d, kP: %d, kT: %d\n",
// p->c0, p->c1, p->c00, p->c10, p->c01, p->c11,p->c20, p->c21, p->c30, p->kP, p->kT);
// 配置为 Background Mode,气压温度都测量
err += i2c_write_spl06(spl06_client, SPL06_REG_MEAS_CFG, CONTINUOUS_P_AND_T + 4);
if (err < 0) {
printk("spl06_init error\n");
return -1;
}
return 0;
}
static int spl06_open(struct inode *inode, struct file *filp)
{
is_timer_active = 1;
mod_timer(&t, jiffies + msecs_to_jiffies(1));
return 0;
}
static ssize_t spl06_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int error;
unsigned char buffer[8]; // 保存 spl06 转换得到的原始数据
// 将校验参数传给应用层,在应用层进行参数计算
// 第一次读取的是校验参数
if (is_timer_active == 1) {
if (cnt != sizeof(struct spl06_calib)) {
printk("spl06_read spl06_calib buf error, cnt %d\n", cnt);
return -1;
}
error = copy_to_user(buf, &spl06_calib_data, sizeof(struct spl06_calib));
if (error != 0) {
printk("copy_to_user spl06_calib error!");
return -1;
}
is_timer_active = 2;
return sizeof(struct spl06_calib);
}
mutex_lock(&spl06_mutex);
memcpy(buffer, spl06_result, sizeof(spl06_result));
mutex_unlock(&spl06_mutex);
error = copy_to_user(buf, buffer, sizeof(buffer));
if (error != 0) {
printk("copy_to_user error!");
return -1;
}
return sizeof(buffer);
}
static int spl06_release(struct inode *inode, struct file *filp)
{
is_timer_active = 0;
return 0;
}
static struct file_operations spl06_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = spl06_open,
.read = spl06_read,
.release = spl06_release,
};
static int spl06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
printk("spl06_probe, HZ = %d\n", HZ);
spl06_client = client;
/* 初始化 spl06 */
ret = spl06_init();
if (ret < 0) {
printk("fail to init spl06\n");
goto alloc_err;
}
// 采用动态分配的方式,获取设备编号,次设备号为 0,
// 设备名称为 i2c_spl06,可通过命令 cat /proc/devices 查看
// DEV_CNT 为 1,当前只申请一个设备编号
ret = alloc_chrdev_region(&spl06_devno, 0, DEV_CNT, DEV_NAME);
if (ret < 0) {
printk("fail to alloc spl06_devno\n");
goto alloc_err;
}
// 关联字符设备结构体 cdev 与文件操作结构体 file_operations
spl06_chr_dev.owner = THIS_MODULE;
cdev_init(&spl06_chr_dev, &spl06_chr_dev_fops);
// 添加设备至 cdev_map 散列表中
ret = cdev_add(&spl06_chr_dev, spl06_devno, DEV_CNT);
if (ret < 0) {
printk("fail to add cdev\n");
goto add_err;
}
/* 创建类 */
class_spl06 = class_create(THIS_MODULE, DEV_NAME);
/* 创建设备 DEV_NAME 指定设备名 */
device_spl06 = device_create(class_spl06, NULL, spl06_devno, NULL, DEV_NAME);
/* 初始化工作队列 */
spl06_wq = create_singlethread_workqueue("spl06_wq");
INIT_WORK(&spl06_work, spl06_work_func);
/* 创建一个定时器,开始以 100hz 的频率来采样 */
setup_timer(&t, spl06_timer_func, 0);
return 0;
add_err:
// 添加设备失败时,需要注销设备号
unregister_chrdev_region(spl06_devno, DEV_CNT);
printk("spl06_probe error! \n");
alloc_err:
return -1;
}
static int spl06_remove(struct i2c_client *client)
{
device_destroy(class_spl06, spl06_devno); // 清除设备
class_destroy(class_spl06); // 清除类
cdev_del(&spl06_chr_dev); // 清除设备号
unregister_chrdev_region(spl06_devno, DEV_CNT); // 取消注册字符设备
del_timer_sync(&t); // 删除定时器
flush_workqueue(spl06_wq); // 确保所有任务完成
destroy_workqueue(spl06_wq); // 销毁工作队列
return 0;
}
static const struct i2c_device_id spl06_device_id[] = {
{"goertek,spl06", 0},
{/* sentinel */}
};
static const struct of_device_id spl06_of_match_table[] = {
{.compatible = "goertek,spl06"},
{/* sentinel */}
};
struct i2c_driver spl06_driver = {
.probe = spl06_probe,
.remove = spl06_remove,
.id_table = spl06_device_id,
.driver = {
.name = "goertek,spl06",
.owner = THIS_MODULE,
.of_match_table = spl06_of_match_table,
},
};
static int __init spl06_driver_init(void)
{
int ret;
printk("spl06_driver_init\n");
ret = i2c_add_driver(&spl06_driver);
return ret;
}
static void __exit spl06_driver_exit(void)
{
printk("spl06_driver_exit\n");
i2c_del_driver(&spl06_driver);
}
module_init(spl06_driver_init);
module_exit(spl06_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>
struct spl06_calib
{
short c0;
short c1;
int c00;
int c10;
short c01;
short c11;
short c20;
short c21;
short c30;
int kP;
int kT;
};
static struct spl06_calib spl06_calib_data;
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]; //保存收到的 spl06 转换结果数据,先为温度,后为气压
int Traw, Praw;
float Traw_sc, Praw_sc;
float Tcomp, Pcomp;
struct spl06_calib *p;
/*打开文件*/
int fd = open("/dev/i2c_spl06", O_RDWR);
if (fd < 0) {
printf("open file : %s failed !\n", argv[0]);
return -1;
}
error = read(fd, &spl06_calib_data, sizeof(struct spl06_calib));
if (error != sizeof(struct spl06_calib)) {
printf("read spl06_calib error! \n");
close(fd);
return -1;
}
usleep(200000);
p = &spl06_calib_data;
printf("c0: %d, c1: %d, c00: %d, c10: %d, c01: %d, c11: %d, c20: %d, c21: %d, c30: %d, kP: %d, kT: %d\n",
p->c0, p->c1, p->c00, p->c10, p->c01, p->c11,p->c20, p->c21, p->c30, p->kP, p->kT);
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(&Praw, receive_data, sizeof(Praw));
memcpy(&Traw, &receive_data[4], sizeof(Traw));
/* 根据 datasheet 计算实际气压和温度 */
Traw_sc = Traw * 1.0 / p->kT;
Praw_sc = Praw * 1.0 / p->kP;
//printf("Traw %d, Praw %d\n", Traw, Praw);
//printf("Traw_sc %f, Praw_sc %f\n", Traw_sc, Praw_sc);
Tcomp = p->c0 * 0.5 + p->c1 * Traw_sc;
Pcomp = p->c00;
Pcomp += Praw_sc * (p->c10 + Praw_sc * (p->c20 + Praw_sc * p->c30)) + Traw_sc * p->c01;
Pcomp += Traw_sc * Praw_sc * (p->c11 + Praw_sc * p->c21);
printf("Tcomp %f, Pcomp %f\n", Tcomp, Pcomp);
usleep(10000);
}
/*关闭文件*/
error = close(fd);
if (error < 0) {
printf("close file error! \n");
}
return 0;
}