《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
继续宣传一下韦老师的视频
70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】
从第八节开始,就开始进行具体的设备驱动开发,今天来学习一下I2C总线设备的驱动开发。
开始学习!
为什么要用这个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
#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");
编译 加载。
然后就是各种遇到问题了。
首先是i2c设备没有,执行i2cdetect报错
FATAL: Module i2c-dev not found in directory
各种方法之下,还是没有解决。于是!
之前遇到的问题就开始显现了,没有i2c设备,加上之前的没有wifi设备,感觉还是不能这么下去了,于是重新编译内核。
先把树莓派用官方工具烧写好。
获取当前树莓派的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源码的根目录。
更新内核之后,连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 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;
}
再次尝试。
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的操作手册了。我们这里简单的实现一个读取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对应的是自己编写的驱动。
今年十一放假还是七天,放七天,上七天,是不是感觉和没休一样,放的都上回来了。我们这时代到底是不是进步,怎么感觉假期一点没多呢,而且感觉就算放假了,也没什么精力去玩了,
哎,就这样吧,周五了,再不去,食堂的烧饼加鸡蛋就没了