Linux:定時器

來源:互聯網
上載者:User

 引子:使用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);

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.