imx6ull-驅動開發篇5——新字符設備驅動實驗

目錄

前言

新字符設備驅動原理

申請設備號

注冊設備號

釋放設備號

注冊方法

字符設備結構cdev

cdev_init 函數

cdev_add 函數

cdev_del 函數

自動創建設備節點

mdev 機制

類創建函數

類刪除函數

創建設備函數

刪除設備函數

設置文件私有數據

實驗程序編寫

led.c

ledApp.c

Makefile

編譯代碼

運行測試


前言

在上兩講內容里:我們已經掌握了 Linux 字符設備驅動開發的基本步驟。

驅動開發篇3——字符設備驅動開發實驗
?????驅動開發篇4——LED 驅動開發實驗

使用 register_chrdev 函數注冊字符設備,使用unregister_chrdev 函數注銷字符設備,但這兩個函數是老版本驅動使用的函數。

本講實驗里,我們就來學習一下如何編寫新字符設備驅動,使用Linux內核推薦的新字符設備驅動API函數,在驅動模塊加載的時候自動創建設備節點文件。

新字符設備驅動原理

使用 register_chrdev 函數注冊字符設備的時候只需要給定一個主設備號即可,但是這樣會帶來兩個問題:

  • 需要我們事先確定好哪些主設備號沒有使用。
  • 會將一個主設備號下的所有次設備號都使用掉。

解決這兩個問題最好的方法就是要使用設備號的時候向 Linux 內核申請,需要幾個就申請幾個,由 Linux 內核分配設備可以使用的設備號。

申請設備號

如果沒有指定設備號的話就使用如下函數來申請設備號:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

參數

類型

說明

dev

dev_t *

??輸出參數??,存儲分配到的設備號(主設備號 + 次設備號)。

baseminor

unsigned

起始次設備號(通常為?0)。

count

unsigned

要連續分配的次設備號數量(如?1表示單個設備)。

name

const char *

設備名稱(出現在?/proc/devices中)。

示例代碼:

dev_t devno;  // 存儲分配到的設備號
int ret;// 動態分配設備號(主設備號自動分配,次設備號從 0 開始)
ret = alloc_chrdev_region(&devno, 0, 1, "my_device");
if (ret < 0) {printk(KERN_ERR "Failed to allocate device number\n");return ret;
}

注冊設備號

如果給定了設備的主設備號和次設備號,就使用如下所示函數來注冊設備號即可:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

參數

類型

說明

from

dev_t

??起始設備號??(包含主設備號和次設備號,用?MKDEV(major, minor)生成)。

count

unsigned

要連續注冊的次設備號數量(如?1表示單個設備)。

name

const char *

設備名稱(出現在?/proc/devices中)。

示例代碼:

dev_t devno;
int ret;// 生成設備號(主設備號250,次設備號0)
devno = MKDEV(MY_MAJOR, MY_MINOR);// 靜態注冊設備號(注冊1個次設備號)
ret = register_chrdev_region(devno, 1, "my_device");
if (ret < 0) {printk(KERN_ERR "Failed to register device number\n");return ret;
}

釋放設備號

注銷字符設備之后要釋放掉設備號 , 不管是通過 alloc_chrdev_region 函數還是register_chrdev_region 函數申請的設備號,統一使用如下釋放函數:

void unregister_chrdev_region(dev_t from, unsigned count)

參數

類型

說明

from

dev_t

??起始設備號??(需與注冊時一致,用?MKDEV(major, minor)生成)。

count

unsigned

要釋放的連續次設備號數量(需與注冊時一致)。

示例代碼:

int major; /* 主設備號 */
int minor; /* 次設備號 */
dev_t devid; /* 設備號 */if (major) { /* 定義了主設備號 */devid = MKDEV(major, 0); /* 大部分驅動次設備號都選擇 0*/register_chrdev_region(devid, 1, "test");
} else { /* 沒有定義設備號 */alloc_chrdev_region(&devid, 0, 1, "test"); /* 申請設備號 */major = MAJOR(devid); /* 獲取分配號的主設備號 */minor = MINOR(devid); /* 獲取分配號的次設備號 */
}...unregister_chrdev_region(devid, 1); /* 注銷設備號 */

注冊方法

字符設備結構cdev

在 Linux 中使用 cdev 結構體表示一個字符設備, cdev 結構體在 include/linux/cdev.h 文件中的定義如下:

struct cdev {struct kobject kobj;                  // 內嵌的 kobject(用于設備模型)struct module *owner;                 // 指向所屬模塊的指針(通常為 THIS_MODULE)const struct file_operations *ops;    // 設備操作函數集(如 open、read、write)struct list_head list;                // 鏈表節點(用于管理所有 cdev)dev_t dev;                            // 設備號(主設備號 + 次設備號)unsigned int count;                   // 關聯的次設備號數量
};

成員

類型

說明

kobj

struct kobject

內嵌的內核對象,用于設備模型和 sysfs 交互。

owner

struct module *

指向擁有該設備的模塊(通常設為?THIS_MODULE),防止模塊卸載時設備正在使用。

ops

const struct file_operations *

設備操作函數集(如?.open.read.write),驅動必須實現。

list

struct list_head

鏈表節點,內核通過此字段管理所有注冊的?cdev

dev

dev_t

設備號(通過?MKDEV(major, minor)生成)。

count

unsigned int

設備關聯的次設備號數量(通常為 1)。

cdev_init 函數

定義好 cdev 變量以后就要使用 cdev_init 函數對其進行初始化。

cdev_init 函數原型如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

參數

類型

說明

cdev

struct cdev *

要初始化的?cdev結構體指針(通常為驅動定義的全局變量)。

fops

const struct file_operations *

設備操作函數集(如?openreadwrite),由驅動實現。

示例代碼:

struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops);  // 綁定 file_operations
my_cdev.owner = THIS_MODULE;    // 設置所屬模塊

cdev_add 函數

cdev_add 函數用于向 Linux 系統添加字符設備(cdev 結構體變量)。

首先使用 cdev_init 函數完成對 cdev 結構體變量的初始化,然后使用 cdev_add 函數向 Linux 系統添加這個字符設備。

cdev_add 函數原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

參數

類型

說明

p

struct cdev *

已通過?cdev_init初始化的?cdev結構體指針。

dev

dev_t

設備號(主設備號 + 起始次設備號,用?MKDEV(major, minor)生成)。

count

unsigned

連續分配的次設備號數量(通常為?1)。

示例代碼:

cdev_add(&testcdev, devid, 1); /* 添加字符設備 */

cdev_del 函數

卸載驅動的時候一定要使用 cdev_del 函數從 Linux 內核中刪除相應的字符設備。

cdev_del函數原型如下:

void cdev_del(struct cdev *p)

參數

類型

說明

p

struct cdev *

要注銷的?cdev指針(由?cdev_add注冊)。

示例代碼:

#include <linux/cdev.h>static struct cdev my_cdev;
static dev_t devno;static int __init my_init(void) {// 1. 分配設備號(動態或靜態)int ret = alloc_chrdev_region(&devno, 0, 1, "mydev");if (ret < 0) return ret;// 2. 初始化 cdevcdev_init(&my_cdev, &my_fops);// 3. 注冊到內核ret = cdev_add(&my_cdev, devno, 1);if (ret < 0) {unregister_chrdev_region(devno, 1); // 失敗時釋放設備號return ret;}// 4. 創建設備節點(可選,udev 可能自動創建)device_create(my_class, NULL, devno, NULL, "mydev");return 0;
}static void __exit my_exit(void) {device_destroy(my_class, devno);      // 移除設備節點cdev_del(&my_cdev);                   // 注銷 cdevunregister_chrdev_region(devno, 1);   // 釋放設備號
}

自動創建設備節點

在前面的 Linux 驅動實驗中,當我們使用 modprobe 加載驅動程序以后,還需要使用命令“mknod”手動創建設備節點。

那么如何實現自動創建設備節點呢?怎么在驅動中實現自動創建設備節點的功能以后,使用 modprobe 加載驅動模塊成功的話就會自動在/dev 目錄下創建對應的設備文件。

mdev 機制

udev 是一個用戶程序,在 Linux 下通過 udev 來實現設備文件的創建與刪除, udev 可以檢測系統中硬件設備狀態,可以根據系統中硬件設備狀態來創建或者刪除設備文件。

使用 busybox 構建根文件系統的時候, busybox 會創建一個 udev 的簡化版本—mdev。

mdev 核心功能:

  • 動態創建設備節點??:在 /dev目錄下自動生成設備文件(如 /dev/sda1)。
  • ??響應熱插拔事件??:處理設備插入(add)、移除(remove)等事件。
  • ??輕量級??:BusyBox 的一部分,適合嵌入式系統。

自動創建設備節點的工作是在驅動程序的入口函數中完成的,一般在 cdev_add 函數后面添加自動創建設備節點相關代碼。

首先要創建一個 class 類, class 是個結構體,定義在文件include/linux/device.h 里面。

類創建函數

class_create 是類創建函數, class_create 是個宏定義,內容如下:

#define class_create(owner, name) \
({ \static struct lock_class_key __key; \__class_create(owner, name, &__key); \
})struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)

將宏 class_create 展開以后內容如下:

struct class *class_create (struct module *owner, const char *name)
  • 參數 owner 一般為 THIS_MODULE,
  • 參數 name 是類名字。
  • 返回值是個指向結構體 class 的指針,也就是創建的類。
    ?

類刪除函數

卸載驅動程序的時候需要刪除掉類,類刪除函數為 class_destroy,函數原型如下:

void class_destroy(struct class *cls);

參數 cls 就是要刪除的類。

示例代碼:

static struct class *my_class;static int __init my_init(void) {my_class = class_create(THIS_MODULE, "my_device");if (IS_ERR(my_class)) {return PTR_ERR(my_class);}return 0;
}static void __exit my_exit(void) {class_destroy(my_class);
}

創建設備函數

建好類以后還不能實現自動創建設備節點,我們還需要在這個類下創建一個設備。

使用 device_create 函數在類下面創建設備, device_create 函數原型如下:

struct device *device_create(struct class *class,      // 所屬的設備類(由 class_create 創建)struct device *parent,    // 父設備(通常為 NULL)dev_t devt,               // 設備號(主設備號 + 次設備號)void *drvdata,           // 驅動私有數據(可傳遞到驅動的 file_operations)const char *fmt, ...      // 設備名稱(支持格式化字符串,如 "mydevice%d")
);

參數

類型

說明

class

struct class *

設備所屬的類(如?my_class = class_create(THIS_MODULE, "mydev"))。

parent

struct device *

父設備(用于設備層次結構,通常設為?NULL)。

devt

dev_t

設備號(通過?MKDEV(major, minor)生成)。

drvdata

void *

傳遞給驅動的私有數據(可在?file_operations中通過?file->private_data訪問)。

fmt, ...

const char *

設備名稱(如?"mydrv"或帶格式的?"mydrv%d")。

刪除設備函數

同樣的,卸載驅動的時候需要刪除掉創建的設備。

設備刪除函數為 device_destroy,函數原型如下:

void device_destroy(struct class *class,  // 設備所屬的類(需與 device_create 使用的 class 一致)dev_t devt           // 設備號(需與 device_create 使用的 dev_t 一致)
);

參數

類型

說明

class

struct class *

設備所屬的類(由?class_create創建)。

devt

dev_t

設備號(主設備號 + 次設備號,需與?device_create調用時一致)

示例代碼:

struct class *class; /* 類 */
struct device *device; /* 設備 */
dev_t devid; /* 設備號 *//* 驅動入口函數 */
static int __init led_init(void)
{/* 創建類 */class = class_create(THIS_MODULE, "xxx");/* 創建設備 */device = device_create(class, NULL, devid, NULL, "xxx");return 0;
}/* 驅動出口函數 */
static void __exit led_exit(void)
{/* 刪除設備 */device_destroy(class, devid);/* 刪除類 */class_destroy(class);
}module_init(led_init);
module_exit(led_exit);

設置文件私有數據

每個硬件設備都有一些屬性,比如主設備號(dev_t),類(class)、設備(device)、開關狀態(state)等。對于一個設備的所有屬性信息我們最好將其做成一個結構體。

編寫驅動 open 函數的時候將設備結構體作為私有數據添加到設備文件中,如下所示:

/* 設備結構體 */
struct test_dev {dev_t devid;          /* 設備號 */struct cdev cdev;     /* cdev 字符設備結構 */struct class *class;  /* 設備類 */struct device *device; /* 設備實例 */int major;           /* 主設備號 */int minor;          /* 次設備號 */
};struct test_dev testdev;/* open 函數 */
static int test_open(struct inode *inode, struct file *filp)
{filp->private_data = &testdev;  /* 設置私有數據 */return 0;
}

在 open 函數里面設置好私有數據以后,在 write、 read、 close 等函數中直接讀取 private_data即可得到設備結構體。

實驗程序編寫

在上一講內容的基礎上修改:LED 驅動開發實驗

重點是使用了新的字符設備驅動、設置了文件私有數據、添加了自動創建設備節點相關內容。

.json文件

vscode的c_cpp_properties.json文件里,頭文件路徑包含linux內核源碼:

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_ga/include","/home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include","/home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated/"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "clang-x64"}],"version": 4
}

led.c

led.c文件主要是LED 燈驅動部分的程序,使用了新的字符設備驅動方法。

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define NEWCHRLED_CNT			1		  	/* 設備號個數 */
#define NEWCHRLED_NAME			"newchrled"	/* 名字 */
#define LEDOFF 					0			/* 關燈 */
#define LEDON 					1			/* 開燈 *//* 寄存器物理地址 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)/* 映射后的寄存器虛擬地址指針 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/* newchrled設備結構體 */
struct newchrled_dev{dev_t devid;			/* 設備號 	 */struct cdev cdev;		/* cdev 	*/struct class *class;		/* 類 		*/struct device *device;	/* 設備 	 */int major;				/* 主設備號	  */int minor;				/* 次設備號   */
};struct newchrled_dev newchrled;	/* led設備 *//** @description		: LED打開/關閉* @param - sta 	: LEDON(0) 打開LED,LEDOFF(1) 關閉LED* @return 			: 無*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);	writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);	writel(val, GPIO1_DR);}	
}/** @description		: 打開設備* @param - inode 	: 傳遞給驅動的inode* @param - filp 	: 設備文件,file結構體有個叫做private_data的成員變量* 					  一般在open的時候將private_data指向設備結構體。* @return 			: 0 成功;其他 失敗*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &newchrled; /* 設置私有數據 */return 0;
}/** @description		: 從設備讀取數據 * @param - filp 	: 要打開的設備文件(文件描述符)* @param - buf 	: 返回給用戶空間的數據緩沖區* @param - cnt 	: 要讀取的數據長度* @param - offt 	: 相對于文件首地址的偏移* @return 			: 讀取的字節數,如果為負值,表示讀取失敗*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description		: 向設備寫數據 * @param - filp 	: 設備文件,表示打開的文件描述符* @param - buf 	: 要寫給設備寫入的數據* @param - cnt 	: 要寫入的數據長度* @param - offt 	: 相對于文件首地址的偏移* @return 			: 寫入的字節數,如果為負值,表示寫入失敗*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];		/* 獲取狀態值 */if(ledstat == LEDON) {	led_switch(LEDON);		/* 打開LED燈 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);	/* 關閉LED燈 */}return 0;
}/** @description		: 關閉/釋放設備* @param - filp 	: 要關閉的設備文件(文件描述符)* @return 			: 0 成功;其他 失敗*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 設備操作函數 */
static struct file_operations newchrled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = 	led_release,
};/** @description	: 驅動出口函數* @param 		: 無* @return 		: 無*/
static int __init led_init(void)
{u32 val = 0;/* 初始化LED *//* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1時鐘 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);	/* 清楚以前的設置 */val |= (3 << 26);	/* 設置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、設置GPIO1_IO03的復用功能,將其復用為*    GPIO1_IO03,最后設置IO屬性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03設置IO屬性*bit 16:0 HYS關閉*bit [15:14]: 00 默認下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 關閉開路輸出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驅動能力*bit [0]: 0 低轉換率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、設置GPIO1_IO03為輸出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);	/* 清除以前的設置 */val |= (1 << 3);	/* 設置為輸出 */writel(val, GPIO1_GDIR);/* 5、默認關閉LED */val = readl(GPIO1_DR);val |= (1 << 3);	writel(val, GPIO1_DR);/* 注冊字符設備驅動 *//* 1、創建設備號 */if (newchrled.major) {		/*  定義了設備號 */newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);} else {						/* 沒有定義設備號 */alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);	/* 申請設備號 */newchrled.major = MAJOR(newchrled.devid);	/* 獲取分配號的主設備號 */newchrled.minor = MINOR(newchrled.devid);	/* 獲取分配號的次設備號 */}printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	/* 2、初始化cdev */newchrled.cdev.owner = THIS_MODULE;cdev_init(&newchrled.cdev, &newchrled_fops);/* 3、添加一個cdev */cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);/* 4、創建類 */newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);if (IS_ERR(newchrled.class)) {return PTR_ERR(newchrled.class);}/* 5、創建設備 */newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);if (IS_ERR(newchrled.device)) {return PTR_ERR(newchrled.device);}return 0;
}/** @description	: 驅動出口函數* @param 		: 無* @return 		: 無*/
static void __exit led_exit(void)
{/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注銷字符設備驅動 */cdev_del(&newchrled.cdev);/*  刪除cdev */unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注銷設備號 */device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huax");

ledApp.c

ledApp.c文件和上一講實驗的代碼一致,沒有修改。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF 	0
#define LEDON 	1/** @description		: main主程序* @param - argc 	: argv數組元素個數* @param - argv 	: 具體參數* @return 			: 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打開led驅動 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]);	/* 要執行的操作:打開或關閉 *//* 向/dev/led文件寫入數據 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 關閉文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}

Makefile

makefile文件只需要修改?obj-m 變量的值,改為 newchrled.o。

KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := newchrled.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

編譯代碼

運行以下命令:編譯出驅動模塊文件和測試 ledApp.c 測試程序。

make -j32
arm-linux-gnueabihf-gcc ledApp.c -o ledApp

運行測試

將編譯出來的 led.ko 和 ledApp 這

ls /dev/newchrled -l

兩個文件拷貝到我們制作的根目錄 rootfs/lib/modules/4.1.15目錄中。

重啟開發板,進入到目錄 lib/modules/4.1.15 中,輸入如下命令加載 led.ko 驅動模塊:

depmod //第一次加載驅動的時候需要運行此命令
modprobe newchrled.ko //加載驅動

驅動加載成功以后會輸出申請到的主設備號和次設備號,如圖:

可以看出,申請到的主設備號為 249,次設備號為 0。

驅動加載成功以后會自動在/dev 目錄下創建設備節點文件/dev/newchrdev。

輸入如下命令查看/dev/newchrdev 這個設備節點文件是否存在:

ls /dev/newchrled -l

結果如圖:

使用 ledApp 軟件來測試驅動是否工作正常,命令如下:

./ledApp /dev/newchrled 1 //打開 LED 燈
./ledApp /dev/newchrled 0 //關閉 LED 燈

觀察LED燈是否能夠正常打開、關閉。

卸載驅動的話輸入如下命令:

rmmod led.ko

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

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

相關文章

2025年最新SCI-灰熊增脂優化器(Grizzly Bear Fat Increase, GBF)-附完整Matlab免費代碼

1、簡介 本文介紹了一種新的受自然啟發的優化算法&#xff0c;稱為灰熊增脂優化器&#xff08;GBFIO&#xff09;。GBFIO算法模仿灰熊積累體脂為過冬做準備的自然行為&#xff0c;利用它們的狩獵、捕魚和吃草、蜂蜜等策略。因此&#xff0c;GBFIO算法建模并考慮了三個數學步驟來…

Python爬蟲02_Requests實戰網頁采集器

一、Request請求偽裝解析 #UA&#xff1a;User-Agent&#xff08;請求載體身份標識&#xff09; #UA檢測&#xff1a;門戶網站的服務器會檢測對應請求的載體身份標識&#xff0c;如果檢測到請求的載體身份呢標識為某一款瀏覽器&#xff0c;說明該請求是一個正常的請求&#xff…

vue+elementui實現問卷調查配置可單選、多選、解答

效果&#xff1a;<template> <div><el-form :inline"true" :model"form" :rules"rules" ref"ruleForm"> <el-tabs type"border-card" v-model"cardType"><el-tab-pane name"1&qu…

Docker初學者需要了解的幾個知識點(三):Docker引擎與Docker Desktop

Docker引擎與Docker Desktop簡單說&#xff1a;Docker 引擎是干活的 “核心工具”&#xff0c;負責實際創建、運行容器&#xff0c;就像汽車的發動機&#xff0c;沒它跑不起來。Docker Desktop是個 “套裝軟件”&#xff0c;它把 Docker 引擎打包進去了&#xff0c;還加了圖形化…

Python將Word轉換為Excel

現有大量的Word文檔&#xff0c;每個文檔中有大量的表格&#xff0c;需要將其轉換為Excel。 Python處理源碼 # 需要安裝pip install xlsxwriter import pandas as pd from docx import Document from pathlib import Path from datetime import datetimedef process_docx(filep…

攀爬誤報率↓82%!陌訊多模態算法在周界防護的實戰解析

?摘要?? 原創聲明 本文解析邊緣計算優化下陌訊視覺算法在攀爬識別場景的魯棒性提升&#xff0c;實測數據來自陌訊技術白皮書&#xff08;2025&#xff09;。針對傳統安防系統在復雜光影、姿態變化中的誤檢問題&#xff0c;重點闡述動態決策機制與輕量化部署方案&#xff0c;…

Redis 存在哪些問題

內存相關問題 1. 內存消耗大 無壓縮機制&#xff1a;數據以明文形式存儲&#xff0c;占用內存較大元數據開銷&#xff1a;每個key-value對都有額外的元數據開銷內存碎片&#xff1a;頻繁的更新操作可能產生內存碎片 2. 內存容量限制 單機容量受限&#xff1a;受限于單臺服務器的…

ECMAScript2025(ES16)新特性

概述 ECMAScript2025于2025年6月26日正式發布&#xff0c; 本文會介紹ECMAScript2025(ES16)&#xff0c;即ECMAScript的第16個版本的新特性。 以下摘自官網&#xff1a;ecma-262 ECMAScript 2025, the 16th edition, added a new Iterator global with associated static and…

Vim 編輯器工作模式及操作指南

Vim 編輯器工作模式及操作指南 一、工作模式概述 Vim編輯器主要包含四種工作模式&#xff0c;分別是&#xff1a; 命令模式&#xff08;默認進入模式&#xff09;輸入模式&#xff08;編輯模式&#xff09;末行模式&#xff08;指令模式&#xff09;可視模式 二、模式切換及操作…

Rabbitmq中常見7種模式介紹

p&#xff1a;生成者&#xff0c;生成消息的程序c&#xff1a;消費者&#xff0c;消費消息的程序Queue&#xff1a;消息隊列&#xff0c;用于緩存消息&#xff0c;生產者向里面投遞消息&#xff0c;消費者從里面拿取消息消費X&#xff1a;交換機&#xff0c;在rabbitMQ中&#…

SpringAI 1.0.0發布:打造企業級智能聊天應用

官方文檔 gitee的demo 1、前言 2025年5月&#xff0c;SpringAI 1.0.0終于正式發布。這不僅是另一個普通的庫&#xff0c;更是將Java和Spring推向AI革命前沿的戰略性舉措。給Java生態帶來了強大且全面的AI工程解決方案。眾多企業級應用在SpringBoot上運行關鍵業務&#xff0c…

全球各界關注與討論鴿姆智庫的多維視角分析?

【摘要】全球各界對鴿姆智庫的關注與討論主要集中在以下多維視角&#xff1a; 一、技術創新維度 ?通用思維框架&#xff08;GTF&#xff09;與中文智慧編程系統&#xff08;CWPS&#xff09;? GTF通過模擬人類格式塔認知&#xff0c;實現模式補全與圖形-背景分離功能&#xf…

1??4?? OOP:類、封裝、繼承、多態

文章目錄一、類與實例&#xff1a;從抽象到具體1?? 類&#xff08;Class&#xff09;&#xff1a;抽象的模板2?? 實例&#xff08;Instance&#xff09;&#xff1a;具體的對象3?? __init__ 方法&#xff1a;初始化實例屬性二、封裝&#xff1a;數據與邏輯的“打包”1??…

靜態鏈接 qt 失敗

配置靜態構建 qt 如下所示&#xff0c;執行配置的時候添加 -static 選項即可。 $skiped_modules ("qttools""qtdoc""qttranslations""qtlanguageserver""qtdeclarative""qtquicktimeline""qtquick3d"…

Qt 多線程界面更新策略

在Qt開發中&#xff0c;界面&#xff08;UI&#xff09;更新是高頻操作——無論是后臺任務的進度展示、傳感器數據的實時刷新&#xff0c;還是網絡消息的即時顯示&#xff0c;都需要動態更新界面元素。但Qt對UI操作有一個核心限制&#xff1a;所有UI組件的創建和更新必須在主線…

1.09---區塊鏈節點到底做了什么?從全節點到輕客戶端

鯤志博主出品 Web2 開發者的 Web3 修煉之路 ??【好看的靈魂千篇一律,有趣的鯤志一百六七!】- 歡迎認識我~~ 作者:鯤志說 (公眾號、B站同名,視頻號:鯤志說996) 科技博主:極星會 星輝大使 全棧研發:java、go、python、ts,前電商、現web3 主理人:COC杭州開發者…

Linux線程概念與控制(下)

目錄 前言 2.線程控制 1.驗證理論 2.引入pthread線程庫 3.linux線程控制的接口 3.線程id及進程地址空間布局 4.線程棧 前言 本篇是緊接著上一篇的內容&#xff0c;在有了相關線程概念的基礎之上&#xff0c;我們將要學習線程控制相關話題&#xff01;&#xff01; 2.線程…

力扣面試150題--只出現一次的數字

Day 91 題目描述## 思路 交換律&#xff1a;a ^ b ^ c <> a ^ c ^ b 任何數于0異或為任何數 0 ^ n > n 相同的數異或為0: n ^ n > 0 根據以上 很容易想到做法&#xff0c;將數組中所有的數異或起來&#xff0c;得到的就是只出現一次的數 class Solution {public in…

【運維基礎】Linux 進程調度管理

Linux 進程調度管理 進程調度器 現代計算機系統中既包含只有單個CPU且任何時候都只能處理單個指令的低端系統到具有幾百個cpu、每個cpu有多個核心的高性能超級計算機&#xff0c;可以并行執行幾百個指令。所有這些系統都有一個共同點&#xff1a;系統進程線程數量超出了CPU數量…

深度學習篇---層與層之間搭配

在深度學習中&#xff0c;各種層&#xff08;比如卷積層、激活函數、池化層等&#xff09;的搭配不是隨意的&#xff0c;而是像 “搭積木” 一樣有規律 —— 每一層的作用互補&#xff0c;組合起來能高效提取特征、穩定訓練&#xff0c;最終提升模型性能。下面用通俗易懂的方式…