700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > STM32MP157驱动开发——Linux I2C驱动

STM32MP157驱动开发——Linux I2C驱动

时间:2021-06-22 05:58:42

相关推荐

STM32MP157驱动开发——Linux I2C驱动

相关文章:正点原子教程第四十章——Linux I2C驱动实验

0.前言

为了简化笔记的编写以及降低工作量,本节开始相关的基础知识部分通过引入原子哥的教材链接来完成,有兴趣的可以进入学习。

上一节学完 RGB LCD 本来想直接学习 RGB 转 HDMI 实验,但是转换芯片需要一个 I2C 引脚来控制芯片功能,所以还是回来先学习 I2C。

一、原理简述

SCL:串行时钟线

SDA:串行数据线

标准速度:100Kb/秒

快速模式:400Kb/秒

需要上拉电阻(通常4.7k),空闲时均处于高电平。

1.起始位

SCL 为高电平时,SDA 出现下降沿

2.停止位

SCL 为高电平时,SDA 出现上升沿

3.数据传输

SDA 上的数据变化只能在 SCL 低电平期间发生,这样是为了保证 SCL 高电平时 SDA的数据稳定。

4.应答信号

由从机发出,但时钟由主机提供。从机通过将 SDA 拉低来表示发出应答信号。

5.I2C写时序

起始信号–>发送 I2C 设备地址–>从机发送应答信号–>重新发送起始信号–>发送写入数据的寄存器地址–>从机应答–>发送写入数据–>从机应答–>停止信号

其中 “发送 I2C 设备地址” 数据时,高7位为设备地址,最后一位为 1 :读操作;0:写操作。

6.I2C读时序

起始信号–>发送 I2C 设备地址(最后一位置1)–>从机发送应答信号–>重新发送起始信号–>发送读取数据的寄存器地址–>从机应答

前半部分仍为先写入数据。后半部分有所不同:

–>重新发送起始信号–>重新发送 I2C 从设备地址(最后一位置0)–>从机应答–>在从机的返回数据里读取–>主机发送 NO ACK,完成读取(从机不应答)–>停止信号

二、设备驱动开发

1.不使用设备树

通过 i2c_board_info 结构体来描述设备:

406 struct i2c_board_info {407 char type[I2C_NAME_SIZE];408 unsigned short flags;409 unsigned short addr;410 const char *dev_name;411 void *platform_data;412 struct device_node *of_node;413 struct fwnode_handle *fwnode;414 const struct property_entry *properties;415 const struct resource *resources;416 unsigned int num_resources;417 int irq;418 };

其中 type 和 addr 这两个成员变量必须要设置,一个是 I2C 设备的名字,一个是 I2C 设备的器件地址。

例:arch/arm/mach-imx/mach-armadillo5x0.c 文件中有关 I2C 设备 s35390a 的信息:

246 static struct i2c_board_info armadillo5x0_i2c_rtc = {247 I2C_BOARD_INFO("s35390a", 0x30),248 };#define I2C_BOARD_INFO(dev_type, dev_addr) \.type = dev_type, .addr = (dev_addr)

使用 I2C_BOARD_INFO 来完成 armadillo5x0_i2c_rtc 的初始化,I2C_BOARD_INFO 宏设置 i2c_board_info 的 type 和 addr 这两个成员变量。

2.使用设备树

通过在设备树中创建 I2C 节点来实现。在 STM32MP157 开发板上有一个 I2C 器件 AP3216C,以此为例。

原理图:

AP3216C 使用了 I2C5,I2C5_SCL 使用的是 PA11,I2C_SDA 使用的是 PA12。 AP3216C 还有个中断引脚,这里没有用到。

1.修改设备树

在 stm32mp15-pinctrl.dtsi 中,将 PA11、PA12 复用为 I2C:(ST官方已写好)

2.追加子节点

在 stm32mp157d-atk.dts 文件中,添加一个 i2c5 节点,并在节点中添加“ap3216c@1e”子节点:

&i2c5 {pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c5_pins_a>;pinctrl-1 = <&i2c5_pins_sleep_a>;status = "okay";ap3216c@1e {compatible = "alientek,ap3216c";reg = <0x1e>;};};

其中@后面的“1e”是 ap3216c 的器件地址,reg 属性也是设置 ap3216c 器件地址的,因此 reg 设置为 0x1e。

设备树修改完成以后使用“make dtbs”重新编译,然后使用新的设备树启动 Linux 内核,如果设备树修改正确,会在 /sys/bus/i2c/devices 目录下看到一个名为“0-001e”的子目录,进入目录就可以看到名为"name"的文件,name 文件保存着此设备名字。

注意:在开发完 RGB LCD 驱动之后,每次编译设备树需要使用make uImage dtbs LOADADDR=0xC2000040 -j8命令,不然有可能会报一些奇奇怪怪的Warning。暂时还没发现这些warning会产生什么影响,后续发现了来填上。

3.驱动编写

I2C 总线使用的是 I2C 子系统,与 platform 平台驱动的开发流程相似,都是将某一类总线驱动统一为某一个系统的驱动框架,减少冗余代码和冗余开发流程。

新建"21_iic"文件夹,在里面创建 ap3216c.c 和 ap3216creg.h 两个文件,ap3216c.c 为 AP3216C 的驱动代码,ap3216creg.h 是 AP3216C 寄存器头文件。先在 ap3216creg.h 中定义好 AP3216C 的寄存器,输入如下内容:

#ifndef AP3216C_H#define AP3216C_H#define AP3216C_ADDR 0X1E /* AP3216C 器件地址 *//* AP3316C 寄存器 */#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */#define AP3216C_IRDATALOW 0x0A /* IR 数据低字节 */#define AP3216C_IRDATAHIGH 0x0B /* IR 数据高字节 */#define AP3216C_ALSDATALOW 0x0C /* ALS 数据低字节 */#define AP3216C_ALSDATAHIGH 0X0D /* ALS 数据高字节 */#define AP3216C_PSDATALOW 0X0E /* PS 数据低字节 */#define AP3216C_PSDATAHIGH 0X0F /* PS 数据高字节 */#endif

ap3216c.c:

#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ide.h>#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/gpio.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/of_gpio.h>#include <linux/semaphore.h>#include <linux/timer.h>#include <linux/i2c.h>#include <asm/mach/map.h>#include <asm/uaccess.h>#include <asm/io.h>#include "ap3216creg.h"#define AP3216C_CNT 1#define AP3216C_NAME "ap3216c"struct ap3216c_dev {struct i2c_client *client; /* i2c 设备 */dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */unsigned short ir, als, ps; /* 三个光传感器数据 */};/*读取多个寄存器*/static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len){int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->client;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr; /* ap3216c 地址 */msg[0].flags = 0; /* 标记为发送数据 */msg[0].buf = &reg; /* 读取的首地址 */msg[0].len = 1; /* reg 长度 *//* msg[1]读取数据 */msg[1].addr = client->addr; /* ap3216c 地址 */msg[1].flags = I2C_M_RD; /* 标记为读取数据 */msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度 */ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;}/*向多个寄存器写入数据*/static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len){u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->client;b[0] = reg; /* 寄存器首地址 */memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组 b 里面 */msg.addr = client->addr; /* ap3216c 地址 */msg.flags = 0; /* 标记为写数据 */msg.buf = b; /* 要写入的数据缓冲区 */msg.len = len + 1; /* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);}/*读取 ap3216c 指定寄存器值,读取一个寄存器*/static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg){u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);return data;}/*向 ap3216c 指定寄存器写入指定的值,写一个寄存器*/static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data){u8 buf = 0;buf = data;ap3216c_write_regs(dev, reg, &buf, 1);}/** 读取 AP3216C 的数据,包括 ALS,PS 和 IR, 注意!如果同时* 打开 ALS,IR+PS 两次数据读取的时间间隔要大于 112.5ms*/void ap3216c_readdata(struct ap3216c_dev *dev){unsigned char i =0;unsigned char buf[6];/* 循环读取所有传感器数据 */for(i = 0; i < 6; i++) {buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);}if(buf[0] & 0X80) /* IR_OF 位为 1,则数据无效 */dev->ir = 0;else /* 读取 IR 传感器的数据 */dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);dev->als = ((unsigned short)buf[3] << 8) | buf[2];if(buf[4] & 0x40) /* IR_OF 位为 1,则数据无效 */dev->ps = 0;else /* 读取 PS 传感器的数据 */dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);}/*打开设备*/static int ap3216c_open(struct inode *inode, struct file *filp){/* 从 file 结构体获取 cdev 指针, 再根据 cdev 获取 ap3216c_dev 首地址 */struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;struct ap3216c_dev *ap3216cdev = container_of(cdev, struct ap3216c_dev, cdev);/* 初始化 AP3216C */ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);mdelay(50);ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);return 0;}/*从设备读取数据*/static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){short data[3];long err = 0;struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev, cdev);ap3216c_readdata(dev);data[0] = dev->ir;data[1] = dev->als;data[2] = dev->ps;err = copy_to_user(buf, data, sizeof(data));return 0;}/*关闭/释放设备*/static int ap3216c_release(struct inode *inode, struct file *filp){return 0;}/* AP3216C 操作函数 */static const struct file_operations ap3216c_ops = {.owner = THIS_MODULE,.open = ap3216c_open,.read = ap3216c_read,.release = ap3216c_release,};/*probe函数*/static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){int ret;struct ap3216c_dev *ap3216cdev;ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev), GFP_KERNEL);if(!ap3216cdev)return -ENOMEM;/* 注册字符设备驱动 *//* 1、创建设备号 */ret = alloc_chrdev_region(&ap3216cdev->devid, 0, AP3216C_CNT, AP3216C_NAME);if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", AP3216C_NAME, ret);return -ENOMEM;}/* 2、初始化 cdev */ap3216cdev->cdev.owner = THIS_MODULE;cdev_init(&ap3216cdev->cdev, &ap3216c_ops);/* 3、添加一个 cdev */ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid, AP3216C_CNT);if(ret < 0) {goto del_unregister;}/* 4、创建类 */ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);if (IS_ERR(ap3216cdev->class)) {goto del_cdev;}/* 5、创建设备 */ap3216cdev->device = device_create(ap3216cdev->class, NULL, ap3216cdev->devid, NULL, AP3216C_NAME);if (IS_ERR(ap3216cdev->device)) {goto destroy_class;}ap3216cdev->client = client;/* 保存 ap3216cdev 结构体 */i2c_set_clientdata(client,ap3216cdev);return 0;destroy_class:device_destroy(ap3216cdev->class, ap3216cdev->devid);del_cdev:cdev_del(&ap3216cdev->cdev);del_unregister:unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);return -EIO;}/*i2c 驱动的 remove 函数*/static int ap3216c_remove(struct i2c_client *client){struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);/* 注销字符设备驱动 *//* 1、删除 cdev */cdev_del(&ap3216cdev->cdev);/* 2、注销设备号 */unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);/* 3、注销设备 */device_destroy(ap3216cdev->class, ap3216cdev->devid);/* 4、注销类 */class_destroy(ap3216cdev->class);return 0;}/* 传统匹配方式 ID 列表 */static const struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c", 0},{}};/* 设备树匹配列表 */static const struct of_device_id ap3216c_of_match[] = {{.compatible = "alientek,ap3216c" },{/* Sentinel */ }};/* i2c 驱动结构体 */static struct i2c_driver ap3216c_driver = {.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = ap3216c_of_match,},.id_table = ap3216c_id,};/*驱动入口函数*/static int __init ap3216c_init(void){int ret = 0;ret = i2c_add_driver(&ap3216c_driver);return ret;}/*驱动出口函数*/static void __exit ap3216c_exit(void){i2c_del_driver(&ap3216c_driver);}/* module_i2c_driver(ap3216c_driver) */module_init(ap3216c_init);module_exit(ap3216c_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("amonter");MODULE_INFO(intree, "Y");

①自定义一个 ap3216c_dev 结构体,其中的client 成员变量用来存储从设备树提供的 i2c_client 结构体,ir、als 和 ps 分别存储 AP3216C 的 IR、ALS 和 PS数据

②ap3216c_read_regs 函数实现多字节读取,但是 AP3216C 好像不支持连续多字节读取,此函数在测试其他 I2C 设备的时候可以实现多给字节连续读取,但是在 AP3216C上不能连续读取多个字节, 不过读取一个字节没有问题的

③ap3216c_write_regs 函数实现连续多字节写操作

④ap3216c_read_reg 函数用于读取 AP3216C 的指定寄存器数据,用于一个寄存器的数据读取

⑤ap3216c_write_reg 函数用于向 AP3216C 的指定寄存器写入数据,用于一个寄存器的数据写操作

⑥ap3216c_readdata 读取 AP3216C 的 PS、 ALS 和 IR 等传感器原始数据值

⑦open、read、release、probe等就是标准的 iic 设备驱动框架,是基于字符驱动的一层封装。

ap3216c_dev 结构体里有一个 cdev 的变量成员,open函数中使用filp->f_path.dentry->d_inode->i_cdev获取这个成员变量的地址,再用 container_of 获取ap3216c_dev的首地址。

probe函数中i2c_set_clientdata 函数将 ap3216cdev 变量的地址绑定到 client,进行绑定之后,可以通过 i2c_get_clientdata 来获取 ap3216cdev 变量指针。

remove函数调用 i2c_get_clientdata 函数来得到 ap3216cdev 变量的地址,后面执行的一系列卸载、注销操作。

测试App:

#include "stdio.h"#include "unistd.h"#include "sys/types.h"#include "sys/stat.h"#include "sys/ioctl.h"#include "fcntl.h"#include "stdlib.h"#include "string.h"#include <poll.h>#include <sys/select.h>#include <sys/time.h>#include <signal.h>#include <fcntl.h>/** @description : main 主程序* @param - argc : argv 数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/int main(int argc, char *argv[]){int fd;char *filename;unsigned short databuf[3];unsigned short ir, als, ps;int ret = 0;if (argc != 2){printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0){printf("can't open file %s\r\n", filename);return -1;}while (1){ret = read(fd, databuf, sizeof(databuf));if (ret == 0){/* 数据读取成功 */ir = databuf[0]; /* ir 传感器数据 */als = databuf[1]; /* als 传感器数据 */ps = databuf[2]; /* ps 传感器数据 */printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);}usleep(200000); /*100ms */}close(fd); /* 关闭文件 */return 0;}

在 while 循环中不断的读取 AP3216C 的设备文件,从而得到 ir、 als 和 ps 这三个数据值,并输出到终端。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。