摘要:利用定時器產生PWM波。然後利用32的外部中斷和定時器來測量32輸出的波形 硬體:STM32F103C8T6核心板、示波器、串列埠除錯助手 所用到的的引腳為PA8和PA0。
測量方案:在第一次外部中斷(上升沿觸發)到之時,開啟定時器,同時計數器清零。然後等待第二次中斷到來,在第二次外部中斷(上升沿觸發)到之時,獲取計數器的計數值,同時關閉計數器。因為知道了計數器計數一個數的時間,所以在第二次外部中斷(上升沿觸發)到之時,獲取計數器的計數值,透過這個值就知道一個脈衝的時間週期。時間週期的倒數就是外部訊號的頻率。
一、利用TIM1的CH1產生PWM波pwm.c
#include "pwm.h"void TIM1_PWM_Init(u16 arr,u16 psc){ GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外設時鐘使能 //設定該引腳為複用輸出功能,輸出TIM1 CH1的PWM脈衝波形 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //複用推輓輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //輸出PWM的頻率為200 000/100=2 000 HZ=2K 實際示波器測量 2.00055K TIM_TimeBaseStructure.TIM_Prescaler =psc; //驅動(微控制器提供給)計數器的時鐘是72 000 000/36 0=200kHZ TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設定時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的引數初始化TIMx的時間基數單位 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //選擇定時器模式:TIM脈衝寬度調製模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能 TIM_OCInitStructure.TIM_Pulse = 3600; //設定待裝入捕獲比較暫存器的脈衝值 這個值要為arr:自動重灌值的一半,佔空比才為50% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高 TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的引數初始化外設TIMx TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主輸出使能 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1預裝載使能 TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的預裝載暫存器 TIM_Cmd(TIM1, ENABLE); //使能TIM1 }
pwm.h
#ifndef __PWM_H#define __PWM_H#include "sys.h"void TIM1_PWM_Init(u16 arr,u16 psc);#endif
main.c
#include "delay.h"#include "sys.h" #include "pwm.h"int main(void){ delay_init(); //延時函式初始化 //10k 7199 //20k 3599 //8k 8999 TIM1_PWM_Init(7199,0); //不分頻。PA8輸出PWM頻率=72000K/(7199+1)=10Khz while(1) { }}
定時器1的通道1對應的是PA8引腳,連線示波器可以測出波形
二、將PA8與PA0相連線這裡利用PA8輸出的PWM波形讓PA0外部中斷引腳測量。
三、利用STM32的外部中斷和定時器測量頻率我們知道在配置定時器時最重要的就是配置定時器的預分頻係數和重灌載值。定時器的本質就是一個計數器,計數到我們設定的值後就會溢位,也就是重新從0開始開始計數。設定預分頻係數就是設定計數器的頻率,假設為71,F1的系統時鐘為72M,經過72分頻,給計數器的時鐘頻率就是1M,週期就是1/1M=1us。也是就1us計一個數。那麼計幾個數呢?這就要看重灌載值ARR,這裡我們設定為0XFFFF,也就是計數65536個數,就是計滿整個暫存器的值。為什麼要分頻係數為72,重灌載值為0XFFFF?這裡給出詳細的分析過程。
1、為什麼要分頻係數為72F1的系統時鐘為72M,F1的系統時鐘為72M,如果不分頻的話,提供給定時器的時鐘就直接是72MHZ。72MHz是個什麼概念?72MHz它對應的週期就是(1/72000000)秒,也就是計數器從0計數到最大值65535,只需要花費(65535/72000000)秒≈1ms。這句話的意思就是如果你不分頻,計數器最大隻能定時1ms。那麼你的定時器每隔1ms就會溢位一次。如果經過72分頻,給計數器的時鐘頻率就是1M,週期就是1/1M=1us,也是就1us計一個數。換句話就是可以取樣的波形頻率為1M,提高了取樣頻率。另一方面也是容易計算,計一個數1us,計count個數就是count個us,頻率就是1000 000/count(HZ)。
2、為什麼要重灌載值為0XFFFF最大采樣間隔是跟定時器的中斷間隔相關的,定時器產生溢位中斷後計數值CNT會自動清0,定時器的中斷間隔由分頻係數Prescaler和自動重灌載暫存器Period決定,分頻係數前面已經確定,那最大采樣間隔只需要考慮自動重灌載暫存器Period的設定,比如頻分析係數72-1,自動重灌暫存器值65535,則中斷間隔=65536/72000000/72=65.536ms,即最大采樣間隔65.536ms,如果65.536ms內沒有檢測到一個脈衝,則這麼設定間隔是不合理的,必須想辦法犧牲最小的取樣時間1us(擴大分頻係數)或者擴大自動重灌暫存器值(16位,<65535)來增加定時器中斷間隔,也可以編寫自己的應用函式來計算溢位的定時時間。
一般來說我們使用外部中斷是不需要用到定時器的,大家看原子和野火的外部中斷實驗也沒有用到外部中斷。但是我們現在不是利用外部中斷簡單的處理一件事,而是利用外部中斷測量頻率,而測頻率就涉及到時間,而只要涉及到時間,就需要用到定時器了。測量外部訊號的頻率,就是測量PWM波對吧!如果我們測量到一個週期的時間,那麼不就知道了訊號的頻率了嗎?
測量方案:在第一次外部中斷(上升沿觸發)到之時,開啟定時器,同時計數器清零。然後等待第二次中斷到來,在第二次外部中斷(上升沿觸發)到之時,獲取計數器的計數值,關閉計數器。因為我們知道了計數器計數一個數的時間,所以我們到在第二次外部中斷(上升沿觸發)到之時,獲取計數器的計數值,透過這個值就知道一個脈衝的時間週期。時間週期的倒數就是外部訊號的頻率。
具體程式碼如下:
void EXTI0_IRQHandler(void){ if(EXTI_GetITStatus(EXTI_Line0)!= RESET) { EXTI_ClearITPendingBit(EXTI_Line0);//清除EXTI0線路掛起位 if(CaptureNumber == 0)//第1次上升沿觸發 { TIM_Cmd(TIM2,ENABLE);//使能定時器2 TIM_SetCounter(TIM2,0); //清零計數器的值,因為一開始就開始計數了 CaptureNumber++; } else if(CaptureNumber==1)//第2次上升沿觸發 { TimeCntValue = TIM_GetCounter(TIM2); Capture = TimeCntValue; CaptureNumber = 0; TIM_Cmd(TIM2,DISABLE);//使能定時器2 } } }int main(void){ float x; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(); uart_init(115200); TIM2_Init(); TIM1_PWM_Init(7199,0); //不分頻,輸出PWM頻率=72000K/(7199+1)=10Khz EXTIA0_Init(); while(1) { printf("Fre=%.2f kHz\r\n",1000000/Capture); delay_ms(1000); }}
當然你可能覺得這只是測量訊號的一個週期脈衝不夠準確,那麼也可以測量100次脈衝的時間再除以100,就是一個脈衝的時間,然後再取倒數就可以算出頻率,這種方法也是可以的。具體程式碼如此:
void EXTI0_IRQHandler(void){ if(EXTI_GetITStatus(EXTI_Line0)!= RESET) { EXTI_ClearITPendingBit(EXTI_Line0);//清除EXTI0線路掛起位 if(CaptureNumber == 0)//第1次上升沿觸發 { TIM_Cmd(TIM2,ENABLE);//使能定時器2 TIM_SetCounter(TIM2,0); //清零計數器的值,因為一開始就開始計數了 CaptureNumber++; } else if(CaptureNumber>0&& CaptureNumber<100) { TimeCntValue0 = TIM_GetCounter(TIM2); CaptureNumber++; } else if(CaptureNumber==100)//第100次上升沿觸發 { TimeCntValue = TIM_GetCounter(TIM2); Capture = TimeCntValue/100; CaptureNumber = 0; TIM_Cmd(TIM2,DISABLE);//使能定時器2 } } }int main(void){ float x; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(); uart_init(115200); TIM2_Init(); TIM1_PWM_Init(7199,0); //不分頻,輸出PWM頻率=72000K/(7199+1)=10Khz EXTIA0_Init(); while(1) { printf("Fre=%.2f kHz\r\n",1000000/Capture); delay_ms(1000); }}
程式流程圖:
串列埠列印結果:
當然測量訊號頻率的方法我們直接利用TIM的輸入捕獲的方法就可以實現。用外部中斷只是另外一個測量方案,具體用哪一種還要看具體情況。