当前位置:网站首页>STM32CubeMX 學習(5)輸入捕獲實驗

STM32CubeMX 學習(5)輸入捕獲實驗

2022-06-25 08:07:00 小輝_Super

個人學習記錄

一、新建工程

在這裏插入圖片描述

二、選擇芯片型號

我使用的開發板是正點原子 STM32F103ZET6 核心板

在這裏插入圖片描述

三、配置時鐘

開發板焊接了外部晶振,所以我 RCC(Reset and Cock Control) 配置選擇了 Crystal/Ceramic Resonator(石英/陶瓷諧振器),配置完成後,右邊的 Pinout view 裏相關引脚就會被標綠。

在這裏插入圖片描述

外部高速時鐘配置完成後,進入 Clock Configuration 選項,根據實際情况,將系統時鐘配置為 72 MHz,配置步驟如下,最後按下回車,軟件會自動調整分頻和倍頻參數。

在這裏插入圖片描述

四、配置調試模式

ST-Link 就是 Serial Wire 調試模式,一定要設置!!!
以前使用 M0 的芯片,不配置這個模式沒出現問題,但現在這個型號,如果不配置 Serial Wire 模式,程序一旦通過 ST-Link 燒錄到芯片中,芯片就再也不能被ST-Link 識別了。(後來我是通過 STMISP 工具燒錄程序/擦除後才恢複正常的)

在這裏插入圖片描述

五、定時器(輸入捕獲)參數配置

我將 TIM2 的通道 1 作為輸入捕獲測試通道,STM32CubeMX 會默認配置 PA0 作為輸入捕獲的 IO 口(PA0 有該複用功能,且不需要重映像,所以自動將 PA0 設為 TIM_CH1 的 GPIO),定時器的參數設定如下圖所示(輸入捕獲的配置可以不用改,默認捕獲上昇沿):

在這裏插入圖片描述

分頻系數為 72-1,意思就是 72 分頻(0錶示 1 分頻,1 錶示 2 分頻,以此類推),TIM2 的時鐘頻率為 72 MHz(下圖中,APB1 Timer clocks 的時鐘頻率為 72MHz,TIM2 掛載在 APB1 上)。將其進行 72 分頻後,頻率變成了 1MHz,即每秒計數 1000000 次。周期設置為 1000-1(這裏要减一,應該是因為計數值最小為 0),代錶著一個完整的計時周期為 1000 次計數,結合定時器計數頻率,定時器一次計時溢出所需的時間為 1ms。【頻率决定了輸入捕獲的捕獲周期,計時值設為 1000 只是為了方便計算】

在這裏插入圖片描述

輸入捕獲需要開啟定時器的中斷,無論是計時溢出還是輸入捕獲都需要使用到中斷。

在這裏插入圖片描述

六、生成 Keil 工程

設置 IDE 和 工程目錄及名稱:

在這裏插入圖片描述

將每種外設的代碼存放到不同的 .c /.h 文件中,便於管理(不然都會被放到 main.c 中)。

在這裏插入圖片描述

下面是生成 Keil 工程中關於 TIM2(輸入捕獲)初始化的代碼:

/* TIM2 init function */
void MX_TIM2_Init(void)
{
    

  /* USER CODE BEGIN TIM2_Init 0 */

  /* USER CODE END TIM2_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {
    0};
  TIM_MasterConfigTypeDef sMasterConfig = {
    0};
  TIM_IC_InitTypeDef sConfigIC = {
    0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 72 - 1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 1000 - 1;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    
    Error_Handler();
  }
  if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
  {
    
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    
    Error_Handler();
  }
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
  {
    
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */

  /* USER CODE END TIM2_Init 2 */

}

七、中斷函數寫在哪

在使用標准庫時,我們是將中斷處理寫在最底層的中斷處理函數中,如 EXTI0_IRQHandler(),但 Hal 庫增加了回調函數,將中斷底層一些必要的操作 “隱藏” 了起來(如清除中斷)。

中斷的調用順序是(以 EXTI0 為例):EXTI0_IRQHandler() —> HAL_GPIO_EXTI_IRQHandler() —> HAL_GPIO_EXTI_Callback()

TIM2 的中斷服務函數已經在 stm32f1xx_it.c 中定義(STM32CubeMX 自動生成的)

/** * @brief This function handles TIM2 global interrupt. */
void TIM2_IRQHandler(void)
{
    
  /* USER CODE BEGIN TIM2_IRQn 0 */

  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */

  /* USER CODE END TIM2_IRQn 1 */
}

HAL_TIM_IRQHandler() 是 HAL 庫的定時器總中斷,裏面代碼很多,這裏不展示,我們只需要知道一點——當 TIM2 計數值溢出或發生其他事件(如捕獲到上昇/下降沿信號)時,系統會執行一系列的中斷回調函數,其中包括我們將要用到的 計數溢出回調函數HAL_TIM_PeriodElapsedCallback() 和 輸入捕獲回調函數HAL_TIM_IC_CaptureCallback()

八、測試示例

實驗中用到了串口,上文配置中沒提及,串口配置可以參考 STM32CubeMx 學習(2)USART 串口實驗

我的實驗代碼的核心部分為中斷回調函數:

// 定時器計數溢出中斷處理回調函數
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    
	if(IC_DONE_FLAG == 0)  // 未完成捕獲
	{
    
		if(IC_START_FLAG == 1)  // 已經捕獲到了高電平
		{
    
			IC_TIMES++;  // 捕獲次數加一
		}
	}
}

//定時器輸入捕獲中斷處理回調函數
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)// 捕獲中斷發生時執行
{
    
	if(IC_DONE_FLAG == 0)  // 未完成捕獲
	{
    
		if(IC_START_FLAG == 1)  // 原來是高電平,現在捕獲到一個下降沿
		{
    
			IC_VALUE = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);  // 獲取捕獲值
			TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);  // 先清除原來的設置
			TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);// 配置為上昇沿捕獲
			IC_START_FLAG = 0;  // 標志複比特
			IC_DONE_FLAG = 1;  // 完成一次高電平捕獲
		}
		else  // 捕獲還未開始,第一次捕獲到上昇沿
		{
    
			IC_TIMES = 0;  // 捕獲次數清零
			IC_VALUE = 0;  // 捕獲值清零
			IC_START_FLAG = 1;  // 設置捕獲到了上邊沿的標志
			TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);  // 先清除原來的設置
			TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);// 配置為下降沿捕獲
		}
		__HAL_TIM_SET_COUNTER(htim,0);  // 定時器計數值清零
	}
}

完整 main.c

/* USER CODE BEGIN Header */
/** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2022 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */


uint32_t IC_TIMES;  // 捕獲次數,單比特1ms
uint8_t IC_START_FLAG;  // 捕獲開始標志,1:已捕獲到高電平;0:還沒有捕獲到高電平
uint8_t IC_DONE_FLAG;  // 捕獲完成標志,1:已完成一次高電平捕獲
uint16_t IC_VALUE;  // 輸入捕獲的捕獲值

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/** * @brief The application entry point. * @retval int */
int main(void)
{
    
  /* USER CODE BEGIN 1 */
	
	uint32_t time = 0;
  /* 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 */

  HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);  //開啟TIM2的捕獲通道1
  __HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE);  //使能更新中斷
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    
		HAL_Delay(10);
		
		if(IC_DONE_FLAG == 1)  // 如果完成一次高電平捕獲
		{
    
			IC_DONE_FLAG = 0;  // 標志清零
			time = IC_TIMES * 1000;  // 脈沖時間為捕獲次數 * 1000us
			time += IC_VALUE;  // 加上捕獲時間(小於1ms的部分)
			printf("High level: %d us\n", time);
		}
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/** * @brief System Clock Configuration * @retval None */
void SystemClock_Config(void)
{
    
  RCC_OscInitTypeDef RCC_OscInitStruct = {
    0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {
    0};

  /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */


// 定時器計數溢出中斷處理回調函數
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    
	if(IC_DONE_FLAG == 0)  // 未完成捕獲
	{
    
		if(IC_START_FLAG == 1)  // 已經捕獲到了高電平
		{
    
			IC_TIMES++;  // 捕獲次數加一
		}
	}
}

//定時器輸入捕獲中斷處理回調函數
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)// 捕獲中斷發生時執行
{
    
	if(IC_DONE_FLAG == 0)  // 未完成捕獲
	{
    
		if(IC_START_FLAG == 1)  // 原來是高電平,現在捕獲到一個下降沿
		{
    
			IC_VALUE = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);  // 獲取捕獲值
			TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);  // 先清除原來的設置
			TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);// 配置為上昇沿捕獲
			IC_START_FLAG = 0;  // 標志複比特
			IC_DONE_FLAG = 1;  // 完成一次高電平捕獲
		}
		else  // 捕獲還未開始,第一次捕獲到上昇沿
		{
    
			IC_TIMES = 0;  // 捕獲次數清零
			IC_VALUE = 0;  // 捕獲值清零
			IC_START_FLAG = 1;  // 設置捕獲到了上邊沿的標志
			TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);  // 先清除原來的設置
			TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);// 配置為下降沿捕獲
		}
		__HAL_TIM_SET_COUNTER(htim,0);  // 定時器計數值清零
	}
}
/* USER CODE END 4 */

/** * @brief This function is executed in case of error occurrence. * @retval None */
void Error_Handler(void)
{
    
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
    
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */
void assert_failed(uint8_t *file, uint32_t line)
{
    
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


實驗效果:

PA0 對應我開發板上的一個按鍵,當輕觸(未按下)該按鍵時,串口會不停打印一些無用的高電平持續時間,這些無用脈沖的持續時間很接近, 都是 10ms 左右,說明按鍵的抖動電平持續時間大約為 10ms。

當長按按鍵,再松開,就會打印按鍵按下的時間,比如下圖兩個被紅圈圈中的數據,第一次的高電平持續時間為4.35s,第二次高電平持續時間為 1.59s。

在這裏插入圖片描述

原网站

版权声明
本文为[小輝_Super]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206250637342177.html