1. 程式人生 > >定時器,看門狗&RTC

定時器,看門狗&RTC

定時器作為Soc中的常見外設,和其他外設並沒有什麼不同,通常和計數器聯絡在一起,定時器常用來實現定時執行程式碼,相當於Soc的鬧鐘,可以讓Soc具有計時功能。

定時器原理

定時器通過內部的計數器的計數來實現的,計數器根據時鐘頻率來工作,時鐘源來自APB匯流排,通過時鐘模組的分頻器分頻之後到達計數器,每個時鐘週期計一個數,定時器的時間就是計數器的計數值*時鐘週期,定時器中有一個TCNT暫存器,計時開始時放入一個總的計數值,每個時鐘週期減1,當TCNT為0的時候就會觸發定時器中斷,定時時間由兩個因素決定:

  • TCNT計數值
  • 時鐘週期

例如,如果計數值為1000,時鐘週期為1ms,則定時時間為1s。

定時器和其他外設的關係

看門狗本質上是一個定時器,不同在於定時時間到了之後會復位CPU,RTC是實時時鐘,可以知道實時時間。蜂鳴器是一個發聲裝置,在ARM中蜂鳴器是由定時器驅動的。

S5PV210中的定時器

PWM定時器

PWM定時器是最常用的定時器,如果沒有特指,定時器就指的是PWM定時器,Soc需要使用PWM定時器產生PWM訊號。

系統定時器

用來產生固定時間間隔(TCNT * 時鐘頻率),稱為SYSTIC,用來為作業系統提供週期性的TIC訊號,用於驅動作業系統訊號。

看門狗

看門狗可以設定在定時時間到了之後產生中斷或者發出復位訊號來複位CPU

RTC

實時時鐘的用處是隨時可以知道當前的時間點。

PWM定時器

PWM定時器的最大特點是可以產生PWM波形,S5PV210有5個PWM定時器,0,1,2,3各自對應一個GPIO,可以通過這些GPIO產生並輸出波形訊號,4沒有對應的外部GPIO,因此不能產生波形,只能用於產生內部定時器中斷,定時器使用APB-PCLK時鐘源,PCLK-PSYS,0,1共同使用一個8位預分頻器,2,3,4共同使用另外一個8位的預分頻器,每一個定時器都有一個私有的分頻器,預分頻器和分頻器構成了兩級分頻,將PCLK-PSYS時鐘源生成的時鐘供給給定時器作為時鐘週期,PWM的時鐘系統如下:
PWM時鐘系統
從圖中可以看到

  • PWM使用PCLK作為時鐘源,可以說其掛載在APB總線上。
  • PRESCALER就是預分頻器,可見有兩個預分頻器,分別被0,1和2,3,4共享,預分頻器為8位,分頻比率就是1-256,
  • MUX就是每個PWM定時器的分頻器,是一個多選一的開關,分頻器為私有的,為每個定時器單獨分配頻率
  • ControlLogic為每個PWM定時器的控制單元,也就代表了該定時器,
  • TCNTB就是該定時器的計數器暫存器,
  • TCMPB是另外一個暫存器
  • DeadZoneGenerator,死區生成器

PWM定時器分析

預分頻器和分頻器是串聯的,所以分頻數是兩級的乘積,分頻係數分別在TCFG0和TCFG1兩個暫存器中設定,根據使用者手冊,TCFG0中的0-7位設定PRESCALER0預分頻器,8-15位設定PRESCALER1預分頻器,範圍是1-256,分頻器是一個MUX開關,多選一開關決定了走哪個分頻係數,分別是1/1,1/2,1/4,1/8,1/16,在PCLK-PSYS在66MHz的情況下,兩級分頻後的時鐘週期範圍是,0.030μm~62.061μm,結合TCNTB的設定,能定出來的時間最長為266548.27s,相當於74個小時.

TCNT&TCNTB是相對應的,TCMP&TCMPB是相對應的,TCNTB是有地址的暫存器,供程式設計師操作,TCNT沒有暫存器地址,程式設計師不能訪問,在內部與TCNTB相對應,負責減1,是一對影子暫存器,相對於TCMP&TCMPB也是一樣的道理。TCNTO暫存器也和TCNT相對應,有暫存器地址,是用來讀TCNT暫存器的。

工作流程是:

  • 事先算好TCNT中要減的數,然後將其寫入TCNTB暫存器中
  • 在啟動Timer前,將TCNTB中的值刷入TCNT暫存器中,
  • 然後啟動定時器開始計時,在計時過程中如果想知道TCNT暫存器中的值,可以讀取TCNTO暫存器中的值。

定時器工作的時候,一次定時算一個工作迴圈,預設單迴圈工作,如果需要多迴圈工作,就可以使用自動裝載機制,定時器一個週期到了之後會自動從TCNTB中裝載值到TCNT中再次啟動定時器繼續迴圈。

定時只需要TCNT,TCNTB兩個暫存器即可,TCNTO通常用於捕獲計時,TCMP和TCMPB用於生成PWM波形。

PWM波

PWM波是一個週期性波形,在每一個週期內波形完全相同,每個週期內由一個高電平和一個低電平組成,TCMPB決定了PWM波的佔空比,TCNTB決定了PWM波的週期,需要注意的是PWM定時器用來產生PWM波形時不需要中斷干預

死區生成器

PWM用在功率電路中對交流電壓進行整流的時候,用Soc的GPIO生成的PWM波形分別驅動兩路整流的IGBT,要求不能同時輸出高電平或者同時輸出低電平,所以可以留死區來實現,S5PV210中的死區生成器一旦開啟,生產出來的PWM波形就自帶了死區。

PWM定時器程式設計驅動蜂鳴器

用PWM波的電壓訊號驅動蜂鳴器,把PWM波形的週期設定為1/要發出聲音訊率,佔空比只要確保能驅動蜂鳴器即可。

蜂鳴器的引腳為PWMTOUT2,對應核心板上GPD0_2,GPD0_2位於GPD0CON暫存器(0xE020_00A0)地址的8-11位上,我們把它設定為0010表示TOUT_2模式,就是PWM波形輸出模式,而且可以看出使用的是Timer2定時器。

Timer2相關的暫存器有:
TCFG0,TCFG1,CON,TCNTB,TCMPB,TCNTO2。

程式碼如下:

#define GPD0CON 0xE02000A0
#define TCFG0 0xE2500000
#define TCFG1 0xE2500004
#define CON 0xE2500008
#define TCNTB2 0xE2500024
#define TCMPB2 0xE2500028

#define rGPD0CON *((int *)GPD0CON)
#define rTCFG0 *((int *)TCFG0)
#define rTCFG1 *((int *)TCFG1)
#define rCON *((int *)CON)
#define rTCNTB2 *((int *)TCNTB2)
#define rTCMPB2 *((int *)TCMPB2)


// 初始化PWM timer2,輸出PWM波形,頻率是2KHz,duty為50%
void timer2_pwm_init(void) {
    // 設定GPD0_2引腳。配置為XpwmTOUT2
    rGPD0CON &= ~(0xf<<8);
    rGPD0CON |= (2<<8);
    // 配置PWM定時器引數

    rTCFG0 &= ~(0xff<<8);  // 預分頻器prescaler1的8-15位
    rTCFG0 |= (65<<8);      // 設定預分頻器分頻後的頻率為1MHz

    rTCFG1 &= ~(0x0f<<8);  // 分頻器MUX2的8-15位
    rTCFG1 |= (1<<8);       // 1/2 分頻後為500KHz

    rTCNTB2 = 250;          // 2KHz = 0.5ms/2μm = 250
    rTCMPB2 = 120;          // duty = 50%

    // 第一次需要手動將TCNTB的值重新整理到TCNT中去,以後就可以auto-reload了
    rCON |= (1<<13);        // 刷完之後就關閉即可

    // 開啟自動裝載,使波形反覆發出
    rCON |= (1<<15);
    // 開啟timer2定時器
    rCON |= (1<<12);
}

看門狗

看門狗定時器和普通定時器並無本質區別,看門狗可以設定一個時間,在這個時間到之前會不斷計時,在時間到之後會不斷重啟系統,系統在正常工作的時候不希望會被重啟,但是系統受到干擾或者極端環境下可能會產生異常工作或者不工作,這時就只能通過重啟系統解決,在有些裝置不能人工重啟的時候,就只能通過看門狗來重啟,一般是在應用程式中開啟看門狗裝置,初始化一個時間,應用程式開啟一個執行緒來喂狗,當系統或者應用程式異常後,喂狗執行緒停止,則看門狗定時器復位,但是有時候在實際中並不會用Soc自帶的看門狗,而是用專門的看門狗晶片來保證可靠性

看門狗結構

看門狗結構框圖如下:
看門狗結構圖

PCLK_PSYS經過兩級分頻生成WDT時鐘週期,把要定的時間寫入WTDAT中,刷入到WTCNT中進行遞減,減到0之後根據WTCON的設定來產生中斷或者復位訊號,典型應用中應該產生復位訊號,然後在定時時間到之前給WTDAT重新寫值以防止復位

看門狗主要定時器

看門狗主要定時器有:

  • WTCON:看門狗控制器
  • WTDAT:看門狗定時時間
  • WTCNT:看門狗計時器
  • WTCLRINT:中斷清除

程式碼編寫

#define WTCON 0xE2700000
#define WTDAT 0xE2700004
#define WTCNT 0xE2700008
#define WTCLRINT 0xE270000C

#define rWTCON (*(volatile unsigned int *)WTCON)
#define rWTDAT (*(volatile unsigned int *)WTDAT)
#define rWTCNT (*(volatile unsigned int *)WTCNT)
#define rWTCLRINT (*(volatile unsigned int *)WTCLRINT)

void wdt_init_interrupt(void) {
    // 先設定預分頻器
    rWTCON &= ~(0xff<<8);
    rWTCON |= (65<<8);      //得到1MHz
    // 設定MUX
    rWTCON &= ~(3<<3);
    rWTCON |= (3<<3);       // 週期為128μs
    // 使能中斷,禁止復位
    rWTCON |= (1<<2);
    rWTCON &= ~(1<<0);
    // 設定定時時間
    rWTDAT = 10000;         // 定時1.28s
    rWTCNT = 10000;

    // 開啟看門狗
    rWTCON |= (1<<5);
}

void isr_wdt(void) {
    // 看門狗時間到了之後應該做的事
    printf("wdt interrupt");
    // 清除中斷
    intc_clearvectaddr();
    rWTCLRINT = 1;
}

RTC

Soc中的一個內部外設,使用自身獨立的晶振,提供RTC時鐘源,32.768KHz,內部有用於記錄時間的暫存器,一般為了在關機時時間仍可以繼續,還會提供一塊電池來供電,RTC框圖如下:
RTC框圖
RTC主要包含7個時間暫存器以及鬧鐘發生器,鬧鐘發生器可以定時間,時間到了之後就會產生RTC_ALARM中斷,用於通知系統鬧鐘定時到了。

RTC主要暫存器

  • INTP:中斷掛起,表示中斷髮生,RTC主要有RTC_ALARM和RTC_TICK兩個中斷
  • RTCCON:RTC控制暫存器
  • RTCALM & ALM***:都是和鬧鐘相關的暫存器
  • BCD***:時間暫存器,時間儲存都是以BCD編碼

RTC時間讀取和設定

#define     RTC_BASE     (0xE2800000)
#define     rINTP        (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define     rRTCCON      (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define     rTICCNT      (*((volatile unsigned long *)(RTC_BASE + 0x44)))
#define     rRTCALM      (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define     rALMSEC      (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define     rALMMIN      (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define     rALMHOUR     (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define     rALMDATE     (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define     rALMMON      (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define     rALMYEAR     (*((volatile unsigned long *)(RTC_BASE + 0x68)))
#define     rRTCRST      (*((volatile unsigned long *)(RTC_BASE + 0x6c)))
#define     rBCDSEC      (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define     rBCDMIN      (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define     rBCDHOUR     (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define     rBCDDATE     (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define     rBCDDAY      (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define     rBCDMON      (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define     rBCDYEAR     (*((volatile unsigned long *)(RTC_BASE + 0x88)))
#define     rCURTICCNT   (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define     rRTCLVD      (*((volatile unsigned long *)(RTC_BASE + 0x94)))

// 把一個十進位制數轉成BCD,56->0x56
static unsigned int num_2_bcd(unsigned int num){
    return (((num / 10)<<4) | num % 10);
}

// 把BCD轉成十進位制數
static unsigned int bcd_2_num(unsigned int bcd){
    return ((bcd & 0xf0)>>4) * 10 + (bcd &(0x0f));
}

void rtc_set_time(const struct rtc_time *p) {
    // 開啟RTC讀寫開關
    rRTCCON |= (1<<0);
    // 寫RTC時間,年是距離2000年的偏移
    rBCDYEAR = num_2_bcd(p->year - 2000);
    rBCDMON = num_2_bcd(p->month);
    rBCDDATE = num_2_bcd(p->date);
    rBCDHOUR = num_2_bcd(p->hour);
    rBCDMIN = num_2_bcd(p->minute);
    rBCDSEC = num_2_bcd(p->second);
    rBCDDAY = num_2_bcd(p->day);
    // 關閉RTC讀寫開關
    rRTCCON |= (0<<0);
}

void rtc_get_time(struct rtc_time *p) {
    // 開啟RTC讀寫開關
    rRTCCON |= (1<<0);
    // 讀RTC時間
    p->year = bcd_2_num(rBCDYEAR + 2000);
    p->month = bcd_2_num(rBCDMON);
    p->date = bcd_2_num(rBCDDATE);
    p->hour = bcd_2_num(rBCDHOUR);
    p->minute = bcd_2_num(rBCDMIN);
    p->second = bcd_2_num(rBCDSEC);
    p->day = bcd_2_num(rBCDDAY);
    // 關閉RTC讀寫開關
    rRTCCON |= (0<<0);
}