硬件环境:
1、荔枝派nano(f1c100s)
2、使用f1c100s的i2c0,PE11和PE12引脚
软件环境:
1、Linux 4.15
2、BME280使用介绍
文章目录
- 一、I2C子系统
- 1、应用层访问i2c设备
- 2、驱动层访问i2c设备
- 2.1、i2c总线设备驱动模型
- 2.2、i2c_client 和 i2c_driver
- 二、程序编写
- 1、驱动程序
- 2、i2c_client实现
- 3、测试应用程序
- 三、总结
一、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、以上出现的专业术语或名词解释或个人理解有不妥,恳请指出!