使用内核timer实现软件PWM——S3C6410 嵌入式linux开发
内核定时器使用
内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 <Linux/timer.h> 和 kernel/timer.c 文件中。
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:
-
没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。
-
不能执行休眠(或可能引起休眠的函数)和调度。
-
任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_base *base;
/* ... */
};
其中 expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。base 字段是内核内部实现所用的。需要注意的是 expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。
定时器配置方法
- 使用宏
DEFINE_TIMER(timer_name, function_name, expires_value, data);
2.配置timer_list 结构体
struct timer_list mytimer;
void init_mytimer(void){
init_timer(&mytimer);
mytimer.data = 5;
mytimer.expires = jiffies +usecs_to_jiffies(50);
mytimer.function = my_timer_callback;
// add_timer(&mytimer); // add_timer()是一个timer注册函数,这里如果执行add_timer(),则启动定时器。先不启动。
return;
}
3.动态修改定时器的值
重新注册(修改)
要修改一个定时器的调度时间,可以通过调用 mod_timer(struct timer_list *timer, unsigned long expires) 。mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。
mod_timer(&mytimer,jiffies + usecs_to_jiffies(timer_value)); //这里定时时间是timer_value微秒
4.注销内核timer
注销一个定时器,可以通过 del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer) 。
其中 del_timer_sync 是用在 SMP 系统上的(在非SMP系统上,它等于del_timer),当要被注销的定时器函数正在另一个 cpu 上运行时,del_timer_sync() 会等待其运行完,所以这个函数会休眠。另外还应避免它和被调度的函数争用同一个锁。对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。
5.定时器状态获取
timer_pending是用来判断一个处在定时器管理队列中的定时器对象是否已经被调度执行,add_timer只是把一个定时器对象加入到内核 的管理队列,但是何时执行实际上由时钟中断(更确切地,是内核在时钟中断的softirq部分才开始扫描定时器管理队列),一个定时器对象pending 意味着它的回调函数尚未被调度执行,而一旦一个定时器对象被调度执行,之后它将被从定时器管理队列中摘除,除非它再次被提交。
int timer_pending(const struct timer_list *timer); //返回0 则没被调度执行,1 为正在被调度执行
使用的注意事项(本人的经验)
我这次使用了4个内核timer输出四路软件PWM信号控制电机,并且编写了应用程序,用过蓝牙控制电机的转速,后来发现在应用层使用时,电机只要转起来就不听指挥,一直都是一个速度,改变不了。找了半天错误,发现原来是我四个timer_list mytimer ;(定义timer 结构体时四个名称一样,导致的)这里的错误。
因为我在内核层编写驱动时,是用的四个驱动文件,每个驱动文件都声明了一个 timer_list 结构体,并且名字相同,当初以为文件不同不会影响,所以没理他,结果发现这样不行。也就是说,timer_list 的名字是不能相同的,即使是在不同的内核文件中定义。应该Linux内核初始化timer时 是使用了这个名称的。
最后附上我的驱动文件源码(供参考 tiny6410 linux-2.6.38)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <plat/regs-timer.h>
#include <mach/regs-irq.h>
#include <asm/mach/time.h>
#include <linux/clk.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio-bank-e.h>
#include <mach/gpio-bank-f.h>
#include <mach/gpio-bank-k.h>
#include <mach/gpio-bank-g.h>
#define DEVICE_NAME "sizhou_pwm"
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
//static unsigned long timer_thre = 0;
//static unsigned long timer_duty;
static struct semaphore lock;// 定义互斥信号量,用于pwm设备至多只能被一个进程打开
static int timer_value = 500;
static int timer_value2 = 10000 - 500;
static struct timer_list mytimer;
static bool select = false;
static void start_timer(bool ff);
void my_timer_callback(unsigned long tmp ){
if(timer_value ==0) {
tmp = readl(S3C64XX_GPFDAT);
tmp &= ~(0x01<<14);
__raw_writel(tmp,S3C64XX_GPFDAT);
return ;
}
if(select ==false){
start_timer(false);
select = true;
printk("in the pwm 1\n");
}
else{
start_timer(true);
select = false;
printk("in the pwm 0\n");
}
printk("in callback\n");
}
void init_mytimer(void){
init_timer(&mytimer);
mytimer.data = 5;
mytimer.expires = jiffies +usecs_to_jiffies(50);
mytimer.function = my_timer_callback;
// add_timer(&mytimer);
return;
}
void start_timer(bool thre){
int ret;
unsigned long tmp;
if(timer_value ==0) return;
if(thre){
tmp = readl(S3C64XX_GPFDAT);
tmp |= (0x01<<14);
__raw_writel(tmp,S3C64XX_GPFDAT);
printk("writel reg\n");
ret = mod_timer(&mytimer,jiffies + usecs_to_jiffies(timer_value));
printk("mod time\n");
}else{
tmp = readl(S3C64XX_GPFDAT);
tmp &= ~(0x01<<14);
__raw_writel(tmp,S3C64XX_GPFDAT);
ret = mod_timer(&mytimer,jiffies + usecs_to_jiffies(timer_value2));
}
return;
}
static void PWM_Set_Freq( unsigned long freq, unsigned long duty )
{
timer_value = 10*duty;
timer_value2 = 10000 - timer_value;
printk("timer_value\n");
start_timer(true);
printk("start timer \n");
}
void PWM_Stop( void )
{
unsigned long tmp;
del_timer(&mytimer);
tmp = readl(S3C64XX_GPFCON);
tmp &= ~(0x3U << 28);
tmp |= (0x00U <<28 );
writel(tmp, S3C64XX_GPFCON);
}
static int s3c64xx_pwm_open(struct inode *inode, struct file *file)
{
init_mytimer();
if (!down_trylock(&lock))
return 0;
else
return -EBUSY;
}
static int s3c64xx_pwm_close(struct inode *inode, struct file *file)
{
unsigned long tmp;
del_timer(&mytimer);
tmp = readl(S3C64XX_GPFCON);
tmp &= ~(0x3U << 28);
tmp |= (0x0U << 28);
writel(tmp, S3C64XX_GPFCON);
up(&lock);
return 0;
}
static long s3c64xx_pwm_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
// static int fre =1000;
switch (cmd) {
case PWM_IOCTL_SET_FREQ:
PWM_Set_Freq(arg,0);
fre = arg;
break;
case 3:
PWM_Set_Freq(fre,arg); //这个函数 参数只用3
break;
case PWM_IOCTL_STOP:
PWM_Stop();
default:
PWM_Stop();
break;
}
return 0;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = s3c64xx_pwm_open,
.release = s3c64xx_pwm_close,
.unlocked_ioctl = s3c64xx_pwm_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init dev_init(void)
{
int ret1;
unsigned long tmp;
tmp = readl(S3C64XX_GPFCON); //gpf14 == pwm
tmp &= ~(0x3U << 28);
tmp |= (0x1U << 28);
writel(tmp, S3C64XX_GPFCON);
tmp = readl(S3C64XX_GPFDAT);
tmp &= ~(0x01<<14);
__raw_writel(tmp,S3C64XX_GPFDAT);
sema_init(&lock, 1);
ret1 = misc_register(&misc);
printk("requst successfully~~~\n");
printk (DEVICE_NAME"\tinitialized\n");
return ret1;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");
MODULE_DESCRIPTION("S3C6410 PWM Driver");