Linux驱动开发(九)---树莓派I2C设备驱动开发(BME280)

54 篇文章 37 订阅
22 篇文章 80 订阅

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
继续宣传一下韦老师的视频

70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

在这里插入图片描述
从第八节开始,就开始进行具体的设备驱动开发,今天来学习一下I2C总线设备的驱动开发。
开始学习!
在这里插入图片描述

BME280

在这里插入图片描述

在这里插入图片描述
为什么要用这个IIC设备呢,因为我这没有其他的传感器。
在这里插入图片描述

就这个还是用之前做手表留下的底板,焊接好外围做了一个传感器。顺带复习了一下焊接技术
在这里插入图片描述

设备树

这个不同于前面的虚拟总线模型,这个是真实存在了I2C总线,所以设备的DTS,需要放在已经定义好的I2C总线下,我们可以看到,DTS的前面有如下定义
在这里插入图片描述
后面又包含了
在这里插入图片描述
我们要定义自己的I2C的客户端,就需要放在I2C1的引用下。
在这里插入图片描述

那么为什么不放在I2C0下,因为没有找到,为啥不放在I2C2下,因为没有看到引脚定义,反正就是感觉放在1下比较稳妥。
在这里插入图片描述
这个DTS编译的时候,会报一个警告,

arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dts:245.10-249.4: Warning (i2c_bus_reg): /soc/i2c@7e804000/mybme280: I2C bus unit address format error, expected "76"

解决办法就是
在这里插入图片描述

别问为什么,就是这么豪横。
在这里插入图片描述

更新设备树之后,不是在device-tree下查看有没有这个设备了,而是需要搜索一下才能看到。

在这里插入图片描述

然后进入相关路径,可以进行如下查看,发现数据没问题。注意查看reg参数的使用,用的是hexdump命令

root@raspberrypi:/home/pgg# cd /sys/firmware/devicetree/base/soc/i2c@7e804000/mybme280@76
root@raspberrypi:/sys/firmware/devicetree/base/soc/i2c@7e804000/mybme280@76# cat 
compatible  name        reg         status      
root@raspberrypi:/sys/firmware/devicetree/base/soc/i2c@7e804000/mybme280@76# cat compatible 
pgg,bme280root@raspberrypi:/sys/firmware/devicetree/base/soc/i2c@7e804000/mybme280@76# hex
hex2hcd  hexdump  
root@raspberrypi:/sys/firmware/devicetree/base/soc/i2c@7e804000/mybme280@76# hexdump reg 
0000000 0000 7600                              
0000004
root@raspberrypi:/sys/firmware/devicetree/base/soc/i2c@7e804000/mybme280@76# 

设备树创建设备没有问题,咱们继续
在这里插入图片描述

驱动框架

接下来写一个最简单的I2C驱动框架,包括注册i2c_driver

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>


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

	return 0;
}

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

static const struct of_device_id bme280_of_match[] = {
	{.compatible = "pgg,bme280"},
	{}
};


static struct i2c_driver bme280_drv = {
	.driver = {
		.name = "mybme280_drv",
		.of_match_table	 = bme280_of_match,
	},
	.probe = bme280_probe,
	.remove = bme280_remove,
};

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

static void bme280_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	i2c_del_driver(&bme280_drv);
}

module_init(bme280_init);
module_exit(bme280_exit);

MODULE_LICENSE("GPL");

编译 加载。
然后就是各种遇到问题了。
在这里插入图片描述

首先是i2c设备没有,执行i2cdetect报错

FATAL: Module i2c-dev not found in directory

各种方法之下,还是没有解决。于是!

插播解决内核升级问题

之前遇到的问题就开始显现了,没有i2c设备,加上之前的没有wifi设备,感觉还是不能这么下去了,于是重新编译内核。

先把树莓派用官方工具烧写好。

  1. 用正常的树莓派系统,导出配置

获取当前树莓派的config
已经开机的树莓派上会有这个节点:/proc/config.gz,从这个节点可以获取本树莓派的config。
如果没有这个节点的话则需要先加载模块:

sudo modprobe configs

把 config.gz 内容复制到要编译的电脑上:

scp pi@[ip]:/proc/config.gz .

解压,保存为.confg文件。

zcat config.gz > .config

注:必须在linux环境下解压,在mac下会乱码。

把此config文件复制到linux源码的根目录。

  1. 重新编译内核。并更新内核。

更新内核之后,连wifi都变得可用了。
在这里插入图片描述

继续驱动开发

测试模块加载及运行

root@raspberrypi:/home/pgg/work/driver# insmod mybme280.ko 
root@raspberrypi:/home/pgg/work/driver# dmesg 
[  844.402925] drivers/char/mybmp280.c bme280_init 53
[  844.403116] drivers/char/mybmp280.c bme280_probe 19

没问题,已经成功运行probe函数。
在观看韦老师的视频时,他发现了一个内核的问题,就是id_table参数,在他的版本中是必须有的,算是内核的一个缺陷。不过在我这个版本中,没有这个问题。
在这里插入图片描述

继续注册设备,回到我们原来想要的file_operation,于是,增加如下代码,注册一个bme280的operations

static int major;
static struct class *bme280_class;

static long bme280_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations bme280_fops = {
	.owner	 = THIS_MODULE,
	.unlocked_ioctl = bme280_ioctl,
};


static int bme280_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/* register_chrdev */
	major = register_chrdev(0, "bme280", &bme280_fops );  

	/* class_create */
	bme280_class = class_create(THIS_MODULE, "bme280_class");

	/* device_create */
	device_create(bme280_class, NULL, MKDEV(major, 0), NULL, "mybme280");

	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;
}

不过这里使用了是ioctl,这个接口就比单纯的read或者write更加灵活,这类似于一个带参数的read。
用户侧程序

int main(int argc, char **argv)
{
	int fd;
	int buf[2];

	if ((argc != 4) && (argc != 5))
	{
		printf("Usage: %s <dev> r <addr>\n", argv[0]);
		printf("       %s <dev> w <addr> <val>\n", argv[0]);
		return -1;
	}
	
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf(" can not open %s\n", argv[1]);
		return -1;
	}
	buf[0]=1;
	buf[1]=2;
	
	ioctl(fd, 0, buf);
	close(fd);
	return 0;
}

再次尝试。

root@raspberrypi:/home/pgg/work/driver# gcc -o mybme280_user mybme280_user.c 
root@raspberrypi:/home/pgg/work/driver# insmod mybme280.ko 
root@raspberrypi:/home/pgg/work/driver# ./mybme280_user /dev/mybme280 r 10
Read addr 0xa, get data 0x0
root@raspberrypi:/home/pgg/work/driver# dmesg 
[ 3621.693273] drivers/char/mybmp280.c bme280_init 83
[ 3621.693472] drivers/char/mybmp280.c bme280_probe 36
[ 3643.259323] drivers/char/mybmp280.c bme280_ioctl 22

妥妥的执行到了ioctl函数。
在这里插入图片描述

读取BME280数据

这里就需要理解BME280的操作手册了。我们这里简单的实现一个读取ID的操作。
ID的地址为0xD0,包含了芯片的身份标示码chip_id[7:0],上电复位后可读。成功的话会读到0x60。
网上说BMP280BME280一样,差点上了个大B当。ID不一样,BMP280读出来是0x58
在这里插入图片描述

每个读取的过程都是如下,参考单片机的过程,参考洛华x《 BMP280气压温度传感器详细使用教程》

  IICBegin();
  IICWrite(0xEC);
  IICWrite(0xD0);//选定0xD0寄存器,开始读取修正参数
  
  IICBegin();
  IICWrite(0xED);//0x76地址加上1的最低位,R/W选为读
  id=IICRead(true); 

IIC的发送,每次都以开始信号为起始,所以类似的这种操作是两组数据。那么封装到ioctl中,产生了如下代码。不知道为啥这里的读取地址不再需要加1,还是使用mybme280_client->addr。

static int bmp_readid(void)
{
	unsigned char addr=0xd0;
	unsigned char id=0xec;
	struct i2c_msg msgs[2];
	
	/* 读AT24C02 */
	msgs[0].addr  = mybme280_client->addr;
	msgs[0].flags = 0; /* 写 */
	msgs[0].len   = 1;
	msgs[0].buf   = &addr;

	msgs[1].addr  = (mybme280_client->addr);
	msgs[1].flags = I2C_M_RD; /* 读 */
	msgs[1].len   = 1;
	msgs[1].buf   = &id;
		
	i2c_transfer(mybme280_client->adapter, msgs, 2);

	printk("get bme280 id %x \n", id);
	
	return 0;
}

我们在ioctl中引用一下这个函数,就可以试着读出ID是不是0x60了。

root@raspberrypi:/home/pgg/work/driver# ./mybme280_user /dev/mybme280 -r 10
root@raspberrypi:/home/pgg/work/driver# dmesg 
[  314.434372] drivers/char/mybme280.c bme280_ioctl 65
[  314.635714] get bme280 id 60 

没毛病。
后续的读取温度及计算就先不搞了,这里主要还是学习IIC驱动的开发,并不是传感器的使用。
在这里插入图片描述

问题总结

查看设备树生成设备

这里有三种方式,可以用前面查找方式

find / -name "*mybme280*"

也可以直接查看I2C下的设备

cd /sys/bus/i2c/devices/|ls

还可以用i2c-tool带的命令i2cdetect检测一下1号总线

i2cdetect -y 1

在这里插入图片描述

查看设备匹配的驱动

开始的时候,无法挂载上驱动,不执行probe函数,后来发现设备提前加载了bmp280的驱动,
在这里插入图片描述
手动卸载了相关驱动,再安装自己编译的驱动,也还是加载原有的驱动。
在这里插入图片描述

所以先把驱动移除备份
在这里插入图片描述
然后再次加载自己开发的驱动,才可以了
自己的驱动加载之后,会出现在下面目录
在这里插入图片描述

查看驱动的匹配可以通过下面方式,进入设备目录,就能看到driver对应的是自己编写的驱动。
在这里插入图片描述

结束语

今年十一放假还是七天,放七天,上七天,是不是感觉和没休一样,放的都上回来了。我们这时代到底是不是进步,怎么感觉假期一点没多呢,而且感觉就算放假了,也没什么精力去玩了,
在这里插入图片描述
哎,就这样吧,周五了,再不去,食堂的烧饼加鸡蛋就没了
在这里插入图片描述

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖哥王老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值