第二十章高精度延時實驗
延時函數是很常用的API函數,在前面的實驗中我們使用循環來實現延時函數,但是使用循環來實現的延時函數不準確,誤差會很大。雖然使用到延時函數的地方精度要求都不會很嚴格(要求嚴格的話就使用硬體定時器了),但是延時函數肯定是越精確越好,這樣延時函數就可以使用在某些對時許要求嚴格的場合。本章我們就來學習一下如何使用硬體定時器來實現高精度延時。
20.1高精度延時簡介
20.1.1 GPT定時器簡介
學過STM32的同學應該知道,在使用STM32的時候可以使用SYSTICK來實現高精度延時。I.MX6U沒有SYSTICK定時器,但是I.MX6U有其他定時器啊,比如第十八章講解的EPIT定時器。本章我們使用I.MX6U的GPT定時器來實現高精度延時,順便學習一下GPT定時器,GPT定時器全稱為General Purpose Timer。
GPT定時器是一個32位向上定時器(也就是從0X00000000開始向上遞增計數),GPT定時器也可以跟一個值進行比較,當計數器值和這個值相等的話就發生比較事件,產生比較中斷。GPT定時器有一個12位的分頻器,可以對GPT定時器的時鐘源進行分頻,GPT定時器特性如下:
①、一個可選時鐘源的32位向上計數器。
②、兩個輸入捕獲通道,可以設置觸發方式。
③、三個輸出比較通道,可以設置輸出模式。
④、可以生成捕獲中斷、比較中斷和溢出中斷。
⑤、計數器可以運行在重新啟動(restart)或(自由運行)free-run模式。
GPT定時器的可選時鐘源如圖20.1.1.1所示:
圖20.1.1.1 GPT時鐘源
從圖20.1.1.1可以看出一共有五個時鐘源,分別為:ipg_clk_24M、GPT_CLK(外部時鐘)、ipg_clk、ipg_clk_32k和ipg_clk_highfreq。本例程選擇ipg_clk為GPT的時鐘源,ipg_clk-66MHz。
GPT定時器結構如圖20.1.1.2所示:
圖20.1.1.2 GPT定時器結構圖
圖20.1.1.2中各部分意義如下:
①、此部分為GPT定時器的時鐘源,前面已經說過了,本章例程選擇ipg_clk作為GPT定時器時鐘源。
②、此部分為12位分頻器,對時鐘源進行分頻處理,可設置0~4095,分別對應1~4096分頻。
③、經過分頻的時鐘源進入到GPT定時器內部32位計數器。
④和⑤、這兩部分是GPT的兩路輸入捕獲通道,本章不講解GPT定時器的輸入捕獲。
⑥、此部分為輸出比較寄存器,一共有三路輸出比較,因此有三個輸出比較寄存器,輸出比較寄存器是32位的。
⑦、此部分位輸出比較中斷,三路輸出比較中斷,當計數器裡面的值和輸出比較寄存器裡面的比較值相等就會觸發輸出比較中斷。
GPT定時器有兩種工作模式:重新啟動(restart)模式和自由運行(free-run)模式,這兩個工作模式的區別如下:
重新啟動(restart)模式:當GPTx_CR(x=1,2)寄存器的FRR位清零的時候GPT工作在此模式。在此模式下,當計數值和比較寄存器中的值相等的話計數值就會清零,然後重新從0X00000000開始向上計數,只有比較通道1才有此模式!向比較通道1的比較寄存器寫入任何數據都會復位GPT計數器。對於其他兩路比較通道(通道2和3),當發生比較事件以後不會復位計數器。
自由運行(free-run)模式:當GPTx_CR(x=1,2)寄存器的FRR位置1時候GPT工作在此模式下,此模式適用於所有三個比較通道,當比較事件發生以後並不會復位計數器,而是繼續計數,直到計數值為0XFFFFFFFF,然後重新回滾到0X00000000。
接下來看一下GPT定時器幾個重要的寄存器,第一個就是GPT的配置寄存器GPTx_CR,此寄存器的結構如圖20.1.1.3所示:
圖20.1.1.3寄存器GPTx_CR
寄存器GPTx_CR我們用到的重要位如下:
SWR(bit15):復位GPT定時器,向此位寫1就可以復位GPT定時器,當GPT復位完成以後此為會自動清零。
FRR(bit9):運行模式選擇,當此位為0的時候比較通道1工作在重新啟動(restart)模式。當此位為1的時候所有的三個比較通道均工作在自由運行模式(free-run)。
CLKSRC(bit8:6):GPT定時器時鐘源選擇位,為0的時候關閉時鐘源;為1的時候選擇ipg_clk作為時鐘源;為2的時候選擇ipg_clk_highfreq為時鐘源;為3的時候選擇外部時鐘為時鐘源;為4的時候選擇ipg_clk_32k為時鐘源;為5的時候選擇ip_clk_24M為時鐘源。本章例程選擇ipg_clk作為GPT定時器的時鐘源,因此此位設置位1(0b001)。
ENMODE(bit1):GPT使能模式,此位為0的時候如果關閉GPT定時器,計數器寄存器保存定時器關閉時候的計數值。此位為1的時候如果關閉GPT定時器,計數器寄存器就會清零。
EN(bit):GPT使能位,為1的時候使能GPT定時器,為0的時候關閉GPT定時器。
接下來看一下GPT定時器的分頻寄存器GPTx_PR,此寄存器結構如圖20.1.1.4所示:
圖20.1.1.4寄存器GPTx_PR寄存器
寄存器GPTx_PR我們用到的重要位就一個:PRESCALER(bit11:0),這就是12位分頻值,可設置0~4095,分別對應1~4096分頻。
接下來看一下GPT定時器的狀態寄存器GPTx_SR,此寄存器結構如圖20.1.1.5所示:
圖20.1.1.5 GPTx_SR寄存器結構
寄存器GPTx_SR重要的位如下:
ROV(bit5):回滾標誌位,當計數值從0XFFFFFFFF回滾到0X00000000的時候此位置1。
IF2~IF1(bit4:3):輸入捕獲標誌位,當輸入捕獲事件發生以後此位置1,一共有兩路輸入捕獲通道。如果使用輸入捕獲中斷的話需要在中斷處理函數中清除此位。
OF3~OF1(bit2:0):輸出比較中斷標誌位,當輸出比較事件發生以後此位置1,一共有三路輸出比較通道。如果使用輸出比較中斷的話需要在中斷處理函數中清除此位。
接著看一下GPT定時器的計數寄存器GPTx_CNT,這個寄存器保存著GPT定時器的當前計數值。最後看一下GPT定時器的輸出比較寄存器GPTx_OCR,每個輸出比較通道對應一個輸出比較寄存器,因此一個GPT定時器有三個OCR寄存器,它們的作都是相同的。以輸出比較通道1為例,其輸出比較寄存器為GPTx_OCR1,這是一個32位寄存器,用於存放32位的比較值。當計數器值和寄存器GPTx_OCR1中的值相等就會產生比較事件,如果使能了比較中斷的話就會觸發相應的中斷。
關於GPT的寄存器就介紹到這裡,關於這些寄存器詳細的描述,請參考《I.MX6ULL參考手冊》第1432頁的30.6小節。
20.1.2定時器實現高精度延時原理
高精度延時函數的實現肯定是要藉助硬體定時器,前面說了本章實驗使用GPT定時器來實現高精度延時。如果設置GPT定時器的時鐘源為ipg_clk=66MHz,設置66分頻,那麼進入GPT定時器的最終時鐘頻率就是66/66=1MHz,周期為1us。GPT的計數器每計一個數就表示「過去」了1us。如果計10個數就表示「過去」了10us。通過讀取寄存器GPTx_CNT中的值就知道計了個數,比如現在要延時100us,那麼進入延時函數以後紀錄下寄存器GPTx_CNT中的值為200,當GPTx_CNT中的值為300的時候就表示100us過去了,也就是延時結束。GPTx_CNT是個32位寄存器,如果時鐘為1MHz的話,GPTx_CNT最多可以實現0XFFFFFFFFus=4294967295us≈4294s≈72min。也就是說72分鐘以後GPTx_CNT寄存器就會回滾到0X00000000,也就是溢出,所以需要在延時函數中要處理溢出的情況。關於定時器實現高精度延時的原理就講解到這裡,原理還是很簡單的,高精度延時的實現步驟如下:
1、設置GPT1定時器
首先設置GPT1_CR寄存器的SWR(bit15)位來復位寄存器GPT1。復位完成以後設置寄存器GPT1_CR寄存器的CLKSRC(bit8:6)位,選擇GPT1的時鐘源為ipg_clk。設置定時器GPT1的工作模式,
2、設置GPT1的分頻值
設置寄存器GPT1_PR寄存器的PRESCALAR(bit111:0)位,設置分頻值。
3、設置GPT1的比較值
如果要使用GPT1的輸出比較中斷,那麼GPT1的輸出比較寄存器GPT1_OCR1的值可以根據所需的中斷時間來設置。本章例程不使用比較輸出中斷,所以將GPT1_OCR1設置為最大值,即:0XFFFFFFFF。
4、使能GPT1定時器
設置好GPT1定時器以後就可以使能了,設置GPT1_CR的EN(bit0)位為1來使能GPT1定時器。
5、編寫延時函數
GPT1定時器已經開始運行了,可以根據前面介紹的高精度延時函數原理來編寫延時函數,針對us和ms延時分別編寫兩個延時函數。
20.2硬體原理分析
本試驗用到的資源如下:
、一個LED燈:LED0。
、定時器GPT1。
本實驗通過高精度延時函數來控制LED0的閃爍,可以通過示波器來觀察LED0的控制IO輸出波形,通過波形的頻率或者周期來判斷延時函數精度是否正常。
20.3 實驗程序編寫
本實驗對應的例程路徑為:開發板光碟-> 1、裸機例程->12_highpreci_delay。
本章實驗在上一章例程的基礎上完成,更改工程名字為「delay」,直接修改bsp_delay.c和bsp_delay.h這兩個文件,將bsp_delay.h文件改為如下所示內容:
示例代碼20.3.1 bsp_delay.h文件代碼
1 #ifndef __BSP_DELAY_H
2 #define __BSP_DELAY_H
3/***************************************************************
4 Copyright zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名 : bsp_delay.h
6作者 : 左忠凱
7版本 : V1.0
8 描述 : 延時頭文件。
9其他 : 無
10論壇 : www.openedv.com
11日誌 : 初版V1.0 2019/1/4 左忠凱創建
12
13 V2.0 2019/1/15 左忠凱修改
14 添加了一些函數聲明。
15 ***************************************************************/
16 #include "imx6ul.h"
17
18/* 函數聲明 */
19void delay_init(void);
20void delayus(unsignedint usdelay);
21void delayms(unsignedint msdelay);
22void delay(volatileunsignedint n);
23void gpt1_irqhandler(void);
24
25 #endif
bsp_delay.h文件就是一些函數聲明,很簡單。在文件bsp_delay.c中輸入如下內容:
示例代碼20.3.2 bsp_delay.c文件代碼
/***************************************************************
Copyright zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_delay.c
作者 : 左忠凱
版本 : V1.0
描述 : 延時文件。
其他 : 無
論壇 : www.openedv.com
日誌 : 初版V1.0 2019/1/4 左忠凱創建
V2.0 2019/1/15 左忠凱修改
使用定時器GPT實現高精度延時,添加了:
delay_init 延時初始化函數
gpt1_irqhandler gpt1定時器中斷處理函數
delayus us延時函數
delayms ms延時函數
***************************************************************/
1 #include "bsp_delay.h"
2
3/*
4 * @description : 延時有關硬體初始化,主要是GPT定時器
5 GPT定時器時鐘源選擇ipg_clk=66Mhz
6 * @param : 無
7 * @return : 無
8 */
9void delay_init(void)
10{
11 GPT1->CR =0;/* 清零 */
12 GPT1->CR =1<<15;/* bit15置1進入軟復位 */
13while((GPT1->CR >>15)&0x01);/*等待覆位完成 */
14
15/*
16 * GPT的CR寄存器,GPT通用設置
17 * bit22:20 000 輸出比較1的輸出功能關閉,也就是對應的引腳沒反應
18 * bit9: 0 Restart模式,當CNT等於OCR1的時候就產生中斷
19 * bit8:6 001 GPT時鐘源選擇ipg_clk=66Mhz
20 */
21 GPT1->CR =(1<<6);
22
23/*
24 * GPT的PR寄存器,GPT的分頻設置
25 * bit11:0 設置分頻值,設置為0表示1分頻,
26 * 以此類推,最大可以設置為0XFFF,也就是最大4096分頻
27 */
28 GPT1->PR =65;/* 66分頻,GPT1時鐘為66M/(65+1)=1MHz */
29
30/*
31 * GPT的OCR1寄存器,GPT的輸出比較1比較計數值,
32 * GPT的時鐘為1Mz,那麼計數器每計一個值就是就是1us。
33 * 為了實現較大的計數,我們將比較值設置為最大的0XFFFFFFFF,
34 * 這樣一次計滿就是:0XFFFFFFFFus = 4294967296us = 4295s = 71.5min
35 * 也就是說一次計滿最多71.5分鐘,存在溢出。
36 */
37 GPT1->OCR[0]=0XFFFFFFFF;
38 GPT1->CR |=1<<0;/* 使能GPT1 */
39
40/* 一下屏蔽的代碼是GPT定時器中斷代碼,
41 * 如果想學習GPT定時器的話可以參考一下代碼。
42 */
43 #if0
44/*
45 * GPT的PR寄存器,GPT的分頻設置
46 * bit11:0 設置分頻值,設置為0表示1分頻,
47 * 以此類推,最大可以設置為0XFFF,也就是最大4096分頻
48 */
49
50 GPT1->PR =65;/* 66分頻,GPT1時鐘為66M/(65+1)=1MHz */
51/*
52 * GPT的OCR1寄存器,GPT的輸出比較1比較計數值,
53 * 當GPT的計數值等於OCR1裡面值時候,輸出比較1就會發生中斷
54 * 這裡定時500ms產生中斷,因此就應該為1000000/2=500000;
55 */
56 GPT1->OCR[0]=500000;
57
58/*
59 * GPT的IR寄存器,使能通道1的比較中斷
60 * bit0: 0 使能輸出比較中斷
61 */
62 GPT1->IR |=1<<0;
63
64/*
65 * 使能GIC裡面相應的中斷,並且註冊中斷處理函數
66 */
67 GIC_EnableIRQ(GPT1_IRQn);/*使能GIC中對應的中斷*/
68 system_register_irqhandler(GPT1_IRQn,
(system_irq_handler_t)gpt1_irqhandler,
NULL);
69 #endif
70
71}
72
73 #if0
74/* 中斷處理函數 */
75void gpt1_irqhandler(void)
76{
77staticunsignedchar state =0;
78 state =!state;
79/*
80 * GPT的SR寄存器,狀態寄存器
81 * bit2: 1 輸出比較1發生中斷
82 */
83if(GPT1->SR &(1<<0))
84{
85 led_switch(LED2,state);
86}
87 GPT1->SR |=1<<0;/* 清除中斷標誌位 */
88}
89 #endif
90
91/*
92 * @description : 微秒(us)級延時
93 * @param – value : 需要延時的us數,最大延時0XFFFFFFFFus
94 * @return : 無
95 */
96void delayus(unsignedint usdelay)
97{
98unsignedlong oldcnt,newcnt;
99unsignedlong tcntvalue =0;/* 走過的總時間 */
100
101 oldcnt =GPT1->CNT;
102while(1)
103{
104 newcnt =GPT1->CNT;
105if(newcnt !=oldcnt)
106{
107if(newcnt >oldcnt) /* GPT是向上計數器,並且沒有溢出 */
108 tcntvalue +=newcnt - oldcnt;
109else/* 發生溢出 */
110 tcntvalue +=0XFFFFFFFF-oldcnt +newcnt;
111 oldcnt =newcnt;
112if(tcntvalue >=usdelay) /* 延時時間到了 */
113break;/* 跳出 */
114}
115}
116}
117
118/*
119 * @description : 毫秒(ms)級延時
120 * @param - msdelay : 需要延時的ms數
121 * @return : 無
122 */
123void delayms(unsignedint msdelay)
124{
125int i =0;
126for(i=0;i<msdelay; i++)
127{
128 delayus(1000);
129}
130}
131
132/*
133 * @description : 短時間延時函數
134 * @param - n : 要延時循環次數(空操作循環次數,模式延時)
135 * @return : 無
136 */
137void delay_short(volatileunsignedint n)
138{
139while(n--){}
140}
141
142/*
143 * @description : 延時函數,在396Mhz的主頻下
144 * 延時時間大約為1ms
145 * @param - n : 要延時的ms數
146 * @return : 無
147 */
148void delay(volatileunsignedint n)
149{
150while(n--)
151{
152 delay_short(0x7ff);
153}
154}
文件bsp_delay.c中一共有5個函數,分別為:delay_init、delayus、delayms 、delay_short和delay。除了delay_short和delay以外,其他三個都是新增加的。函數delay_init是延時初始化函數,主要用於初始化GPT1定時器,設置其時鐘源、分頻值和輸出比較寄存器值。第43到68行被屏蔽掉的程序是GPT1的中斷初始化代碼,如果要使用GPT1的中斷功能的話可以參考此部分代碼。第73到89行被屏蔽掉的程序是GPT1的中斷處理函數gpt1_irqhandler,同樣的,如果需要使用GPT1中斷功能的話可以參考此部分代碼。
函數delayus和delayms就是us級和ms級的高精度延時函數,函數delayus就是按照我們在20.1.2小節講解的高精度延時原理編寫的,delayus函數處理GPT1計數器溢出的情況。函數delayus只有一個參數usdelay,這個參數就是要延時的us數。delayms函數很簡單,就是對delayus(1000)的多次疊加,此函數也只有一個參數msdelay,也就是要延時的ms數。
最後修改mian.c文件,內容如下:
示例代碼20.3.3 main.c文件代碼
/**************************************************************
Copyright zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : mian.c
作者 : 左忠凱
版本 : V1.0
描述 : I.MX6U開發板裸機實驗12 高精度延時實驗
其他 : 本實驗我們學習如何使用I.MX6U的GPT定時器來實現高精度延時,
以前的延時都是靠空循環來實現的,精度很差,只能用於要求
不高的場合。使用I.MX6U的硬體定時器就可以實現高精度的延時,
最低可以做到20us的高精度延時。
論壇 : www.openedv.com
日誌 : 初版V1.0 2019/1/15 左忠凱創建
**************************************************************/
1 #include "bsp_clk.h"
2 #include "bsp_delay.h"
3 #include "bsp_led.h"
4 #include "bsp_beep.h"
5 #include "bsp_key.h"
6 #include "bsp_int.h"
7 #include "bsp_keyfilter.h"
8
9/*
10 * @description : main函數
11 * @param : 無
12 * @return : 無
13 */
14int main(void)
15{
16 unsignedchar state =OFF;
17
18 int_init();/* 初始化中斷(一定要最先調用!) */
19 imx6u_clkinit();/* 初始化系統時鐘 */
20 delay_init();/* 初始化延時 */
21 clk_enable();/* 使能所有的時鐘 */
22 led_init();/* 初始化led */
23 beep_init();/* 初始化beep */
24
25 while(1)
26 {
27 state =!state;
28 led_switch(LED0,state);
29 delayms(500);
30 }
31
32 return0;
33}
main.c函數很簡單,在第20行調用delay_init函數進行延時初始化,最後在while循環中周期性的點亮和熄滅LED0,調用函數delayms來實現延時。
20.4編譯下載驗證
20.4.1編寫Makefile和連結腳本
因為本章例程並沒有新建任何文件,所以只需要修改Makefile中的TARGET為delay即可,連結腳本報紙不變。
20.4.2編譯下載
使用Make命令編譯代碼,編譯成功以後使用軟體imxdownload將編譯完成的delay.bin文件下載到SD卡中,命令如下:
chmod 777 imxdownload //給予imxdownload可執行權限,一次即可
./imxdownload delay.bin /dev/sdd //燒寫到SD卡中
燒寫成功以後將SD卡插到開發板的SD卡槽中,然後復位開發板。程序運行正常的話LED0會以500ms為周期不斷的亮、滅閃爍。可以通過肉眼觀察LED亮滅的時間是否為500ms。但是肉眼觀察肯定不準確,既然本章號稱高精度延時實驗,那麼就得經得住專業儀器的測試。我們將「示例代碼20.3.3」中第29行,也就是mian函數while循環中的延時改為「delayus(20)」,也就是LED0亮滅的時間各為20us,那麼一個完整的周期就是20+20=40us,LED0對應的IO頻率就應該是1/0.00004=25000Hz=25KHz。使用示波器測試LED0對應的IO頻率,結果如圖20.4.3.1所示:
圖20.4.3.120us延時波形
從圖20.4.3.1可以看出,LED0對應的IO波形頻率為22.3KHz,周期是44.9us,那麼main函數中while循環執行一次的時間就是44.9/2=22.45us,大於我們設置的20us,看起來好像是延時不準確。但是我們要知道這22.45us是main函數裡面while循環總執行時間,也就是下面代碼的總執行時間:
while(1)
{
state = !state;
led_switch(LED0, state);
delayus(20);
}
在上面代碼中不止有delayus(20)延時函數,還有控制LED燈亮滅的函數,這些代碼的執行也需要時間的,即使是delayus函數,其內部也是要消耗一些時間的。假如我們將while循環裡面的代碼改為如下形式:
while(1)
{
GPIO1->DR &= ~(1<<3);
delayus(20);
GPIO1->DR |= (1<<3);
delayus(20);
}
上述代碼我們通過直接操作寄存器的方式來控制IO輸出高低電平,理論上while循環執行時間會更小,並且while循環裡面使用了兩個delayus(20),因此執行一次while循環的理論時間應該是40us,和上面做的實驗一樣。重新使用示波器測量一下,結果如圖20.4.3.2所示:
圖20.4.3.2修改while循環後的波形
從圖20.4.3.2可以看出,此時while循環執行一次的時間是41.8us,那麼一次delayus(20)的時間就是41.8/2=20.9us,很接近我們的20us理論值。但是還是因為有其他程序開銷存在,在加上示波器測量誤差,所以不可能測量出絕對的20us。但是其已經非常接近了,基本可以證明我們的高精度延時函數是成功的、可以用的。