Linux:定时器

 引子:使用OK6410 通过 IMU_EV30 采集 ADIS16405 的数据,采集频率为100Hz,需要10ms的定时器。首先考虑了a POSIX per-process timer,后发现板子负担轻的时候还行,负担重了定时很不精确,最后使用PWM定时器解决了问题。现总结一下。

1、a POSIX per-process timer

include <signal.h> // 信号
include<time.h>
include<sys/time.h>

timer_t timer; //定时器 a POSIX per-process timer ,timer_t其实是个long型

struct sigevent evp; // 事件,定时器到期时的事件
memset(&evp, 0, sizeof(evp))
evp.sigev_notify = SIGEV_THREAD;
       SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。
       SIGEV_SIGNAL: 当定时器到期,内核会将sigev_signo所指定的信号传送给进程。
       SIGEV_THREAD: 当定时器到期,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为一个参数。
evp.sigev_notify_function = writeCom; //线程函数:void  writeCom (union sigval v)
evp.sigev_value.sival_int = 3; //作为,函数:void  writeCom (union sigval v) 的参数

ret = timer_create(CLOCK_REALTIME, &evp, &timer); // create a POSIX per-process timer
       CLOCK_REALTIME :Systemwide realtime clock. 实时时钟
       CLOCK_MONOTONIC:Represents monotonic time. Cannot be set.
       CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer.
       CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.
       CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME.
       CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC
       如果evp为NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。struct sigevent 结构中的成员evp->sigev_notify说明了定时器到期时应该采取的行动。

struct itimerspec ts; //定义时间间隔
ts.it_interval.tv_sec = 0;
ts.it_interval.tv_nsec = 10000000; //纳秒 e-9s
ts.it_value.tv_sec = 3; //定时器初始值
ts.it_value.tv_nsec = 0;
ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL); //配置并开启
如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。
如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。

2、PWM定时器

需要设置PWM寄存器的值,寄存器的相关说明s3c6410手册中很详细,这里重点说一下实现过程。先来看一下需要解决的问题:
1. 如何对寄存器进行操作
2. 如何注册中断函数
3. 前两个问题不能在用户程序中解决,那么就有了第3个问题,如何编写类似驱动的东西,即内核模块开发
其实不想一下子学习这么多,但是没办法一个定时器引出了这么多问题。

2.1 内核模块的框架

详见:文章《Linux:编译内核模块(来自国嵌的视频教学)》(http://blog.csdn.net/leaglave_jyan/article/details/6652435)

2.2 读写寄存器

static unsigned long PWM_Base_addr;
PWM_Base_addr = (unsigned long)ioremap(ox7F006000, ox44); // 将ox7F006000 开始的 ox44 个字节映射到内存,将映射后的地址赋给PWM_Base_addr

例如:对于寄存器 TCON

volatile unsigned int *pTCON = (volatile unsigned int *)(PWM_Base_addr  + ox08);
使用 *pTCON &= oxFFFFFFF0; 对其进行操作

2.3 配置并开启定时器

2.3.1 分频

不考虑使用外部时钟的情况,定时器使用的时钟为:PCLK(开发板启动时会在终端显示PCLK的频率)
PCLK会通过经过两次分频,降低频率供定时器使用。
第一级分频使用8位时钟预定标器进行预分频,对于s3c6410定时器0和定时器1共用一个预定标器,定时器2、3、4共用第二个,预分频后的输出将会进入第二级分频器。
第二级的分频器是针对每个定时器的,并不像预分频存在共用的情况,但是对于每个定时器第二级的分频只有5种选择:2分频、4分频、8分频、16分频和使用外部时钟
定时器输入时钟频率 = PCLK/(预定标器值+1)/(第二级的分频值)
预定标器1和预定标器2的值由寄存器TCFG0设定
第二级分频由寄存器TCFG1设定

2.3.2 定时器工作过程 (转自http://blog.csdn.net/luoamforever/article/details/5483772

1、设定TCMPBn、TCNTBn两个寄存器,它们表示定时器n的比较值,初始计数值;
2、启动定时器n,通过设置TCON,TCMPBn、TCNTBn的值被装入TCMPn、TCNTn中,在定时器n的工作频率下,TCNTn开始减1计数,其值可以通过TCNTOn寄存器读取;
3、当TCNTn的值等于TCMPn的值时,定时器n的输出管脚TOUTn反转;TCNTn继续减1计数;
4、当TCNTn的值到达0时,其输出管脚TOUTn再次反转,并触发定时器n的中断(如果中断使能);
5、如果在TCON寄存器中将定时器n设为“自动加载”,则TCMPBn和TCNTBn寄存器的值被自动装入TCMPn和TCNTn中,开始下一个计数流程。

2.3.3 相关代码

使用定时器0,经读TCON寄存器发现定时器4在使用着
*pTCFG0 |= ox7C; // 定时器0 预分频设为124
*pTCFG1 |= ox03; //定时器0 8分频,分频后 PCLK/(124+1)/8 = 65500Hz
*pTCNTB0 = 664; // 定时间隔 10ms ,特别注意是664不是665,好多地方没讲到,一开始使用665一直觉得差一点,结果发现了定时器的频率为 65500/(664+1)。
*pTCON |= ox02; // 启动手动更新
*pTCON &= 0xFFFFFFFD; //关闭手动更新
*pTCON |= ox09; // 启动自动更新
*pTINT_CSTAT |= 0x01; //定时器0中断使能

2.4 中断处理函数注册

ret = request_irq(IRQ_TIMER0, timer_handl, IRQF_DISABLED,"Timer_0",NULL);
IRQ_TIMER0 定时器0 的中断号,Timer_0 定时器名称。
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
*                registered first in an shared interrupt is considered for
*                performance reasons)
static irqreturn_t timer_handl(int irq, void *dev_id, struct pt_regs *regs); // 中断处理函数
中断程序的返回值是一个特殊类型——irqreturn_t。但是中断程序的返回值却只有两个值IRQ_NONE和IRQ_HANDLED。
IRQ_NONE:中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号;
IRQ_HANDLED:接收到了准确的中断信号,并且作了相应正确的处理。

irqreturn.h 中有如下宏定义
typedef int irqreturn_t
 #define IRQ_NONE (0)
#define IRQ_ HANDLED (1)

中断处理程序:断处理程序不能访问用户空间地址,访问用户空间的代码都是可能导致睡眠的,因为没人保证用户空间的页面就在内存中,可能被交换出去了,这样在临界区当然不能访问

2.5 设备注册和添加

#define MYTIMER_MAJOR 235
static stuct file_operations timer_fops = {
      .ower = THIS_MODULE,
      .ioctl = timer_ioctl,
};  // 这里仅实现了一个函数ioctl (),在函数ioctl ()中实现对定时器寄存器的操作和定时中断的注册,虽然这没有open,但是可以调用open打开设备,并且始终会成功打开。

dev_t devno = MKDEV(MYTIMER_MAJOR, 0);
ret = register_chrdev_region(devno, 1 ,"timer");

struct cdev cdev;
sruct class *PWM_Class;
cdev_init(&cdev, &timer_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &timer_fops;
cdev_add(&cdev, devno, 1); 

PWM_Class = class_create(THIS_MODULE;, "PWMTimerclass");
device_create(PWM_Class, NULL, MKDEV(MYTIMER_MAJOR, 0), NULL, "timer%d", 0);// 这里的 timer%d 将会出现在开发板 /dev 目录下,即/dev/timer0

相反的操作

device_destroy(PWM_Class, MKDEV(MYTIMER_MAJOR, 0),);
class_destroy(PWM_Class);
cdev_del(&cdev);
unregister_chrdev_region(MKDEV(MYTIMER_MAJOR, 0), 1);

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值