「正點原子Linux連載」第四十二章新字符設備驅動實驗

2020-12-05 正點原子

經過前兩章實驗的實戰操作,我們已經掌握了Linux字符設備驅動開發的基本步驟,字符設備驅動開發重點是使用register_chrdev函數註冊字符設備,當不再使用設備的時候就使用unregister_chrdev函數註銷字符設備,驅動模塊加載成功以後還需要手動使用mknod命令創建設備節點。register_chrdev和unregister_chrdev這兩個函數是老版本驅動使用的函數,現在新的字符設備驅動已經不再使用這兩個函數,而是使用Linux內核推薦的新字符設備驅動API函數。本節我們就來學習一下如何編寫新字符設備驅動,並且在驅動模塊加載的時候自動創建設備節點文件。

42.1 新字符設備驅動原理

42.1.1 分配和釋放設備號

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

①、需要我們事先確定好哪些主設備號沒有使用。

②、會將一個主設備號下的所有次設備號都使用掉,比如現在設置LED這個主設備號為200,那麼0~1048575(2^20-1)這個區間的次設備號就全部都被LED一個設備分走了。這樣太浪費次設備號了!一個LED設備肯定只能有一個主設備號,一個次設備號。

解決這兩個問題最好的方法就是要使用設備號的時候向Linux內核申請,需要幾個就申請幾個,由Linux內核分配設備可以使用的設備號。這個就是我們在40.3.2小節講解的設備號的分配,如果沒有指定設備號的話就使用如下函數來申請設備號:

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

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

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

參數from是要申請的起始設備號,也就是給定的設備號;參數count是要申請的數量,一般都是一個;參數name是設備名字。

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

void unregister_chrdev_region(dev_t from, unsigned count)

新字符設備驅動下,設備號分配示例代碼如下:

示例代碼42.1.1.1 新字符設備驅動下設備號分配

1int major;/* 主設備號 */

2int minor;/* 次設備號 */

3 dev_t devid;/* 設備號 */

4

5if(major){/* 定義了主設備號 */

6 devid =MKDEV(major, 0); /* 大部分驅動次設備號都選擇0 */

7 register_chrdev_region(devid,1,"test");

8}else{/* 沒有定義設備號 */

9 alloc_chrdev_region(&devid,0,1,"test");/* 申請設備號 */

10 major =MAJOR(devid); /* 獲取分配號的主設備號 */

11 minor =MINOR(devid); /* 獲取分配號的次設備號 */

12}

第1~3行,定義了主/次設備號變量major和minor,以及設備號變量devid。

第5行,判斷主設備號major是否有效,在Linux驅動中一般給出主設備號的話就表示這個設備的設備號已經確定了,因為次設備號基本上都選擇0,這算個Linux驅動開發中約定俗成的一種規定了。

第6行,如果major有效的話就使用MKDEV來構建設備號,次設備號選擇0。

第7行,使用register_chrdev_region函數來註冊設備號。

第9~11行,如果major無效,那就表示沒有給定設備號。此時就要使用alloc_chrdev_region函數來申請設備號。設備號申請成功以後使用MAJOR和MINOR來提取出主設備號和次設備號,當然了,第10和11行提取主設備號和次設備號的代碼可以不要。

如果要註銷設備號的話,使用如下代碼即可:

示例代碼42.1.1.2 cdev結構體

1 unregister_chrdev_region(devid,1);/* 註銷設備號 */

註銷設備號的代碼很簡單。

42.1.2 新的字符設備註冊方法

1、字符設備結構

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

示例代碼42.1.2.1 cdev結構體

1struct cdev {

2 struct kobject kobj;

3 struct module *owner;

4 conststruct file_operations *ops;

5 struct list_head list;

6 dev_t dev;

7 unsignedint count;

8};

在cdev中有兩個重要的成員變量:ops和dev,這兩個就是字符設備文件操作函數集合file_operations以及設備號dev_t。編寫字符設備驅動之前需要定義一個cdev結構體變量,這個變量就表示一個字符設備,如下所示:

struct cdev test_cdev;

2、cdev_init函數

定義好cdev變量以後就要使用cdev_init函數對其進行初始化,cdev_init函數原型如下:

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

參數cdev就是要初始化的cdev結構體變量,參數fops就是字符設備文件操作函數集合。使用cdev_init函數初始化cdev變量的示例代碼如下:

示例代碼42.1.2.2 cdev_init函數使用示例代碼

1struct cdev testcdev;

2

3/* 設備操作函數 */

4staticstruct file_operations test_fops ={

5 .owner =THIS_MODULE,

6 /* 其他具體的初始項 */

7};

8

9 testcdev.owner =THIS_MODULE;

10 cdev_init(&testcdev,&test_fops);/* 初始化cdev結構體變量*/

3、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指向要添加的字符設備(cdev結構體變量),參數dev就是設備所使用的設備號,參數count是要添加的設備數量。完善示例代碼42.1.2.2,加入cdev_add函數,內容如下所示:

示例代碼42.1.2.2 cdev_add函數使用示例

1struct cdev testcdev;

2

3/* 設備操作函數 */

4staticstruct file_operations test_fops ={

5 .owner =THIS_MODULE,

6 /* 其他具體的初始項 */

7};

8

9 testcdev.owner =THIS_MODULE;

10 cdev_init(&testcdev,&test_fops);/* 初始化cdev結構體變量 */

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

示例代碼42.1.2.2就是新的註冊字符設備代碼段,Linux內核中大量的字符設備驅動都是採用這種方法向Linux內核添加字符設備。如果在加上示例代碼42.1.1.1中分配設備號的程序,那麼就它們一起實現的就是函數register_chrdev的功能。

3、cdev_del函數

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

void cdev_del(struct cdev *p)

參數p就是要刪除的字符設備。如果要刪除字符設備,參考如下代碼:

示例代碼42.1.2.3 cdev_del函數使用示例

1 cdev_del(&testcdev); /* 刪除cdev */

cdev_del和unregister_chrdev_region這兩個函數合起來的功能相當於unregister_chrdev函數。

42.2 自動創建設備節點

在前面的Linux驅動實驗中,當我們使用modprobe加載驅動程序以後還需要使用命令"mknod"手動創建設備節點。本節就來講解一下如何實現自動創建設備節點,在驅動中實現自動創建設備節點的功能以後,使用modprobe加載驅動模塊成功的話就會自動在/dev目錄下創建對應的設備文件。

42.2.1 mdev機制

udev是一個用戶程序,在Linux下通過udev來實現設備文件的創建與刪除,udev可以檢測系統中硬體設備狀態,可以根據系統中硬體設備狀態來創建或者刪除設備文件。比如使用modprobe命令成功加載驅動模塊以後就自動在/dev目錄下創建對應的設備節點文件,使用rmmod命令卸載驅動模塊以後就刪除掉/dev目錄下的設備節點文件。使用busybox構建根文件系統的時候,busybox會創建一個udev的簡化版本—mdev,所以在嵌入式Linux中我們使用mdev來實現設備節點文件的自動創建與刪除,Linux系統中的熱插拔事件也由mdev管理,在/etc/init.d/rcS文件中如下語句:

echo /sbin/mdev > /proc/sys/kernel/hotplug

上述命令設置熱插拔事件由mdev來管理,關於udev或mdev更加詳細的工作原理這裡就不詳細探討了,我們重點來學習一下如何通過mdev來實現設備文件節點的自動創建與刪除。

42.2.1 創建和刪除類

自動創建設備節點的工作是在驅動程序的入口函數中完成的,一般在cdev_add函數後面添加自動創建設備節點相關代碼。首先要創建一個class類,class是個結構體,定義在文件include/linux/device.h裡面。class_create是類創建函數,class_create是個宏定義,內容如下:

示例代碼42.2.1.1 class_create函數

1 #define class_create(owner,name) \

2({\

3staticstruct lock_class_key __key;\

4 __class_create(owner,name,&__key); \

5})

6

7struct class *__class_create(struct module *owner,constchar*name,

8struct lock_class_key *key)

根據上述代碼,將宏class_create展開以後內容如下:

struct class *class_create (struct module *owner, const char *name)

class_create一共有兩個參數,參數owner一般為THIS_MODULE,參數name是類名字。返回值是個指向結構體class的指針,也就是創建的類。

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

void class_destroy(struct class *cls);

參數cls就是要刪除的類。

42.2.2 創建設備

上一小節創建好類以後還不能實現自動創建設備節點,我們還需要在這個類下創建一個設備。使用device_create函數在類下面創建設備,device_create函數原型如下:

struct device *device_create(struct class *class,

struct device *parent,

dev_t devt,

void *drvdata,

const char *fmt, ...)

device_create是個可變參數函數,參數class就是設備要創建哪個類下面;參數parent是父設備,一般為NULL,也就是沒有父設備;參數devt是設備號;參數drvdata是設備可能會使用的一些數據,一般為NULL;參數fmt是設備名字,如果設置fmt=xxx的話,就會生成/dev/xxx這個設備文件。返回值就是創建好的設備。

同樣的,卸載驅動的時候需要刪除掉創建的設備,設備刪除函數為device_destroy,函數原型如下:

void device_destroy(struct class *class, dev_t devt)

參數classs是要刪除的設備所處的類,參數devt是要刪除的設備號。

42.2.3 參考示例

在驅動入口函數裡面創建類和設備,在驅動出口函數裡面刪除類和設備,參考示例如下:

示例代碼42.2.3.1 創建/刪除類/設備參考代碼

1struct class *class;/* 類 */

2struct device *device;/* 設備 */

3 dev_t devid;/* 設備號 */

4

5/* 驅動入口函數 */

6staticint __init xxx_init(void)

7{

8 /* 創建類 */

9 class =class_create(THIS_MODULE,"xxx");

10 /* 創建設備 */

11 device =device_create(class,NULL, devid,NULL,"xxx");

12 return0;

13}

14

15/* 驅動出口函數 */

16staticvoid __exit led_exit(void)

17{

18 /* 刪除設備 */

19 device_destroy(newchrled.class,newchrled.devid);

20 /* 刪除類 */

21 class_destroy(newchrled.class);

22}

23

24 module_init(led_init);

25 module_exit(led_exit);

42.3設置文件私有數據

每個硬體設備都有一些屬性,比如主設備號(dev_t),類(class)、設備(device)、開關狀態(state)等等,在編寫驅動的時候你可以將這些屬性全部寫成變量的形式,如下所示:

示例代碼42.3.1 變量形式的設備屬性

dev_t devid;/* 設備號 */

struct cdev cdev;/* cdev */

struct class *class;/* 類 */

struct device *device;/* 設備 */

int major;/* 主設備號 */

int minor;/* 次設備號 */

這樣寫肯定沒有問題,但是這樣寫不專業!對於一個設備的所有屬性信息我們最好將其做成一個結構體。編寫驅動open函數的時候將設備結構體作為私有數據添加到設備文件中,如下所示:

示例代碼42.3.2設備結構體作為私有數據

/* 設備結構體 */

1struct test_dev{

2 dev_t devid;/* 設備號 */

3 struct cdev cdev;/* cdev */

4 struct class *class;/* 類 */

5 struct device *device;/* 設備 */

6 int major;/* 主設備號 */

7 int minor;/* 次設備號 */

8};

9

10struct test_dev testdev;

11

12/* open函數 */

13staticint test_open(struct inode *inode,struct file *filp)

14{

15 filp->private_data =&testdev;/* 設置私有數據 */

16 return0;

17}

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

42.4硬體原理圖分析

本章實驗硬體原理圖參考8.3小節即可。

42.5實驗程序編寫

本實驗對應的例程路徑為:開發板光碟->2、Linux驅動例程->3_newchrled。

本章實驗在上一章實驗的基礎上完成,重點是使用了新的字符設備驅動、設置了文件私有數據、添加了自動創建設備節點相關內容。

42.5.1 LED燈驅動程序編寫

新建名為"3_newchrled"文件夾,然後在3_newchrled文件夾裡面創建vscode工程,工作區命名為"newchrled"。工程創建好以後新建newchrled.c文件,在newchrled.c裡面輸入如下內容:

示例代碼42.5.1.1 newchrled.c文件

1 #include <linux/types.h>

2 #include <linux/kernel.h>

3 #include <linux/delay.h>

4 #include <linux/ide.h>

5 #include <linux/init.h>

6 #include <linux/module.h>

7 #include <linux/errno.h>

8 #include <linux/gpio.h>

9 #include <linux/cdev.h>

10 #include <linux/device.h>

11 #include <asm/mach/map.h>

12 #include <asm/uaccess.h>

13 #include <asm/io.h>

14

15/***************************************************************

16 Copyright ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

17文件名 : newchrled.c

18作者 : 左忠凱

19版本 : V1.0

20描述 : LED驅動文件。

21其他 : 無

22論壇 : www.openedv.com

23日誌 : 初版V1.0 2019/6/27 左忠凱創建

24 ***************************************************************/

25 #define NEWCHRLED_CNT 1 /* 設備號個數 */

26 #define NEWCHRLED_NAME "newchrled" /* 名字 */

27 #define LEDOFF 0 /* 關燈 */

28 #define LEDON 1 /* 開燈 */

29

30/* 寄存器物理地址 */

31 #define CCM_CCGR1_BASE (0X020C406C)

32 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)

33 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)

34 #define GPIO1_DR_BASE (0X0209C000)

35 #define GPIO1_GDIR_BASE (0X0209C004)

36

37/* 映射後的寄存器虛擬地址指針 */

38staticvoid __iomem *IMX6U_CCM_CCGR1;

39staticvoid __iomem *SW_MUX_GPIO1_IO03;

40staticvoid __iomem *SW_PAD_GPIO1_IO03;

41staticvoid __iomem *GPIO1_DR;

42staticvoid __iomem *GPIO1_GDIR;

43

44/* newchrled設備結構體 */

45struct newchrled_dev{

46 dev_t devid; /* 設備號 */

47struct cdev cdev; /* cdev */

48struct class *class; /* 類 */

49struct device *device; /* 設備 */

50int major; /* 主設備號 */

51int minor; /* 次設備號 */

52};

53

54struct newchrled_dev newchrled; /* led設備 */

55

56/*

57 * @description : LED打開/關閉

58 * @param - sta : LEDON(0) 打開LED,LEDOFF(1) 關閉LED

59 * @return : 無

60 */

61void led_switch(u8 sta)

62{

63 u32 val =0;

64if(sta ==LEDON){

65 val =readl(GPIO1_DR);

66 val &=~(1<<3);

67 writel(val,GPIO1_DR);

68}elseif(sta ==LEDOFF){

69 val =readl(GPIO1_DR);

70 val|=(1<<3);

71 writel(val,GPIO1_DR);

72}

73}

74

75/*

76 * @description : 打開設備

77 * @param – inode : 傳遞給驅動的inode

78 * @param - filp : 設備文件,file結構體有個叫做private_data的成員變量

79 * 一般在open的時候將private_data指向設備結構體。

80 * @return : 0 成功;其他失敗

81 */

82staticint led_open(struct inode *inode,struct file *filp)

83{

84 filp->private_data =&newchrled;/* 設置私有數據 */

85return0;

86}

87

88/*

89 * @description : 從設備讀取數據

90 * @param - filp : 要打開的設備文件(文件描述符)

91 * @param - buf : 返回給用戶空間的數據緩衝區

92 * @param - cnt : 要讀取的數據長度

93 * @param – offt : 相對於文件首地址的偏移

94 * @return : 讀取的字節數,如果為負值,表示讀取失敗

95 */

96static ssize_t led_read(struct file *filp,char __user *buf,

size_t cnt,loff_t *offt)

97{

98return0;

99}

100

101/*

102 * @description : 向設備寫數據

103 * @param – filp : 設備文件,表示打開的文件描述符

104 * @param - buf : 要寫給設備寫入的數據

105 * @param - cnt : 要寫入的數據長度

106 * @param – offt : 相對於文件首地址的偏移

107 * @return : 寫入的字節數,如果為負值,表示寫入失敗

108 */

109static ssize_t led_write(struct file *filp,constchar __user *buf,

size_t cnt,loff_t *offt)

110{

111int retvalue;

112unsignedchar databuf[1];

113unsignedchar ledstat;

114

115 retvalue =copy_from_user(databuf, buf, cnt);

116if(retvalue <0){

117 printk("kernel write failed!\r\n");

118return-EFAULT;

119}

120

121 ledstat =databuf[0]; /* 獲取狀態值 */

122

123if(ledstat ==LEDON){

124 led_switch(LEDON);/* 打開LED燈 */

125}elseif(ledstat ==LEDOFF){

126 led_switch(LEDOFF);/* 關閉LED燈 */

127}

128return0;

129}

130

131/*

132 * @description : 關閉/釋放設備

133 * @param – filp : 要關閉的設備文件(文件描述符)

134 * @return : 0 成功;其他失敗

135 */

136staticint led_release(struct inode *inode,struct file *filp)

137{

138return0;

139}

140

141/* 設備操作函數 */

142staticstruct file_operations newchrled_fops ={

143.owner =THIS_MODULE,

144.open =led_open,

145.read =led_read,

146.write =led_write,

147.release =led_release,

148};

149

150/*

151 * @description : 驅動出口函數

152 * @param : 無

153 * @return : 無

154 */

155staticint __init led_init(void)

156{

157 u32 val =0;

158

159/* 初始化LED */

160/* 1、寄存器地址映射 */

161 IMX6U_CCM_CCGR1 =ioremap(CCM_CCGR1_BASE,4);

162 SW_MUX_GPIO1_IO03 =ioremap(SW_MUX_GPIO1_IO03_BASE,4);

163 SW_PAD_GPIO1_IO03 =ioremap(SW_PAD_GPIO1_IO03_BASE,4);

164 GPIO1_DR =ioremap(GPIO1_DR_BASE,4);

165 GPIO1_GDIR =ioremap(GPIO1_GDIR_BASE,4);

166

167/* 2、使能GPIO1時鐘 */

168 val =readl(IMX6U_CCM_CCGR1);

169 val &=~(3<<26);/* 清楚以前的設置 */

170 val |=(3<<26);/* 設置新值 */

171 writel(val,IMX6U_CCM_CCGR1);

172

173/* 3、設置GPIO1_IO03的復用功能,將其復用為

174 * GPIO1_IO03,最後設置IO屬性。

175 */

176 writel(5,SW_MUX_GPIO1_IO03);

177

178/* 寄存器SW_PAD_GPIO1_IO03設置IO屬性 */

179 writel(0x10B0,SW_PAD_GPIO1_IO03);

180

181/* 4、設置GPIO1_IO03為輸出功能 */

182 val =readl(GPIO1_GDIR);

183 val &=~(1<<3);/* 清除以前的設置 */

184 val |=(1<<3);/* 設置為輸出 */

185 writel(val,GPIO1_GDIR);

186

187/* 5、默認關閉LED */

188 val =readl(GPIO1_DR);

189 val |=(1<<3);

190 writel(val,GPIO1_DR);

191

192/* 註冊字符設備驅動 */

193/* 1、創建設備號 */

194if(newchrled.major){ /* 定義了設備號 */

195 newchrled.devid = MKDEV(newchrled.major,0);

196 register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);

197}else{ /* 沒有定義設備號 */

198 alloc_chrdev_region(&newchrled.devid,0, NEWCHRLED_CNT,

NEWCHRLED_NAME); /* 申請設備號 */

199 newchrled.major = MAJOR(newchrled.devid); /* 獲取主設備號 */

200 newchrled.minor = MINOR(newchrled.devid); /* 獲取次設備號 */

201}

202 printk("newcheled major=%d,minor=%d\r\n",newchrled.major,

newchrled.minor);

203

204/* 2、初始化cdev */

205 newchrled.cdev.owner = THIS_MODULE;

206 cdev_init(&newchrled.cdev,&newchrled_fops);

207

208/* 3、添加一個cdev */

209 cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

210

211/* 4、創建類 */

212 newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);

213if(IS_ERR(newchrled.class)){

214return PTR_ERR(newchrled.class);

215}

216

217/* 5、創建設備 */

218 newchrled.device = device_create(newchrled.class,NULL,

newchrled.devid,NULL, NEWCHRLED_NAME);

219if(IS_ERR(newchrled.device)){

220return PTR_ERR(newchrled.device);

221}

222

223return0;

224}

225

226/*

227 * @description : 驅動出口函數

228 * @param : 無

229 * @return : 無

230 */

231staticvoid __exit led_exit(void)

232{

233/* 取消映射 */

234 iounmap(IMX6U_CCM_CCGR1);

235 iounmap(SW_MUX_GPIO1_IO03);

236 iounmap(SW_PAD_GPIO1_IO03);

237 iounmap(GPIO1_DR);

238 iounmap(GPIO1_GDIR);

239

240/* 註銷字符設備*/

241 cdev_del(&newchrled.cdev);/* 刪除cdev */

242 unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);

243

244 device_destroy(newchrled.class, newchrled.devid);

245 class_destroy(newchrled.class);

246}

247

248 module_init(led_init);

249 module_exit(led_exit);

250 MODULE_LICENSE("GPL");

251 MODULE_AUTHOR("zuozhongkai");

第25行,宏NEWCHRLED_CNT表示設備數量,在申請設備號或者向Linux內核添加字符設備的時候需要設置設備數量,一般我們一個驅動一個設備,所以這個宏為1。

第26行,宏NEWCHRLED_NAME表示設備名字,本實驗的設備名為"newchrdev",為了方便管理,所有使用到設備名字的地方統一使用此宏,當驅動加載成功以後就生成/dev/newchrled這個設備文件。

第44~52行,創建設備結構體newchrled_dev。

第54行,定義一個設備結構體變量newchrdev,此變量表示led設備。

第82~86行,在led_open函數中設置文件的私有數據private_data指向newchrdev。

第194~221行,根據前面講解的方法在驅動入口函數led_init中申請設備號、添加字符設備、創建類和設備。本實驗我們採用動態申請設備號的方法,第202行使用printk在終端上顯示出申請到的主設備號和次設備號。

第241~245行,根據前面講解的方法,在驅動出口函數led_exit中註銷字符新設備、刪除類和設備。

總體來說newchrled.c文件中的內容不複雜,LED燈驅動部分的程序和上一章一樣。重點就是使用了新的字符設備驅動方法。

42.5.2 編寫測試APP

本章直接使用上一章的測試APP,將上一章的ledApp.c文件複製到本章實驗工程下即可。

42.6 運行測試

42.6.1 編譯驅動程序和測試APP

1、編譯驅動程序

編寫Makefile文件,本章實驗的Makefile文件和第四十章實驗基本一樣,只是將obj-m變量的值改為newchrled.o,Makefile內容如下所示:

示例代碼42.6.1.1 Makefile文件

1 KERNELDIR:=/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

......

4 obj-m := newchrled.o.o

......

11 clean:

12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,設置obj-m變量的值為newchrled.o。

輸入如下命令編譯出驅動模塊文件:

make-j32

編譯成功以後就會生成一個名為"newchrled.ko"的驅動模塊文件。

2、編譯測試APP

輸入如下命令編譯測試ledApp.c這個測試程序:

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

編譯成功以後就會生成ledApp這個應用程式。

42.6.2 運行測試

將上一小節編譯出來的newchrled.ko和ledApp這兩個文件拷貝到rootfs/lib/modules/4.1.15目錄中,重啟開發板,進入到目錄lib/modules/4.1.15中,輸入如下命令加載newchrled.ko驅動模塊:

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

modprobe newchrled.ko //加載驅動

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

圖42.6.2.1 申請到的主設備號和次設備號

從圖42.6.2.1可以看出,申請到的主設備號為249,次設備號為0。驅動加載成功以後會自動在/dev目錄下創建設備節點文件/dev/newchrdev,輸入如下命令查看/dev/newchrdev這個設備節點文件是否存在:

ls /dev/newchrled -l

結果如圖42.6.2.2所示:

圖42.6.2.2 /dev/newchrled設備節點文件

從圖42.6.2.2中可以看出,/dev/newchrled這個設備文件存在,而且主設備號為249,此設備號為0,說明設備節點文件創建成功。

驅動節點創建成功以後就可以使用ledApp軟體來測試驅動是否工作正常,輸入如下命令打開LED燈:

./ledApp /dev/newchrled 1 //打開LED燈

輸入上述命令以後觀察I.MX6U-ALPHA開發板上的紅色LED燈是否點亮,如果點亮的話說明驅動工作正常。在輸入如下命令關閉LED燈:

./ledApp /dev/newchrled 0 //關閉LED燈

輸入上述命令以後觀察I.MX6U-ALPHA開發板上的紅色LED燈是否熄滅。如果要卸載驅動的話輸入如下命令即可:

rmmodnewchrled.ko

相關焦點

  • 嵌入式Linux設備驅動開發之:實驗內容——test驅動
    本文引用地址:http://www.eepw.com.cn/article/257106.htm1.實驗目的該實驗是編寫最簡單的字符驅動程序,這裡的設備也就是一段內存,實現簡單的讀寫功能,並列出常用格式的Makefile以及驅動的加載和卸載腳本。讀者可以熟悉字符設備驅動的整個編寫流程。
  • 「正點原子Linux連載」第二十章高精度延時實驗
    第二十章高精度延時實驗延時函數是很常用的API函數,在前面的實驗中我們使用循環來實現延時函數,但是使用循環來實現的延時函數不準確,誤差會很大。雖然使用到延時函數的地方精度要求都不會很嚴格(要求嚴格的話就使用硬體定時器了),但是延時函數肯定是越精確越好,這樣延時函數就可以使用在某些對時許要求嚴格的場合。本章我們就來學習一下如何使用硬體定時器來實現高精度延時。
  • 從串口驅動到Linux驅動模型,想轉Linux的必會!
    /linux/driver/tty/serial/samsung.c)driver是驅動程序的目錄。如圖所示:前文對linux設備驅動程序有了一個大概的描述。下面我們具體看一下linux下的驅動。縱覽linux/drivers目錄,大概還有35個以上的子目錄,每個子目錄基本上就代表了一種設備驅動,有atm、block、char、misc、input、net、usb、sound、video等。這裡只描述在嵌入式系統裡面用得最為廣泛的3種設備。1.字符設備(char device)字符設備是Linux最簡單的設備,可以像文件一樣訪問。
  • 「linux專欄」嘔心瀝血兩天,就為linux中安裝拼音輸入法
    之前呢,小編也百度過,都說直接下載linux版本的搜狗輸入法之後,直接點擊sougoupinyin.deb的安裝文件就能直接安裝。但是,事實卻很扎心,小編的REHL8作業系統系統並不支持這樣的操作,點擊之後反而是打開了安裝包,並沒有進入安裝程序。
  • 數字溫度傳感器DS1621在Linux下的IIC接口驅動設計
    驅動程序則為應用程式和硬體設備之間提供了操作訪問的接口,使應用程式可以像操作普通文件一樣對硬體設備操作訪問。Linux內核把驅動程序劃分為3種類型:字符設備、塊設備和網絡設備。其中,字符設備和塊設備可以像文件一樣被訪問。DS1621的IIC驅動屬於字符設備。
  • 大暮維人新連載標題判明 「BIORG TRINITY」連載啟動
    大暮維人新連載標題判明 「BIORG TRINITY」連載啟動 動漫 178動漫頻道 ▪ 2012-12-19 14:24:25
  • 嵌入式Linux設備驅動開發之:GPIO驅動程序實例
    在此主要以發光二極體(LED)和蜂鳴器為例,討論GPIO設備的驅動程序。它們的硬體驅動電路的原理圖如圖11.4所示。圖11.4LED(左)和蜂鳴器(右)的驅動電路原理圖在圖11.4中,可知使用S3C2410處理器的通用I/O口GPF4、GPF5、GPF6和GPF7分別直接驅動LEDD12、D11、D10以及D9,而使用GPB0埠驅動蜂鳴器。4個LED分別在對應埠(GPF4~GPF7)為低電平時發亮,而蜂鳴器在GPB0為高電平時發聲。
  • Linux2.6內核驅動移植參考
    作者:晏渭川 隨著Linux2.6的發布,由於2.6內核做了教的改動,各個設備的驅動程序在不同程度上要 進行改寫。為了方便各位Linux愛好者我把自己整理的這分文檔share出來。該文當列舉 了2.6內核同以前版本的絕大多數變化,可惜的是由於時間和精力有限沒有詳細列出各個 函數的用法。
  • 動畫新作「來自新世界」主要人設公開 漫畫版連載先行
    動畫新作「來自新世界」主要人設公開 漫畫版連載先行 動漫 178動漫頻道 ▪
  • 時隔兩年,大人氣漫畫「神薙」7月連載再開!
    「神薙」是由武梨繪裡老師在月刊《Comic REX》上連載的人氣漫畫作品,曾於2008年10月由我們的寬叔改編為TV動畫並播出。由於武梨繪裡老師的身體情況不佳,所以「神薙」的漫畫從2009年2月開始就一直處於無限期修載中,而最近,武梨繪裡老師表示,雖然身體依然欠佳,但為了不辜負眾多FANS的期待,她決定將在今年7月27日發售的新一期《Comic REX》上恢復「神薙」的連載!據悉,恢復連載時預定將會同時放出三話,之後武梨繪裡老師將視身體情況進行不定期連載。
  • 「神薙」漫畫迎來完結,其作者武梨繪裡將在冬天開新連載!
    「神薙」漫畫迎來完結,其作者武梨繪裡將在冬天開新連載!178ACG ▪ 2017-07-25 10:37:33 即將在7月27日發售的 Comic REX 9月號中,漫畫「神薙
  • 正點原子-戰艦V3第十章 外部中斷實驗
    這一章,我們將向大家介紹如何使用 STM32F1 的外部輸入中斷。在前面幾章的學習中,我們掌握了 STM32F1 的 IO 口最基本的操作。的 IO 口在第六章有詳細介紹,而中斷管理分組管理在前面也有詳細的闡述。
  • 「殺戮都市」作者奧浩哉新作漫畫「犬屋敷」開始連載
    「殺戮都市」作者奧浩哉新作漫畫「犬屋敷」開始連載 動漫 178動漫頻道 ▪
  • 正點原子-戰艦V3第三十七章 MPU6050 六軸傳感器實驗
    本章,我們介紹當下最流行的一款六軸(三軸加速度+三軸角速度(陀螺儀))傳感器:MPU6050,該傳感器廣泛用於四軸、平衡車和空中滑鼠等設計,具有非常廣泛的應用範圍。在戰艦 STM32F1 開發板上,我們通過 PA15 控制 ATK-MPU6050 模塊 AD0 接 GND,因而選擇 MPU6050 的 IIC 地址是 0X68(不含最低位),IIC 通信的時序我們在之前已經介紹過(第二十八章,IIC 實驗),這裡就不再細說了。
  • 火星異種外傳漫畫「RAIN HARD」連載開啟
    火星異種外傳漫畫「RAIN HARD」連載開啟 2014-09-11 10:42:36
  • 「火星異種」作者貴家悠因身體不適暫停連載更新
    「火星異種」作者貴家悠因身體不適暫停連載更新 新聞 178動漫原創 ▪ 2018-09-13 10:04:53
  • 「黑礁」將於2017年春再開連載!廣江禮威筆下的黑暗暴力世界~
    「黑礁」將於2017年春再開連載!動漫 178ACG ▪ 2016-12-19 10:34:07 今日發售的「月刊
  • 「高二輔導」高中物理選修3-5第十八章《原子結構》章末總結
    高中物理選修3-5第十八章《原子結構》在高考中基本上以選擇題的形式出現,主要涉及到基本知識的記憶與理解。其中最重點的要數第四節《氫原子光譜》。>(1)a粒子散射實驗(2)盧瑟福原子的核式結構模型3、氫原子光譜(1)光譜的種類(2)氫原子的線狀譜(3)氫原子光譜公式4、波爾的原子模型
  • 正點原子-戰艦V3第三十六章 DHT11 數字溫溼度傳感器實驗
    36.2 硬體設計由於開發板上標準配置是沒有 DHT11 這個傳感器的,只有接口,所以要做本章的實驗,大家必須找一個 DHT11 插在預留的 DHT11 接口上。本章實驗功能簡介:開機的時候先檢測是否有 DHT11 存在,如果沒有,則提示錯誤。只有在檢測到 DHT11 之後才開始讀取溫溼度值,並顯示在 LCD 上,如果發現了 DHT11,則程序每隔 100ms 左右讀取一次數據,並把溫溼度顯示在 LCD 上。
  • 冬番《Dr.STONE 新石紀》第二季「STONE WARS」
    由稻垣理一郎擔任原作、Boichi負責作畫,於《周刊少年JUMP》連載中的少年漫畫《Dr.STONE新石紀》,其電視動畫第二季「STONE WARS」確定將於2021年1月14日開始放送,最新宣傳影片已正式公開。