您现在的位置是:首页 >技术交流 >信息x腹稿网站首页技术交流

信息x腹稿

(ノへ ̄、)。 2024-10-02 12:01:04
简介信息x腹稿

 1、自我介绍

    你好,我叫,是就读于电子信息专业的24届研究生。在校期间获得过两次一等奖学金、两次省级竞赛一等奖,英语过了6级,我的研究方向是水下slam多传感器融合方向,用过c/c++/python三种编程语言。

2、系统移植项目

这个项目是将一块imx6ull系列板子移植上系统。我移植参考的是NXP的i.MX6ULL评估板。我去uboot官网上下载2016.03版本的代码,内核是4.15、buildroot构建根文件系统。将板子原理图与NXP原理图对比   发现区别是在与屏幕、网络接口phy芯片款式。然后编写底层驱动,以便于后续进行一些算法应用的部署

uboot

  Kconfig  每个源码下提供选项

defconfig是系统的默认配置文件,系统编译这个文件就会将配置项保存带.config

  .config  源码顶层目录下保存选择结果

   makefile  每个源码目录下根据.config中的内容来告知编译系统应该编译什么

网络驱动

1、对比将nxp评估板原理图与之对比,发现ENET的复位引脚连接到了I.MX6ULL 的SNVS_TAMPER7

也就是说需要把SNVS_TAMPER7复用为gpio5_io07,在板级支持文件中找到io复位参数结构体,写上这个io的配置设置这个引脚无其他作用,而仅用于通用输入/输出(GPIO)功能

MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL)

2、原来的phy芯片是KSZ8081,现在是LAB8720a

1、看看芯片地址对不对

2、因为原来的驱动可以兼容现在的芯片,只需要找到phy驱动代码,找到通用的PHY驱动函数,将lan8720a相关的配置加入条件编译代码段,这样就可以识别我们的phy芯片

屏幕驱动

因为这个屏幕的lcd的io、背光io相同,只需要修改一些参数就可以,在板级支持包中找打显示结构体

像分辨率、屏幕型号、时钟周期

pixclock=(1/51200000)*10^12=19531

内核

网络驱动

内核驱动有兼容,只需要修改设备树和一些配置就可以

1、复位引脚

找到设备树删除SNVS_TAMPER7之前的配置,设置其为ENET的复位引脚gpio_io07找到“iomuxc_snvs节点,添加一个pinctrl节点添加io配置,设置为复位引脚

3、然后找到芯片驱动节点将上面pinctrl节点加入然后 加入gpio子系统中,并且修改芯片地址

4、最后使用图形化配置找到自带的phy驱动,使能就可以使用了

 驱动代码直接使用nfs传输,设备树直接在开发板中修改设备树文件

1、修改好设备树,在内核顶层make dtbs ,然后替代tftp目录中的设备树文件

2、使用内核源码编译生成驱动程序,然后传送到开发板中,使用insmod动态加载

LCD驱动

(24 位色 RGB888,支持多点触摸)

一个像素点占据3个字节,rgb三个通道的值每个是0~255之间

1、初始化参数

这些参数都是屏幕自己独有的,设定好了的 

2、初始化LCD像素时钟

3、设置RGBLCD显存

应用程序通过操作显存来操作LCD,在LCD上显示字符、图片等信息

显存需要申请,驱动程序设置的显存和应用程序访问的显存要是一物理地址

framebuff是显存抽象后的一种设备,允许上层应用程序在图形模式,framebuff是lcd硬件中的HAL(硬件抽象层)

通过将framebuff机制将LCD抽象为dev/fbx,应用程序通过操作/dev/fbx来操作lcd

lcd 控制器通用(半导体原厂),我们只需要修改屏幕参数

驱动程序

不需要写

NXPLinux 内核默认已经开启了 LCD 驱动,因此我们是可以看到/dev/fb0 这样一个设备

在mxfb.c中

请求·

设备树的修改

查看屏幕原理图io引脚是否与官方相同,不同就要修改设备树

lcdif 就是屏幕的设备树节点(删去了节点中的屏幕复位引脚,)

 <&pinctrl_lcdif_dat   //24 根数据线配置项。
  &pinctrl_lcdif_ctrl>;     //4 根控制线配置项,包括 CLK、ENABLE、VSYNC 和HSYNC

具体io配置

//修改数据线io的配置电气属性(ox79改成0x49降低了驱动能力)


pinctrl_lcdif_dat: lcdifdatgrp {   //pinctrl_lcdif_dat标签,lcdifdatgrp是值
			fsl,pins = < 
				MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x49 //MX6UL_PAD_LCD_DATA00__LCDIF_DATA00将被配置为具有0x49值的一个属性
				MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x49
				MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x49
				MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x49
				MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x49
				MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x49
				MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x49
				MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x49
				MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x49
				MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x49
				MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x49
				MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x49
				MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x49
				MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x49
				MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x49
				MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x49
				MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x49
				MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x49
				MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x49
				MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x49
				MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x49
				MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x49
				MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x49
			>;
		};

//  控制线io的配置

		pinctrl_lcdif_ctrl: lcdifctrlgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_CLK__LCDIF_CLK	    0x49//这个引脚是用于连接液晶显示屏接口(LCDIF)和处理器时钟总线(Clock Bus)的引脚。0x49表示该引脚的电气属性和功能设置
				MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x49
				MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x49
				MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x49
			>;
		};

//背光io配置
		pinctrl_pwm1: pwm1grp //LCD 背光 PWM 引脚配置项。
		{
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
			>;
		};


LCD 屏幕节点修改,修改相应的属性值,换成我们所使用的 LCD 屏幕参数

&lcdif {
	pinctrl-names = "default";
	// 指定LCD 所使用的 IO控制器 信息,这里使用了两个io控制器:pinctrl_lcdif_dat 和 pinctrl_lcdif_ctrl
	pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl>;

//pinctrl_lcdif_dat为RGB LCD 的 24 根数据线配置项,24个io需要设置
//pinctrl_lcdif_ctrl4 根控制线配置项,4个io需要设置

	display = <&display0>; // 指定使用的显示屏设备为 display0
	status = "okay";       //设置设备节点的状态为 "okay",表示设备可以正常使用

	display0: display 、//display0表示具体的某个设备名称,display表示该节点是一个显示器节点
	{
		bits-per-pixel = <32>;    /*一个像素占用几个bit,使用rgb888*/
		bus-width = <24>;          /*总线宽度*/

		display-timings {
			native-mode = <&timing0>; /* 时序信息 */
			timing0: timing0 {
			clock-frequency = <51200000>;   /* LCD 像素时钟,单位 Hz */
			hactive = <1024>;               /* LCD X 轴像素个数 */
			vactive = <600>;                /* LCD Y 轴像素个数 */
			hfront-porch = <160>;           /* LCD hfp 参数 */
			hback-porch = <140>;            /* LCD hbp 参数 */
			hsync-len = <20>;               /* LCD hspw 参数 */
			vback-porch = <20>;             /* LCD vbp 参数 */
			vfront-porch = <12>;            /* LCD vfp 参数 */
			vsync-len = <3>;                /* LCD vspw 参数 */

			hsync-active = <0>;             /* hsync 数据线极性 */
			vsync-active = <0>;             /* vsync 数据线极性 */
			de-active = <1>;                /* de 数据线极性 */
			pixelclk-active = <1>;          /* clk 数据线先极性 */
			};
		};
	};
};


LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息

pinctrl_pwm1: pwm1grp //LCD 背光 PWM 引脚配置项。
		{
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0  //个引脚被用作PWM输出功能的引脚
			>;
		};
&pwm1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_pwm1>;
	status = "okay";
};


接下来我们依次来看一下上面这两个节点改如何去修改

最后在内核代码的第一个目录下运行命令,编译设备树

make dtbs

一些完善

设置 LCD 作为终端控制台

1、修改bootargs 参数

setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:
/home/xzj/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:
of

console=tty1  设置屏幕作为控制台

console=ttymxc0,115200,也就是设置串口也作为控制台

开了两个控制台界面

2、修改/etc/inittab 文件(开机启动)

tty1::askfirst:-/bin/sh   设置LCD为终端

LCD 自动关闭解决方法

1、在drivers/tty/vt/vt.c找到blankinterval 设置为0

2、编写一个软件阻止息屏,并且设置为开机自动启动

开/etc/init.d/rcS

写入

 cd /usr/bin
./lcd_always_on
  cd ..

设备树与驱动程序匹配

lcd控制器节点

 对应的驱动程序的compatible属性是fsl,imx6ull-lcdif

IIC驱动(传感器芯片是ap3216c)

linux内核基于驱动分离和分层的思想,将I2C驱动分成为

总线驱动:也就是i2c控制器驱动,也叫做适配器驱动(就是控制i2c设备的)

设备驱动:针对具体的i2设备而编写的驱动

两个重要的结构体(这些都在控制器驱动代码中)

i2c_adapter:i2c控制器结构体

在该结构体中有两个结构体

1、i2c——algorithm (控制器也i2c设备之间通信,使用下面的结构体进行具体的通信)

2、master_xfer  i2c适配结构体(控制器)的传输函数

具体i2c设备驱动两个重要结构体

i2c_client和i2c_driver 前者描述设备信息,后者描述驱动内容

i2c_client;一个设备对应一个i2c_client,每检测到一个i2c设备就会给分配一个i2c_client

i2c_client:表示I2C设备,不需要我们自己创建i2c_client,我们一般在设备树里面添加具体的I2C芯片,比如fxls8471,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client,

i2c_driver:类似platform驱动一样,有probe函数,匹配成功以后执行,同样还有设备树和非设备的匹配

i2c设备驱动框架,i2c­­_driver初始化与注册,需要II2C设备驱动编写人员编写的,IIC驱动程序就是初始化i2c_driver,然后向系统注册。注册使用i2c_register_driver、i2c_add_driver,如果注销i2c_driver使用i2c_del_driver

i2c_transform   会调用内核中的master_xfe用来进行数据传输

i2c_msgs    描述信息

msgs结构体包含的数据

地址

读写标志位

读取首地址

读取长度

添加I2C设备信息

在设备树中添加,I2C设备挂到那个I2C控制器下就在那个控制器下添加对应的节点

i2c设备的地址是七位

控制器的设备树代码在dtsi文件中,具体i2c设备的设备树文在imx.dts文件中

i2c设备数据收发流程

一般我们需要在probe函数中初始化i2c设备,就是必须能够对i2c设备寄存器进行读写操作

这里就是i2c_transfer函树,它会调用i2c_algorithm里面的master_xfer函数

设备树

通过原理图得知,ap3216c芯片接到了i2c1上,然后我们在设备树文件中找到i2c1控制器

这个节点,将我们ap3216c设备节点写在这个节点下

通常会使用一个或多个寄存器来控制一组引脚。这些寄存器可能包含位字段,每个位字段对应于特定的引脚功能、状态或配置选

该开发板设备树中好几个i2c控制器,i2c1 i2c2等

在dtsi设备树中找i2c控制器节点,然后通过compliate属性发现 控制器驱动在i2c-imx.c中

查看原理图发现,UART4_RXD这个io作为I2C1_SDA,UART4_TXD这个io作为I2C1_SCL

要设置某个引脚的作用需要设置对应的一个或者多个寄存器,我们不需要一个一个找,在与设备树同一目录下的imx6ull-pinfuns文件中找到

 左边的这个宏定义就代表要设置UART4_TXD这个io作为I2C1_SCL的所有寄存器(也就是上面的几个)

芯片地址看数据手册

设备树代码

&i2c1 {
  clock-frequency = <100000>;//12c频率
  pinctrl-names = "default";
  pinctrl-0 = <&pinctrl_i2c1>;//控制的io口
  status = "okay";
  
  ap3216c@1e {
  compatible = "alientek,ap3216c";//匹配属性
  reg = <0x1e>;//i2c设备的地址
  };
 };

关于io地址

设备树中将具体的数据地址宏定义成文字

设备驱动程序

makefile文件

i2c_client  设备客户端也就是i2c设备,msg是传输的消息,在其中有属性表示是读还是写

读要两次操作

写只要一次操作

#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"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: ap3216c.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: AP3216C驱动程序
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"//设备节点的名字

//设备结构体
struct ap3216c_dev {
	dev_t devid;			/* 设备号 主设备号+次设备号	 */
	struct cdev cdev;		/* cdev 字符设备对象,字符设备驱动的一种结构体类型	*/
	struct class *class;	/* 类 	用于创建设备节点,用于表示一组设备的类别。设备类通常包含相似或相关的设备,比如 USB 设备、网络设备	*/
	struct device *device;	/* 设备  用于创建设备节点,则是一个具体的设备对象,它表示一个硬件设备在系统中的实例	 */
	struct device_node	*nd; /* 设备节点,表示设备节点的一种结构体类型。设备节点是设备树中的一个节点。 */
	int major;			/* 主设备号 ,主设备号表示那种设备(字符、块)次设备号表示具体的设备*/
	void *private_data;	/* 私有数据 */
	unsigned short ir, als, ps;		/* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;//

/*
 * @description	: 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
//读取多个寄存器数据
/* 第一个消息用于发送要读取的寄存器地址,第二个则用于读取指定长度的数据 ,
通过使用 i2c_transfer() 函数来传输这两个消息,函数可以将读取到的数据保存到传入的 val 缓冲区中*/
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->private_data;

	/* 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);//第一个参数表示i2c适配器、第二个参数表示消息、第三个是发送消息的个数
	if(ret == 2) //成功发送或者结束到两个消息就会返回消息数目
	{
		ret = 0;
	} else {
		printk("i2c rd failed=%d reg=%06x len=%d
",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
//控制器向ap32168中写入多个数据
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];//定义一个长度为256的数组 b
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	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);//将msg数组进行传输
}

/*
 * @description	: 读取ap3216c指定寄存器值,读取一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
//通过i2c控制器向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;

#if 0
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	return i2c_smbus_read_byte_data(client, reg);
#endif
}

/*
 * @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
//通过i2c控制器向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);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。
 */
//读取三个光传感器的数据,一个数据两个字节到dev中
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];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */

static int ap3216c_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &ap3216cdev;

	/* 初始化AP3216C */
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	short data[3];
	long err = 0;

	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
	
	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;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @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,
};

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 1、构建设备号 */
	if (ap3216cdev.major) //如果主设备号存在
	{
		ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);//次设备号设备为0,合并成设备号
		//注册设备号,第二个参数表示注册一个,第三个参数是设备名字(也就是设备节点名字)
		register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
	} else {
		//注册设备号,次设备号从0开始
		alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
		//分配主设备号
		ap3216cdev.major = MAJOR(ap3216cdev.devid);
	}

	/* 2、注册设备 */
	//
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);//设备操作函数集 ap3216c_ops 和字符设备对象 ap3216cdev.cdev
	//该函数调用的作用是将字符设备对象 ap3216cdev.cdev 添加到内核中,并将其与分配的设备号 ap3216cdev.devid 关联起来。
	//这样操作系统就能够通过设备号来识别和操作这个字符设备了
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3、创建类 */
	//AP3216C_NAME 的设备类,并将其指针赋值给 ap3216cdev.class,以便后续的设备对象能够使用这个设备类
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	//如果创建出错,会报错
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);
	}

	/* 4、创建设备 */
	//,用于在内核中创建一个新的设备对象,并将其注册到设备模型中。
	//来创建一个名为 AP3216C_NAME 的设备对象,并将其指针赋值给 ap3216cdev.device。
	//该设备对象属于 ap3216cdev.class 所指定的设备类,其设备号为 ap3216cdev.devid,
	//此处的 NULL 参数表示设备对象的父设备为顶层总线设备,也就是没有父设备。创建成功后,该设备对象的指针被返回给 ap3216cdev.device
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		return PTR_ERR(ap3216cdev.device);
	}

	ap3216cdev.private_data = client;

	return 0;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ap3216c_remove(struct i2c_client *client)
{
	/* 删除设备 */
	cdev_del(&ap3216cdev.cdev);//从系统中删除一个字符设备
	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);// 是Linux内核中的一个函数,用于释放先前为字符设备保留的设备号区域

	/* 注销掉类和设备 */
	device_destroy(ap3216cdev.class, ap3216cdev.devid);//用于销毁之前创建的设备对象
	class_destroy(ap3216cdev.class);///销毁名为 ap3216cdev 的设备类对象,这可能会在设备不再需要时调用,以便释放系统资源
	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,//无设备树匹配函数
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ap3216c_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ap3216c_driver);//函数的返回值将被用于确定是否成功加载了驱动程序
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
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("zuozhongkai");



触摸屏驱动

触摸芯片是FT5426

触摸点上报时序:type B

示例代码 64.1.3.1 Type B 触摸点数据上报时序
1 ABS_MT_SLOT 0  上报触摸点id
2 ABS_MT_TRACKING_ID 45   关联一个对触摸点进行添加、删除的东西
3 ABS_MT_POSITION_X x[0]   上报x坐标
4 ABS_MT_POSITION_Y y[0]    上报y坐标
5 ABS_MT_SLOT 1         上报第二个点
6 ABS_MT_TRACKING_ID 46
7 ABS_MT_POSITION_X x[1]
8 ABS_MT_POSITION_Y y[1]
9 SYN_REPORT         上报结束

驱动框架

1、12c驱动框架

2、复位引脚和中断引脚、包括中断

3、input子系统框架

4、初始化触摸ic、中断、input子系统

5、在中断服务函数里面读取触摸坐标值,然后上报坐标

最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型
选择使用 Type A 还是 Type B 时序。大多数的设备都是 Type B 类型

3、应用项目

4、神经网络项目

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。