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


    前文回顾

    《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"
    
    • 1

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

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

    更新设备树之后,不是在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# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    驱动框架

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

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

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

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

    FATAL: Module i2c-dev not found in directory
    
    • 1

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

    插播解决内核升级问题

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

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

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

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

    sudo modprobe configs
    
    • 1

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

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

    解压,保存为.confg文件。

    zcat config.gz > .config
    
    • 1

    注:必须在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
    
    • 1
    • 2
    • 3
    • 4

    没问题,已经成功运行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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

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

    int main(int argc, char **argv)
    {
    	int fd;
    	int buf[2];
    
    	if ((argc != 4) && (argc != 5))
    	{
    		printf("Usage: %s  r \n", argv[0]);
    		printf("       %s  w  \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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    再次尝试。

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    妥妥的执行到了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); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    我们在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 
    
    • 1
    • 2
    • 3
    • 4

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

    问题总结

    查看设备树生成设备

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

    find / -name "*mybme280*"
    
    • 1

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

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

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

    i2cdetect -y 1
    
    • 1

    在这里插入图片描述

    查看设备匹配的驱动

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

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

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

    结束语

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

  • 相关阅读:
    solidworks底部状态栏显示不出来
    FileInputStream文件字节输入流
    虚幻C+++基础 day2
    人才近悦远来,望城区夯实“强省会”智力底座
    unsafe value used in a resource URL context,angular框架下嵌入iframe报错问题解决
    TechEmpower 21轮Web框架 性能评测 -- C# 的性能 和 Rust、C++并驾齐驱
    南京邮电大学编译原理课后作业答案
    【unocss】apply聚合语法,unocss配置
    唯品会:高利润,慢增长?
    Android 动态分区详解(五) 为什么没有生成 super.img?
  • 原文地址:https://blog.csdn.net/baidu_19348579/article/details/126026624