Linux之I2C實驗是在AP3216C的基礎上實現的,進一步熟悉修改設備樹和編譯設備樹,以及學習如何編寫I2C驅動和APP測試程序。
1、AP3216C的原理圖
AP3216C集成了一個光強傳感器ALS,一個接近傳感器PS和一個紅外LED,為三合一的環境傳感器。它主要是給手機之類的產品使用,比如:返回“當前環境的光強”以便調整手機屏幕的亮度;當用戶接聽電話時,將手機放置在耳邊后,它會自動關閉屏幕,防止用戶錯誤觸碰。
AP3216C用到了I2C5接口,其中SCL連接PA11,SDA連接到PA12。如果用到AP3216C的中斷功能的話,則需要初始化AP_INT,該引腳連接到PE4。本驅動不需要使用中斷功能,因此只需要將PA11和PA12這個兩個IO復用為AF4功能即可。
2、修改設備樹
SOC廠商已經替我們編寫好了“I2C適配器驅動”,我們需要做的就是編寫具體的設備驅動。
2.1、打開設備樹頭文件“stm32mp15-pinctrl.dtsi”,找到“i2c5_pins_a”,內容如下:
i2c5_pins_a: i2c5-0 { /*在默認狀態下使用*/
pins {
pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */
<STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */
bias-disable;
drive-open-drain;
slew-rate = <0>;
};
};
i2c5_pins_sleep_a: i2c5-1 { /*在睡眠狀態下使用*/
pins {
pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */
<STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */
};
};
2.2、打開“stm32mp157d-atk.dts”,添加內容如下(注意:不是在根節點“/”下添加):
&i2c5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c5_pins_a>;
pinctrl-1 = <&i2c5_pins_sleep_a>;
status = "okay";
ap3216c@1e {
/*向i2c5添加ap3216c子節點,“@”后面的“1e”就是ap3216c的I2C器件地址*/
compatible = "zgq,ap3216c";/*compatible屬性值為"zgq,ap3216c"*/
reg = <0x1e>;/*reg屬性是設置ap3216c的器件地址0x1e*/
};
};
2.3、查看PA11和PA12是否被使用
打開設備樹頭文件“stm32mp15-pinctrl.dtsi”,查看PA11和PA12是否被使用了。
①點擊“編輯”,點擊“查找”,輸入“STM32_PINMUX('A', 11”,然后“回車”,沒有發現PA11被復用;
②點擊“編輯”,點擊“查找”,輸入“STM32_PINMUX('A',12”,然后“回車”,發現PA12被復用,屏蔽該語句,見下圖:
2.4、編譯設備樹
①在終端,輸入“make uImage dtbs LOADADDR=0XC2000040 -j8回車”,執行編譯“Image”和“dtbs”,并指定裝載的起始地址為0XC2000040,j8表示指定采用8線程執行。“make dtbs”,用來指定編譯設備樹。見下圖:
②輸入“ls arch/arm/boot/uImage?-l”
查看是否生成了新的“uImage”文件
③輸入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb?-l”
查看是否生成了新的“stm32mp157d-atk.dtb”文件
4)、拷貝輸出的文件:
①輸入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回車”,執行文件拷貝,準備燒錄到EMMC;
②輸入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb?/home/zgq/linux/atk-mp1/linux/bootfs/ -f回車”,執行文件拷貝,準備燒錄到EMMC
③輸入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回車”,執行文件拷貝,準備從tftp下載;
④輸入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb?/home/zgq/linux/tftpboot/ -f回車”,執行文件拷貝,準備從tftp下載;
⑤輸入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回車”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目錄下的所有文件和文件夾
⑥輸入“ls -l /home/zgq/linux/tftpboot/回車”,查看“/home/zgq/linux/tftpboot/”目錄下的所有文件和文件夾
⑦輸入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回車”
給“stm32mp157d-atk.dtb”文件賦予可執行權限
⑧輸入“chmod 777 /home/zgq/linux/tftpboot/uImage回車”?,給“uImage”文件賦予可執行權限
⑨輸入“ls /home/zgq/linux/tftpboot/ -l回車”,查看“/home/zgq/linux/tftpboot/”目錄下的所有文件和文件夾
3、編寫AP3216C驅動和APP
3.1、創建“/home/zgq/linux/Linux_Drivers/AP3216C/”目錄
1)、打開終端,輸入“cd /home/zgq/linux/Linux_Drivers/回車”,切換到“/home/zgq/linux/Linux_Drivers/”目錄;
2)、輸入“ls回車”,列舉“/home/zgq/linux/Linux_Drivers/”目錄的所有文件和文件夾;
3)、輸入“mkdir AP3216C回車”,創建“/home/zgq/linux/Linux_Drivers/input_key/”目錄;
4)、輸入“ls回車”,列舉“/home/zgq/linux/Linux_Drivers/”目錄的所有文件和文件夾;
3.2、編寫AP3216C驅動程序之頭文件“AP3216C.h”
1)、打開虛擬機中的VSCode,點擊“文件”,點擊“打開文件夾”,然后點擊“zgg,linux,Linux_Drivers,AP3216C”,如下圖:
2)、點擊上圖中的確定,然后點擊“文件”,點擊“新建文件”,點擊“文件”,點擊“另存為”,輸入“AP3216C.h”。
3)、點擊“保存”。輸入下面的內容:
#ifndef?AP3216C_H
#define?AP3216C_H
/************************************************
?* 描述 : AP3216C寄存器地址描述頭文件
?* **********************************************/
#define?AP3216C_ADDR 0X1E /* AP3216C器件地址 */
/* AP3316C寄存器 */
#define?AP3216C_SYSTEMCONG ?0x00 /* 配置寄存器 */
#define?AP3216C_INTSTATUS ???0X01 /* 中斷狀態寄存器 */
#define?AP3216C_INTCLEAR ????0X02 /* 中斷清除寄存器 */
#define?AP3216C_IRDATALOW ??0x0A /*紅外LED的IR數據低字節 */
#define?AP3216C_IRDATAHIGH ??0x0B /*紅外LED的IR數據高字節 */
#define?AP3216C_ALSDATALOW ?0x0C /*光強傳感器ALS數據低字節 */
#define?AP3216C_ALSDATAHIGH ?0X0D /*光強傳感器ALS數據高字節 */
#define?AP3216C_PSDATALOW ??0X0E /*接近傳感器PS數據低字節 */
#define?AP3216C_PSDATAHIGH ?0X0F /*接近傳感器PS數據高字節 */
#endif
3.2、編寫AP3216C驅動程序之頭文件“AP3216C.c”
1)、打開虛擬機中的VSCode,點擊“文件”,點擊“新建文件”,點擊“文件”,點擊“另存為”,輸入“AP3216C.c”。
2)、點擊“保存”。輸入下面的內容:
#include?<linux/types.h>
//數據類型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include?<linux/kernel.h>//必須要包含的頭文件
#include?<linux/init.h>//必須要包含的頭文件
#include?<linux/delay.h>
//Linux內核中用到的延時函數
//使能ndelay(),udelay(),mdelay()
#include?<linux/ide.h>//使能copy_from_user(),copy_to_user()
#include?<linux/module.h>//使能AP3216C_init(),AP3216C_exit()
#include?<linux/errno.h>
#include?<linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include?<linux/cdev.h>//使能cdev結構
#include?<linux/device.h>//使能class結構和device結構
#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?"AP3216C.h"/*頭文件名*/
/*
沒有定義一個全局變量,那是因為linux內核不推薦使用;
全局變量要使用內存的就用devm_kzalloc()之類的函數去申請空間。
*/
#define?AP3216C_NAME "ap3216c"/*設備名字,APP程序要對它進行操作*/
#define?AP3216C_CNT 1 //設備數量
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;
????/* 三個光傳感器數據 */
????/*ir用來存儲AP3216C的紅外LED的IR數據*/
????/*als用來存儲AP3216C的光強傳感器ALS數據*/
????/*ps用來存儲AP3216C的接近傳感器PS數據*/
};
/*
函數功能: 從AP3216C讀取多個寄存器數據,注意:AP3216C不支持連續讀取多個字節
參數dev : ap3216c設備
參數reg : 要讀取的寄存器首地址
參數val : 讀取到的數據
參數len : 要讀取的數據長度
返回值: 操作結果
*/
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 = ? ?????????/*讀取的寄存器首地址*/
????msg[0].len = 1; ????????????/*reg長度*/
????
????/* msg[1]讀取數據 */
????msg[1].addr = client->addr; /*AP3216C地址*/
????msg[1].flags = I2C_M_RD; ???/*標記為讀取數據*/
????msg[1].buf = val; ??????????/*讀取數據緩沖區,pointer to msg data*/
????msg[1].len = len; ??????????/*要讀取的數據長度,msg length*/
????
????ret = i2c_transfer(client->adapter, msg, 2);
????/*先發送“AP3216C地址“和發送“讀取的寄存器首地址“,接著讀取“該寄存器的數據“*/
????/*因為是先寫后讀,因此消息有2個*/
????if(ret == 2)
????{
????????ret = 0;
????}
????else
????{
????????printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
????????ret = -EREMOTEIO;
????}
????return?ret;
}
/*
函數功能: 向AP3216C多個寄存器寫入數據,注意:AP3216C不支持連續寫多個字節
參數dev: ap3216c設備
參數reg: 要寫入的寄存器首地址
參數val: 要寫入的數據緩沖區
參數len: 要寫入的數據長度
返回值: 操作結果
*/
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);
????/*將首地址為buf中的數據拷貝到首地址為&b[1]的存儲區中,字節數量為len*/
????
????msg.addr = client->addr; /*AP3216C地址*/
????msg.flags = 0; /*標記為寫數據*/
????
????msg.buf = b; ??????/*要寫入的數據緩沖區,pointer to msg data*/
????msg.len = len + 1; /*要寫入的數據長度,因為reg占1個字節,所以這里要加1*/
????
????return i2c_transfer(client->adapter, &msg, 1);
????/*發送“AP3216C地址“,發送“要寫入數據的寄存器首地址“,接著寫入“該寄存器的數據“*/
????/*因為只有“一條寫消息“,因此消息數量為1*/
}
/*
函數功能: 讀取AP3216C指定寄存器值,讀取一個寄存器,注意:AP3216C不支持連續讀取多個字節
參數dev: ap3216c設備
參數reg: 要讀取的寄存器
返回值: 讀取到的寄存器值
*/
static unsigned char?ap3216c_read_reg(struct?ap3216c_dev *dev, u8 reg)
{
????u8 data = 0;
????
????ap3216c_read_regs(dev, reg, &data, 1);
????/*從AP3216C讀取多個寄存器數據,注意:AP3216C不支持連續讀取多個字節*/
????return?data;
}
/*
函數功能: 向ap3216c指定寄存器寫入指定的值,寫一個寄存器,注意:AP3216C不支持連續寫多個字節
參數dev: ap3216c設備
參數reg: 要寫的寄存器
參數data: 要寫入的值
返回值: 無
*/
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多個寄存器寫入數據,由于AP3216C不支持連續寫多個字節,因此這里只寫入1個字節*/
}
/*
函數功能: 讀取AP3216C的原始數據值,包括光強傳感器ALS,接近傳感器PS和紅外LED的IR
注意!如果同時打開ALS,IR+PS兩次數據讀取的時間間隔要大于112.5ms
參數ir : ir數據
參數ps : ps數據
參數ps : als數據
返回值: 無。
*/
void?ap3216c_readdata(struct?ap3216c_dev *dev)
{
????unsigned char?i =0;
????unsigned char?buf[6];
????
????/*循環讀取所有傳感器數據*/
????//當i=0時,讀取“紅外LED的IR數據低字節“
????//當i=1時,讀取“紅外LED的IR數據高字節“
????//當i=2時,讀取“光強傳感器ALS數據低字節“
????//當i=3時,讀取“光強傳感器ALS數據高字節“
????//當i=4時,讀取“接近傳感器PS數據低字節“
????//當i=5時,讀取“接近傳感器PS數據高字節“
????for(i = 0; i < 6; i++)
????{
????????buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
????????/*讀取AP3216C指定寄存器值,讀取一個寄存器*/
????}
????
????if(buf[0] & 0X80) /* IR_OF位為1,則數據無效 */
??????dev->ir = 0;
????else?
????dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
????/*保存"紅外LED的IR傳感器的數據"*/
????
????dev->als = ((unsigned short)buf[3] << 8) | buf[2];
????/*保存光強傳感器ALS數據*/
????
????if(buf[4] & 0x40) /* IR_OF位為1,則數據無效 */
??????dev->ps = 0;
????else?
??????dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
??????/*保存"PS傳感器的數據"*/
}
/*
函數功能: 打開設備
參數inode : 傳遞給驅動的inode
參數filp : 設備文件,file結構體有個叫做private_data的成員變量
* 一般在open的時候將private_data指向設備結構體。
返回值: 0 成功;其他 失敗
*/
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_dev結構中的cdev成員和這個成員的指針值cdev,計算出ap3216c_dev型結構變量的首地址*/ ??
????/* 初始化AP3216C */
ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
//將0x04寫入AP3216C的配置寄存器
????mdelay(50);
ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);
//將0x03寫入AP3216C的配置寄存器
????return?0;
}
????
/*
函數功能: 從設備讀取數據
參數filp : 要打開的設備文件(文件描述符)
參數buf : 返回給用戶空間的數據緩沖區
參數cnt : 要讀取的數據長度
參數offt : 相對于文件首地址的偏移
返回值: 讀取的字節數,如果為負值,表示讀取失敗
*/
/* 從設備讀取數據,保存到首地址為buf的數據塊中,長度為cnt個字節 */
//file結構指針變量flip表示要打開的設備文件
//buf表示用戶數據塊的首地址
//cnt表示用戶數據的長度,單位為字節
//loff_t結構指針變量off表示“相對于文件首地址的偏移”
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_dev結構中的cdev成員和這個成員的指針值cdev,計算出ap3216c_dev型結構變量的首地址*/
????
????ap3216c_readdata(dev);/*讀取AP3216C的原始數據值*/
????
????data[0] = dev->ir;//保存“紅外LED的IR數據“
????data[1] = dev->als;//保存“光強傳感器ALS的數據“
????data[2] = dev->ps;//保存“接近傳感器PS的的數據“
????err = copy_to_user(buf, data, sizeof(data));
????/*將data[]中數據拷貝到buf[]中*/
????return?0;
}
/*
函數功能: 關閉/釋放設備
參數filp : 要關閉的設備文件(文件描述符)
返回值: 0 成功;其他 失敗
*/
static int?ap3216c_release(struct?inode *inode, struct?file *filp)
{
????return?0;
}
/* AP3216C操作函數 */
/*聲明file_operations結構變量ap3216c_ops*/
/*它是指向設備的操作函數集合變量*/
static const struct?file_operations ap3216c_ops = {
????.owner = THIS_MODULE,
????/*表示該文件的操作結構體所屬的模塊是當前的模塊,即這個模塊屬于內核*/
????.open = ap3216c_open,
????.read = ap3216c_read,
????.release = ap3216c_release,
};
/*
函數功能: i2c驅動的probe函數,當驅動與設備匹配以后,此函數就會執行
參數client : i2c設備
參數id : i2c設備ID
返回值: 0,成功;其他負值,失敗
*/
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);
????//注冊字符設備驅動
????//ap3216cdev->devid:保存申請到的設備號
????//baseminor=0:次設備號的起始地址
????//count=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;
????/*使用THIS_MODULE將owner指針指向當前這個模塊*/
????cdev_init(&ap3216cdev->cdev, &ap3216c_ops);
????//初始化字符設備
????//ap3216cdev->cdev是等待初始化的結構體變量
????//ap3216c_ops就是字符設備文件操作函數集合,就是AP3216C操作函數
???
????/* 3、添加一個cdev */
????ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid, AP3216C_CNT);
????//添加字符設備
????//&ap3216cdev->cdev表示指向要添加的字符設備,即字符設備結構cdev變量
????//ap3216cdev->devid表示設備號
????//count=AP3216C_CNT表示需要添加的設備數量
????if(ret < 0) {
????????goto?del_unregister;
????}
????
????/* 4、創建類 */
????ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);
????//創建類
????/*使用THIS_MODULE將owner指針指向當前這個模塊*/
????//使用AP3216C_NAME作為“類名字“
????//返回值是指向結構體class的指針,也就是創建的類
????if?(IS_ERR(ap3216cdev->class)) {
????????goto?del_cdev;
????}
????
????/* 5、創建設備 */
????ap3216cdev->device = device_create(ap3216cdev->class, NULL, ap3216cdev->devid, NULL, AP3216C_NAME);
????//創建設備
????//設備要創建在ap3216cdev->class類下面
????//NULL表示沒有父設備
????//ap3216cdev->devid是設備號;
????//參數drvdata=NULL,設備沒有使用數據
????//AP3216C_NAME是設備名字
????//如果設置fmt=AP3216C_NAMEE的話,就會生成/dev/AP3216C_NAME設備文件。
????//返回值就是創建好的設備。
????if?(IS_ERR(ap3216cdev->device)) {
????????goto?destroy_class;
????}
????ap3216cdev->client = client;
????/*保存ap3216cdev結構體*/
????i2c_set_clientdata(client,ap3216cdev);
????/*將ap3216cdev變量的地址綁定client*/
????/*就可以通過i2c_get_clientdata(client)獲取ap3216cdev變量指針*/
????
????return 0;
????destroy_class:
????device_destroy(ap3216cdev->class, ap3216cdev->devid);
????/*注銷設備,刪除創建的設備*/
????/*參數ap3216cdev->class是設備所處的類,ap3216cdev->devid是設備號*/
????del_cdev:
????cdev_del(&ap3216cdev->cdev);
????//刪除字符設備
????/*&ap3216cdev->cdev表示指向需要刪除的字符設備,即字符設備結構ap3216cdev->cdev變量*/
????del_unregister:
????unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);
???/*注銷設備號,釋放設備號 */
???//ap3216cdev->devid:需要釋放的設備號
???//AP3216C_CNT:需要釋放的次設備號數量;
????return?-EIO;
}
/*
函數功能: i2c驅動的remove函數,移除i2c驅動的時候此函數會執行
參數client : i2c設備
返回值: 0,成功;其他負值,失敗
*/
static int?ap3216c_remove(struct?i2c_client *client)
{
????struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);
????/*通過i2c_get_clientdata(client)獲取ap3216cdev變量指針*/
????/* 注銷字符設備驅動 */
????/* 1、刪除cdev */
????cdev_del(&ap3216cdev->cdev);
????//刪除字符設備
????/*&ap3216cdev->cdev表示指向需要刪除的字符設備,即字符設備結構ap3216cdev->cdev變量*/
????/* 2、注銷設備號 */
????unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);
????/*注銷設備號,釋放設備號 */
????/*ap3216cdev->devid:需要釋放的設備號*/
????/*AP3216C_CNT:需要釋放的次設備號數量*/
????/* 3、注銷設備 */
????device_destroy(ap3216cdev->class, ap3216cdev->devid);
????/*注銷設備,刪除創建的設備*/
????/*參數ap3216cdev->class是設備所處的類,ap3216cdev->devid是設備號*/
????/* 4、注銷類 */
????class_destroy(ap3216cdev->class);
????/*刪除類,ap3216cdev->class就是要刪除的類*/
????return?0;
}
/*傳統匹配方式ID列表*/
static?const struct i2c_device_id ap3216c_id[] = {
????{"zgq,ap3216c", 0},
????{}
};
/*設備樹匹配列表*/
static const struct?of_device_id ap3216c_of_match[] = {
????{ .compatible = "zgq,ap3216c" },
????/*在stm32mp157d-atk.dts設備樹文件中,定義“compatible = "zgq,ap3216c”*/
????{ /*這是一個空元素,在編寫of_device_id時最后一個元素一定要為空*/
??????/* Sentinel */?
????}
};
/*初始化i2c_driver結構變量ap3216c_driver,i2c驅動結構體 */
static struct?i2c_driver ap3216c_driver = {
????.probe = ap3216c_probe,
????/*platform的probe函數為ap3216c_probe()*/
????.remove = ap3216c_remove,
????/*platform的remove函數為ap3216c_remove()*/
????.driver = {
????????.owner = THIS_MODULE,
????????/*表示該文件的操作結構體所屬的模塊是當前的模塊,即這個模塊屬于I2C內核*/
????????.name = "ap3216c",/* 驅動名字,用于和設備匹配 */
????????.of_match_table = ap3216c_of_match,/*設備樹匹配表*/
????},
????.id_table = ap3216c_id,/*傳統匹配方式ID列表*/
};
//函數功能:驅動入口函數初始化
static int?__init ap3216c_init(void)
{
????int?ret = 0;
????
????ret = i2c_add_driver(&ap3216c_driver);
????//根據i2c_driver結構變量ap3216c_driver,向Linux內核注冊一個platform驅動
????return?ret;
}
//函數功能:驅動出口函數初始化
static void?__exit ap3216c_exit(void)
{
????i2c_del_driver(&ap3216c_driver);
????//根據i2c_driver結構變量ap3216c_driver,卸載一個platform驅動
}
module_init(ap3216c_init);//聲明ap3216c_init()為驅動入口函數
module_exit(ap3216c_exit);//聲明ap3216c_exit()為驅動出口函數
MODULE_LICENSE("GPL");//LICENSE采用“GPL協議”
MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_INFO(intree, "Y");//去除顯示“loading out-of-tree module taints kernel.”
3.2、編寫AP3216C驅動程序之頭文件“AP3216C_APP.c”
1)、打開虛擬機中的VSCode,點擊“文件”,點擊“新建文件”,點擊“文件”,點擊“另存為”,輸入“AP3216C_APP.c”。見下圖:
2)、點擊“保存”。輸入下面的內容:
#include?"stdio.h"
#include?"unistd.h"
//Linux系統編程下用到的延時函數
//使能usleep(),sleep()
//#include <delay.h>
//Linux內核中用到的延時函數
//使能ndelay(),udelay(),mdelay()
#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>
//APP運行命令: ./AP3216C_APP /dev/AP3216C
//argv[]是指向輸入參數./AP3216C_APP /dev/AP3216C
/*
參數argc: argc[]數組元素個數
參數argv[]:是一個指針數組
返回值: 0 成功;其他 失敗
*/
int?main(int?argc, char?*argv[])
{
??int?fd;
??char?*filename;
??unsigned short?data[3];
??unsigned short?ir, als, ps;
??int?ret = 0;
??if?(argc != 2)
??{
????printf("Error Usage!\r\n");
????return?-1;
??}
????
????filename = argv[1];//argv[1]指向字符串“/dev/AP3216C"
????fd = open(filename, O_RDWR);
????//打開AP3216C驅動
????//如果打開“/dev/ap3216c”文件成功,則fd為“文件描述符”
????//fd=0表示標準輸入流; fd=1表示標準輸出流;fd=2表示錯誤輸出流;
????if(fd < 0)
????{
????????printf("can't open file %s\r\n", filename);
????????return?-1;
????}
????
????while?(1)
????{
????????ret = read(fd, data, sizeof(data));/* 讀取數據 */
????????if(ret == 0)
????????{ /* 數據讀取成功 */
??????????ir = data[0]; /* 紅外LED的ir傳感器數據 */
??????????als = data[1]; /* 光強傳感器als傳感器數據 */
??????????ps = data[2]; /* 接近傳感器ps傳感器數據 */
??????????printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
????????}
??????????usleep(200000);
??????????//延時200000微秒,即200毫秒,不會占用cpu資源
????}
????close(fd); /* 關閉文件,關閉設備 */
????//fd表示要關閉的“文件描述符”
????//返回值等于0表示關閉成功
????//返回值小于0表示關閉失敗
????return?0;
}
3.3、新建Makefile
1)、打開虛擬機中的VSCode,點擊“文件”,點擊“新建文件”,點擊“文件”,點擊“另存為”,輸入“Makefile”。
2)、點擊“保存”。輸入下面的內容:
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”將其后面的字符串賦值給KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”獲取當前打開的路徑
#使用“$(變量名)”引用“變量的值”
MyAPP := AP3216C_APP
AP3216C_drv-objs = AP3216C.o
obj-m := AP3216C_drv.o
CC := arm-none-linux-gnueabihf-gcc
drv:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
app:
$(CC) ?$(MyAPP).c ?-o $(MyAPP)
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm $(MyAPP)
install:
sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f
3.4、添加“c_cpp_properties.json”
按下“Ctrl+Shift+P”,打開VSCode控制臺,然后輸入“C/C++:Edit Configurations(JSON)”,打開以后會自動在“.vscode ”目錄下生成一個名為“c_cpp_properties.json” 的文件。
修改c_cpp_properties.json內容如下所示:
{
????"configurations": [
????????{
????????????"name": "Linux",
????????????"includePath": [
????????????????"${workspaceFolder}/**",
????????????????"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",
????????????????"/home/zgq/linux/Linux_Drivers/AP3216C",
????????????????"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",
????????????????"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",
????????????????"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"
????????????],
????????????"defines": [],
????????????"compilerPath": "/usr/bin/gcc",
????????????"cStandard": "gnu11",
????????????"cppStandard": "gnu++14",
????????????"intelliSenseMode": "gcc-x64"
????????}
????],
????"version": 4
}
3.5、編譯設備驅動和APP
輸入“make clean回車”
輸入“make drv回車”
輸入“make app回車”
輸入“make install回車”
輸入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回車”查看是存在“AP3216C_APP和AP3216C_drv.ko”
3.6、通電測試
1)、查看“/sys/bus/i2c/devices”目錄下存放著所有I2C設備,如果設備樹修改正確的話,會在“/sys/bus/i2c/devices”目錄下看到一個名為“0-001e”的子目錄
①用新的umage和stm32mpl57d-atk.dtb啟動開發板。
②輸入“root回車”。
③輸入“cd /sys/bus/i2c/devices/回車”切換到“/sys/bus/i2c/devices/”目錄。
④輸入“ls回車”
⑤輸入“cd 0-001e回車”切換到“/sys/bus/i2c/devices/0-001e/”目錄。
⑥輸入“ls回車”
⑦輸入“cat?name” 查看“name”文件,這個name文件保存著此設備名字ap3216c;
2)、測試
①啟動開發板,從網絡下載程序
②輸入“root”
③輸入“cd /lib/modules/5.4.31/”
在nfs掛載中,切換到“/lib/modules/5.4.31/”目錄,
注意:“lib/modules/5.4.31/”在虛擬機中是位于“/home/zgq/linux/nfs/rootfs/”目錄下,但在開發板中,卻是位于根目錄中。
④輸入“ls -l”
⑤輸入“depmod”,驅動在第一次執行時,需要運行“depmod”
⑥輸入“lsmod”查看有哪些驅動在工作;
⑦輸入“modprobe AP3216C_drv.ko”,加載“AP3216C_drv.ko”模塊
⑧輸入“lsmod”查看有哪些驅動在工作;
輸入“cd /dev回車”切換到“dev”目錄;
輸入“ls”查看是否有“ap3216c”
⑨輸入“cd /lib/modules/5.4.31/”
⑩輸入“./AP3216C_APP?/dev/ap3216c回車”