最近在项目中遇到需要定时1ms,用msleep定时不准,误差有时候很大;用mdelay又会忙等待,在延迟过程中无法运行其他任务;所以只能自己写一个定时器驱动了。
平台用的是s3c2440cpu ,内核linux2.6.32.2 ,编译器arm-linux-gcc-4.3.2
根据cpu手册,设置
Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
{prescaler value} = 0~255
{divider value} = 2, 4, 8, 16
所以定时范围为0.04us---5.3686 sec 计数寄存器只有16位,感觉太小了
为了和大家分享贴出来,主要是根据网上的资料(http://www.arm9home.net/read.php?tid-3113-page-1.html)和数据手册写了一个自己项目使用的timer0驱动:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/hardware.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>
#include <mach/regs-gpio.h>
#include <mach/regs-irq.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#include <plat/regs-timer.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
//#include <linux/timer0_ioc.h>
#include "timer0_ioc.h"
#define DEVICE_NAME "timer0"
static wait_queue_head_t ioctl_timer_ready;
unsigned long Ftclk,Fpclk=50000000; //s3c2440a的默认Fpclk为50MHz
unsigned int tcfg0,tcfg1,tcon;
int sleep_flag=0;
struct cdev *p_cdev; //声明一个指向字符设备结构体的指针
#define timer_irq IRQ_TIMER0
static irqreturn_t timer_interrupt(void)
{
printk("Timer0 interrupt occured!\n");
sleep_flag = 1;
wake_up_all(&ioctl_timer_ready);
return IRQ_HANDLED;
}
static int timer_open(struct inode *inode,struct file *filp)
{
int ret;
tcfg0 = inl(S3C2410_TCFG0);
tcfg1 = inl(S3C2410_TCFG1);
tcon = inl(S3C2410_TCON);
outl((tcfg0 &= ~0xff) | 255,S3C2410_TCFG0); //设置预分频
outl((tcfg1 &= ~0xf) | 3,S3C2410_TCFG1); //设置分频和模式
Ftclk=Fpclk/(255+1)/16; //参考datasheet公式
outl(Ftclk,S3C2410_TCNTB(0)); //写入定时初值
outl(0,S3C2410_TCMPB(0)); //写入终点比较值
outl(tcon | S3C2410_TCON_T0MANUALUPD,S3C2410_TCON); //手动刷新一次,将数据装入TCNT和TCMP
tcon = inl(S3C2410_TCON) & ~S3C2410_TCON_T0MANUALUPD;
outl(tcon | (S3C2410_TCON_T0START|S3C2410_TCON_T0RELOAD),S3C2410_TCON); //设置自动装载初值,开始计数
printk(KERN_EMERG "Register IRQ_TIMER0 successfully!\n");
ret=request_irq(timer_irq,&timer_interrupt,IRQF_DISABLED,DEVICE_NAME,NULL);
if(ret<0){
printk(KERN_EMERG "Register IRQ_TIMER0 failed!\n");
return ret;
}
}
int timer_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
short int b_data;
int ret=0;
switch(cmd) {
case READ_TIMER_INT_FLAG: //读取中断
wait_event_interruptible(ioctl_timer_ready, (sleep_flag == 1));
sleep_flag = 0;
ret = copy_to_user((u8 *)(arg),(u8 *)(&ret), 4);
break;
case SET_TIMER_TIME:
copy_from_user((u8 *)(&b_data),(u8 *)arg, 2);
Ftclk = (Ftclk*(unsigned long)b_data)/1000;
outl(Ftclk,S3C2410_TCNTB(0)); //写入定时初值
outl(0,S3C2410_TCMPB(0)); //写入终点比较值
outl(tcon | S3C2410_TCON_T0MANUALUPD,S3C2410_TCON);
tcon = inl(S3C2410_TCON) & ~S3C2410_TCON_T0MANUALUPD;
outl(tcon | (S3C2410_TCON_T0START|S3C2410_TCON_T0RELOAD),S3C2410_TCON); //设置自动装载初值,开始计数
printk(KERN_EMERG "timer's time is %ld\r\n",Ftclk);
break;
default:
printk("no ioctl code now\n");
return 1;
}
return ret;
}
static int timer_close(struct inode *inode,struct file *filp)
{
free_irq(timer_irq,NULL);
return 0;
}
static struct file_operations timer_fops={
.owner=THIS_MODULE,
.open=timer_open,
.ioctl = timer_ioctl,
.release=timer_close,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &timer_fops,
};
static int __init dev_init(void)
{
int ret;
ret = misc_register(&misc);
init_waitqueue_head(&ioctl_timer_ready);
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HJW");
module_init(dev_init);
module_exit(dev_exit);
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include "timer0_ioc.h"
int main(void)
{
int fd;
short int time = 3000;
int arg;
int ioctl_ret;
fd=open("/dev/timer0",O_RDWR);
if(fd<0){
printf("Open /dev/timer failed!\n");
exit(1);
}
else
{
printf("Open device successfully!\n");
ioctl(fd,SET_TIMER_TIME,&time);
}
while(1)
{
ioctl_ret=ioctl(fd,READ_TIMER_INT_FLAG,&arg);
if(ioctl_ret == -108)
{
printf("ioctl failed!\r\n");
}
else
{
printf("This is interrupt!\r\n");
}
}
close(fd);
return 0;
}
这次经验告诉我:
us,ms级定时必须用硬件定时,当然如果你在定时期间没有其他任务需要执行可以使用udelay,mdelay,也可以。
s级定时直接可以用sleep了。
本人初出茅庐,欢迎提出意见,共同交流进步。