一、輸入捕獲測頻率
?接線圖:
測信號的輸入引腳為PA6,信號從PA6進來,待測的PWM信號也是STM32自己生成的,輸出引腳是PA0,所以接線這里直接用一根線將PA0引到PA6就可以了。?如果有信號發生器的話,也可以設置成方波信號輸出,高電平設置成3.3v,低電平0v,然后直接接到PA6,另一個就是共地。
PWM初始化模塊:
目前這里我們要借用一下之前寫過的PWM模塊代碼,以便生成待測信號,所以這里程序直接復制,之前的PWM驅動LED呼吸燈工程,在這個工程基礎上寫。然后在這個PWM模塊代碼進行改進,目前這個代碼邏輯是,初始化TIM2的通道1,產生一個PWM的波形,輸出引腳是PA0,然后通過Set_Compare函數,可以調節CCR1的寄存器的值,從而控制PWM的占空比,但是目前這個PWM頻率是在初始化已經寫好的了,是固定的,操作起來不太方便。所以我們要在最后再加一個函數,用來便捷地調節PWM頻率。
那這里如何調節PWM頻率??
通過公式,我們知道PWM頻率=更新頻率=72M/(PSC+1/(ARR+1),所以PSC和ARR都可以調節頻率,但是占空比=CCR/(ARR+1),所以通過ARR調節頻率,同時還會影響到占空比,而通過PSC調節頻率,不會影響占空比,顯然比較方便。所以這里我們直接固定ARR為100-1,通過調節PSC來改變PWM頻率,這里的ARR為100-1,CCR的數值直接就是占空比。
這里實際使用也是有技巧的,一般可以根據分辨率的要求,先確定好ARR值,比如分辨率[1/(ARR+1)],1%就足夠了。那ARR就是給100-1,這樣PSC決定頻率,CCR決定占空比。如果想要更高的分辨率,比如0.1%,那ARR就先固定1000-1,這樣頻率就是72M/預分頻/1000,占空比就是CCR/1000,這樣也好算。
然后ARR我們固定給100-1,初始化操作的PSC就先不管,后面再寫一個函數,在初始化之后單獨修改PSC。?
void PWM_SetPrescaler(uint16_t Prescaler) //配置TIMx預調度器。
{TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//Prescaler立即被加載
}
代碼詳解:?
?void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode)//配置TIMx預調度器
參數 | 說明 |
TIMx | 其中x為1 ~ 17,選擇TIM外設 |
Prescaler | 指定預Prescaler寄存器值 |
TIM_PSCReloadMode | 指定TIM預分頻器的重裝模式。該參數可以是以下值之一:TIM_PSCReloadMode_Update:在更新事件中加載precaler;TIM_PSCReloadMode_Immediate:立即加載precaler |
TIM_PSCReloadMode的兩個選擇參數說白了還是影子寄存器的預裝載問題,就是你寫入的值是立刻生效還是在更新事件中生效。
- 立刻生效:可能在值發生改變時產生切斷波形的現象,比如PWM一個周期剛過去一半,立刻生效了,那就立刻切斷當前波形,開始新的一個周期。在頻率變化時,這里會出現一個不完整的周期。
- 更新事件生效:就是會有一個緩存器,延遲參數的寫入時間,等一個周期結束了,在更新事件時,再統一改變參數,?保證每個周期的完整。
在這里我們選擇哪個參數都無所謂,我們要求不高,哪個都可以,那這里就選擇立刻生效吧。?
主函數這里調用之后就生成一個頻率為?1KHz,占空比為50%的信號了。
int main(void)
{OLED_Init();PWM_Init();PWM_SetPrescaler(720 - 1); //頻率=72M/(PSC - 1)/(ARR - 1) 這里(ARR - 1)=100PWM_SetCompare1(50); //Duty= CCR/(ARR - 1)while(1){}
}
輸入捕獲初始化:
這里因為是自己測自己,所以還要建一個輸入捕獲模塊代碼,測一下PA0口的頻率和占空比。
?
步驟:?
①RCC開啟時鐘(把要用的TIM外設和GPIO外設時鐘都打開)?
②GPIO初始化,把GPIO配置為輸入模式?,一般選擇上拉或者浮空輸入模式
③配置時基單元,讓CNT計數器在內部時鐘驅動下自增運行?
④配置輸入捕獲單元,包括濾波器、極性選擇、直連通道還是交叉通道、分頻器這些參數,用一個結構體就可以配置
⑤選擇從模式的觸發源觸發源為TF1FP1,這里調用一個庫函數給一個參數即可
⑥選擇觸發之后執行的操作
⑦開啟定時器?
?代碼:
?這里的時鐘選擇,要選擇TIM3,也是APB1外設時鐘,因為PWM模塊還需要TIM2輸出PWM,所以要開啟TIM3時鐘。GPIO初始化需要查看引腳定義表,TIM3通道1對應的是PA6引腳,如果選擇其它定時器或者其它通道,那這個引腳就需要根據引腳定義表改變
?
這里的TIM_Prescaler值決定了測周法的標準頻率Fc,72M/預分頻就是就計數器自增的頻率,就是計數器標準頻率,這個需要根據你信號頻率的分布范圍來調整,這里暫時先給72-1,這樣標準頻率就是72M/72=1MHz,這樣方便計算。?
?void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)//根據指定初始化TIM外設TIM_ICInitStruct中的參數
?輸入捕獲初始化結構定義
參數 | 說明 |
TIM_Channel | 指定TIM通道(1~4通道) |
TIM_ICPolarity | 極性選擇,指定輸入信號的活動邊緣(上升、下降、雙邊沿觸發) |
TIM_ICSelection | 指定輸入(直連或者交叉輸入) |
TIM_ICPrescaler | 指定輸入捕獲預分頻(1、2、4、8分頻,1分頻就是不分頻) |
TIM_ICFilter | 指定輸入捕獲過濾器,取值范圍為0x0 ~ 0xF之間的數字,數值越大,濾波效果越好 |
濾波器和分頻器的區別:?
雖然兩個都是計次的東西,但是濾波器計次,并不會改變信號的原有頻率,一般濾波器的采樣頻率都會遠高于信號頻率,所以它只會濾除高頻噪聲,使信號更加平滑,1KHz濾波之后仍然是1KHz,信號頻率不會發生變化。而分頻器就是對信號本身進行計次,會改變頻率,1KHz,二分頻之后就是500Hz,四分頻之后就是250Hz。
對應步驟⑤ :
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource)//選擇輸入觸發器源?
參數 | 說明 |
TIMx | 其中x可以是1、2、3、4、5、8、9、12或15來選擇TIM外設。 |
TIM_InputTriggerSource | 輸入觸發器源。 |
?TIM_InputTriggerSource參數選擇:下面兩圖對應
對應步驟⑥?
?void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode)//選擇TIMx從模式
參數 | 說明 |
TIMx | 其中x可以是1、2、3、4、5、8、9、12或15來選擇TIM外設。 |
TIM_SlaveMode | 指定定時器從模式。 |
TIM_SlaveMode參數選擇:?
最后,啟動定時器后,CNT就會在內部時鐘的驅動下不斷自增,即使信號沒有過來它也會自增。一直自增也沒有關系,因為在有信號過來的時候,它就會在從模式下自動清零,不會影響測量。初始化完成后,整個電路就能實現全自動測量了。?
void IC_Init(void)
{/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //開啟TIM3的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟GPIOA的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA6引腳初始化為上拉輸入/*配置時鐘源*/TIM_InternalClockConfig(TIM3); //選擇TIM3為內部時鐘,若不調用此函數,TIM默認也為內部時鐘/*時基單元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定義結構體變量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //時鐘分頻,選擇不分頻,此參數用于配置濾波器時鐘,不影響時基單元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //計數器模式,選擇向上計數TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //計數周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //預分頻器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重復計數器,高級定時器才會用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //將結構體變量交給TIM_TimeBaseInit,配置TIM3的時基單元/*輸入捕獲初始化*/TIM_ICInitTypeDef TIM_ICInitStructure; //定義結構體變量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //選擇配置定時器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF; //輸入濾波器參數,可以過濾信號抖動TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //極性,選擇為上升沿觸發捕獲TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕獲預分頻,選擇不分頻,每次信號都觸發捕獲TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //輸入信號交叉,選擇直通,不交叉TIM_ICInit(TIM3, &TIM_ICInitStructure); //將結構體變量交給TIM_ICInit,配置TIM3的輸入捕獲通道/*選擇觸發源及從模式*/TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //觸發源選擇TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //從模式選擇復位//即TI1產生上升沿時,會觸發CNT歸零/*TIM使能*/TIM_Cmd(TIM3, ENABLE); //使能TIM3,定時器開始運行
}
注意:????
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);?這里輸出比較和輸入捕獲都有四個通道,OCInit四個通道,每個通道占用一個函數,而ICInit是四個通道共用一個函數的,在結構體會有額外一個參數選擇哪個通道。
- void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
- void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
- void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
- void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);?
- uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//獲取TIMx輸入捕獲1值。
- uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);//獲取TIMx輸入捕獲2值。
- uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);//獲取TIMx輸入捕獲3值。
- uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx); //獲取TIMx輸入捕獲4值。
?這8個函數都是讀取各個通道的CCR值,是相對應的,輸出比較模式下,CCR是只寫的,用TIM_SetCompare寫入;輸入捕獲模式下,CCR是只讀的,用TIM_GetCapture讀出。
返回值:各Capture Compare 寄存器值。
查看頻率讀取CCR(頻率)
?這里需要執行公式Fx=Fc/N,之前說Fc=72M/(PSC+1),PSC=72-1,所以72M/72=1MHz,然后除以N,N就是讀取CCR的值,用uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx)函數讀取
uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1); //測周法得到頻率fx = fc / N,這里不執行+1的操作也可
}
主函數:?
int main(void)
{OLED_Init();PWM_Init();IC_Init();OLED_ShowString(1,1,"Freq:00000Hz");/*使用PWM模塊提供輸入捕獲的測試信號:將待測信號傳給PA0,PA0又通過導線輸入到PA6,PA6是TIM3的通道1,通道1通過輸入捕獲模塊,測得頻率*/PWM_SetPrescaler(720 - 1); //頻率=72M/(PSC - 1)/(ARR - 1) 這里(ARR - 1)=100PWM_SetCompare1(50); //Duty= CCR/(ARR - 1)while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);//不斷刷新顯示輸入捕獲測得的頻率}
}
二、PWMI模式測頻率&占空比
這里輸入捕獲代碼部分直接對上一個代碼進行升級就可以了。配置成兩個通道同時捕獲一個引腳
?
這里輸入捕獲有兩種配置方法:效果一樣?
1、直接復制多一份通道初始化
2、 使用ST公司封裝好的函數
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)//根據指定配置TIM外設TIM_ICInitStruct中的參數來測量外部PWM信號
?作用:這個函數你只要傳入一個通道的參數,在函數里它會自動把剩下一個通道初始化成相反的配置,比如這里傳入通道1,直連,上升沿,函數就會順帶配置通道2,交叉,下降;傳入通道2,直連,上升沿,函數就會順帶配置通道1,交叉,下降沿。這個函數只能傳入通道1和通道2,不能傳通道3和通道4。
?獲取占空比函數:
?根據上節課PWMI分析【STM32】TIM輸入捕獲-學習筆記-CSDN博客,高電平的計數值存在CCR2里,整個周期的計數值存在CCR1里,用CCR2/CCR1就可以得到占空比了。
/*** 函 數:獲取輸入捕獲的占空比* 參 數:無* 返 回 值:捕獲得到的占空比*/
uint32_t IC_GetDuty(void)
{return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,這里不執行+1的操作也可
}
主函數:
int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化PWM_Init(); //PWM初始化IC_Init(); //輸入捕獲初始化/*顯示靜態字符串*/OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列顯示字符串Freq:00000HzOLED_ShowString(2, 1, "Duty:00%"); //2行1列顯示字符串Duty:00%/*使用PWM模塊提供輸入捕獲的測試信號*/PWM_SetPrescaler(720 - 1); //PWM頻率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50); //PWM占空比Duty = CCR / 100while (1){OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不斷刷新顯示輸入捕獲測得的頻率OLED_ShowNum(2, 6, IC_GetDuty(), 2); //不斷刷新顯示輸入捕獲測得的占空比}
}
?測頻率性能:32:43
誤差分析:34:45?