上海网站建设科技公司,重庆建设工程造价管理协会,国内时事新闻,深圳网站建设比较Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 在人类的发展进化中#xff0c;时间是一个非常重要神秘的物质量。任何事物都是在时间的长河中流淌发生、发展、变化。我们进行驱动开发中对时间的定义和使用也是…Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 在人类的发展进化中时间是一个非常重要神秘的物质量。任何事物都是在时间的长河中流淌发生、发展、变化。我们进行驱动开发中对时间的定义和使用也是必须要掌握的重要知识点。 本节笔记主要学习Linux内核定时器的驱动开发主要包括内核时间管理和定时器、硬件原理图分析【LED】、驱动开发和测试。最重要的内容为定时器驱动开发部分。 本笔记的脑图如下 一、Linux时间管理和内核定时器
1.内核时间管理 作为一个应用者不需要研究太深的具体实现。会用相应的API函数即可。 图形化配置界面可以设置系统节拍率。具体路径为 Kernel Features Timer frequency(choice[y]) 使用CONFIG_HZ来设置自己的系统时钟。 HZ表示一秒的节拍数也就是频率。 使用jiffies来记录系统从启动以来的系统节拍数。 jiffies/HZ就是系统运行时间单位为秒。 处理32位jiffies的绕回用到的几个函数。如下
函数 描述time_after(unknown,known)unkonwn超过known时为真time_before(unknown,known)unkonwn没有超过known时为真time_after_eq(unknown,known)unkonwn超过或等于known时为真time_before_eq(unknown,known)unkonwn没有超过或等于known时为真
2.内核定时器 需要周期性处理的工作都要用到定时器。Linux定时器采用系统时钟来实现只需要提供超时时间和定时处理函数即可。 内核定时器超时会自动关闭。 使用timer_list结构体表示内核定时器。该结构体的定义如下
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
定义好定时器以后需要以下函数对其进行初始化
函数说明init_timer初始化add_timer注册del_timer删除del_timer_sync等待其他处理器使用完定时器后删除mod_timer修改定时值 内核定时器的使用流程如下
1 struct timer_list timer; /* 定义定时器 */
2
3 /* 定时器回调函数 */
4 void function(unsigned long arg)
{
6 /*
7 * 定时器处理代码
8 */
9
10 /* 如果需要定时器周期性运行的话就使用 mod_timer
11 * 函数重新设置超时值并且启动定时器。
12 */
13 mod_timer(dev-timertest, jiffies msecs_to_jiffies(2000));
14 }
15
16 /* 初始化函数 */
17 void init(void)
18 {
19 init_timer(timer); /* 初始化定时器 */
20
21 timer.function function; /* 设置定时处理函数 */
22 timer.expiresjffies msecs_to_jiffies(2000);/* 超时时间 2 秒 */
23 timer.data (unsigned long)dev; /* 将设备结构体作为参数 */
24
25 add_timer(timer); /* 启动定时器 */
26 }
27
28 /* 退出函数 */
29 void exit(void)
30 {
31 del_timer(timer); /* 删除定时器 */
32 /* 或者使用 */
33 del_timer_sync(timer);
34 }
3.Linux内核短延时函数
函数 描述 void ndelay(unsigned long nsecs) 纳秒 void udelay(unsigned long usecs) 微秒 void mdelay(unsigned long mseces) 毫秒 二、硬件原理图分析 LED0 接到了 GPIO_3 上 GPIO_3 就是 GPIO1_IO03当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平输出 0 就亮输出 1 就灭。
三、驱动程序开发
1.修改设备树文件 使用已有的即可。
2.定时器驱动程序开发
38 struct timer_dev{
39 dev_t devid; /* 设备号 */
40 struct cdev cdev; /* cdev */
41 struct class *class; /* 类 */
42 struct device *device; /* 设备 */
43 int major; /* 主设备号 */
44 int minor; /* 次设备号 */
45 struct device_node *nd; /* 设备节点 */
46 int led_gpio; /* key 所使用的 GPIO 编号 */
47 int timeperiod; /* 定时周期,单位为 ms */
48 struct timer_list timer; /* 定义一个定时器 */
49 spinlock_t lock; /* 定义自旋锁 */
50 }; 定时器设备结构体在 48 行定义了一个定时器成员变量 timer。
60 static int led_init(void)
61 {
62 int ret 0;
63
64 timerdev.nd of_find_node_by_path(/gpioled);
65 if (timerdev.nd NULL) {
66 return -EINVAL;
67 }
68
69 timerdev.led_gpio of_get_named_gpio(timerdev.nd ,led-gpio,
0);
70 if (timerdev.led_gpio 0) {
71 printk(cant get led\r\n);
72 return -EINVAL;
73 }
74
75 /* 初始化 led 所使用的 IO */
76 gpio_request(timerdev.led_gpio, led); /* 请求 IO */
77 ret gpio_direction_output(timerdev.led_gpio, 1);
78 if(ret 0) {
79 printk(cant set gpio!\r\n);
80 }
81 return 0;
82 } LED 灯初始化函数从设备树中获取 LED 灯信息然后初始化相应的 IO。
91 static int timer_open(struct inode *inode, struct file *filp)
92 {
93 int ret 0;
94 filp-private_data timerdev; /* 设置私有数据 */
95
96 timerdev.timeperiod 1000; /* 默认周期为 1s */
97 ret led_init(); /* 初始化 LED IO */
98 if (ret 0) {
99 return ret;
100 }
101 return 0;
102 } 函数 timer_open对应应用程序的 open 函数应用程序调用 open 函数打开/dev/timer 驱动文件的时候此函数就会执行。此函数设置文件私有数据为 timerdev并且初始化定时周期默认为 1 秒最后调用 led_init 函数初始化 LED 所使用的 IO。
111 static long timer_unlocked_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
112 {
原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com
1228
I.MX6U 嵌入式 Linux 驱动开发指南
113 struct timer_dev *dev (struct timer_dev *)filp-private_data;
114 int timerperiod;
115 unsigned long flags;
116
117 switch (cmd) {
118 case CLOSE_CMD: /* 关闭定时器 */
119 del_timer_sync(dev-timer);
120 break;
121 case OPEN_CMD: /* 打开定时器 */
122 spin_lock_irqsave(dev-lock, flags);
123 timerperiod dev-timeperiod;
124 spin_unlock_irqrestore(dev-lock, flags);
125 mod_timer(dev-timer, jiffies
msecs_to_jiffies(timerperiod));
126 break;
127 case SETPERIOD_CMD: /* 设置定时器周期 */
128 spin_lock_irqsave(dev-lock, flags);
129 dev-timeperiod arg;
130 spin_unlock_irqrestore(dev-lock, flags);
131 mod_timer(dev-timer, jiffies msecs_to_jiffies(arg));
132 break;
133 default:
134 break;
135 }
136 return 0;
137 } 函数 timer_unlocked_ioctl对应应用程序的 ioctl 函数应用程序调用 ioctl函数向驱动发送控制信息此函数响应并执行。此函数有三个参数 filp cmd 和 arg其中 filp是对应的设备文件 cmd 是应用程序发送过来的命令信息 arg 是应用程序发送过来的参数在本章例程中 arg 参数表示定时周期。 CLOSE_CMD 关闭定时器命令 调用 del_timer_sync 函数关闭定时器。OPEN_CMD打开定时器命令调用 mod_timer 函数打开定时器定时周期为 timerdev 的 timeperiod 成员变量定时周期默认是 1 秒。SETPERIOD_CMD设置定时器周期命令参数 arg 就是新的定时周期设置 timerdev 的 timeperiod 成员变量为 arg 所表示定时周期指。并且使用 mod_timer 重新打开定时器使定时器 以新的周期运行。
139 /* 设备操作函数 */
140 static struct file_operations timer_fops {
141 .owner THIS_MODULE,
142 .open timer_open,
143 .unlocked_ioctl timer_unlocked_ioctl,
144 }; 定时器驱动操作函数集 timer_fops。
146 /* 定时器回调函数 */
147 void timer_function(unsigned long arg)
148 {
149 struct timer_dev *dev (struct timer_dev *)arg;
150 static int sta 1;
151 int timerperiod;
152 unsigned long flags;
153
154 sta !sta; /* 每次都取反实现 LED 灯反转 */
155 gpio_set_value(dev-led_gpio, sta);
156
157 /* 重启定时器 */
158 spin_lock_irqsave(dev-lock, flags);
159 timerperiod dev-timeperiod;
160 spin_unlock_irqrestore(dev-lock, flags);
161 mod_timer(dev-timer, jiffies
msecs_to_jiffies(dev-timeperiod));
162 } 函数 timer_function定时器服务函数此函有一个参数 arg在本例程中arg 参数就是 timerdev 的地址这样通过 arg 参数就可以访问到设备结构体。当定时周期到了以后此函数就会被调用。在此函数中将 LED 灯的状态取反实现 LED 灯闪烁的效果。因为内核定时器不是循环的定时器执行一次以后就结束了因此在 161 行又调用了 mod_timer 函数重新开启定时器。
169 static int __init timer_init(void)
170 {
171 /* 初始化自旋锁 */
172 spin_lock_init(timerdev.lock);
173
174 /* 注册字符设备驱动 */
175 /* 1、创建设备号 */
176 if (timerdev.major) { /* 定义了设备号 */
177 timerdev.devid MKDEV(timerdev.major, 0);
178 register_chrdev_region(timerdev.devid, TIMER_CNT,
TIMER_NAME);
179 } else { /* 没有定义设备号 */
180 alloc_chrdev_region(timerdev.devid, 0, TIMER_CNT,
TIMER_NAME);
181 timerdev.major MAJOR(timerdev.devid); /* 获取主设备号 */
182 timerdev.minor MINOR(timerdev.devid); /* 获取次设备号 */
183 }
184
185 /* 2、初始化 cdev */
186 timerdev.cdev.owner THIS_MODULE;
187 cdev_init(timerdev.cdev, timer_fops);
188
189 /* 3、添加一个 cdev */
190 cdev_add(timerdev.cdev, timerdev.devid, TIMER_CNT);
191
192 /* 4、创建类 */
193 timerdev.class class_create(THIS_MODULE, TIMER_NAME);
194 if (IS_ERR(timerdev.class)) {
195 return PTR_ERR(timerdev.class);
196 }
197
198 /* 5、创建设备 */
199 timerdev.device device_create(timerdev.class, NULL,
timerdev.devid, NULL, TIMER_NAME);
200 if (IS_ERR(timerdev.device)) {
201 return PTR_ERR(timerdev.device);
202 }
203
204 /* 6、初始化 timer设置定时器处理函数,还未设置周期所有不会激活定时器 */
205 init_timer(timerdev.timer);
206 timerdev.timer.function timer_function;
207 timerdev.timer.data (unsigned long)timerdev;
208 return 0;
209 } 第 169~209 行函数 timer_init驱动入口函数。在第 205~207 行初始化定时器设置定时器的定时处理函数为 timer_function另外设置要传递给 timer_function 函数的参数为 timerdev的地址。在此函数中并没有调用 timer_add 函数来开启定时器因此定时器默认是关闭的除非应用程序发送打开命令。
216 static void __exit timer_exit(void)
217 {
218
219 gpio_set_value(timerdev.led_gpio, 1); /* 卸载驱动的时候关闭 LED */
220 del_timer_sync(timerdev.timer); /* 删除 timer */
221 #if 0
222 del_timer(timerdev.tiemr);
223 #endif
224
225 /* 注销字符设备驱动 */
226 cdev_del(timerdev.cdev); /* 删除 cdev */
227 unregister_chrdev_region(timerdev.devid, TIMER_CNT);
228
229 device_destroy(timerdev.class, timerdev.devid);
230 class_destroy(timerdev.class);
231 } 驱动出口函数在 219 行关闭 LED也就是卸载驱动以后 LED 处于熄灭状态。第 220 行调用 del_timer_sync 函数删除定时器也可以使用 del_timer 函数。
3.编写测试APP
21 /* 命令值 */
22 #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 关闭定时器 */
23 #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打开定时器 */
24 #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令 */
25
26 /*
27 * description : main 主程序
28 * param - argc : argv 数组元素个数
29 * param - argv : 具体参数
30 * return : 0 成功;其他 失败
31 */
32 int main(int argc, char *argv[])
33 {
34 int fd, ret;
35 char *filename;
36 unsigned int cmd;
37 unsigned int arg;
38 unsigned char str[100];
39
40 if (argc ! 2) {
41 printf(Error Usage!\r\n);
42 return -1;
43 }
44
45 filename argv[1];
46
47 fd open(filename, O_RDWR);
48 if (fd 0) {
49 printf(Cant open file %s\r\n, filename);
50 return -1;
51 }
52
53 while (1) {
54 printf(Input CMD:);
55 ret scanf(%d, cmd);
56 if (ret ! 1) { /* 参数输入错误 */
57 gets(str); /* 防止卡死 */
58 }
59
60 if(cmd 1) /* 关闭 LED 灯 */
61 cmd CLOSE_CMD;
62 else if(cmd 2) /* 打开 LED 灯 */
63 cmd OPEN_CMD;
64 else if(cmd 3) {
65 cmd SETPERIOD_CMD; /* 设置周期值 */
66 printf(Input Timer Period:);
67 ret scanf(%d, arg);
68 if (ret ! 1) { /* 参数输入错误 */
69 gets(str); /* 防止卡死 */
70 }
71 }
72 ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */
73 }
74 close(fd);
75 } ①、运行 APP 以后提示我们输入要测试的命令输入 1 表示关闭定时器、输入 2 表示打开定时器输入 3 设置定时器周期。 ②、如果要设置定时器周期的话需要让用户输入要设置的周期值单位为毫秒。
第 22~24 行命令值。 第 53~73 行 while(1)循环让用户输入要测试的命令然后通过第 72 行的 ioctl 函数发送给驱动程序。如果是设置定时器周期命令 SETPERIOD_CMD那么 ioctl 函数的 arg 参数就是用户输入的周期值。 四、运行测试
1.编译驱动程序和测试AP 编译成功以后就会生成一个名为“ timer.ko”的驱动模块文件。
编译成功以后就会生成 timerApp 这个应用程序。 2.运行测试
根据提示输入指令及周期观察LED的交替亮变化即可。
五、总结 本节笔记主要学习Linux内核定时器的驱动开发主要包括内核时间管理和定时器、硬件原理图分析【LED】、驱动开发和测试。最重要的内容为定时器驱动开发部分。 本文为参考正点原子开发板配套教程整理而得仅用于学习交流使用不得用于商业用途。