linux 下调试 spl06-001 气压传感器

供自己备忘;

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, &reg, 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, &reg, 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值