prettyprint

2020年11月29日 星期日

使用STM32F103C8T6 TIMER Input Capture 偵測輸入方波的頻率與寬度

本實驗目的使用STM32 MCU Timer Input Capture 功能來製作一簡易檢測輸入方波的頻率與佔空比。

參考文件:AN4776 Application note: General-purpose timer cookbook for STM32 microcontrollers 

使用軟體工具:STM32CubeIDE、HAL Library。

使用元件:

  1. STM32F103C8T6 BlackPill開發版 x1
  2. LCD 1602A x1(不含I2C或SPI介面)
  3. 10K電位計
  4. 小麵包版 17x10 兩塊
  5. 杜邦線

功能需求:

  1. 偵測輸入頻遇範圍:100Hz~1MHz
  2. 顯示當下頻率、波寬(Duty)
  3. 顯示偵測200ms時間內之平均頻率。
  4. 避免偵測高頻時由於中斷(Interrupt)太頻繁而無法讓慢速LCD顯示。
原理解說:
  1. 當偵測到輸入訊號(Rising or Falling Edge)時,暫存器CNT值會存入CCR。
  2. 使用TIMER2 Channel1與Channel2分別偵測輸入訊號之Rising Edge與Falling Edge。
  3. 輸入訊號接在TIM2 Channel1 Input。
  4. TIMER2 設定為Slave Reset Mode,Trigger Source選用TI1FP1,Channel1 設為Input Capture Direct Mode, Channel2 設為Input Capture indirect mode,當Channel1偵測到TI1FP1 Rising Edge 時register CNT值存入CCR1並reset CNT,Channel2偵測到TI1FP2 Falling Edge時 register CNT值存入CCR2。
  5. clock source 使用開發版的8M震盪器。Prescale為2,ARR(auto reload register)為65535,則最小偵測頻率為62Hz,8,000,000/2/(65535+1)=61.035。
  6. 輸入頻率為8M/(Prescale+1)/(CCR1+1),Duty為(CCR2+1)x100/(CCR1+1)
實作過程:
  • TIMER 3參數設定:
    避免因為偵測高頻時 input capture interrupt過於頻繁而使得慢速LCD被Block住而無法顯示,因此使用TIMER3 Base timer mode每200ms啟用TIMER2抓取輸入訊號200ms後顯示當時頻率,脈寬(Duty)與200ms內所有抓取訊號頻率的平均值。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { if (!show_step) { HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); show_step=1; } else { HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2); if(cnt_avg > 3) { freq_avg = (double)((HAL_RCC_GetPCLK1Freq())/(htim2.Instance->PSC+1))/((val1_cnt_sum/(cnt_avg-3))+1); val1_cnt_sum=0; cnt_avg=0; } show_step=2; } }

  • TIME2參數設定。


void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); } if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); if (val1 !=0) { freq = (double)((HAL_RCC_GetPCLK1Freq())/(htim->Instance->PSC+1))/(val1+1); cnt_avg++; if (cnt_avg>3) //忽略前3次不計入平均 { val1_cnt_sum += val1; } duty=(val2+1)*100/(val1+1); } } }

  • Clock Configuration


線路圖
輸入訊號接PA0與GND



完成圖






實測影片
以另一片STM32_F4VE開發版產生PWM當輸入測試訊號,訊號序列為100Hz(5%)、500Hz(50%)、1kHz(50%)、5kHz(12%)、20kHz(50%)、40kHz(10%)、100kHz(75%)、500kHz(25%)、1MHz(50%)。







使用LCD library

完整程式碼 

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics. * All rights reserved.</center></h2> * * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "lcd.h" /* 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 ---------------------------------------------------------*/ TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim3; /* USER CODE BEGIN PV */ uint32_t val1, val2, cnt_avg=0; double val1_cnt_sum=0; uint16_t duty; double freq, freq_avg; uint8_t show_step=0,toggle=0; char buf[20]; Lcd_PortType ports[] = { GPIOA,GPIOA,GPIOA,GPIOB,GPIOB, GPIOB, GPIOB, GPIOB }; Lcd_PinType pins[] = {GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_15,GPIO_PIN_3,GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7}; Lcd_HandleTypeDef lcd; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); static void MX_TIM3_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); } if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); if (val1 !=0) { freq = (double)((HAL_RCC_GetPCLK1Freq())/(htim->Instance->PSC+1))/(val1+1); cnt_avg++; if (cnt_avg>3) //忽略前3次不計入平均 { val1_cnt_sum += val1; } duty=(val2+1)*100/(val1+1); } } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { if (!show_step) { HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); show_step=1; } else { HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2); if(cnt_avg > 3) { freq_avg = (double)((HAL_RCC_GetPCLK1Freq())/(htim2.Instance->PSC+1))/((val1_cnt_sum/(cnt_avg-3))+1); val1_cnt_sum=0; cnt_avg=0; } show_step=2; } } void lcd_display_data() { if (((++toggle)%10)==0) { sprintf(buf, "Duty:%d%s", duty,"%"); Lcd_cursor(&lcd, 0,0); Lcd_string(&lcd,buf); sprintf(buf, " "); Lcd_string(&lcd,buf); } else { sprintf(buf, "Favg:%.4f", freq_avg); Lcd_cursor(&lcd, 0,0); Lcd_string(&lcd,buf); sprintf(buf, " "); Lcd_string(&lcd,buf); } sprintf(buf,"Freq:%.4f", freq); Lcd_cursor(&lcd, 1,0); Lcd_string(&lcd,buf); sprintf(buf, " "); Lcd_string(&lcd,buf); if (!(toggle%2)) { Lcd_cursor(&lcd, 0, 15); Lcd_string(&lcd, "*"); } } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ 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_TIM3_Init(); /* USER CODE BEGIN 2 */ lcd = Lcd_create(ports, pins, GPIOA, GPIO_PIN_9, GPIOA, GPIO_PIN_10, LCD_8_BIT_MODE); Lcd_clear(&lcd); Lcd_string(&lcd, "Starting"); show_step=0; HAL_TIM_Base_Start_IT(&htim3); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { if (show_step==2) { HAL_TIM_Base_Stop_IT(&htim3); lcd_display_data(); show_step=0; HAL_TIM_Base_Start_IT(&htim3); } /* 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.PLL.PLLState = RCC_PLL_NONE; 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_HSE; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } /** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_SlaveConfigTypeDef sSlaveConfig = {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 = 1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 65535; 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(); } sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sSlaveConfig.TriggerFilter = 0; if (HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig) != 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(); } sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ } /** * @brief TIM3 Initialization Function * @param None * @retval None */ static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 7999; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12 |GPIO_PIN_15, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 |GPIO_PIN_7, GPIO_PIN_RESET); /*Configure GPIO pins : PA9 PA10 PA11 PA12 PA15 */ GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12 |GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pins : PB3 PB4 PB5 PB6 PB7 */ GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 |GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* 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 */ /* 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, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


1 則留言:

  1. 也可以用PWM IN抓值,比較快得到值。再加上濾波器對量測值做類平均。
    個人用在計數長短脈波用。

    回覆刪除