Linux第104步_基于AP3216C之I2C實驗

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 /*紅外LEDIR數據低字節 */

#define?AP3216C_IRDATAHIGH ??0x0B /*紅外LEDIR數據高字節 */

#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回車

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67755.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67755.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67755.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于單片機的盲人智能水杯系統(論文+源碼)

1 總體方案設計 本次基于單片機的盲人智能水杯設計&#xff0c;采用的是DS18B20實現杯中水溫的檢測&#xff0c;采用HX711及應力片實現杯中水里的檢測&#xff0c;采用DS1302實現時鐘計時功能&#xff0c;采用TTS語音模塊實現語音播報的功能&#xff0c;并結合STC89C52單片機作…

高清種子資源獲取指南 | ??@seedlinkbot

在如今的數字時代&#xff0c;高清影視、音樂、游戲等資源的獲取方式不斷豐富。對于追求高質量資源的用戶而言&#xff0c;一個高效的資源分享平臺至關重要。而 ??seedlinkbot 正是這樣一個便捷的資源獲取工具&#xff0c;為用戶提供高質量的種子資源索引和下載信息。 1. ??…

[paddle] 矩陣相關的指標

行列式 det 行列式定義參考 d e t ( A ) ∑ i 1 , i 2 , ? , i n ( ? 1 ) σ ( i 1 , ? , i n ) a 1 , i 1 a 2 , i 2 , ? , a n , i n det(A) \sum_{i_1,i_2,\cdots,i_n } (-1)^{\sigma(i_1,\cdots,i_n)} a_{1,i_1}a_{2,i_2},\cdots, a_{n,i_n} det(A)i1?,i2?,?,in?…

Spring Boot項目如何使用MyBatis實現分頁查詢

寫在前面&#xff1a;大家好&#xff01;我是晴空?。如果博客中有不足或者的錯誤的地方歡迎在評論區或者私信我指正&#xff0c;感謝大家的不吝賜教。我的唯一博客更新地址是&#xff1a;https://ac-fun.blog.csdn.net/。非常感謝大家的支持。一起加油&#xff0c;沖鴨&#x…

【論文筆記】Fast3R:前向并行muti-view重建方法

眾所周知&#xff0c;DUSt3R只適合做稀疏視角重建&#xff0c;與sapnn3r的目的類似&#xff0c;這篇文章以并行的方法&#xff0c;擴展了DUSt3R在多視圖重建中的能力。 abstract 多視角三維重建仍然是計算機視覺領域的核心挑戰&#xff0c;尤其是在需要跨不同視角實現精確且可…

本地部署DeepSeek教程(Mac版本)

第一步、下載 Ollama 官網地址&#xff1a;Ollama 點擊 Download 下載 我這里是 macOS 環境 以 macOS 環境為主 下載完成后是一個壓縮包&#xff0c;雙擊解壓之后移到應用程序&#xff1a; 打開后會提示你到命令行中運行一下命令&#xff0c;附上截圖&#xff1a; 若遇…

deepseek本地部署會遇到哪些坑

在本地部署DeepSeek(或其他類似AI模型)時,可能會遇到以下常見問題及解決方案: 1. 硬件資源不足 問題表現: GPU不兼容(如型號過舊)、顯存不足(OOM錯誤)或CPU模式性能極低。解決方案: 確認GPU支持CUDA,檢查顯存需求(如至少16GB顯存)。使用nvidia-smi監控顯存,通過降…

微機原理與接口技術期末大作業——4位搶答器仿真

在微機原理與接口技術的學習旅程中&#xff0c;期末大作業成為了檢驗知識掌握程度與實踐能力的關鍵環節。本次我選擇設計并仿真一個 4 位搶答器系統&#xff0c;通過這個項目&#xff0c;深入探索 8086CPU 及其接口技術的實際應用。附完整壓縮包下載。 一、系統設計思路 &…

解決國內服務器 npm install 卡住的問題

在使用國內云服務器時&#xff0c;經常會遇到 npm install 命令執行卡住的情況。本文將分享一個典型案例以及常見的解決方案。 問題描述 在執行以下命令時&#xff1a; mkdir test-npm cd test-npm npm init -y npm install lodash --verbose安裝過程會卡在這個狀態&#xf…

【Redis】Redis 經典面試題解析:深入理解 Redis 的核心概念與應用

Redis 是一個高性能的鍵值存儲系統&#xff0c;廣泛應用于緩存、消息隊列、排行榜等場景。在面試中&#xff0c;Redis 是一個高頻話題&#xff0c;尤其是其核心概念、數據結構、持久化機制和高可用性方案。 1. Redis 是什么&#xff1f;它的主要特點是什么&#xff1f; 答案&a…

昆侖萬維Java開發面試題及參考答案

進程和線程的區別是什么? 進程和線程都是操作系統中非常重要的概念,它們在多個方面存在顯著的區別。 從定義上看,進程是操作系統進行資源分配和調度的基本單位。每個進程都有自己獨立的內存空間,包括代碼段、數據段、堆棧段等。例如,當你在電腦上同時打開瀏覽器和音樂播放…

Visual Studio Code應用本地部署的deepseek

1.打開Visual Studio Code&#xff0c;在插件中搜索continue&#xff0c;安裝插件。 2.添加新的大語言模型&#xff0c;我們選擇ollama. 3.直接點connect&#xff0c;會鏈接本地下載好的deepseek模型。 參看上篇文章&#xff1a;deepseek本地部署-CSDN博客 4.輸入需求生成可用…

DeepSeek技術深度解析:從不同技術角度的全面探討

DeepSeek技術深度解析&#xff1a;從不同技術角度的全面探討 引言 DeepSeek是一個集成了多種先進技術的平臺&#xff0c;旨在通過深度學習和其他前沿技術來解決復雜的問題。本文將從算法、架構、數據處理以及應用等不同技術角度對DeepSeek進行詳細分析。 一、算法層面 深度學…

SpringBoot 整合 Mybatis:注解版

第一章&#xff1a;注解版 導入配置&#xff1a; <groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version> </dependency> 步驟&#xff1a; 配置數據源見 Druid…

[Linux]如何將腳本(shell script)轉換到系統管理服務器(systemd service)來運行?

[InfluxDB]Monitor Tem. and Volt of RaspberryPi and Send Message by Line Notify 在Linux中&#xff0c;shell腳本(shell script)常用於運行各種自動化的流程&#xff0c;包含API串接&#xff0c;設置和啟動應用服務等等&#xff0c;腳本語法也相對易學易讀&#xff0c;因此…

2025年最新在線模型轉換工具優化模型ncnn,mnn,tengine,onnx

文章目錄 引言最新網址地點一、模型轉換1. 框架轉換全景圖2. 安全的模型轉換3. 網站全景圖 二、轉換說明三、模型轉換流程圖四、感謝 引言 在yolov5&#xff0c;yolov8&#xff0c;yolov11等等模型轉換的領域中&#xff0c;時間成本常常是開發者頭疼的問題。最近發現一個超棒的…

理解知識蒸餾中的散度損失函數(KLDivergence/kldivloss )-以DeepSeek為例

1. 知識蒸餾簡介 什么是知識蒸餾&#xff1f; 知識蒸餾&#xff08;Knowledge Distillation&#xff09;是一種模型壓縮技術&#xff0c;目標是讓一個較小的模型&#xff08;學生模型&#xff0c;Student Model&#xff09;學習一個較大、性能更優的模型&#xff08;教師模型…

Electron使用WebAassembly實現CRC-8 MAXIM校驗

Electron使用WebAssembly實現CRC-8 MAXIM校驗 將C/C語言代碼&#xff0c;經由WebAssembly編譯為庫函數&#xff0c;可以在JS語言環境進行調用。這里介紹在Electron工具環境使用WebAssembly調用CRC-8 MAXIM格式校驗的方式。 CRC-8 MAXIM校驗函數WebAssebly源文件 C語言實現CR…

Vue3.0實戰:大數據平臺可視化

文章目錄 創建vue3.0項目項目初始化項目分辨率響應式設置項目頂部信息條創建頁面主體創建全局引入echarts和axios后臺接口創建express銷售總量圖實現完整項目下載項目任何問題都可在評論區,或者直接私信即可。 創建vue3.0項目 創建項目: vue create vueecharts選擇第三項:…

vector容器(詳解)

本文最后是模擬實現全部講解&#xff0c;文章穿插有彩色字體&#xff0c;是我總結的技巧和關鍵 1.vector的介紹及使用 1.1 vector的介紹 https://cplusplus.com/reference/vector/vector/&#xff08;vector的介紹&#xff09; 了解 1. vector是表示可變大小數組的序列容器。…