思路:
1、采用外部輸入中斷的方式獲取PWM波形高低電平跳變(上升沿和下降沿),所以外部輸入中斷引腳配置為上升沿和下降沿中斷有效;
2、定時器定時時鍾計數,可按照項目需求的精度確定定時器時鍾大小,示例中精度選擇為1us,所以定時器時鍾設置為1us或者1Mhz。
實現代碼如下:(代碼實現了簡單的封裝,調用簡單,可以免費使用)
pwm_input.h
1 /************************************************************ 2 ** 捕獲PWM信號占空比 3 ** 編寫:Awesome QQ: 2281280195 4 ** 代碼可以免費移植使用 5 *************************************************************/ 6 7 #ifndef PWM_INPUT_H 8 #define PWM_INPUT_H 9 10 #define us_to_ns(x) ((x)*1000) 11 #define ns_to_us(x) ((uint32_t)((x)/1000.0)) 12 13 typedef unsigned int uint32_t; 14 15 typedef enum PWM_Status{ 16 PWM_STATUS_NONE = 0, 17 PWM_STATUS_RAISE, //出現上升沿,處於高電平期間 18 PWM_STATUS_FALLING, //出現下降沿,處於低電平期間 19 PWM_STATUS_SUCCESS, 20 }PWM_Status; 21 22 typedef enum io_status{ 23 IO_STATUS_LOW, //低電平 24 IO_STATUS_HIGH, //高電平 25 }IO_STATUS; 26 27 typedef struct _pwm{ 28 volatile PWM_Status pwm_status; //電平標志 29 volatile uint32_t timer_uint_ns; //定時器計數器單位時間 30 volatile uint32_t timer_max_cnt; //定時器計數寄存器最大定時值 31 volatile uint32_t rise_start_time; //上升沿定時器CNT值 32 volatile uint32_t fall_start_time; //下降沿定時器CNT值 33 volatile uint32_t rise_timer_count; //高電平期間,定時器更新次數 34 volatile uint32_t fall_timer_count; //低電平期間,定時器更新次數 35 volatile uint32_t pulse_tmp; //中間值,用戶不需要直接訪問 36 volatile uint32_t period_tmp; //中間值,用戶不需要直接訪問 37 volatile uint32_t pulse; //占空比高電平時間,單位ns 38 volatile uint32_t period; //周期,單位ns 39 PWM_Status (*pwm_input_io_changed)(struct _pwm* pwm, IO_STATUS io_status, uint32_t current_time); //IO電平變化調用函數 40 void (*pwm_input_timer_count)(struct _pwm* pwm); //定時器更新調用 41 uint32_t (*get_pwm_input_pulse)(struct _pwm* pwm); //獲取高電平時間,ns 42 uint32_t (*get_pwm_input_period)(struct _pwm* pwm); //獲取周期 ns 43 }PWM; 44 45 void pwm_input_init(PWM* pwm, uint32_t uint_ns, uint32_t max_cnt); //初始化PWM對象 46 47 PWM_Status pwm_input_io_changed(PWM* pwm, IO_STATUS io_status, uint32_t current_time); //外部觸發IO中斷調用函數 48 49 void pwm_input_timer_count(PWM* pwm); //定時器中斷調用該函數 50 51 uint32_t get_pwm_input_pulse(struct _pwm* pwm); //獲取高電平時間,單位ns 52 53 uint32_t get_pwm_input_period(struct _pwm* pwm); //獲取周期,單位ns 54 55 #endif
pwm_input.c
1 /************************************************************ 2 ** 捕獲PWM信號占空比 3 ** 編寫:Awesome QQ: 2281280195 4 ** 代碼可以免費移植使用 5 *************************************************************/ 6 7 #include "pwm_input.h" 8 9 /* 初始化pwm對象,傳入兩個參數: 定時器單位時間,定時器最大計數值 */ 10 void pwm_input_init(PWM* pwm, uint32_t uint_ns, uint32_t max_cnt){ 11 if((void*)0==pwm) return; 12 pwm->pwm_status = PWM_STATUS_NONE; 13 pwm->timer_uint_ns = uint_ns; 14 pwm->timer_max_cnt = max_cnt; 15 pwm->fall_start_time = 0; 16 pwm->fall_timer_count = 0; 17 pwm->pulse_tmp = 0; 18 pwm->period_tmp = 0; 19 pwm->period = 0; 20 pwm->pulse = 0; 21 pwm->rise_timer_count = 0; 22 pwm->rise_start_time = 0; 23 pwm->pwm_input_io_changed = pwm_input_io_changed; 24 pwm->pwm_input_timer_count = pwm_input_timer_count; 25 pwm->get_pwm_input_pulse = get_pwm_input_pulse; 26 pwm->get_pwm_input_period = get_pwm_input_period; 27 } 28 29 /* IO輸入上升沿中斷或者下降沿中斷,一個周期包括一個上升沿,一個下降沿, 30 ** 在接下來的上升沿觸發時計算PWM周期 31 */ 32 PWM_Status pwm_input_io_changed(PWM* pwm, IO_STATUS io_status, uint32_t current_time){ 33 if((void*)0==pwm) return PWM_STATUS_NONE; 34 if(IO_STATUS_HIGH==io_status){ 35 if(pwm->pwm_status==PWM_STATUS_NONE){ //第一個為上升沿 36 pwm->pwm_status = PWM_STATUS_RAISE; //上升沿開始 37 //記錄第一個周期上升開始時間 38 pwm->rise_timer_count = 0; 39 pwm->rise_start_time = current_time; 40 }else if(pwm->pwm_status==PWM_STATUS_FALLING){ //已經采集到下降沿,整個周期采集完成 41 //計算周期 42 if(pwm->fall_timer_count==0) {
//定時計數寄存器后台一直更新,所以需編寫下面邏輯代碼,保證數據有效性
pwm->period_tmp=pwm->pulse_tmp+(current_time-pwm->fall_start_time);
} 44 45 pwm->rise_timer_count = 0; 46 pwm->rise_start_time = current_time; 47 pwm->pwm_status = PWM_STATUS_RAISE; 48 49 pwm->period = pwm->period_tmp*pwm->timer_uint_ns; 50 pwm->pulse = pwm->pulse_tmp*pwm->timer_uint_ns; 51 52 return PWM_STATUS_SUCCESS; //成功計算一個PWM周期 53 54 } 55 }else { 56 if(pwm->pwm_status==PWM_STATUS_RAISE){ //已經采集到上升沿,在進入下降沿中斷的時候計算占空比時間 57 58 pwm->fall_start_time = current_time;
//定時計數寄存器后台一直更新,所以需編寫下面邏輯代碼,保證數據有效性 59 if( pwm->rise_timer_count==0 ) {
pwm->pulse_tmp = current_time - pwm->rise_start_time;
} 61 62 pwm->fall_timer_count = 0; 63 pwm->pwm_status = PWM_STATUS_FALLING; 64 65 } 66 } 67 return pwm->pwm_status; 68 } 69 70 /* 定時器更新調用 */ 71 void pwm_input_timer_count(PWM* pwm){ 72 if((void*)0==pwm) return; 73 if(pwm->pwm_status==PWM_STATUS_NONE){ 74 pwm->rise_timer_count = 0; 75 pwm->fall_timer_count = 0; 76 }else if(pwm->pwm_status==PWM_STATUS_RAISE){ 77 ++pwm->rise_timer_count; 78 }else if(pwm->pwm_status==PWM_STATUS_FALLING){ 79 ++pwm->fall_timer_count; 80 } 81 } 82 83 /* 獲取高電平時間,單位ns */ 84 uint32_t get_pwm_input_pulse(struct _pwm* pwm){ 85 if((void*)0==pwm) return 0; 86 87 return pwm->pulse; 88 } 89 90 /* 獲取周期,單位ns */ 91 uint32_t get_pwm_input_period(struct _pwm* pwm){ 92 if((void*)0==pwm) return 0; 93 94 return pwm->period; 95 }
主代碼調用
1 /* 簡單調用實例 */ 2 3 uint32_t pulse, period; //定義PWM占空比時間、周期結果變量,單位采用us 4 5 PWM current_pwm; 6 7 //初始化PWM對象,定時器單位時間為1us,最大定時值為0x10000 8 pwm_input_init(¤t_pwm, us_to_ns(1), 0x10000); 9 10 //外部IO中斷覆蓋調用,基於cubeMX開發 11 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 12 { 13 IO_STATUS flag = IO_STATUS_LOW; uint32_t curr_cnt;
///********** 此處很重要 ***********/
//如果剛好遇上定時器更新時間,但定時器中斷優先級低於外部IO中斷,所以,此處做特別處理,防止時間出錯,因為定時器計數器是不斷更新的
if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE) != RESET){
if (__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_UPDATE) != RESET){
current_pwm.pwm_input_timer_count(¤t_pwm);
curr_cnt = htim3.Instance->CNT;
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
}
}else curr_cnt = htim3.Instance->CNT;
17 if(GPIO_Pin==GPIO_PIN_6){ 18 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)==GPIO_PIN_SET){ //高電平,上升沿 19 flag = IO_STATUS_HIGH; 20 }else flag = IO_STATUS_LOW; //低電平,下降沿 21 if( PWM_STATUS_SUCCESS==current_pwm.pwm_input_io_changed(¤t_pwm, flag, curr_cnt) ){ //判斷是否完整獲取過一個周期 22 pulse = ns_to_us(current_pwm.pulse); //獲取占空比時間,單位us 23 period = ns_to_us(current_pwm.period); //獲取周期,單位us29 } 30 } 31 } 32 33 //定時器中斷調用覆蓋,基於cubeMX開發 34 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 35 { 36 if(htim==&htim3){ //使用定時器三 37 current_pwm.pwm_input_timer_count(¤t_pwm); //更新定時器計數值 38 } 39 }
獲取輸入PWM信號的占空比程序采用了面向對象的封裝方法,可以使得程序調用簡單。注意外部IO中斷優先級要高於或者等於定時器的優先級。