---
# System prepended metadata

title: 'F2812的Event Manager : Timer、中斷'
tags: [F2812]

---

[TOC]

# 前言
- 本篇以實現Event Manager的Timer周期中斷為目標，介紹EV、Timer、以及EV的中斷

# 事件管理器 (EV) 簡介
- 詳見 <手把手教你DSP>第12章、EV Reference Guide手冊
- DSP的EV module相當於STM32的通用計時器，除了計時也包含其他QEP、PWM、捕獲比較功能，只是DSP把幾個通用計時器使用EV一起管理
- DSP有兩個EV，EVA與EVB。這兩個EV module功能相同，每個EV有兩個定時器，3個比較單元、3個捕獲單元、一個QEP電路等等，這些額外功能都基於EV內的計時器，所以本筆記一樣先看看EV的計時器使用方法吧。
- EV Reference Guide手冊的Figure 1-2 有完整的EV架構

# EV 的 Timer

## 簡介
- 每個EV有兩個16位的Timer
    - EVA : TIM1、TIM2
    - EVB : TIM3、TIM4
- Timer的計時提供其他EV功能時鐘基礎


## 計時原理與暫存器
- 想讓計時器開始計數並不難，只要有Timer的時鐘源輸入，Timer 的 Counter 暫存器就會依據 Control 暫存器的設置開始計時，下表是EV Timer的相關暫存器
![image](https://hackmd.io/_uploads/r1S_IAPAC.png =70%x)
- 表中暫存器GPTCONx (x=A, B)控制EVx的所有Timer。而每個Timer基本上由以下暫存器組成
    - x=1,2,3,4
    - **TxCNT** : 儲存計數值，依據TxCON與Timer 時鐘源增減計數值
    - **TxCMPR** : 儲存比較值，用於比較功能時TxCNT的比較對象
    - **TxPR** : 儲存週期值，這個週期值是TxCNT的計數上限，因此能依據計數模式推算timer週期
    - **TxCON** : 控制timer運作方式，包含計數模式、時鐘源選擇、Timer使能等等
- 因為TxCMPR用於比較功能，單論計時的話並不會用到，這邊先不提
- TxCON暫存器做為控制TIMx將會是需要設置最多的，以下只提及TxCON暫存器較常設定功能
![image](https://hackmd.io/_uploads/SJudZJdAA.png =80%x)
![image](https://hackmd.io/_uploads/BJ6xS1O00.png =80%x)
![image](https://hackmd.io/_uploads/H1vbBJ_CA.png =80%x)


### TIMx使能 (TENABLE)
- 設為1後Timer才會啟用，要記得檢查這個

### 時鐘源 (TCLKS1、TCLKS0)
- 內部時鐘 : 為HSPCLK高速時鐘
- 外部時鐘 : 由外部給入
- QEP電路 : 使用QEP功能時由QEP做觸發

### Prescaler (TPS2、TPS1、TPS0)
- 將輸入的內部時鐘或外部時鐘做分頻

### 計數模式(TMODE1、TMODE0)
- 兩個bit控制，所以總共有4個計數模式
- 以Timer中斷來說最常用的是<font color="red">連續遞增上數模式</font>，其餘見書或手冊
- **連續遞增上數模式**
    - 當TIMx時鐘源被觸發，TxCNT在此模式下就會往上加1，並且在與TxPR值相同時，下次觸發就會歸0
    - ![image](https://hackmd.io/_uploads/HJAZDkuRR.png =70%x)

# EV的中斷

## 中斷事件
- 特定事件滿足時會依據設定觸發對應中斷，下表列出所有EVA、EVB管理的中斷事件
- 其中<font color="red">TxPINT</font>是定時器周期中斷，發生於當TxCNT的值與TxPR值相同時，這個在達成Timer週期性觸發中斷時較常使用
    ![image](https://hackmd.io/_uploads/ry4u7l_A0.png =80%x)
    ![image](https://hackmd.io/_uploads/HJwcXl_AC.png =80%x)
    
## 管理中斷的暫存器
- 上面表12-23、12-24這麼多種EV內的中斷事件，每個都需要有對應的中斷旗標與使能(Enable)位做控制
- **Interrupt Flag Register** 
    - 由<font color="red">EVAIFRx(x=A,B,C)</font>和<font color="blue">EVBIFRx(x=A,B,C)</font>共6個暫存器對應所有中斷事件的中斷旗標
- **Interrupt Mask Register**
    - 由<font color="red">EVAIMRx(x=A,B,C)</font>和<font color="blue">EVBIMRx(x=A,B,C)</font>共6個暫存器對應所有中斷事件的使能位
- 6個暫存器各個bit的對應中斷見書或者[DSP_EV IFR、IMR暫存器表](https://hackmd.io/3mgMb261SEW9gmO7IGNqRQ?both#EVB)，表內也含有優先級和對應PIE中斷組別

## 中斷流程
- EV的中斷信號在整個中斷系統中相當於外設部分的中斷，詳見[DSP的中斷系統](https://hackmd.io/xCWvBIXRRpeKnSnIzPNkOQ)
- 發生中斷事件後，對應的中段旗標會立起，如果對應的中斷有使能的話，則會直接將中斷信號送往PIE中斷，如果後續中斷"開關"都有開的話，那麼CPU就會響應這個中斷，執行ISR函式
- 執行ISR函式後記得要<font color="red">手動把EV的中斷旗標IFR復位</font>，否則會重複觸發

</br>
</br>

# 使用EV Timer實現週期中斷實作
- <font color="red">範例專案、腳位見FTP</font>
- <font color="red">LED記得接電阻</font>
-  目標 : 使用EV Timer計時功能創建一個週期觸發的中斷，每0.001秒觸發一次(1 kHz)，並每觸發500次後切換LED燈亮暗，相當於每0.5秒LED亮暗反轉
-  使用外設 : 
    -  EVA的TIM1
    -  GPIOF2作為輸出控制LED燈
- 流程 : 
    - 依據所需頻率，設定高速時鐘的預除器以決定HSPCLK高速時鐘頻率
    - 設定PIE級中斷
    - 設定CPU級中斷
    - 對T1CON暫存器做設定，設定EV TIM1的prescaler和Period、其他計數模式、中斷等設定
    - 設定GPIOF2
    - 寫ISR函數
## 查詢與設定步驟
- 以下程式皆添加在初始化範例的 User Initilization Step

### 高速時鐘頻率
- 首先設定高速時鐘的預除器為10，對HISPCP寫入 0b101
``` c++=
#define HSPCLK_PSC_10 5

EALLOW; //This is needed to write to EALLOW protected registers
    SysCtrlRegs.HISPCP.all = HSPCLK_PSC_10; // HSPCLK = SYSCLKOUT/HSPCLK_PSC_10
EDIS;
```



### TIM1設定
- 設定計數器為上數模式，所以對 T1CON 的 TMODE 兩個 bit 寫入 0b10 
- 設定TIM1的預除器為1，所以對 T1CON 的 TPS 寫 0 
- 選擇內部時鐘，對 T1CON 的 TCLKS10 兩個 bit 寫入 0b00
- 啟用Timer，對 T1CON 的 TENABLE 寫 1
- 設定TIM1的T1PR暫存器使觸發頻率為1 kHz，設定值計算為 (150M/10)/1000=15000。注意暫存器只有16 bits，設定值小於65535
- 將T1CNT清0
- TIM1中斷
    - 我們使用的是周期中斷，依據[IFR、IMR暫存器表](https://hackmd.io/3mgMb261SEW9gmO7IGNqRQ?both)表12-23，TIM1周期中斷對應T1PINT中斷信號
    - 清除T1PINT中斷旗標，查詢EVA的IFR，發現位於EVAIFRA中，表12-26，
    - 使能T1PINT中斷，查詢EVA的IMR，發現位於EVAIMARA中，表12-29

``` c++=
#define UP_COUNTING_MODE  2
#define GPTIM_PRESCALAR_1 0
#define GPTIM_ENABLE 1
#define GPTIM_CLK_SRC_INTERNAL 0

#define HSPCLK_FREQ SYSCLKOUT / (2*HSPCLK_PSC_10)
#define SAMPLING_FREQ_1k 1000

void init_eva_timer1(void){

	// Using for 1ms Timer.
	//---------------------------------------------------------------------------	

    // Count up, internal clk/1 (HSPCLK/10 = 15MHz), enable Timer
    // EvaRegs.T1CON.all = 0x1040; // 00-0-10-000-0-1-00-00-0-0
    EvaRegs.T1CON.bit.TMODE = UP_COUNTING_MODE;
    EvaRegs.T1CON.bit.TPS = GPTIM_PRESCALAR_1;
    EvaRegs.T1CON.bit.TCLKS10 =GPTIM_CLK_SRC_INTERNAL;
    EvaRegs.T1CON.bit.TENABLE = GPTIM_ENABLE;

    // Set the Period as 1ms
    EvaRegs.T1PR = HSPCLK_FREQ / SAMPLING_FREQ_1k;

    // Clear the counter for GP timer 1
    EvaRegs.T1CNT = 0x0000;

    // Enable Period interrupt bits for GP timer 1
    EvaRegs.EVAIFRA.bit.T1PINT = 1;		// Resets T1PINT FLAG 
										// (EVA Interrupt Flag Register A)
    EvaRegs.EVAIMRA.bit.T1PINT = 1; 	// T1PINT enable
										// (EVA Interrupt Mask Register A)
}
```

### PIE級中斷設定
- 已中斷信號為 T1PINT，查詢[F2812中斷查詢](https://hackmd.io/-naxV-upSE6WgtpQUBUrKw)表11-4，找到T1PINT對應INT2.4，即PIE組2的第4條
- 使能該PIE中斷，對PIEIER2的第四個bit寫1
- 將中斷向量表的T1PINT的ISR改成自己的函式
``` c++=
// 前綴的interrupt表明是ISR，要記得加
interrupt void eva_timer1_isr(void);

PieCtrlRegs.PIEIER2.all = M_INT4;

EALLOW;	 registers
    PieVectTable.T1PINT = &eva_timer1_isr;	// Timer1 ISR
EDIS;
```

### CPU級中斷設定
- 使能INT2的中斷
``` c++=
IER |= M_INT2;
```
### GPIO設定
- 將GPIOF2設為一般I/O，對GPFMUX的第2個bit寫0
- 將GPIOF2設為Output，對GPFDIR的第2個bit寫1

``` c++=
void GPIO_select(void){		// for IMU GPIO pin select
    EALLOW;							
    //------------------------
    // Set GPIO F pin 2 as output I/O.
    GpioMuxRegs.GPFMUX.bit.SPICLKA_GPIOF2 = 0;
    GpioMuxRegs.GPFDIR.bit.GPIOF2 = 1; 
    EDIS;
}
```
## 撰寫ISR
- 宣告一個全域counter，2; 當counter數了500次後，就反轉GPIOF2的輸出
- 把EVAIFRA 的 <font color="red">T1PINT中斷旗標清0</font>
- 因為是INT2，所以是PIEACK的 第2個bit 負責把關。將通路打開以繼續接收該PIE2組中斷，對PIEACK的第二個bit寫1

``` c++=
interrupt void eva_timer1_isr(void){

    // ISR Task
    //---------------------------------------------------------------------------
    count++;

    if(count>500){
        // GpioDataRegs.GPFDAT.bit.GPIOF2 =1;
        GpioDataRegs.GPFDAT.bit.GPIOF2 = ~GpioDataRegs.GPFDAT.bit.GPIOF2;
        //GpioDataRegs.GPFTOGGLE.bit.GPIOF2 = 1;
        count =0;
    }


    // Enable more interrupts from this timer
    //---------------------------------------------------------------------------
    // EvaRegs.EVAIMRA.bit.T1PINT = 1;

    // Clear Interrupt Flag
    //---------------------------------------------------------------------------
    // Note: To be safe, use a mask value to write to the entire
    // EVAIFRA register.  Writing to one bit will cause a read-modify-write
    // operation that may have the result of writing 1's to clear
    // bits other then those intended.
    EvaRegs.EVAIFRA.bit.T1PINT = 1;

    //---------------------------------------------------------------------------
    // Acknowledge interrupt to receive more interrupts from PIE group 2
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;

    return;
}
```


