輸入捕獲學習
《用CubeMX學習STM32》
注釋 點擊上面藍字進入完整專欄,這個系列所有文章都會整合到這個專欄
5、STM32定時器輸入捕獲
前言: STM32定時器輸入捕獲簡介
STM32的輸入捕獲可以用於捕獲脈寬, 測量時間 . 例如超聲波測距模塊就是需要用輸入捕獲功能, 通過測量輸入脈沖的高電平脈寬 , 從而計算出測量物體的距離 ;
定時器PWM工作模式上篇博客講過了, 上篇是輸出PWM, 本篇是要輸入, 即外面的信號送給單片機的引腳, 然后單片機測量出脈寬 ;
注: 下面根據正點原子的標准庫函數教程分析, 並用CubeMX完成配置以及HAL庫函數編程
如圖所示 : 以測量高電平脈寬為例, 我們先設置定時器通道為上升沿捕獲, 到1的時候觸發定時器計數, 然后立刻設置為下降沿捕獲, 到2的時候就捕獲到下降沿, 再記錄輸入捕獲寄存器的值, 兩個時間差就是高電平時長tH;
需要注意的是, 在tH這段高電平時間內, 是由很多個向上計數的脈沖來計數的。在這里面計數可能溢出N多次; 下面是原子的庫函數指南pdf里面講解的圖
在tH這段高電平里面, 可能有多個向上計數的脈沖, 而那個三角向上計數脈沖也可能溢出多次。就是利用這N多個向上計數的脈沖來計算tH的值的。 ARR的值是我們自己設定的,所以可以知道溢出一次是多長時間, 每溢出一次, 都給溢出次數加一。 溢出次數以及檢測高低電平的數據記錄在自己設定的一個變量里面
N*ARR + CCRx2即為CNT計數次數, 從而就可以算出計數時間, 算出高電平時長
N: 溢出次數 ARR: 溢出一次的時間 在一個tH內,溢出的次數不一定正好是整數, 所以用記錄下CCRx2的值, 用以補充, 這樣tH的值就更精確了
這是一個八位的變量,可以將其看做8位寄存器,不同的位儲存不同的數據
5.1 操作簡介
通過信號發生器給單片機對應引腳輸入一個給定頻率和占空比的矩形波信號, 單片機通過輸入捕獲測量出高電平時長; 通過串口發送至PC端的串口調試助手查看測量的脈寬是否准確
5.2 STM32CubeMX配置初始化+IAR編程
Step1 : Cube配置
-
USART1串口1配置(按照串口那一篇配置串口即可–>串口通信 )
注: 詳細解釋轉至串口通信
-
(2)TIM5參數配置
-
使用TIM5的通道一(TIM5_CH1)接收外部輸入的信號。配置如下
注: 上一篇介紹了如何計算定時器溢出時間,這里溢出時間為1us 點擊查看—>定時器中斷及定時器產生PWM -
使能TIM5中斷(要在中斷里面計數高電平脈寬)
-
NVIC設置(同樣可以查看上一篇看詳細講解NVIC配置以及中斷分組詳解)
-
-
(3) 工程配置(Project Manager)
注 : 高級設置默認即可 -
(4) 生成代碼(Generate Code)
Step2 : Keil/IAR編程
-
(1)重定向printf函數(重定向之后我們才可以使用printf函數將調試信息打印到串口調試助手) 下面是串口通信那一篇博客寫的話,直接搬到這里:
在學習C語言的時候, 大家肯定都用過printf這個函數, printf可以將指定字符打印到電腦的顯示器上;
但是, 單片機要使用這個就要把他打印的方向改一下, 不是打印在電腦的命令行中, 而是打印到串口里面,傳輸到串口調試助手. 因此我們需要重定向printf函數;
重定向后我們要將調試信息打印到USART1中, 需要對printf所依賴的打印函數fputc()重定向 .
在usart.c里面添加重定向代碼
以后這段代碼直接抄就好了, copy下來用/* USER CODE BEGIN 0 */ #include "stdio.h" #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int _io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__*/ // 重定向C語言中的printf函數 PUTCHAR_PROTOTYPE { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); return ch; } /* USER CODE END 0 */
-
(2) 查看一下定時器相關代碼學習
-
打開之后跟正點原子的標准庫函數寫的代碼對比一下, 又利於自己理解CubeMX配置的機理, 以后會更得心應手, 慢慢的自己就可以一直標准庫函數用HAL庫寫了。
這張圖左邊是CubeMX配置后自動生成的代碼, 藍色框框里面就是對應的CubeMX里面的配置; 右側是原子的標准庫例程代碼, 可以對比一下, 增強理解tips:CSDN只能上傳不超過5M的圖片, 所以這個圖片經過了壓縮 , 放大看可以看清晰一點。
-
(3) 編寫中斷部分函數
-
因為要在中斷中捕獲上升沿和下降沿, 所以主要代碼寫在中斷服務函數里面
下圖是計數中斷
TIM5CH1_CAPTURE_STA雖然是我們定義的一個變量,但可以把它看做是一個8位的寄存器
-
下圖是捕獲中斷
在HAL_TIM_PeriodElapsedCallback()回調函數中用以處理計數次數和時間; 在HAL_TIM_IC_CaptureCallback()回調函數負責處理捕獲到的上升沿和下降沿,
並隨着捕獲到上升沿而更改為下降沿捕獲, 隨着捕獲到下降沿而更改定時器為上升沿捕獲.
下面是完整代碼:
/* USER CODE BEGIN 1 */ /* bit7 捕獲完成標識 bit6 捕獲到高電平標識 bit5~0 捕獲高電平后定時器溢出的次數 */ uint8_t TIM5CH1_CAPTURE_STA = 0; // 輸入捕獲狀態 uint32_t TIM5CH1_CAPTURE_VAL; // 輸入捕獲值(TIM2/TIM5是32位的定時器所以這里定義為uint32_t) // 中斷服務函數里面會自動調用這個回調函數 這個是定時器更新中斷中處理的函數 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM5) // 判斷是定時器5發生中斷 { if ((TIM5CH1_CAPTURE_STA & 0x80) == 0) // 還未成功捕獲 { if (TIM5CH1_CAPTURE_STA & 0x40) // 捕獲到高電平 { if ( (TIM5CH1_CAPTURE_STA & 0x3f) == 0x3f ) // 如果高電平太長 做溢出處理 { TIM5CH1_CAPTURE_STA |= 0x80; // 標記成功捕獲了一次 TIM5CH1_CAPTURE_VAL = 0xffffffff; } else { TIM5CH1_CAPTURE_STA++; // 若沒有溢出, 就只讓TIM5CH1_CAPTURE_STA自加就ok } } } } } // 定時器輸入捕獲中斷處理回調函數,該函數在 HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) 中會被調用 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if ( (TIM5CH1_CAPTURE_STA & 0x80) == 0 ) // 還未成功捕獲 { if (TIM5CH1_CAPTURE_STA & 0x40) // 捕獲到一個下降沿 { TIM5CH1_CAPTURE_STA |= 0x80; // 標記成功捕獲到一次高電平脈寬 TIM5CH1_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1); // 獲取當前的捕獲值. 即CCRx2 TIM_RESET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1); // 清除原來的設置 TIM_SET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); // 配置TIM5通道1上升沿捕獲 } else { TIM5CH1_CAPTURE_STA = 0; // 清空自定義的狀態寄存器 TIM5CH1_CAPTURE_VAL = 0; // 清空捕獲值 TIM5CH1_CAPTURE_STA |= 0x40;// 標記捕獲到了上升沿 __HAL_TIM_DISABLE(&htim5); //關閉定時器5 __HAL_TIM_SET_COUNTER(&htim5,0); TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1); //一定要先清除原來的設置!! TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//定時器5通道1設置為下降沿捕獲 __HAL_TIM_ENABLE(&htim5);//使能定時器5 } } } /* USER CODE END 1 */
tips:每句話都有注釋, 不要一看到密密麻麻代碼就不看了, 看一下並不是很難理解。 也不要因為看到全是大寫字母的函數或者變量而犯怵, 靜下心來用兩分鍾看一看很容易看懂
- 還有一個問題:就是這里為什么用HAL_TIM_PeriodElapsedCallback而不是其他的callback呢? 原因在IRQ_Handler函數里面。
-
-
(4) 主函數程序(main.c)
-
首先使能定時器中斷、同時定義一個變量備用:
/* USER CODE BEGIN 2 */ HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1); // 開啟輸入捕獲中斷 __HAL_TIM_ENABLE_IT(&htim5,TIM_IT_UPDATE); //使能更新中斷 long long temp = 0; // 定義一個變量用以存儲捕獲到的時間 long long型是為了防止數據溢出 /* USER CODE END 2 */
-
在while(1)循環測量數據並打印
while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_Delay(10); // 信號發生器輸入信號 串口打印高電平時長 ms if (TIM5CH1_CAPTURE_STA & 0x80) // 如果捕獲完成 { temp = TIM5CH1_CAPTURE_STA & 0x3f; temp *= 0xffffffff; // Total Overflow Time(總的溢出時間) temp += TIM5CH1_CAPTURE_VAL; // Get Total High Level Time(獲取總的高電平時長) printf("HIGH: %lld ms\r\n", temp/1000); // Print Total High Level Time(打印總的高電平時長) TIM5CH1_CAPTURE_STA = 0; // Clear Capture State , Open The Next Capture(清除捕獲狀態,打開下一次捕獲) } } /* USER CODE END 3 */
-
-
(5) 至此程序就完成了.
在主函數里面, TIM5CH1_CAPTURE_STA & 0x80的意思是判斷有沒有捕獲到高電平 用TIM5CH1_CAPTURE_STA和0x80相與, 從而判斷TIM5CH1_CAPTURE_STA的6位是否為1, 進而判斷出是否捕獲到高電平; 下面的一些涉及到相與的操作也都類似, 把一個變量看做一個寄存器, 把0x80、 0xffffffff等轉換為二進制就好判斷了, 在演草紙上畫一下就很清楚
-
(6) 編譯下載
上述代碼都是之前經過測試的,但是當前由於疫情,沒有條件展現結果, 如果有人用了這些代碼並測試,有什么問題的話可以下面評論告知,感激不盡。效果展示會在后期補上
待到春暖花開時,願人間皆安,山河無恙。櫻花會如期而至
Author : 李光輝
date : Sun Feb 16 22:50:25 CST 2020
blog ID: Kevin_8_Lee
blog site : https://blog.csdn.net/Kevin_8_Lee