NXP (I.MX6ULL) GPT高精度延时定时器
參考:Linux NXP (I.MX6ULL) GPT高精度延時定時器
作者:一只青木呀
發布時間: 2020-09-20 11:50:14
網址:https://blog.csdn.net/weixin_45309916/article/details/108690475
目錄
- GPT 定時器簡介
- GPT 定時器特性
- GPT 定時器時鐘源選擇
- GPT 定時器結構
- GPT 定時器的兩種工作模式
- GPT 定時器幾個重要的寄存器
- 配置寄存器 GPTx_CR
- 分頻寄存器 GPTx_PR
- 狀態寄存器 GPTx_SR
- 計數寄存器 GPTx_CNT
- 輸出比較寄存器 GPTx_OCR
- 定時器高精度延時實現
- 硬件原理分析
- 實驗程序編寫
- bsp_delay.h
- bsp_delay.c
- 編譯下載及示波器驗證
GPT 定時器簡介
GPT 定時器全稱為 General Purpose Timer,GPT 定時器是一個 32 位向上定時器(也就是從 0X00000000 開始向上遞增計數), GPT 定時器也可以跟一個值進行比較,當計數器值和這個值相等的話就發生比較事件,產生比較中斷。GPT 定時器有一個 12 位的分頻器,可以對 GPT 定時器的時鐘源進行分頻。
GPT 定時器特性
-
①、一個可選時鐘源的 32 位向上計數器。
-
②、三個輸出比較通道,可以設置輸出模式。
-
③、兩個輸入捕獲(配置引腳復用)通道,可以設置觸發方式。EPIT定時器只能定時,不能捕獲。
-
④、可以生成捕獲中斷、比較中斷和溢出中斷。
-
⑤、計數器可以運行在重新啟動(restart)或(自由運行)free-run 模式。
GPT 定時器時鐘源選擇
從上圖中可以看出一共有五個時鐘源,分別為: ipg_clk_24M、 GPT_CLK(外部時鐘)、ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq。本博文選擇 ipg_clk 為 GPT 的時鐘源, ipg_clk_66MHz。
GPT 定時器結構
GPT 定時器結構中各部分意義如下:
①、此部分為 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 定時器幾個重要的寄存器
配置寄存器 GPTx_CR
寄存器 GPTx_CR 我們用到的重要位如下:
| SWR(bit15) | 軟件復位 ,向此位寫 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)。 |
| ENMOD(bit1) | GPT 使能模式,此位為 0 的時候如果關閉 GPT 定時器,計數器寄存器保存定時器關閉時候的計數值(記憶功能)。此位為 1 的時候如果關閉 GPT 定時器,計數器寄存器就會清零。 |
| EN(bit0) | GPT 使能位,為 1 的時候使能 GPT 定時器,為 0 的時候關閉 GPT 定時器。 |
分頻寄存器 GPTx_PR
寄存器 GPTx_PR 我們用到的重要位如下(低12位):
| PRESCALER(bit11:0) | 這就是 12 位分頻值,可設置 0~ 4095,分別對應 1~4096 分頻。 |
狀態寄存器 GPTx_SR
寄存器 GPTx_SR 重要的位如下:
| ROV(bit5) | 回滾標志位(溢出位),當計數值從 0XFFFFFFFF 回滾到 0X00000000 的時候此位置 1。 |
| IF2~IF1(bit4:3) | 輸入捕獲標志位,當輸入捕獲事件發生以后此位置 1,一共有兩路輸入捕獲通道。如果使用輸入捕獲中斷的話需要在中斷處理函數中清除此位。 |
| OF3~OF1(bit2:0) | 輸出比較中斷標志位,當輸出比較事件發生以后此位置 1,一共有三路輸出比較通道。如果使用輸出比較中斷的話需要在中斷處理函數中清除此位。 |
計數寄存器 GPTx_CNT
接著看一下 GPT 定時器的計數寄存器 GPTx_CNT,這個寄存器保存著 GPT 定時器的當前計數值。
輸出比較寄存器 GPTx_OCR
最后看一下 GPT 定時器的輸出比較寄存器 GPTx_OCR,每個輸出比較通道對應一個輸出比較寄存器,因此一個 GPT 定時器有三個 OCR 寄存器,它們的作都是相同的。以輸出比較通道 1 為例,其輸出比較寄存器為 GPTx_OCR1,這是一個 32 位寄存器,用于存放 32 位的比較值。當計數器值和寄存器 GPTx_OCR1 中的值相等就會產生比較事件,如果使能了比較中斷的話就會觸發相應的中斷。
定時器高精度延時實現
高精度延時函數的實現肯定是要借助硬件定時器,使用 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 延時分別編寫兩個延時函數。
硬件原理分析
本試驗用到的資源如下:
①、一個LED 燈:LED0。
②、定時器GPT1。
本實驗通過高精度延時函數來控制LED0 的閃爍,可以通過示波器來觀察LED0 的控制IO輸出波形,通過波形的頻率或者周期來判斷延時函數精度是否正常。
實驗程序編寫
本實驗對應的例程路徑為:開發板光盤-> 1、裸機例程-> 12_highpreci_delay。
本章實驗在上一章例程的基礎上完成,更改工程名字為“delay”,直接修改bsp_delay.c 和bsp_delay.h 這兩個文件,將bsp_delay.h 文件改為如下所示內容:
bsp_delay.h
#ifndef __BSP_DELAY_H #define __BSP_DELAY_H /*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_delay.h 作者 : 左忠凱 版本 : V1.0 描述 : 延時頭文件。 其他 : 無 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創建V2.0 2019/1/15 左忠凱修改添加了一些函數聲明。 ***************************************************************/ #include "imx6ul.h"/* 函數聲明 */ void delay_init(void); void delayus(unsigned int usdelay); void delayms(unsigned int msdelay); void delay(volatile unsigned int n); void gpt1_irqhandler(void);#endifbsp_delay.h 文件就是一些函數聲明,很簡單。在文件bsp_delay.c 中輸入如下內容:
bsp_delay.c
#include "bsp_delay.h"/** @description : 延時有關硬件初始化,主要是GPT定時器GPT定時器時鐘源選擇ipg_clk=66Mhz* @param : 無* @return : 無*/ void delay_init(void) {GPT1->CR = 0; /* 清零,bit0位為0,即停止GPT */GPT1->CR = 1 << 15; /* bit15置1進入軟復位 */while((GPT1->CR >> 15) & 0x01); /*等待復位完成 *//** GPT的CR寄存器,GPT通用設置* bit22:20 000 輸出比較1的輸出功能關閉,也就是對應的引腳沒反應* bit9: 0 Restart模式,當CNT等于OCR1的時候就產生中斷* bit8:6 001 GPT時鐘源選擇ipg_clk=66Mhz* bit*/GPT1->CR = (1<<6);/** GPT的PR寄存器,GPT的分頻設置* bit11:0 設置分頻值,設置為0表示1分頻,* 以此類推,最大可以設置為0XFFF,也就是最大4096分頻*/GPT1->PR = 65; /* 設置為65,即66分頻,因此GPT1時鐘為66M/(65+1)=1MHz *//** GPT的OCR1寄存器,GPT的輸出比較通道1比較計數值,* GPT的時鐘為1Mz,那么計數器每計一個值就是就是1us。* 為了實現較大的計數,我們將比較值設置為最大的0XFFFFFFFF,* 這樣一次計滿就是:0XFFFFFFFFus = 4294967296us = 4295s = 71.5min* 也就是說一次計滿最多71.5分鐘,存在溢出*/GPT1->OCR[0] = 0XFFFFFFFF;GPT1->CR |= 1<<0; //使能GPT1/* 一下屏蔽的代碼是GPT定時器中斷代碼,* 如果想學習GPT定時器的話可以參考一下代碼。*/ #if 0/** GPT的PR寄存器,GPT的分頻設置* bit11:0 設置分頻值,設置為0表示1分頻,* 以此類推,最大可以設置為0XFFF,也就是最大4096分頻*/GPT1->PR = 65; //設置為1,即65+1=66分頻,因此GPT1時鐘為66M/66=1MHz/** GPT的OCR1寄存器,GPT的輸出比較1比較計數值,* 當GPT的計數值等于OCR1里面值時候,輸出比較1就會發生中斷* 這里定時500ms產生中斷,因此就應該為1000000/2=500000;*/GPT1->OCR[0] = 500000;//500ms產生中斷/** GPT的IR寄存器,使能通道1的比較中斷* bit0: 0 使能輸出比較中斷*/GPT1->IR |= 1 << 0;/** 使能GIC里面相應的中斷,并且注冊中斷處理函數*/ //宏定義里中斷ID是87GIC_EnableIRQ(GPT1_IRQn); //使能GIC中對應的中斷system_register_irqhandler(GPT1_IRQn, (system_irq_handler_t)gpt1_irqhandler, NULL); //注冊中斷服務函數 #endif}#if 0 /* 中斷處理函數 */ void gpt1_irqhandler(void) { static unsigned char state = 0;state = !state;/** GPT的SR寄存器,狀態寄存器* bit2: 1 輸出比較1發生中斷*/if(GPT1->SR & (1<<0)) {led_switch(LED2, state);}GPT1->SR |= 1<<0; /* 清除中斷標志位 */ } #endif/** @description : 微秒(us)級延時* @param - value : 需要延時的us數,最大延時0XFFFFFFFFus* @return : 無*/ void delayus(unsigned int usdelay) {unsigned long oldcnt,newcnt;unsigned long tcntvalue = 0; /* 走過的總時間 */oldcnt = GPT1->CNT;while(1){newcnt = GPT1->CNT;if(newcnt != oldcnt){if(newcnt > oldcnt) /* GPT是向上計數器,并且沒有溢出 */tcntvalue += newcnt - oldcnt;//統計時間差else /* 發生溢出 */tcntvalue += 0XFFFFFFFF-oldcnt + newcnt;oldcnt = newcnt;if(tcntvalue >= usdelay)/* 延時時間到了 */break; /* 跳出 */}} }/** @description : 毫秒(ms)級延時* @param - msdelay : 需要延時的ms數* @return : 無*/ void delayms(unsigned int msdelay) {int i = 0;for(i=0; i<msdelay; i++){delayus(1000);} }/** @description : 短時間延時函數* @param - n : 要延時循環次數(空操作循環次數,模式延時)* @return : 無*/ void delay_short(volatile unsigned int n) {while(n--){} }/** @description : 延時函數,在396Mhz的主頻下* 延時時間大約為1ms* @param - n : 要延時的ms數* @return : 無*/ void delay(volatile unsigned int n) {while(n--){delay_short(0x7ff);} }文件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 數。
最后修改main.c 文件,內容如下:
#include "bsp_clk.h" #include "bsp_delay.h" #include "bsp_led.h" #include "bsp_beep.h" #include "bsp_key.h" #include "bsp_int.h" #include "bsp_keyfilter.h"/** @description : main函數* @param : 無* @return : 無*/ int main(void) {unsigned char state = OFF;int_init(); /* 初始化中斷(一定要最先調用!) */imx6u_clkinit(); /* 初始化系統時鐘 */delay_init(); /* 初始化延時 */clk_enable(); /* 使能所有的時鐘 */led_init(); /* 初始化led */beep_init(); /* 初始化beep */while(1) { state = !state;led_switch(LED0, state);delayms(500);}return 0; }main.c 函數很簡單,在第20 行調用delay_init 函數進行延時初始化,最后在while 循環中周期性的點亮和熄滅LED0,調用函數delayms 來實現延時。
編譯下載及示波器驗證
因為本章例程并沒有新建任何文件,所以只需要修改Makefile 中的TARGET 為delay 即可,鏈接腳本保持不變。
使用Make 命令編譯代碼,編譯成功以后使用軟件imxdownload 將編譯完成的delay.bin 文件下載到SD 卡中,命令如下:
chmod 777 imxdownload //給予imxdownload 可執行權限,一次即可 ./imxdownload delay.bin /dev/sdd //燒寫到SD 卡中,不能燒寫到/dev/sda 或sda1 設備里面!燒寫成功以后將SD 卡插到開發板的SD 卡槽中,然后復位開發板。程序運行正常的話LED0會以500ms 為周期不斷的亮、滅閃爍。可以通過肉眼觀察LED 亮滅的時間是否為500ms。但是肉眼觀察肯定不準確,既然本章號稱高精度延時實驗,那么就得經得住專業儀器的測試。我們將“示例代碼20.3.3”中第29 行,也就是main 函數while 循環中的延時改為“delayus(20)”,也就是LED0 亮滅的時間各為20us,那么一個完整的周期就是20+20=40us,LED0 對應的IO 頻率就應該是1/0.00004=25000Hz=25KHz。使用示波器測試LED0 對應的IO 頻率,結果如圖20.4.2.1 所示:
從圖20.4.2.1 可以看出,LED0 對應的IO 波形頻率為22.3KHz,周期是44.9us,那么main函數中while 循環執行一次的時間就是44.9/2=22.45us,大于我們設置的20us,看起來好像是延時不準確。但是我們要知道這22.45us 是main 函數里面while 循環總執行時間,也就是下面代碼的總執行時間:
在上面代碼中不止有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.2.2 所示:
從圖20.4.2.2 可以看出,此時while 循環執行一次的時間是41.8us,那么一次delayus(20)的時間就是41.8/2=20.9us,很接近我們的20us 理論值。但是還是因為有其他程序開銷存在,在加上示波器測量誤差,所以不可能測量出絕對的20us。但是其已經非常接近了,基本可以證明我們的高精度延時函數是成功的、可以用的。
總結
以上是生活随笔為你收集整理的NXP (I.MX6ULL) GPT高精度延时定时器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二叉树学习笔记--先序遍历
- 下一篇: 其它数据类型和Json的转化