文章目錄
-
- 1 驅動程式編寫
- 2 應用測試程式編寫
1 驅動程式編寫
本實驗例程路徑:i.MX6UL終結者CD光牒資料/06_Linux驅動例程/09_gpioled_timer
建立gpioled_timer.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 <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/semaphore.h>
15 #include <linux/timer.h>
16 #include <asm/mach/map.h>
17 #include <asm/uaccess.h>
18 #include <asm/io.h>
19
20 #define TIMER_CNT 1 /* 裝置号個數 */
21 #define TIMER_NAME "timer" /* 名字 */
22 #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 關閉定時器 */
23 #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打開定時器 */
24 #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 設定定時器周期指令 */
25
26 #define LEDON 1 /* 開燈 */
27 #define LEDOFF 0 /* 關燈 */
28
29 /* timer 裝置結構體 */
30 struct timer_dev{
31 dev_t devid; /* 裝置号 */
32 struct cdev cdev; /* cdev */
33 struct class *class; /* 類 */
34 struct device *device; /* 裝置 */
35 int major; /* 主裝置号 */
36 int minor; /* 次裝置号 */
37 struct device_node *nd; /* 裝置節點 */
38 int led_gpio; /* key 所使用的 GPIO 編号 */
39 int timeperiod; /* 定時周期,機關為 ms */
40 struct timer_list timer; /* 定義一個定時器 */
41 spinlock_t lock; /* 定義自旋鎖 */
42 };
43
44 struct timer_dev timerdev; /* timer 裝置 */
45
46 /*
47 * @description : 初始化 LED 燈 IO,open 函數打開驅動的時候
48 * 初始化 LED 燈所使用的 GPIO 引腳。
49 * @param : 無
50 * @return : 無
51 */
52 static int led_init(void)
53 {
54 int ret = 0;
55
56 timerdev.nd = of_find_node_by_path("/gpioled");
57 if (timerdev.nd== NULL) {
58 return -EINVAL;
59 }
60
61 timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);
62 if (timerdev.led_gpio < 0) {
63 printk("can't get led\r\n");
64 return -EINVAL;
65 }
66
67 /* 初始化 led 所使用的 IO */
68 gpio_request(timerdev.led_gpio, "led"); /* 請求 IO */
69 ret = gpio_direction_output(timerdev.led_gpio, 1);
70 if(ret < 0) {
71 printk("can't set gpio!\r\n");
72 }
73 return 0;
74 }
75
76 /*
77 * @description : 打開裝置
78 * @param – inode : 傳遞給驅動的 inode
79 * @param - filp : 裝置檔案,file 結構體有個叫做 private_data 的成員變量
80 * 一般在 open 的時候将 private_data 指向裝置結構體。
81 * @return : 0 成功;其他 失敗
82 */
83 static int timer_open(struct inode *inode, struct file *filp)
84 {
85 int ret = 0;
86 filp->private_data = &timerdev; /* 設定私有資料 */
87
88 timerdev.timeperiod = 1000; /* 預設周期為 1s */
89 ret = led_init(); /* 初始化 LED IO */
90 if (ret < 0) {
91 return ret;
92 }
93 return 0;
94 }
95
96 /*
97 * @description : ioctl 函數,
98 * @param – filp : 要打開的裝置檔案(檔案描述符)
99 * @param - cmd : 應用程式發送過來的指令
100 * @param - arg : 參數
101 * @return : 0 成功;其他 失敗
102 */
103 static long timer_unlocked_ioctl(struct file *filp,
104 unsigned int cmd, unsigned long arg)
105 {
106 struct timer_dev *dev = (struct timer_dev *)filp->private_data;
107 int timerperiod;
108 unsigned long flags;
109
110 switch (cmd) {
111 case CLOSE_CMD: /* 關閉定時器 */
112 del_timer_sync(&dev->timer);
113 break;
114 case OPEN_CMD: /* 打開定時器 */
115 spin_lock_irqsave(&dev->lock, flags);
116 timerperiod = dev->timeperiod;
117 spin_unlock_irqrestore(&dev->lock, flags);
118 mod_timer(&dev->timer, jiffies +
119 msecs_to_jiffies(timerperiod));
120 break;
121 case SETPERIOD_CMD: /* 設定定時器周期 */
122 spin_lock_irqsave(&dev->lock, flags);
123 dev->timeperiod = arg;
124 spin_unlock_irqrestore(&dev->lock, flags);
125 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
126 break;
127 default:
128 break;
129 }
130 return 0;
131 }
132
133 /* 裝置操作函數 */
134 static struct file_operations timer_fops = {
135 .owner = THIS_MODULE,
136 .open = timer_open,
137 .unlocked_ioctl = timer_unlocked_ioctl,
138 };
139
140 /* 定時器回調函數 */
141 void timer_function(unsigned long arg)
142 {
143 struct timer_dev *dev = (struct timer_dev *)arg;
144 static int sta = 1;
145 int timerperiod;
146 unsigned long flags;
147
148 sta = !sta; /* 每次都取反,實作 LED 燈反轉 */
149 gpio_set_value(dev->led_gpio, sta);
150
151 /* 重新開機定時器 */
152 spin_lock_irqsave(&dev->lock, flags);
153 timerperiod = dev->timeperiod;
154 spin_unlock_irqrestore(&dev->lock, flags);
155 mod_timer(&dev->timer, jiffies +
156 msecs_to_jiffies(dev->timeperiod));
157 }
158
159 /*
160 * @description : 驅動入口函數
161 * @param : 無
162 * @return : 無
163 */
164 static int __init timer_init(void)
165 {
166 /* 初始化自旋鎖 */
167 spin_lock_init(&timerdev.lock);
168
169 /* 注冊字元裝置驅動 */
170 /* 1、建立裝置号 */
171 if (timerdev.major) { /* 定義了裝置号 */
172 timerdev.devid = MKDEV(timerdev.major, 0);
173 register_chrdev_region(timerdev.devid, TIMER_CNT,
174 TIMER_NAME);
175 } else { /* 沒有定義裝置号 */
176 alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT,
177 TIMER_NAME);
178 timerdev.major = MAJOR(timerdev.devid); /* 擷取主裝置号 */
179 timerdev.minor = MINOR(timerdev.devid); /* 擷取次裝置号 */
180 }
181
182 /* 2、初始化 cdev */
183 timerdev.cdev.owner = THIS_MODULE;
184 cdev_init(&timerdev.cdev, &timer_fops);
185
186 /* 3、添加一個 cdev */
187 cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
188
189 /* 4、建立類 */
190 timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
191 if (IS_ERR(timerdev.class)) {
192 return PTR_ERR(timerdev.class);
193 }
194
195 /* 5、建立裝置 */
196 timerdev.device = device_create(timerdev.class, NULL,
197 timerdev.devid, NULL, TIMER_NAME);
198 if (IS_ERR(timerdev.device)) {
199 return PTR_ERR(timerdev.device);
200 }
201
202 /* 6、初始化 timer,設定定時器處理函數,還未設定周期,所有不會激活定時器 */
203 init_timer(&timerdev.timer);
204 timerdev.timer.function = timer_function;
205 timerdev.timer.data = (unsigned long)&timerdev;
206 return 0;
207 }
208
209 /*
210 * @description : 驅動出口函數
211 * @param : 無
212 * @return : 無
213 */
214 static void __exit timer_exit(void)
215 {
216
217 gpio_set_value(timerdev.led_gpio, 1); /* 解除安裝驅動的時候關閉 LED */
218 del_timer_sync(&timerdev.timer); /* 删除 timer */
219 #if 0
220 del_timer(&timerdev.tiemr);
221 #endif
222
223 /* 登出字元裝置驅動 */
224 cdev_del(&timerdev.cdev); /* 删除 cdev */
225 unregister_chrdev_region(timerdev.devid, TIMER_CNT);
226
227 device_destroy(timerdev.class, timerdev.devid);
228 class_destroy(timerdev.class);
229 }
230
231 module_init(timer_init);
232 module_exit(timer_exit);
233 MODULE_LICENSE("GPL");
234 MODULE_AUTHOR("topeet");
第 30~42 行,定時器裝置結構體,在 40 行定義了一個定時器成員變量 timer。
第 83~94 行,函數 timer_open,對應應用程式的 open 函數,應用程式調用 open 函數打開/dev/timer 驅動檔案的時候此函數就會執行。此函數設定檔案私有資料為 timerdev,并且初始化定時周期預設為 1 秒,最後調用 led_init 函數初始化 LED 所使用的 IO。
第 103~131 行,函數 timer_unlocked_ioctl,對應應用程式的 ioctl 函數,應用程式調用 ioctl函數向驅動發送控制資訊,此函數響應并執行。此函數有三個參數:filp,cmd 和 arg,其中 filp是對應的裝置檔案,cmd 是應用程式發送過來的指令資訊,arg 是應用程式發送過來的參數,在本章例程中 arg 參數表示定時周期。
一共有三種指令 CLOSE_CMD,OPEN_CMD 和 SETPERIOD_CMD,這三個指令分别為關閉定時器、打開定時器、設定定時周期。這三個指令的左右如下:
CLOSE_CMD:關閉定時器指令,調用 del_timer_sync 函數關閉定時器。
OPEN_CMD:打開定時器指令,調用 mod_timer 函數打開定時器,定時周期為 timerdev 的timeperiod 成員變量,定時周期預設是 1 秒。
SETPERIOD_CMD:設定定時器周期指令,參數 arg 就是新的定時周期,設定 timerdev 的
timeperiod 成員變量為 arg 所表示定時周期值。并且使用 mod_timer 重新打開定時器,使定時器以新的周期運作。
第 134~138 行,定時器驅動操作函數集 timer_fops。
第 141~157 行,函數 timer_function,定時器服務函數,此函數有一個參數 arg,在本例程中arg 參數就是 timerdev 的位址,這樣通過 arg 參數就可以通路到裝置結構體。當定時周期到了以後此函數就會被調用。在此函數中将 LED 燈的狀态取反,實作 LED 燈閃爍的效果。因為核心定時器不是循環的定時器,執行一次以後就結束了,是以在 161 行又調用了 mod_timer 函數重新開啟定時器。
第 203~205 行初始化定時器,設定定時器的定時處理函數為 timer_function,另外設定要傳遞給 timer_function 函數的參數為 timerdev。在此函數中并沒有調用 timer_add 函數來開啟定時器,是以定時器預設是關閉的,除非應用程式發送打開指令。
第 220 行調用 del_timer_sync 函數删除定時器,也可以使用 del_timer 函數。
2 應用測試程式編寫
測試程式我們要實作的内容如下:
① 運作程式以後提示我們輸入要測試的指令,輸入 1 表示關閉定時器、輸入 2 表示打開定時器,輸入 3 設定定時器周期。
② 如果要設定定時器周期的話,需要讓使用者輸入要設定的周期值,機關為毫秒。
建立名為 gpioled_timer_test.c 的檔案,然後輸入如下所示内容:
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 #include "sys/ioctl.h"
9
10 /* 指令值 */
11 #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 關閉定時器 */
12 #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打開定時器 */
13 #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 設定定時器周期指令 */
14
15 /*
16 * @description : main 主程式
17 * @param - argc : argv 數組元素個數
18 * @param - argv : 具體參數
19 * @return : 0 成功;其他 失敗
20 */
21 int main(int argc, char *argv[])
22 {
23 int fd, ret;
24 char *filename;
25 unsigned int cmd;
26 unsigned int arg;
27 unsigned char str[100];
28
29 if (argc != 2) {
30 printf("Error Usage!\r\n");
31 return -1;
32 }
33
34 filename = argv[1];
35
36 fd = open(filename, O_RDWR);
37 if (fd < 0) {
38 printf("Can't open file %s\r\n", filename);
39 return -1;
40 }
41
42 while (1) {
43 printf("Input CMD:");
44 ret = scanf("%d", &cmd);
45 if (ret != 1) { /* 參數輸入錯誤 */
46 gets(str); /* 防止卡死 */
47 }
48
49 if(cmd == 1) /* 關閉 LED 燈 */
50 cmd = CLOSE_CMD;
51 else if(cmd == 2) /* 打開 LED 燈 */
52 cmd = OPEN_CMD;
53 else if(cmd == 3) {
54 cmd = SETPERIOD_CMD; /* 設定周期值 */
55 printf("Input Timer Period:");
56 ret = scanf("%d", &arg);
57 if (ret != 1) { /* 參數輸入錯誤 */
58 gets(str); /* 防止卡死 */
59 }
60 }
61 ioctl(fd, cmd, arg); /* 控制定時器的打開和關閉 */
62 }
63 close(fd);
64 }
第 10~13 行,指令值。
第 42~62 行,while(1)循環,讓使用者輸入要測試的指令,然後通過第 61 行的 ioctl 函數發送給驅動程式。如果是設定定時器周期指令 SETPERIOD_CMD,那麼 ioctl 函數的 arg 參數就是使用者輸入的周期值。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPB1ENnRlT6llaOBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5gzN4QDM0kDM3EjMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)