一、工作原理
輸入捕獲是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左右波動。