1.环境 ubuntu操作系统
2.开发板 stm32mp157a
一. 我们要在si7006传感器读取温湿度首先要分析电路图以及传输总线
1.在传感器带的元器件手册中可以知道温湿度是i2c通信,并且连接到了I2C1_SDA,和I2C1_SCL两条线上。
(1)i2c知识的扩展
I2C(Inter-Integrated Circuit)是一种串行通信协议,他是双线接口制,SCL时钟信号,由主设备控制,SDA数据线,用于主从设备间的数据传输。i2c是多主多从方式,允许多主设备和多从设备连接在同一总线上,主机通过从机地址来识别设备。
2.由扩展板的引脚图可以知道传感器的时钟线和数据线分别连接的GPIO引脚。
3.根据扩展板上的引脚图我们可以的到这样一个简易版电路连接图
二. 根据电路图的分析来编写设备树
1.分析设备树之前,我们要做的就是在内核目录下执行make menuconfig命令进入内核菜单选项,将i2c的核心层和总线驱动层选配到内核中
将上述的代码配置到内核之后,重新编译内核 make uImage LOADADDR=0xc2000000 -j4
2.那要怎么更改设备树呢?让我们逐步引进
根据上图我们知道了I2C1的首地址是0x40012000,根据这个选项我们可以在设备树stm32mp151.dtsi中进行搜索之后定位到i2c1设备树节点。
之后我们可能会疑惑要怎么根据这个设备节点编写属于自己的设备树呢?
内核中都是带着设备树的使用文档的,我们可以去文档中找寻答案。
进入帮助文档中的i2c目录下我们会发现有很多系列的帮助文档,当下用的是st的芯片,所以就要寻找st系列的帮助手册。打开手册查看示例。
我们可以根据这两个文档中的关于stm32mp15系列的示例俩编写自己的设备树。
我们在根据设备树参考文档配置设备树的时候会发现填写pinctrl-0时后边的引脚在设备树的哪里呢?
这时我们可以看到我们我们更改设备树包含的头文件
让我们打开这个头文件设备树搜索我们的i2c设置的引脚。
我们要根据这两个引脚复用的模式来选用pinctrl的引脚模式,接下来我们要从手册来查看他的复用模式。
根据这些我们就知道要引用哪个节点了:
下面是更改完的驱动我们温湿度传感器的设备树。
最后编写完设备树之后别忘了在内核目录下执行 make dtbs命令对设备树进行编译。
三、运用i2c子系统来编写温湿度读取的代码。
(1)以下是设备驱动层的API
`1.分配并初始化对象`
`struct i2c_driver {`
`int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
//匹配成功执行的函数
int (*remove)(struct i2c_client *client);`
`//分离的时候执行的函数`
`struct device_driver driver;`
`//父类`
`const struct i2c_device_id *id_table;`
`//1.idtable匹配方式`
`};`
`struct device_driver {`
`const char *name; //name不用于匹配,但必须填充`
`const struct of_device_id *of_match_table; //2.设备树匹配方式`
`};`
`2.注册、注销`
`\#define i2c_add_driver(driver) \`
`i2c_register_driver(THIS_MODULE, driver) //注册`
`void i2c_del_driver(struct i2c_driver *driver); //注销`
`3.一键注销注销的宏`
`module_i2c_driver(i2c_driver变量名);`
(2)读写消息结构体的封装。
`struct i2c_msg {`
`__u16 addr; //从机地址 client->addr
__u16 flags; //0写 1读`
`__u16 len; //消息长度,单位是字节
__u8 *buf; //指向消息首地址的指针`
`};`
(3)si7006温湿度驱动端代码编写:
`#include <linux/module.h>`
`#include <linux/i2c.h>`
`#include <linux/init.h>`
`#include <linux/fs.h>`
`#include <linux/uaccess.h>`
`#define temperature 0xe3`
`#define humidity 0xe5`
`#define temperature_on _IOR('k',0,int)`
`#define humidity_on _IOR('k',2,int)`
`#define NAME "si7006"`
`struct i2c_client *gclient;`
`int major;`
`struct class *cls;`
`struct device *dev;`
`//发送I2c消息 从机地址`
`int read_temperature(u8 reg)`
`{`
`u8 rbuff[] = {reg};`
`u16 data;`
`int ret;`
`//读操作要封装两个结构体`
`struct i2c_msg r_msg[] = {`
`[0] = {`
`.addr = gclient->addr,`
`.flags = 0,`
`.len = 1,`
`.buf = rbuff,`
`},`
`[1] = {`
`.addr = gclient->addr,`
`.flags = 1,`
`.len = 2,`
`.buf =(u8 *) &data,`
`},`
`};`
`//调用函数发送东西`
`ret = i2c_transfer(gclient->adapter,r_msg,ARRAY_SIZE(r_msg));`
`if (ret == -1) {`
`printk("i2c_read_serial_firmware error\n");`
`return -EAGAIN;`
`}`
`return data << 8 | data >> 8;`
`}`
`//将数据写入的函数`
`//将数据读取的函数`
`int si7006_open(struct inode * inode1 , struct file * file1)`
`{`
`return 0;`
`}`
`long si7006_ioctl(struct file * file1, unsigned int cmd, unsigned long arg)`
`{`
`//开始填控制读温度的接口`
`int data,ret;`
`switch (cmd)`
`{`
`case temperature_on:`
`data = read_temperature(temperature);`
`data = data & 0xffff;`
`ret = copy_to_user((void *)arg,&data,sizeof(data));`
`if(ret)`
`{`
`printk("copy_to_user error\n");`
`return -EAGAIN;`
`}`
`break;`
`case humidity_on:`
`data = read_temperature(humidity);`
`data = data & 0xffff;`
`ret = copy_to_user((void *)arg,&data,sizeof(data));`
`if(ret)`
`{`
`printk("copy_to_user error\n");`
`return -EAGAIN;`
`}`
`break;`
`}`
`return 0;`
`}`
`int si7006_close (struct inode * inode1, struct file * file1)`
`{`
`return 0;`
`}`
`struct file_operations fops =`
`{`
`.open = si7006_open,`
`.unlocked_ioctl = si7006_ioctl,`
`.release = si7006_close,`
`};`
`int si70006_probe(struct i2c_client *client, const struct i2c_device_id *id)`
`{`
`// 结构体里放着设备的地址`
`gclient = client;`
`// 注册字符设备驱动来进行数据的收发`
`// 1.注册字符设备驱动`
`major = register_chrdev(0, NAME, &fops);`
`if (major < 0)`
`{`
`printk("register_chrdev error\n");`
`return major;`
`}`
`// 2.创建设备节点`
`cls = class_create(THIS_MODULE, NAME);`
`if (IS_ERR(cls))`
`{`
`printk("class_create error\n");`
`unregister_chrdev(major, NAME);`
`return PTR_ERR(cls);`
`}`
`dev = device_create(cls, NULL, MKDEV(major, 0), NULL, NAME);`
`if (IS_ERR(dev))`
`{`
`printk("device_create error\n");`
`class_destroy(cls);`
`unregister_chrdev(major, NAME);`
`return PTR_ERR(dev);`
`}`
`return 0;`
`}`
`int si4006_remove(struct i2c_client *client)`
`{`
`device_destroy(cls, MKDEV(major, 0));`
`class_destroy(cls);`
`unregister_chrdev(major, NAME);`
`return 0;`
`}`
`struct of_device_id oftable[] =`
`{`
`{`
`.compatible = "hqyj,si7006",`
`},`
`{}};`
`struct i2c_driver si7006 = {`
`.probe = si70006_probe,`
`.remove = si4006_remove,`
`.driver = {`
`.name = "si7006",`
`.of_match_table = oftable,`
`},`
`};`
`module_i2c_driver(si7006);`
`MODULE_LICENSE("GPL");`
(4)客户端测试代码:
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`#include <fcntl.h>`
`#include <sys/ioctl.h>`
`#define temperature_on _IOR('k',0,int)`
`#define humidity_on _IOR('k',2,int)`
`int main(int argc, char const *argv[])`
`{`
`int tmp,hum,fd;`
`float ftmp,fhum;`
`open("/dev/si7006",O_RDWR);`
`if(-1 ==( fd = open("dev/si7006",O_RDWR)))`
`{`
`printf("打开设备文件失败\n");`
`return 0;`
`}`
`while(1)`
`{`
`ioctl(fd,temperature_on,&tmp);`
`ioctl(fd,humidity_on,&hum);`
`ftmp = (175.72 * tmp) / 65536 - 46.85;`
`fhum = (125 * hum) / 65536.0 - 6;`
`//printf("ftmp = %d fhum = %d\n",ftmp,fhum);`
`printf("temperature_on : %.2f humidity_on : %.2f\n",ftmp,fhum);`
`fflush(stdout);`
`sleep(1);`
`}`
`close(fd);`
`return 0;`
`}`
(5)Makefile编写:
`modname ?=`
`currentDir := $(shell pwd)`
`ifeq ($(arch) , arm)`
`LinuxDir := /home/linux/linux-5.10.61`
`else`
`LinuxDir := /lib/modules/$(shell uname -r)/build`
`endif`
`all:`
`make -C $(LinuxDir) M=$(currentDir) modules`
`@ # -C :指定linux内核源码目录的,linux内核源码必须被编译`
`@ # 借助linux内核源码目录下的模块化编译的规则对当前目录下的`
`@ # 驱动代码进行模块化的编译`
`@ # M : 只对当前目录下的驱动文件进行模块化的编译`
`@ # modules : 采用模块化的方式编译驱动代码`
``
`clean:`
`make -C $(LinuxDir) M=$(currentDir) clean`
`help:`
`@echo 'make arch=<arm|X86> modname=filename all'`
`obj-m += $(modname).o`