1. 問題描述
用DZ60的計時器測週期時發現,TPM(計時器)在初始化後,需要等待500多ms才能勉強測到。
TPM產生的波形如下圖,藍色波形上升沿爲初始化指令執行完成時間點,用一個IO口輸出高電平並延時100ms作爲示波器觸發源,黃色波形爲TPM的PWM方式輸出波形。
2. 問題分析
在DZ60DATASHEET中文版第313頁找到這段話。
大概意思是TPM的通道控制寄存器的更新時機由clksb:clksa控制。而clksb:clksa的功能如表16-3所示。
合起來就是說,對通道控制寄存器的寫入,不是直接寫到寄存器裏面,而是先寫到寄存器對應的緩衝器。當TPM計數器關閉時,寫入的數據從緩衝器寫入完成後立即更新到寄存器;而當TPM有時鐘在運行時,寫入的數據更新則需要等到,計時器從0xfffe變成0xffff纔開始進行。
由此想到,產生問題的TPM初始化是128分頻(16MHZ總線時鐘下,週期爲8us)。16位TPM計數器從0開始計數,則有更新設置需等待時間爲65535×8us=524ms。與所見波形符合。
3. 問題解決
無論TPM用作輸入捕捉,還是PWM或者比較輸出,所有TPM控制寄存器初始化時,都要先把時鐘關掉,即clksb:clksa=0b00。問題解決後得到波形如下:
4. 正確實例
//初始化時必須先關掉時鐘,否則以下兩個寄存器要到將要溢出即(0xfffe->0xffff)時纔會更新。
//--如果設置128分頻,則初始化時間需要 16MHz/128*65535=524ms
//關掉時鐘初始化,則寄存器在寫入完成後立即更新,無需等待。
//TPM2產生中央對齊PWM輸出
TPM_CSTR(TPM_NUM_2) =0b001<span style="color:#ff0000;">00</span>111;
// |||||||
// ||||||+-----PS0\
// |||||+------PS1 ----128倍分頻
// ||||+-------PS2/
// |||+--------CLKSA\
// ||+---------CLKSB/--關時鐘
// |+----------CPWMS---所有通道以中央對齊PWM模式運行
// +-----------TOIE----禁止溢出中斷
//TPM1設置爲輸入捕捉
TPM_CSTR(TPM_NUM_1) =0b000<span style="color:#ff0000;">00</span>111;
// |||||||
// ||||||+-----PS0\
// |||||+------PS1 ----128倍分頻
// ||||+-------PS2/
// |||+--------CLKSA\
// ||+---------CLKSB/--關時鐘
// |+----------CPWMS---所有通道以輸入捕捉運行
// +-----------TOIE----禁止溢出中斷
//TPM2通道設置
TPM2_CHSCSTR(TPM_CHNo)=0b00101000;
// |||||
// ||||+-----ELSA\
// |||+------ELSB/---High-true 脈衝(清除向上比較輸出)
// ||+-------MSA\
// |+--------MSB/----CPWMS=1時,此設置無效
// +---------CHIE----禁止中斷
tmp = Period * (125) / 2;
TPM2MOD = tmp; //週期Period以ms爲單位
TPM2C0V = tmp / 10 * Duty / 10; //佔空比Duty爲0~100整數
TPM2SC_CLKSB = 0;
TPM2SC_CLKSA = 1; <span style="color:#ff0000;">//寄存器設置完成,再打開總線時鐘,避免長時間的寄存器更新。</span>
//TPM2通道設置
TPM1SC_CLKSB = 0;
TPM1SC_CLKSA = 1; <span style="color:#ff0000;">//寄存器設置完成,再打開總線時鐘,避免長時間的寄存器更新。</span>
TPM1SC_TOIE = 1; //使能輸入捕捉中斷
EnableTPM1ChInt(G_TMUch_Start);
中斷程序的寫法如下
interrupt void isrT1Ch0In(void)
{
DisableInterrupt(); //禁止總中斷
if(TPM1C0SC_CH0F == 1) //判斷是否發生輸入捕捉中斷
{
TPM1C0SC_CH0F = 0; //清除標誌位
switch(g_trigged)
{
case 0:
//第一次觸發,保存數據
g_start_data = TPM1C0V;
g_trigged++;
//設置第二次觸發邊沿方式
<span style="color:#ff0000;">TPM_CSTR(TPM_NUM_1) =0b00000111;//重新初始化,避免長時間的寄存器更新。</span>
switch(G_Edge_Stop)
{
case POS_EDGE:
TPM1_CHSCSTR(G_TMUch_Stop)=0b00000100;
// |||||
// ||||+-----ELSA\
// |||+------ELSB/---上升邊沿捕捉
// ||+-------MSA\
// |+--------MSB/----輸入捕捉模式
// +---------CHIE----禁止通道中斷
break;
case NEG_EDGE:
TPM1_CHSCSTR(G_TMUch_Stop)=0b00001000;//下降邊沿捕捉
break;
default:
TPM1_CHSCSTR(G_TMUch_Stop)=0b00000100;//默認爲上升邊沿捕捉
break;
}
<span style="color:#ff0000;"> //寄存器設置完成,再打開總線時鐘,避免長時間的寄存器更新。
TPM1SC_CLKSB = 0;
TPM1SC_CLKSA = 1;
TPM1SC_TOIE = 1;</span>
EnableTPM1ChInt(G_TMUch_Stop);
break;
case 1:
//第二次觸發,保存數據
g_stop_data = TPM1C0V;
g_trigged++;
//時間採集完畢,恢復成IO功能
TPM1_CHSCSTR(G_TMUch_Start)=0b00000000;
TPM1_CHSCSTR(G_TMUch_Stop)=0b00000000; //恢復成IO功能
break;
default:
break;
}
}
EnableInterrupt(); //開放總中斷
}