一、工作原理
輸入捕獲是STM32單片機定時器的一項重要的功能,應用很廣泛,常用於測量脈沖寬度,周期等。
超聲波模塊測距的原理是:單片機給超聲波模塊(我用到的超聲波模塊型號是HC-SR04,下面簡稱HC-SR04)發送一個大於10us的高電平,觸發HC-SR04發出8個40kHz的方波,並自動檢測是否有信號返回,如果有信號返回,就會通過Echo對單片機輸出一個高電平,高電平的持續時間就是超聲波從發射到返回的時間。
換而言之,單片機的工作就是給HC-SR04的Trig端發送一個一個大於10us的高電平,觸發HC-SR04工作,然后利用輸入捕獲功能計算出HC-SR04的Echo端輸入的高電平持續時間就可以測出超聲波發出到返回的時間,聲音在空氣中的傳播速度是340m/s,因此利用公式:測試距離=(高電平時間 * 聲速)/2,就可以算出超聲波模塊與前方的物體之間的距離是多少。原理圖如下:

用一個簡圖來說明輸入捕獲測量高電平延續時間的實現原理:


二、利用CubeMX生成驅動代碼
HC-SR04上有4個引腳:VCC(5V)、GND、Trig(控制端)、Echo(接收端),所以需要配置一個GPIO作為控制HC-SR04的引腳,Echo這個引腳在配置定時器的時候就會自動配置好,不需要單獨配置。另外還需要配置一個串口作為打印口方便調試。
1、時鍾源配置:

2、定時器


3、控制引腳的配置

4、開啟串口

配置完成后生成代碼。
三、修改代碼
1、寫一個us級別的延時:
/********************************************
*函數名稱:void delay_us(__IO uint32_t delay)
*函數形參:__IO uint32_t delay--延時時間
*函數返回值:無
*函數功能:微秒級別延時
*********************************************/
void delay_us(__IO uint32_t delay)
{
int last, curr, val;
int temp;
while (delay != 0)
{
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;
if (curr >= 0)
{
do
{
val = SysTick->VAL;
}
while ((val < last) && (val >= curr));
}
else
{
curr += CPU_FREQUENCY_MHZ * 1000;
do
{
val = SysTick->VAL;
}
while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
2、重定向
/********************************************
*函數名稱:int fputc(int ch, FILE* stream)
*函數功能:重定向
*********************************************/
int fputc(int ch, FILE* stream)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
3、觸發信號
/********************************************
*函數名稱:void Trigger_Signal(__IO uint32_t us)
*函數形參:__IO uint32_t us--觸發信號保持時間
*函數返回值:無
*函數功能:觸發信號
*********************************************/
void Trigger_Signal(__IO uint32_t us)
{
HAL_GPIO_WritePin(Trigger_GPIO_Port,Trigger_Pin,GPIO_PIN_SET);
delay_us(us);
HAL_GPIO_WritePin(Trigger_GPIO_Port,Trigger_Pin,GPIO_PIN_RESET);
printf("發送觸發信號\r\n");
}
4、 輸入捕獲中斷回調函數:
/******************************************** *函數名稱:void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) *函數形參:TIM_HandleTypeDef *htim--定時器句柄 *函數返回值:無 *函數功能:捕獲到高電平后,計數器清零,配置為低電平捕獲, 當捕獲到低電平時,讀出CCR的值
*備注:TIM2_CH2_CAPTURE_STA這個是uint8_t數據類型的全局變量,
它的每一位數據可以自定義為某些狀態,在這里,第7位為1表示捕獲完成,為0表示未完成,
第6位為1表示捕獲到上升沿,為0表示未捕獲到高電平,0~5bit保留;
TIM2_CH2_ELAPSED_CNT也是一個全局變量,表示從捕獲到高電平起,計數器溢出的次數,
TIM2_CH2_CAPTURE_VAL也是一個全局變量,當捕獲到下降沿后把CCR2的值讀取到這個變量里
* *********************************************/ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if((TIM2_CH2_CAPTURE_STA&0x80) != 0x80)//還未完成捕獲 { if((TIM2_CH2_CAPTURE_STA&0x40) == 0) //此前尚未捕獲到上升沿,那么這次捕獲到的就是上升沿 { TIM2_CH2_CAPTURE_VAL = 0; //清零,防止干擾 TIM2_CH2_ELAPSED_CNT = 0; //清零,捕獲到上升沿后重新計算周期溢出次數 TIM2_CH2_CAPTURE_STA |= 0x40; //捕獲到一個上升沿 __HAL_TIM_DISABLE(&htim2); //停止TIM2 __HAL_TIM_SET_COUNTER(&htim2,0); //把TIM2的計數器清零 TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2); //清除原來的設置 TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2,TIM_ICPOLARITY_FALLING); //將TIM2的通道2輸入捕獲設置為下降沿捕獲 __HAL_TIM_ENABLE(&htim2); //使能TIM2 } else { TIM2_CH2_CAPTURE_STA |= 0x80; //捕獲到一個下降沿,代表捕獲完成 TIM2_CH2_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_2); //把此時CCR2的值讀到變量TIM2_CH2_CAPTURE_VAL TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2); //清除原來的設置 TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2,TIM_ICPOLARITY_RISING); //設置為上升沿捕獲 } } }
5、計數周期溢出中斷回調函數:
/********************************************
*函數名稱:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
*函數形參:TIM_HandleTypeDef *htim--定時器句柄
*函數返回值:無
*函數功能:定時器溢出次數計算
*********************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
TIM2_CH2_ELAPSED_CNT++; //每次溢出,該變量增加1
}
6、計算高電平的持續時間:
/********************************************
*函數名稱:uint32_t CalculatePulseWide(void)
*函數形參:無
*函數返回值:無
*函數功能:計算出高電平的寬度
*********************************************/
uint32_t CalculatePulseWide(void)
{
uint32_t PulseWide = 0;
if((TIM2_CH2_CAPTURE_STA&0x80) == 0x80)
{
PulseWide = 0xffff*TIM2_CH2_ELAPSED_CNT+TIM2_CH2_CAPTURE_VAL;
TIM2_CH2_CAPTURE_STA = 0; //計算完將該變量清零,其實即使不清零應該也沒關系,每次捕獲到上升沿也會清零
TIM2_CH2_ELAPSED_CNT = 0; //計算完將該變量清零,其實即使不清零應該也沒關系,每次捕獲到上升沿也會清零
}
return PulseWide;
}
7、計算距離:
/********************************************
*函數名稱:void GetDistance(void)
*函數形參:無
*函數返回值:無
*函數功能:獲取距離
*********************************************/
void GetDistance(void)
{
float temp = 0;
float distance = 0;
Trigger_Signal(20); //發送觸發信號,因為要大於10us,這里就設置為20us
while(!temp) //等待計算出高電平的時間,如果temp為0,說明還未計算出來,繼續等待
{
temp = CalculatePulseWide();
}
printf("temp:%f\r\n",temp);
distance = (float)(temp*0.034)/2; //計算出距離
printf("distant:%.2f CM\r\n",distance);
HAL_Delay(100); //手冊中說明兩次測量的時間間距最好大於60ms,避免引起干擾,這里取100ms
}
8、主函數
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("********** HC-RS04 *********\r\n");
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2); //開啟輸入捕獲中斷
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
GetDistance(); //計算出距離
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
完成代碼修改后,燒錄到單片機上,進行了測量,發現測得的距離跟實際距離之間的誤差不大,當實際距離等於1米的時候,誤差大概在1~5cm左右波動。
