荔枝派nano(f1c100s)基于I2C子系统的BME280驱动

2023-12-30 20:58:19

硬件环境:
1、荔枝派nano(f1c100s)
2、使用f1c100s的i2c0,PE11和PE12引脚
软件环境:
1、Linux 4.15
2、BME280使用介绍

一、I2C子系统

对i2c设备的访问,有两种方法:
1、在应用层直接访问i2c设备;
2、编写i2c设备的驱动程序,也就是在驱动层访问;

1、应用层访问i2c设备

在这里插入图片描述

  • 在应用层可以通过i2c-dev.c驱动程序访问芯片内部i2c控制器的驱动adapter_driver,进而实现访问i2c控制器下的i2c设备。也就是可以在应用程序中直接使用内核提供的i2c-dev.c提供的API函数对i2c设备进行读写操作,不用再去编写该设备的驱动程序。i2ctools就是基于i2c-dev.c实现的在应用层访问i2c设备的工具;

2、驱动层访问i2c设备

在这里插入图片描述

  • 所谓的驱动层访问i2c设备,就是需要我们真正编写某个i2c设备的驱动程序;

2.1、i2c总线设备驱动模型

在这里插入图片描述

  • 可以看到这很像Linux下的platform总线设备驱动模型,但platform总线设备驱动模型是虚拟的,而i2c总线设备驱动模型是真实存在的;
  • 当有新的i2c_client时,i2c总线会匹配其对应的i2c_driver;当有新的i2c_driver注册时,就会匹配还未匹配驱动的i2c_client;一旦匹配成功,i2c_driver的probe函数就被调用;

2.2、i2c_client 和 i2c_driver

  • i2c_client
    i2c_client结构体会存放设备地址(addr)、名字(name)、挂载在哪个i2c控制器下(adapter),等相关硬件信息;

  • i2c_driver
    i2c_driver结构体实现相关的probe、remove等函数;

二、程序编写

程序以访问BME280传感器为例,访问其它i2c设备也是类似的;重在框架;
本驱动程序读取BME280修正参数及温湿度值和大气压值,在应用程序计算最终的温湿度值和大气压值,BME280相关介绍可以参考BME280使用介绍

1、驱动程序

驱动程序编写大致流程:
1、先定义i2c_driver结构体;
2、实现probe和remove函数;
3、实现file_operations结构体的open和read等函数、比如在open函数里初始化i2c设备,read函数里读寄存器;

驱动程序定义i2c_driver结构体:

static const struct of_device_id of_match_ids_bme280[] = {
	{ .compatible = "bosch,bme280",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id bme280_ids[] = {
	{ "bme280",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static struct i2c_driver bme280_driver = {
	.driver = {
		.name = "bme280",
		.of_match_table = of_match_ids_bme280,
	},
	.probe_new = bme280_probe,		//匹配成功后的probe函数
	.remove = bme280_remove,
	.id_table = bme280_ids,
};

实现probe和remove函数:

static struct file_operations bme280_ops = {
	.owner = THIS_MODULE,
	.open  = bme280_open,
	.read  = bme280_read,
};

static int bme280_probe(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	bme280_client = client;		//保存client,里面有设备地址,后续要用

	major = register_chrdev(0, "bme280", &bme280_ops);

	bme280_class = class_create(THIS_MODULE, "bme280_class");
	device_create(bme280_class, NULL, MKDEV(major, 0), NULL, "bme280"); /* /dev/bme280 */
	
	return 0;
}

static int bme280_remove(struct i2c_client *client)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(bme280_class, MKDEV(major, 0));
	class_destroy(bme280_class);

	unregister_chrdev(major, "bme280");
	
	return 0;
}

实现open和read函数:

static ssize_t bme280_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;

	if(size != sizeof(struct bme280_parameter))
		return 0;

	bme280_refresh();
	bme280_read_temp();
	bme280_read_press();
	bme280_read_humi();
	
	err = copy_to_user(buf, &bme280_para, size);
	return size;
}

static int bme280_open (struct inode *node, struct file *file)
{
	/* 在open函数中初始化BME280 */
	/* init bme280 */
	i2c_smbus_write_byte_data(bme280_client, BME280_REGISTER_CTRL_MEAS, 0x55);
	i2c_smbus_write_byte_data(bme280_client, BME280_REGISTER_CONFIG, 0x10);
	
	/* 读取修正参数 */
	/* read bme280 parameter */
	bme280_read_parameter();

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

完整的驱动程序:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>

/* BME280 REGISTER */
#define BME280_REGISTER_ID             (0xD0)
#define BME280_REGISTER_RESET          (0xE0)
#define BME280_REGISTER_STATUS         (0xF3)
#define BME280_REGISTER_CTRL_MEAS      (0xF4)
#define BME280_REGISTER_CONFIG         (0xF5)
#define BME280_REGISTER_PRESS_MSB      (0xF7)
#define BME280_REGISTER_PRESS_LSB      (0xF8)
#define BME280_REGISTER_PRESS_XLSB     (0xF9)
#define BME280_REGISTER_TEMP_MSB       (0xFA)
#define BME280_REGISTER_TEMP_LSB       (0xFB)
#define BME280_REGISTER_TEMP_XLSB      (0xFC)
#define BME280_REGISTER_HUMI_MSB       (0xFD)
#define BME280_REGISTER_HUMI_LSB       (0xFE)

struct bme280_parameter{
    unsigned short int T1;
    short int T2;
    short int T3;
    unsigned short int P1;
    short int P2;
    short int P3;
    short int P4;
    short int P5;
    short int P6;
    short int P7;
    short int P8;
    short int P9;
    unsigned char H1;
    short int H2;
    unsigned char H3;
    short int H4;
    short int H5;
    unsigned char H6;
    int adc_T;
    int adc_P;
    int adc_H;
    int t_fine;
};

static int major;
static struct class *bme280_class;
static struct i2c_client *bme280_client;
static struct bme280_parameter bme280_para;

static void bme280_read_parameter (void)
{
	unsigned char tmp;
	
	//dig_T1
    bme280_para.T1 = i2c_smbus_read_byte_data(bme280_client, 0x89);
    bme280_para.T1 <<= 8;
    bme280_para.T1 |= i2c_smbus_read_byte_data(bme280_client, 0x88);

    //dig_T2
    bme280_para.T2 = i2c_smbus_read_byte_data(bme280_client, 0x8B);
    bme280_para.T2 <<= 8;
    bme280_para.T2 |= i2c_smbus_read_byte_data(bme280_client, 0x8A);

    //dig_T3
    bme280_para.T3 = i2c_smbus_read_byte_data(bme280_client, 0x8D);
    bme280_para.T3 <<= 8;
    bme280_para.T3 |= i2c_smbus_read_byte_data(bme280_client, 0x8C);

    //dig_P1
    bme280_para.P1 = i2c_smbus_read_byte_data(bme280_client, 0x8F);
    bme280_para.P1 <<= 8;
    bme280_para.P1 |= i2c_smbus_read_byte_data(bme280_client, 0x8E);

    //dig_P2
    bme280_para.P2 = i2c_smbus_read_byte_data(bme280_client, 0x91);
    bme280_para.P2 <<= 8;
    bme280_para.P2 |= i2c_smbus_read_byte_data(bme280_client, 0x90);

    //dig_P3
    bme280_para.P3 = i2c_smbus_read_byte_data(bme280_client, 0x93);
    bme280_para.P3 <<= 8;
    bme280_para.P3 |= i2c_smbus_read_byte_data(bme280_client, 0x92);

    //dig_P4
    bme280_para.P4 = i2c_smbus_read_byte_data(bme280_client, 0x95);
    bme280_para.P4 <<= 8;
    bme280_para.P4 |= i2c_smbus_read_byte_data(bme280_client, 0x94);

    //dig_P5
    bme280_para.P5 = i2c_smbus_read_byte_data(bme280_client, 0x97);
    bme280_para.P5 <<= 8;
    bme280_para.P5 |= i2c_smbus_read_byte_data(bme280_client, 0x96);

    //dig_P6
    bme280_para.P6 = i2c_smbus_read_byte_data(bme280_client, 0x99);
    bme280_para.P6 <<= 8;
    bme280_para.P6 |= i2c_smbus_read_byte_data(bme280_client, 0x98);

    //dig_P7
    bme280_para.P7 = i2c_smbus_read_byte_data(bme280_client, 0x9B);
    bme280_para.P7 <<= 8;
    bme280_para.P7 |= i2c_smbus_read_byte_data(bme280_client, 0x9A);

    //dig_P8
    bme280_para.P8 = i2c_smbus_read_byte_data(bme280_client, 0x9D);
    bme280_para.P8 <<= 8;
    bme280_para.P8 |= i2c_smbus_read_byte_data(bme280_client, 0x9C);

    //dig_P9
    bme280_para.P9 = i2c_smbus_read_byte_data(bme280_client, 0x9F);
    bme280_para.P9 <<= 8;
    bme280_para.P9 |= i2c_smbus_read_byte_data(bme280_client, 0x9E);

    //dig_H1
    bme280_para.H1 = i2c_smbus_read_byte_data(bme280_client, 0xA1);

    //dig_H2
    bme280_para.H2 = i2c_smbus_read_byte_data(bme280_client, 0xE2);
    bme280_para.H2 <<= 8;
    bme280_para.H2 |= i2c_smbus_read_byte_data(bme280_client, 0xE1);

    //dig_H3
    bme280_para.H3 = i2c_smbus_read_byte_data(bme280_client, 0xE3);

    //dig_H4
    bme280_para.H4 = i2c_smbus_read_byte_data(bme280_client, 0xE4);
    bme280_para.H4 <<= 4;
    tmp = i2c_smbus_read_byte_data(bme280_client, 0xE5);
    tmp &= 0x0f;
    bme280_para.H4 |= tmp;

    //dig_H
    bme280_para.H5 = i2c_smbus_read_byte_data(bme280_client, 0xE6);
    bme280_para.H5 <<= 4;
    tmp = i2c_smbus_read_byte_data(bme280_client, 0xE5);
    tmp &= 0xf0;
    tmp >>= 4;
    bme280_para.H5 |= tmp;

    //dig_H6
    bme280_para.H6 = i2c_smbus_read_byte_data(bme280_client, 0xE7);
}

static void bme280_refresh (void)
{
	int err;

    // refresh before read data
    err = i2c_smbus_write_byte_data(bme280_client, BME280_REGISTER_CTRL_MEAS, 0x55);
    msleep(45);
}

static void bme280_read_temp (void)
{
	int ret;

    // read temp data
    ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_TEMP_MSB);
    bme280_para.adc_T = ret;
    bme280_para.adc_T <<= 8;

    ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_TEMP_LSB);
    bme280_para.adc_T |= ret;
    bme280_para.adc_T <<= 8;

    ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_TEMP_XLSB);
    bme280_para.adc_T |= ret;
    bme280_para.adc_T >>= 4;
}

static void bme280_read_press (void)
{
	int ret;

	// read press data
	ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_PRESS_MSB);
	bme280_para.adc_P = ret;
	bme280_para.adc_P <<= 8;

	ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_PRESS_LSB);
	bme280_para.adc_P |= ret;
	bme280_para.adc_P <<= 8;

	ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_PRESS_XLSB);
	bme280_para.adc_P |= ret;
	bme280_para.adc_P >>= 4;
}

static void bme280_read_humi (void)
{
	int ret;

	// read humi data
	ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_HUMI_MSB);
	bme280_para.adc_H = ret;
	bme280_para.adc_H <<= 8;

	ret = i2c_smbus_read_byte_data(bme280_client, BME280_REGISTER_HUMI_LSB);
	bme280_para.adc_H |= ret;
}

static ssize_t bme280_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;

	if(size != sizeof(struct bme280_parameter))
		return 0;

	bme280_refresh();
	bme280_read_temp();
	bme280_read_press();
	bme280_read_humi();
	
	err = copy_to_user(buf, &bme280_para, size);
	return size;
}

static int bme280_open (struct inode *node, struct file *file)
{
	/* init bme280 */
	i2c_smbus_write_byte_data(bme280_client, BME280_REGISTER_CTRL_MEAS, 0x55);
	i2c_smbus_write_byte_data(bme280_client, BME280_REGISTER_CONFIG, 0x10);
	
	/* read bme280 parameter */
	bme280_read_parameter();

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static struct file_operations bme280_ops = {
	.owner = THIS_MODULE,
	.open  = bme280_open,
	.read  = bme280_read,
};

static const struct of_device_id of_match_ids_bme280[] = {
	{ .compatible = "bosch,bme280",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id bme280_ids[] = {
	{ "bme280",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static int bme280_probe(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	bme280_client = client;

	major = register_chrdev(0, "bme280", &bme280_ops);

	bme280_class = class_create(THIS_MODULE, "bme280_class");
	device_create(bme280_class, NULL, MKDEV(major, 0), NULL, "bme280"); /* /dev/bme280 */
	
	return 0;
}

static int bme280_remove(struct i2c_client *client)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(bme280_class, MKDEV(major, 0));
	class_destroy(bme280_class);

	unregister_chrdev(major, "bme280");
	
	return 0;
}

static struct i2c_driver bme280_driver = {
	.driver = {
		.name = "bme280",
		.of_match_table = of_match_ids_bme280,
	},
	.probe_new = bme280_probe,
	.remove = bme280_remove,
	.id_table = bme280_ids,
};

static int __init bme280_driver_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return i2c_add_driver(&bme280_driver);
}

static void __exit bme280_driver_exit(void)
{
	i2c_del_driver(&bme280_driver);
}

module_init(bme280_driver_init);
module_exit(bme280_driver_exit);

MODULE_AUTHOR("Cohen0415");
MODULE_LICENSE("GPL");

2、i2c_client实现

我们知道i2c_driver是靠编写程序实现,但没说i2c_client怎么实现;i2c_client也可以通过程序实现,但在这我们通过设备树来实现,i2c控制器驱动程序会自动把设备树中的i2c节点转成i2c_client(个人理解)
本次使用f1c100s的i2c0,引脚PE11(CK)、PE12(DA),只列出部分设备树的定义,如下:
suniv.dtsi:
在这里插入图片描述
在这里插入图片描述
suniv-f1c100s-licheepi-nano.dts:
在i2c节点下添加你的i2c设备
在这里插入图片描述

3、测试应用程序

应用程序比较简单,直接列出完整程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

struct bme280_parameter{
    unsigned short int T1;
    short int T2;
    short int T3;
    unsigned short int P1;
    short int P2;
    short int P3;
    short int P4;
    short int P5;
    short int P6;
    short int P7;
    short int P8;
    short int P9;
    unsigned char H1;
    short int H2;
    unsigned char H3;
    short int H4;
    short int H5;
    unsigned char H6;
    int adc_T;
    int adc_P;
    int adc_H;
    int t_fine;
};

struct bme280_parameter bme280;

float compute_temp()	//通过修正参数计算最终温度值
{
    int var1, var2, T;

    var1 = ((((bme280.adc_T>>3) - (bme280.T1<<1))) * bme280.T2) >> 11;
    var2 = (((((bme280.adc_T>>4)-bme280.T1) * (bme280.adc_T>>4)-bme280.T1) >> 12) * bme280.T3) >> 14;

    bme280.t_fine = var1 + var2;

    T = (bme280.t_fine * 5 + 128) >> 8;
    return (float)T/100;
}

float compute_press()	//通过修正参数计算最终压力值,修正算法来自BME280手册
{
    int64_t var1, var2, p;

    var1 = ((int64_t)bme280.t_fine) - 128000;
    var2 = var1 * var1 * (int64_t)bme280.P6;
    var2 = var2 + ((var1 * (int64_t)bme280.P5) << 17);
    var2 = var2 + (((int64_t)bme280.P4) << 35);
    var1 = ((var1 * var1 * (int64_t)bme280.P3) >> 8) +((var1 * (int64_t)bme280.P2) << 12);
    var1 =(((((int64_t)1) << 47) + var1)) * ((int64_t)bme280.P1) >> 33;

    if (var1 == 0)
    {
        return 0;
    }
    else
    {
        p = 1048576 - bme280.adc_P;
        p = (((p << 31) - var2) * 3125) / var1;
        var1 = (((int64_t)bme280.P9) * (p >> 13) * (p >> 13)) >> 25;
        var2 = (((int64_t)bme280.P8) * p) >> 19;
        p = ((p + var1 + var2) >> 8) + (((int64_t)bme280.P7) << 4);

        return (float)p/256;
    }
}

float compute_humi()	//通过修正参数计算最终湿度值
{
    double var_H;

    var_H = (((double)bme280.t_fine) - 76800.00);
    var_H = (bme280.adc_H - (((double)bme280.H4) * 64.0 + ((double)bme280.H5) / 16384.0 * var_H)) * (((double)bme280.H2) / 65536.0 * (1.0 + ((double)bme280.H6) / 67108864.0 * var_H * (1.0 + ((double)bme280.H3) / 67108864.0 * var_H)));
    var_H = var_H * (1.0 - ((double)bme280.H1) * var_H / 524288.0);

    if(var_H > 100.0)
    {
        var_H = 100.0;
    }
    else if(var_H < 0.0)
    {
        var_H = 0.0;
    }

    return var_H;
}

int main(int argc, char **argv)
{
	int fd;
	int len;

	float temp = 0, press = 0, humi = 0;
	
	fd = open("/dev/bme280", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/bme280\n");
		return -1;
	}

	while(1)
	{
		len = read(fd, &bme280, sizeof(struct bme280_parameter));	
		
		temp = compute_temp();
		press = compute_press();
		humi = compute_humi();
		
		printf("temp=%.2f press=%.2f humi=%.2f\n", temp, press, humi);
		sleep(1);
	}
	
	close(fd);
	
	return 0;
}

三、总结

1、以上出现的专业术语或名词解释或个人理解有不妥,恳请指出!

文章来源:https://blog.csdn.net/CATTLE_L/article/details/135306523
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。