1、前言
開發板上有AP3216三合一整合型光感測器,看了看出廠SDK包中并未添加相關驅動。本次我們就一起來學習一下。
2、AP3216簡介
AP3216C 芯片集成了光強傳感器( ALS: Ambient Light Sensor),接近傳感器( PS: Proximity Sensor),還有一個紅外 LED( IR LED)。
這個芯片設計的用途是給手機之類的使用,比如:返回當前環境光強以便調整屏幕亮度;用戶接聽電話時,將手機放置在耳邊后,自動關閉屏幕避免用戶誤觸碰 。
3、IIC驅動簡介
Linux下IIC有兩種驅動方式:一種是按照字符設備驅動方式來驅動IIC;另一種是走Linux下IIC的框架。按照字符設備驅動的方式可以查閱這一篇文章:Linux IIC 字符設備 驅動例子。
這里我們淺淺地(真的很淺~~)了解學習一下第二種方式,因為找到的AP3216的驅動就是基于IIC驅動框架的,哈哈。
IIC驅動框架圖如
IIC驅動框架可大體分為兩大部分:
① I2C 總線驅動:SOC 的 I2C 控制器驅動,也叫做 I2C 適配器驅動。
② I2C 設備驅動:針對具體的 I2C 設備而編寫的驅動。
其中,訪問抽象層與I2C核心層數據I2C 總線驅動部分;driver驅動層屬于I2C設備驅動部分。
上面框圖對應的代碼調用層次圖如:
下面的AP3216驅動可以對照這張圖來看看。
4、AP3216實驗
我們使用設備樹來描述AP3216設備信息,首先我們沒有在設備樹中添加AP3216相關節點時,我們系統的I2C設備如:
添加I2C pinctrl,板子上AP3216接的是I2C1:
配置寄存器的值都設為0x4001b8b0,這一段是什么意思我們在什么是Pinctrl子系統及GPIO子系統?這篇筆記中也有寫到,就是幾個寄存器及其配置。
接下來在i2c1節點下添加ap3216節點:
編譯設備樹,傳到開發板上,重啟。此時我們系統的I2C設備有:
可見,新增的AP3216 I2C設備名就是我們設備樹里設置的。
下面編寫AP3216驅動(以下代碼來源于網絡):
ap3216.c:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include"ap3216creg.h" /*************************************************************** 文件名:ap3216c.c 描述:AP3216C驅動程序 ***************************************************************/ #defineAP3216C_CNT1 #defineAP3216C_NAME"ap3216c" structap3216c_dev{ dev_tdevid;/*設備號*/ structcdevcdev;/*cdev*/ structclass*class;/*類*/ structdevice*device;/*設備*/ structdevice_node*nd;/*設備節點*/ intmajor;/*主設備號*/ void*private_data;/*私有數據*/ unsignedshortir,als,ps;/*三個光傳感器數據*/ }; staticstructap3216c_devap3216cdev; /* *@description:從ap3216c讀取多個寄存器數據 *@param-dev:ap3216c設備 *@param-reg:要讀取的寄存器首地址 *@param-val:讀取到的數據 *@param-len:要讀取的數據長度 *@return:操作結果 */ staticintap3216c_read_regs(structap3216c_dev*dev,u8reg,void*val,intlen) { intret; structi2c_msgmsg[2]; structi2c_client*client=(structi2c_client*)dev->private_data; /*msg[0]為發送要讀取的首地址*/ msg[0].addr=client->addr;/*ap3216c地址*/ msg[0].flags=0;/*標記為發送數據*/ msg[0].buf=®/*讀取的首地址*/ 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("i2crdfailed=%dreg=%06xlen=%d\n",ret,reg,len); ret=-EREMOTEIO; } returnret; } /* *@description:向ap3216c多個寄存器寫入數據 *@param-dev:ap3216c設備 *@param-reg:要寫入的寄存器首地址 *@param-val:要寫入的數據緩沖區 *@param-len:要寫入的數據長度 *@return:操作結果 */ statics32ap3216c_write_regs(structap3216c_dev*dev,u8reg,u8*buf,u8len) { u8b[256]; structi2c_msgmsg; structi2c_client*client=(structi2c_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;/*要寫入的數據長度*/ returni2c_transfer(client->adapter,&msg,1); } /* *@description:讀取ap3216c指定寄存器值,讀取一個寄存器 *@param-dev:ap3216c設備 *@param-reg:要讀取的寄存器 *@return:讀取到的寄存器值 */ staticunsignedcharap3216c_read_reg(structap3216c_dev*dev,u8reg) { u8data=0; ap3216c_read_regs(dev,reg,&data,1); returndata; #if0 structi2c_client*client=(structi2c_client*)dev->private_data; returni2c_smbus_read_byte_data(client,reg); #endif } /* *@description:向ap3216c指定寄存器寫入指定的值,寫一個寄存器 *@param-dev:ap3216c設備 *@param-reg:要寫的寄存器 *@param-data:要寫入的值 *@return:無 */ staticvoidap3216c_write_reg(structap3216c_dev*dev,u8reg,u8data) { u8buf=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 :無。 */ voidap3216c_readdata(structap3216c_dev*dev) { unsignedchari=0; unsignedcharbuf[6]; /*循環讀取所有傳感器數據*/ for(i=0;i6;?i++)? ????{ ????????buf[i]?=?ap3216c_read_reg(dev,?AP3216C_IRDATALOW?+?i);? ????} ? ????if(buf[0]?&?0X80)??/*?IR_OF位為1,則數據無效?*/ ??dev->ir=0; else/*讀取IR傳感器的數據*/ dev->ir=((unsignedshort)buf[1]<2)?|?(buf[0]?&?0X03);???? ? ?dev->als=((unsignedshort)buf[3]<8)?|?buf[2];?/*?讀取ALS傳感器的數據?????*/?? ? ????if(buf[4]?&?0x40)?/*?IR_OF位為1,則數據無效????*/ ??dev->ps=0; else/*讀取PS傳感器的數據*/ dev->ps=((unsignedshort)(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*/ return0; } /* *@description:從設備讀取數據 *@param-filp:要打開的設備文件(文件描述符) *@param-buf:返回給用戶空間的數據緩沖區 *@param-cnt:要讀取的數據長度 *@param-offt:相對于文件首地址的偏移 *@return:讀取的字節數,如果為負值,表示讀取失敗 */ staticssize_tap3216c_read(structfile*filp,char__user*buf,size_tcnt,loff_t*off) { shortdata[3]; longerr=0; structap3216c_dev*dev=(structap3216c_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)); return0; } /* *@description:關閉/釋放設備 *@param-filp:要關閉的設備文件(文件描述符) *@return:0成功;其他失敗 */ staticintap3216c_release(structinode*inode,structfile*filp) { return0; } /*AP3216C操作函數*/ staticconststructfile_operationsap3216c_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,成功;其他負值,失敗 */ staticintap3216c_probe(structi2c_client*client,conststructi2c_device_id*id) { /*1、構建設備號*/ if(ap3216cdev.major){ ap3216cdev.devid=MKDEV(ap3216cdev.major,0); register_chrdev_region(ap3216cdev.devid,AP3216C_CNT,AP3216C_NAME); }else{ alloc_chrdev_region(&ap3216cdev.devid,0,AP3216C_CNT,AP3216C_NAME); ap3216cdev.major=MAJOR(ap3216cdev.devid); } /*2、注冊設備*/ cdev_init(&ap3216cdev.cdev,&ap3216c_ops); cdev_add(&ap3216cdev.cdev,ap3216cdev.devid,AP3216C_CNT); /*3、創建類*/ ap3216cdev.class=class_create(THIS_MODULE,AP3216C_NAME); if(IS_ERR(ap3216cdev.class)){ returnPTR_ERR(ap3216cdev.class); } /*4、創建設備*/ ap3216cdev.device=device_create(ap3216cdev.class,NULL,ap3216cdev.devid,NULL,AP3216C_NAME); if(IS_ERR(ap3216cdev.device)){ returnPTR_ERR(ap3216cdev.device); } ap3216cdev.private_data=client; return0; } /* *@description:i2c驅動的remove函數,移除i2c驅動的時候此函數會執行 *@param-client:i2c設備 *@return:0,成功;其他負值,失敗 */ staticintap3216c_remove(structi2c_client*client) { /*刪除設備*/ cdev_del(&ap3216cdev.cdev); unregister_chrdev_region(ap3216cdev.devid,AP3216C_CNT); /*注銷掉類和設備*/ device_destroy(ap3216cdev.class,ap3216cdev.devid); class_destroy(ap3216cdev.class); return0; } /*傳統匹配方式ID列表*/ staticconststructi2c_device_idap3216c_id[]={ {"iot,ap3216c",0}, {} }; /*設備樹匹配列表*/ staticconststructof_device_idap3216c_of_match[]={ {.compatible="iot,ap3216c"}, {/*Sentinel*/} }; /*i2c驅動結構體*/ staticstructi2c_driverap3216c_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:無 */ staticint__initap3216c_init(void) { intret=0; ret=i2c_add_driver(&ap3216c_driver); returnret; } /* *@description:驅動出口函數 *@param:無 *@return:無 */ staticvoid__exitap3216c_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("pjw");
驅動詳解可查閱注釋及配合上訴的I2C驅動框架的框圖及數據手冊理解。
ap3216creg.h:
#ifndefAP3216C_H #defineAP3216C_H /*************************************************************** 文件名:ap3216creg.h 描述:AP3216C寄存器地址描述頭文件 ***************************************************************/ #defineAP3216C_ADDR0X1E/*AP3216C器件地址*/ /*AP3316C寄存器*/ #defineAP3216C_SYSTEMCONG0x00/*配置寄存器*/ #defineAP3216C_INTSTATUS0X01/*中斷狀態寄存器*/ #defineAP3216C_INTCLEAR0X02/*中斷清除寄存器*/ #defineAP3216C_IRDATALOW0x0A/*IR數據低字節*/ #defineAP3216C_IRDATAHIGH0x0B/*IR數據高字節*/ #defineAP3216C_ALSDATALOW0x0C/*ALS數據低字節*/ #defineAP3216C_ALSDATAHIGH0X0D/*ALS數據高字節*/ #defineAP3216C_PSDATALOW0X0E/*PS數據低字節*/ #defineAP3216C_PSDATAHIGH0X0F/*PS數據高字節*/ #endif
ap3216應用:
ap3216cApp.c:
#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#include #include #include #include /*************************************************************** 文件名:ap3216cApp.c 描述: ap3216c設備測試APP。 使用方法:./ap3216cApp /dev/ap3216c ***************************************************************/ /* *@description:main主程序 *@param-argc:argv數組元素個數 *@param-argv:具體參數 *@return:0成功;其他失敗 */ intmain(intargc,char*argv[]) { intfd; char*filename; unsignedshortdatabuf[3]; unsignedshortir,als,ps; intret=0; if(argc!=2){ printf("ErrorUsage!\r\n"); return-1; } filename=argv[1]; fd=open(filename,O_RDWR); if(fd0)?{ ??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; }
編寫Makefile,從之前的文章=======拷貝過來修改:
KERN_DIR=/home/book/100ask_imx6ull-sdk/Linux-4.9.88 all: make-C$(KERN_DIR)M=`pwd`modules $(CROSS_COMPILE)gcc-oap3216cAppap3216cApp.c clean: make-C$(KERN_DIR)M=`pwd`modulesclean rm-rfmodules.order rm-fap3216cApp #參考內核源碼drivers/char/ipmi/Makefile #要想把a.c,b.c編譯成ab.ko,可以這樣指定: #ab-y:=a.ob.o #obj-m+=ab.o obj-m+=ap3216.o
編譯得到ap3216.ko及ap3216cApp,傳到板子上運行:
以上就是本次的實驗分享,如果文章對你有幫助,歡迎轉發,謝謝!
參考資料:
1、https://blog.csdn.net/weixin_34032792/article/details/85582751?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=1328690.367.16165120737124801&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control
2、https://blog.csdn.net/p1279030826/article/details/106459333
3、《嵌入式Linux應用開發完全手冊》
編輯:hfy
-
接近傳感器
+關注
關注
5文章
188瀏覽量
24412 -
I2C
+關注
關注
28文章
1481瀏覽量
123304 -
I2C總線
+關注
關注
8文章
388瀏覽量
60830 -
光感測器
+關注
關注
0文章
5瀏覽量
7131
發布評論請先 登錄
相關推薦
評論