一、工具
1、硬件:STM32F103VET6單片機;
2、編譯工具:TrueSTUDIO;
3、輔助工具:STM32CubeMX。
二、單片機系統時鍾配置
三、單片機定時器配置
這里參數設置說明下:
根據時鍾樹可以知道定時器2在APB1總線上(如下圖所示),APB1的總線時鍾由SYSCLK時鍾經過AHB Prescaler分頻器1分頻和APB1 Prescaler分頻器2分頻得到最終36MHz,然而在給定時器做時鍾源時會因為APB1 Prescaler進行了2分頻再次x2進行倍頻使得定時器2時鍾源為72MHz,那么定時器2的周期計算公式t=(psc+1)x(period+1)/72MHz;根據公式可推導出:(7199+1)x(9999+1)/72000000=1s。
1、Counter Setting:
- Prescaler 預分頻器,可以將計數器時鍾頻率除以1到65536之間的任何因子,是一個16bit的值。設置的值寫入單片機定時器的TIMx_PSC寄存器中,設置值 =(預期值-1);
- Counter Mode 計數模式,設置向上計數(計數器從小到大遞增計數 0-1-0)、向下計數(計數器從大到小遞減計數 1-0-1)和居中對齊1~3(計數器先遞增再遞減 0-1-1-0或者先遞減在遞增1-0-0-1等 );
- Count Period 計數周期,決定定時器周期,是一個16bit的值。設置的值寫入單片機定時器的TIMx_ARR寄存器中,設置值=(預期值-1);
- Internal Clock Division 內部時鍾分割,表示由數字濾波器(TIx)使用的時鍾(CK_INT)頻率和采樣時鍾之間的分割比率(什么亂七八糟的,不管);
- auto-reload preload 自動重載預加載,使能就啟動預加載,不使能就不啟動預加載。設置的值寫入單片機定時器的TIMx_CR1寄存器的ARPE位,具體作用如下圖所示:
在定時器工作中,當不啟動預加載,改變ARR的值,會立即生效,如下圖所示:
在定時器工作中,當啟動預加載,改變ARR的值,會等待下一次計數生效,而在當前計數周期會由影子自動重加載寄存器的值決定,如下圖所示:
2、Trigger Output Parameters:
- 這里用不到,暫時不介紹,默認即可。
3、Output Compare Channel n:
- Mode 通道模式設置,設置定時器計數器與比較值相等時輸出引腳的狀態;
- Pulse 脈沖,設置比較寄存器的值(這里建議設置為0,在中斷中改變比較寄存器的值);
- Output compare preload 輸出比較預加載,作用和auto-reload preload 類似;
- CH Polarity 通道起始電平。
打開定時器中斷,如果沒要求優先級默認即可。
四、生成代碼
1、關於生成的定時器配置的代碼如下所示:
static 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_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 7199; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 9999; 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_OC_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(); } sConfigOC.OCMode = TIM_OCMODE_TOGGLE; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) { Error_Handler(); } if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ HAL_TIM_MspPostInit(&htim2); }
2、定時器的中斷代碼如下所示:
/** * @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 */ }
3、引腳和與之相關的配置代碼如下所示:
/** * @brief TIM_Base MSP Initialization * This function configures the hardware resources used in this example * @param htim_base: TIM_Base handle pointer * @retval None */ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) { if(htim_base->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); /* TIM2 interrupt Init */ HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } } void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(htim->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspPostInit 0 */ /* USER CODE END TIM2_MspPostInit 0 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /**TIM2 GPIO Configuration PA2 ------> TIM2_CH3 PA3 ------> TIM2_CH4 */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN TIM2_MspPostInit 1 */ /* USER CODE END TIM2_MspPostInit 1 */ } }
4、啟動定時器代碼:
這里我寫了兩種開始定時器的方式,主要區別是同時啟動和先后啟動,實際測試中這兩種方式的差別基本看不出來。
void bsp_tim2_start(void) { #if 1 __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC3); __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC4); TIM_CCxChannelCmd(htim2.Instance, TIM_CHANNEL_3, TIM_CCx_ENABLE); TIM_CCxChannelCmd(htim2.Instance, TIM_CHANNEL_4, TIM_CCx_ENABLE); __HAL_TIM_ENABLE(&htim2); #else HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_3); HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_4); #endif }
五、完善中斷服務函數:
定時器輸出比較模式產生中斷事件后,會調用函數HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim),需要定義一個該函數,添加中斷需要處理的內容,具體操作如下所示:
函數的內容主要是獲取定時器比較寄存TIMx_CCRn的值(即比較值),然后改變比較器下一次希望比較的值,每次比較器的值和計數器的值相等時就會觸發中斷;注意觀察設置比較值的特點(我這里定時器寄存器TIMx_ARR的值設置的是10000),每個通道的最后一次設置的比較值非常重要,它決定了定時器的該通道是否能夠重復輸出期望的方波(如果你只在初始化定時器的時候設置了一次比較寄存器CCRn的值或者只在中斷中改變一次比較寄存器CCRn的值,單片機對應的引腳只會在第一次啟動程序的第一個周期輸出你期望的方波,之后單片機會以定時器的周期為基准進行電平的反轉,這是我在調試過程中發現的現象,如果你有不同的看法希望能指出)。
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) { uhCapture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_3); /* Set the Capture Compare Register value */ if(uhCapture == 0) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 1000); } else if(uhCapture == 1000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 2000); } else if(uhCapture == 2000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 10000); } else if(uhCapture == 10000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 1000); } } if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4) { uhCapture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_4); /* Set the Capture Compare Register value */ if(uhCapture == 0) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 3000); } else if(uhCapture == 3000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 6000); } else if(uhCapture == 6000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 10000); } else if(uhCapture == 10000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 3000); } } }
六、波形圖
從波形圖可看出,在一個周期內電平多次發生了反轉。
#endif