2011年3月4日 星期五

DIY - PIC: PIC18F4550 Timer1 工作原理 (廿十八)

DIY - PIC PIC18F4550 Timer1 工作原理 (廿十八)

Microchip PIC18F4550 Timer1 模組自身具有低功耗振盪器,可提供額外的時鐘,振盪電路連接在 T1OSI (輸入)引腳和 T1OSO(放大器輸出)引腳之間。Timer1 振盪器也可用作單片機處於功耗管理模式下的低功耗時鐘源,鑒於此振盪器的低功耗特性,它對附近變化較快的信號可能會比較敏感,振盪器電路應該盡可能靠近單片機,並在該振盪器電路周圍佈置接地保護環。在對外部元件數量和代碼開銷要求苛刻的應用中,可由 Timer1 提供即時時鐘(Real-Time Clock RTC)。可將 Timer1 配置為 16 位元讀寫模式。

Timer1 模組的簡化框圖
Timer1 計時器/ 計數器模組具有以下特徵:
  • 可通過軟體選擇,作為16 位計時器或計數器
  • 可讀寫的8 位寄存器(TMR1H TMR1L
  • 可選擇使用器件時鐘或Timer1 內部振盪器作為內部或外部時鐘源
  • 溢出中斷
  • CCP 特殊事件觸發復位
  • 器件時鐘狀態標誌位元(T1RUN
Timer1 工作原理
Timer1 可工作在以下模式:
  • 計時器
  • 同步計數器
  • 非同步計數器

工作模式由時鐘選擇位元 TMR1CST1CON<1>)決定。當 TMR1CS 清零(= 0)時, Timer1 在每個內部指令週期(FOSC/4)遞增。當 TMR1CS 位置1 時,Timer1 Timer1 外部時鐘輸入信號或 Timer1 振盪器信號(如果使能)的每個上升沿遞增。當使能Timer1 時, RC1/T1OSI/UOE RC0/T1OSO/T13CKI 引腳變為輸入引腳。這意味著 TRISC<1:0> 的值與操作無關並且這些引腳的讀取值為0



 如果 CCP 模組被配置為產生特殊事件觸發信號的比較模式(CCP1M3:CCP1M0 CCP2M3:CCP2M0 = 1011),該觸發信號將重定 Timer1。如果使能了 A/D 模組,來自 CCP2 的觸發信號還將啟動 A/D 轉換。要使用這一功能,必須將 Timer1 配置為計時器或同步計數器。在這種情況下,CCPRH:CCPRL 這對寄存器實際上變成了 Timer1 的週期寄存器。如果 Timer1 工作在非同步計數器模式下,重定操作將不起作用。如果對 Timer1 的寫操作和特殊事件觸發信號同時產生,則寫操作優先。
Timer1 中斷由 TMR1 寄存器對(TMR1H:TMR1L)從 0000h 開始遞增,一直到FFFFh,然後溢出從 0000h 重新開始計數。如果允許 Timer1 中斷,該中斷就會在溢出時產生,並將中斷標誌位元 TMR1IF PIR1<0>)置1。可以通過將 Timer1 中斷允許位 TMR1IEPIE1<0>)置 1 或清零來允許或禁止該中斷。


2011 03 04 天氣報告
氣溫:16.1 @ 23:00 
相對濕度:百分之74% 
天氣:天色大致良好

21 則留言:

  1. 我想問說如果RC0和RC1外部不用接振盪器 也是timer1吧?

    回覆刪除
    回覆
    1. 可以不接,簡單直接使用本身的石英振盪頻率 (OSC Pin 13 and Pin 14) 便可。

      刪除
    2. 那我想要請問一下,使用timer1振盪器的時候(不在RC0和RC1外部接振盪器)這樣子PIC接收到的振盪頻率是多少?
      我目前是用40MHz的振盪器在OSC1端輸入。我要做的目的是讓PORTC端的LED每1秒閃一次,因為我不知道PIC的的工作頻率是多少,所以無法確定實驗結果是否正確。

      另外,如果我要使用timer1當作振盪源,在LP oscillator的情形下工作,硬體配置圖該怎麼接?(我有照datasheet上的接法在RC0和RC1之間接上一個32.768kHz的振盪器,但是結果我覺得沒差)主振盪器要拿掉嗎?那我在軟體上有哪些要設定的?(目前使用MPLAB)謝謝。

      刪除
    3. 如使用 40MHz 的振盪器,實際 MCU 內部使用頻率是 40MHz/4,你可參考 http://bugworkshop.blogspot.hk/2011/03/diy-pic-pic18f4550-timer1.html,我是使用 20MHz 的振盪器,由於有中斷程式在內,時間計算會有變化,可以使用 Frequecy Counter or Oscilloscope 來量度時間.
      另外可參考 http://bugworkshop.blogspot.hk/2011/03/blog-post_13.html,利 Timer 來做時鐘,
      如用32.768kHz的振盪器,便要選擇 RCO/RC1 作時基 (T1OSCEN/TMR1CS/T1SYNC),可參考Timer1 工作原理圖.預祝你成功!

      刪除
    4. 您好,我目前的硬體設備都OK了,想要使用一般的timer1(不是LP oscillator),下面是我的程式碼,但是不知道為什麼RC7只會一直亮著?謝謝。

      int constant=0;
      void Timer1_timer();

      void main()
      {
      TRISC=0x0F; //PORTC的高位元當作輸出
      PORTC=0x00;
      PORTCbits.RC7=1;
      while(1)
      {
      Timer1_timer();
      constant=constant+1;
      if(constant==100)
      {
      PORTCbits.RC7 = 1^PORTCbits.RC7;
      constant=0;
      }
      }
      }

      void Timer1_timer()
      {
      INTCON = 0x00;
      PIR1bits.TMR1IF = 0; //清除中斷旗標
      PIE1bits.TMR1IE = 1; //開啟中斷

      INTCONbits.GIE = 1;
      INTCONbits.PEIE = 1;
      OSCCONbits.SCS1=0;
      OSCCONbits.SCS0=0;
      T1CONbits.T1CKPS1 = 0;
      T1CONbits.T1CKPS0 = 0;
      T1CONbits.T1OSCEN = 1;
      T1CONbits.T1SYNC = 1;
      T1CONbits.TMR1CS =0;
      T1CONbits.RD16=1;
      T1CONbits.TMR1ON = 1;
      TMR1H =0x00 ;
      TMR1L = 0x00;

      while(PIR1bits.TMR1IF==0);

      PORTCbits.RC5=1;
      TMR1H = 0x00;
      TMR1L = 0x00;
      PIR1bits.TMR1IF =0;
      PIE1bits.TMR1IE =1;
      }

      刪除
    5. 看你的程式不是用中斷來計時,你可以將while(1) 後的 Timer1_timer(); 改為 Delay10KTCYx(200); LED 應該可以閃動。 但如果你要用中斷來計時,最好將設定中斷在 main 內,中斷程式放在另外副程式,可參考 DIY - PIC: PIC18F4550 單鍵中斷程式 (一百二十) http://bugworkshop.blogspot.hk/2011/11/diy-pic18f4550.html。

      接 LED 燈可參考DIY - PIC:開始製作 PIC18F4550 LED 電路 (十四) http://bugworkshop.blogspot.hk/2011/02/diy-pic-pic18f4550-led.html

      刪除
    6. 作者已經移除這則留言。

      刪除
    7. 作者已經移除這則留言。

      刪除
    8. 在看過您的意見後,問題已經解決了,非常感謝您的回答!!!謝謝

      刪除
    9. 您好,我後來又發現幾個問題,想要在這邊再請教您:
      1.在Timer1中的RD16設置1和0對我來說好像沒什麼差,為什麼?RD16=1和RD16=0差在哪邊?我已景有詳讀過datasheet但是還是不太懂,感覺還是沒差。
      2.我之後又測試了Timer1的LP oscillator模式,電路圖也接完成了,並且在其中一個程式(程式一)測試完成,但是為了特定的需求,必須將程式改成副函式的形式(程式二),但是這樣RC7的外接LED卻不動了,想要請問一下是為什麼?

      程式一:(成功的程式碼)
      int constant=0;

      void main()
      {

      TRISC=0x0F;
      PORTCbits.RC7=1;
      PORTCbits.RC6=0;
      INTCON = 0x00;

      PIR1bits.TMR1IF = 0;
      PIE1bits.TMR1IE = 1;
      INTCONbits.GIE = 1;
      INTCONbits.PEIE = 1;

      OSCCONbits.SCS1=0;
      OSCCONbits.SCS0=1;

      T1CONbits.T1CKPS1 = 0;
      T1CONbits.T1CKPS0 = 0;
      T1CONbits.T1OSCEN = 1;
      T1CONbits.T1SYNC = 1;
      T1CONbits.TMR1CS =1;
      T1CONbits.RD16=1;
      T1CONbits.TMR1ON = 1;
      TMR1H =0x00 ;
      TMR1L = 0x00;
      while(1)
      {
      PORTCbits.RC6=1;
      if (PIR1bits.TMR1IF == 1)
      {
      constant++;
      if(constant==1)
      {
      PORTCbits.RC7 = 1^PORTCbits.RC7;
      constant=0;
      }
      TMR1H = 0x00;
      TMR1L = 0x00;
      PIR1bits.TMR1IF = 0;
      PIE1bits.TMR1IE =1;
      }
      }
      }

      程式二:(失敗的程式碼)

      int constant=0;
      void Timer1_timer();

      void main()
      {
      TRISC=0x0F; //PORTC的高位元當作輸出
      PORTC=0x00;
      PORTCbits.RC7=1;
      while(1)
      {

      Timer1_timer();
      PORTCbits.RC6=1;
      constant=constant+1;
      if(constant==10)
      {
      PORTCbits.RC7 = 1^PORTCbits.RC7;
      constant=0;
      }
      }
      }

      void Timer1_timer()
      {


      INTCON = 0x00;
      PIR1bits.TMR1IF = 0; //清除中斷旗標
      PIE1bits.TMR1IE = 1; //開啟中斷

      INTCONbits.GIE = 1;
      INTCONbits.PEIE = 1;


      OSCCONbits.SCS1=0;
      OSCCONbits.SCS0=1;
      T1CONbits.T1CKPS1 = 0;
      T1CONbits.T1CKPS0 = 0;
      T1CONbits.T1OSCEN = 1;
      T1CONbits.T1SYNC = 1;
      T1CONbits.TMR1CS =1;
      T1CONbits.RD16=1;
      TMR1H =0x00;
      TMR1L =0x00;
      T1CONbits.TMR1ON = 1;

      while(1)
      {

      if((TMR1L==0xFF&&T1CONbits.RD16==0)||(TMR1H==0xFF&&T1CONbits.RD16==1))
      {
      PORTCbits.RC5=1;
      TMR1H = 0x00;
      TMR1L = 0x00;
      T1CONbits.TMR1ON = 0;
      PIR1bits.TMR1IF =0;
      PIE1bits.TMR1IE =1;
      break;
      }
      }

      }

      刪除
    10. 1.在Timer1中的RD16的1和0是代表16位或8位元設置,即 65535 或 255。
      2.程式二會呼叫副程式Timer1_timer(),但中斷副程式不應用主程式呼叫,因為 Timer1 計時完而產生中斷。

      刪除
    11. 但是程式二它中斷不會發生吧?因為當TMR1H=0xFF時就關閉timer1了,不會有0xFF→0x00的事情發生。

      刪除
    12. Timer1_timer()只是一般的副程式吧?謝謝~

      刪除
    13. 如果不用中斷,就不用設定中斷旗號,我建議你用 Delay Loop 便可以了。

      刪除
  2. 您好:
    我想要進一步了解為什麼中斷的觸發事件不可以寫在副函式中而要寫在主程式中?謝謝。

    回覆刪除
    回覆
    1. 正常的中斷觸發是會跳到副程式來執行,因為主程式在執行時,如果中斷觸發時便會跳到副程式來執行,完成後便會返回主程式繼續執行,這個中斷副程式是要 Disable 中斷及執行中斷程式,最後要 Enable 中斷 。但是主程式開始時會設定中斷,當然也可以放在另外的副程式,這個副程式是要 Enable 中斷,所以這兩個副程式有點不同,這樣程式是比較清晰易讀。這個解釋希望可以幫到你!

      刪除
    2. 您的回答讓我突然想通了。謝謝

      刪除
  3. 我發現如果用Timer1做counter的功能當RD16=1時它不會動(RC7的LED不會閃爍),但是當RD16=0時就會動,我有注意到我寫入與讀取TRM1L以及TRM1H的順序了,這是為什麼?下面是可以執行的程式。謝謝
    int constant=0;
    void Timer1_timer(char,char,char);
    char T1CON_1,TMR1L_1,TMR1H_1;


    void main()
    {
    TRISC=0x0F; //PORTC的高位元當作輸出
    PORTC=0x00;
    PORTCbits.RC7=1;
    while(1)
    {
    T1CON_1=0x06;
    TMR1H_1=0x00;
    TMR1L_1=0x00;
    Timer1_timer(T1CON_1,TMR1H_1,TMR1L_1);
    constant++;
    if(constant==10)
    {
    PORTCbits.RC7 = 1^PORTCbits.RC7;
    constant=0;
    }
    }
    }

    void Timer1_timer(char T1CON_1,char TMR1H_1,char TMR1L_1)
    {
    char word1,word2;

    OSCCONbits.SCS1=0;
    OSCCONbits.SCS0=0;
    T1CON=T1CON_1;
    TMR1H=TMR1H_1;
    TMR1L=TMR1L_1;
    T1CONbits.TMR1ON = 1;

    while(1)
    {

    word1=TMR1L;
    word2=TMR1H;

    if(word2==0xFF)
    {
    T1CONbits.TMR1ON = 0;
    break;
    }
    }

    }

    回覆刪除