TIM2 のEncoder機能を使用してみます。 |
|
TIM2_CH1にA相、TIM2_CH2にB相を入力します。 |
1.1. TIM2の設定 Combined ChannelをEncoder Modeに設定します。。今回は4逓倍でカウントしますので、Encoder Modeを Encoder Mode TO1 and TI2に設定します。 割込みも有効にしておきます。 また、PA0とPA1はPullUpに設定しておきます。 |
|
1.2. コーディング HAL_TIM_Encoder_Start_IT関数でTIM2が起動します。割込みコールバック関数はHAL_TIM_IC_CaptureCallbackです。 コールバック関数でCNTからカウント値を読み出し、外部変数g_ulDataに書き込みます。 割込み内で値が変更されるのでvolatile宣言をしておきます。 /* USER CODE BEGIN PV */ volatile uint16_t g_ulData; /* USER CODE END PV */ /* USER CODE BEGIN 2 */ HAL_TIM_Encoder_Start_IT(&htim2, TIM_CHANNEL_ALL); /* USER CODE END 2 */ /* USER CODE BEGIN 4 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { g_ulData = __HAL_TIM_GET_COUNTER(htim); } /* USER CODE END 4 */ /* USER CODE BEGIN 1 */ char msg[32]; uint16_t ulBefore = 0xFFFF; /* USER CODE END 1 */ . . . . /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if (g_ulData != ulBefore) { sprintf(msg, "%05d\r", g_ulData); UsartTransmitStrings(&huart2, msg); ulBefore = g_ulData; } } /* USER CODE END 3 */ |
|
次にDMAですが、、、結論を先に言うと、現在のHAL DriverではEncodeのDMAは対応できません。orz まずは、HAL Driverを使用した場合、どのような理由で動作しないかを説明します。 |
2.1. TIM2の設定 DMAを設定します。DMA RequestはTIM2_UPしか選択できません。TIM2 global interruptはdisableに設定します。 |
2.2. コーディング HAL_TIM_Encoder_Start_DMAでTIM2を起動します。HAL Driverが正常ならこれで動作するはずですが。。。 /* USER CODE BEGIN 2 */ HAL_TIM_Encoder_Start_DMA(&htim2, TIM_CHANNEL_ALL, (uint32_t*)&g_ulData, (uint32_t *)&g_ulData, 1); /* USER CODE END 2 */ |
2.3. 動作確認 HAL Driverを追いかけてみます。まずはMX_TIM2_Init関数内を追いかけてみます。MX_TIM2_Init関数内でHAL_TIM_Encoder_Initが呼び出されます。この中を追いかけてみます。 HAL_TIM_Encoder_Initをステップで送っていくとHAL_TIM_Encoder_MspInitが呼び出されます。この中を追いかけてみます。 HAL_TIM_Encoder_MspInitをステップで送っていくとHAL_DMA_Initが呼び出されます。 ようやくDMAの設定処理にたどり着きました。 HAL_DMA_Initにはhdma_tim2_upのポインタを渡しています。 hdma_tim2_upにはTIM2_UPのDMA情報が設定されます。 この中を追いかけてみます。 HAL_DMA_Initでhdma_tim2_upの設定を行い、最後にhdma->StateをHAL_DMA_STATE_READYに設定します。 HAL_DMA_Initから戻ると__HAL_LINKDMAを実行します。 __HAL_LINKDMAを実行すると、htim2 に反映されます。 htim2.hdma[]はTIM2各イベントのDMAに関する情報が設定されます。 TIM_DMA_ID_UPDATEは0で文字通りUPDATEイベントに対してのDMA設定情報です。 main()に戻り、次にHAL_TIM_Encoder_Start_DMAを実行します。 HAL_TIM_Encoder_Start_DMAの最初はパラメタチェックなので省略します。 進めていくと、DMAの設定処理になります。 htim2のhdmaを使用しますが、TIM_DMA_ID_CC1とTIM_DMA_ID_CC2が使用されています。 つまり、CC1/CC2イベントを使用しようとしています。。。 が、先の__HAL_LINKDMAではIM_DMA_ID_UPDATEに設定していました。 つまりTIM_DMA_ID_CC1及びTIM_DMA_ID_CC1は未設定です。 コールバック関数の設定を行った後、HAL_DMA_Start_ITを呼び出しますが、TIM_DMA_ID_CC1とTIM_DMA_ID_CC2は未設定なので、エラーで戻ります。 結論としては、DMA RequestにTIM2_UPしか選択できないのが原因です。 |
|
原因が判明したので、自力でDMAを使うようにしてみます。 |
2.1. TIM2の設定 まず、TIM2_UPで設定したDMAを削除します。デバイスの設定はここまでですので、Code Generateを実行してください。 |
3.2. コーディング DMA未設定なので、MX_DMA_Init関数は削除されてしまいます。よって、EMBMX_DMA_Init関数を作成します。 CC2はDMA Channel3、CC1はDMA Channel5に割り当てられていますので、Channel3とChannel5に対して初期化します。 /* USER CODE BEGIN 4 */ void EMBMX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Channel2_3_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); /* DMA1_Channel4_5_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel4_5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel4_5_IRQn); } そこでMX_GPIO_Initのユーザコード部分から呼び出すようにします。 /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { /* USER CODE BEGIN MX_GPIO_Init_1 */ /* USER CODE END MX_GPIO_Init_1 */ /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* USER CODE BEGIN MX_GPIO_Init_2 */ EMBMX_DMA_Init(); /* USER CODE END MX_GPIO_Init_2 */ } HAL_TIM_Encoder_MspInit関数のユーザコード部分にコードを追加します。 void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* htim_encoder) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(htim_encoder->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**TIM2 GPIO Configuration PA0-CK_IN ------> TIM2_CH1 PA1 ------> TIM2_CH2 */ GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF2_TIM2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN TIM2_MspInit 1 */ hdma_tim2_cc1.Instance = DMA1_Channel5; hdma_tim2_cc1.Init.Request = DMA_REQUEST_8; hdma_tim2_cc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_tim2_cc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tim2_cc1.Init.MemInc = DMA_MINC_DISABLE; hdma_tim2_cc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_tim2_cc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_tim2_cc1.Init.Mode = DMA_CIRCULAR; hdma_tim2_cc1.Init.Priority = DMA_PRIORITY_LOW; hdma_tim2_cc2 = hdma_tim2_cc1; hdma_tim2_cc2.Instance = DMA1_Channel3; if (HAL_DMA_Init(&hdma_tim2_cc1) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(htim_encoder,hdma[TIM_DMA_ID_CC1],hdma_tim2_cc1); if (HAL_DMA_Init(&hdma_tim2_cc2) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(htim_encoder,hdma[TIM_DMA_ID_CC2],hdma_tim2_cc2); /* USER CODE END TIM2_MspInit 1 */ } } /* USER CODE BEGIN PV */ volatile uint16_t g_ulData; DMA_HandleTypeDef hdma_tim2_cc1; DMA_HandleTypeDef hdma_tim2_cc2; /* USER CODE BEGIN 1 */ void DMA1_Channel2_3_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_tim2_cc2); } void DMA1_Channel4_5_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_tim2_cc1); } /* USER CODE END 1 */ stm32l0xx_hal_msp.cのユーザコード領域に追加します。 基本的にはHAL_TIM_Encoder_Start_DMAを流用しています。 TIM_CCxChannelCmd関数、TIM_DMACaptureCplt関数、TIM_DMACaptureHalfCplt関数も必要ですので、流用して関数名だけ変更しておきます。 void EMBTIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState) { uint32_t tmp; /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(TIMx)); assert_param(IS_TIM_CHANNELS(Channel)); tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */ /* Reset the CCxE Bit */ TIMx->CCER &= ~tmp; /* Set or reset the CCxE Bit */ TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */ } /** * @brief TIM DMA Period Elapse complete callback. * @param hdma pointer to DMA handle. * @retval None */ static void EMBTIM_DMACaptureCplt(DMA_HandleTypeDef *hdma) { TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; if (htim->hdma[TIM_DMA_ID_UPDATE]->Init.Mode == DMA_NORMAL) { htim->State = HAL_TIM_STATE_READY; } #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->PeriodElapsedCallback(htim); #else HAL_TIM_PeriodElapsedCallback(htim); #endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /** * @brief TIM DMA Period Elapse half complete callback. * @param hdma pointer to DMA handle. * @retval None */ static void EMBTIM_DMACaptureHalfCplt(DMA_HandleTypeDef *hdma) { TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->PeriodElapsedHalfCpltCallback(htim); #else HAL_TIM_PeriodElapsedHalfCpltCallback(htim); #endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } HAL_StatusTypeDef EMBHAL_TIM_Encoder_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint16_t *pData, uint16_t Length) { HAL_StatusTypeDef status = HAL_OK; uint32_t tmpsmcr; /* Check the parameters */ assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel)); assert_param(IS_TIM_DMA_CC_INSTANCE(htim->Instance)); /* Set the TIM channel state */ if ((pData == NULL) || (Length == 0U)) { return HAL_ERROR; } TIM_CHANNEL_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY); /* Enable the Input Capture channel */ EMBTIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE); switch (Channel) { case TIM_CHANNEL_1: { /* Set the DMA capture callbacks */ htim->hdma[TIM_DMA_ID_CC1]->XferCpltCallback = TIM_DMACaptureCplt; htim->hdma[TIM_DMA_ID_CC1]->XferHalfCpltCallback = TIM_DMACaptureHalfCplt; /* Set the DMA error callback */ htim->hdma[TIM_DMA_ID_CC1]->XferErrorCallback = TIM_DMAError ; /* Enable the DMA channel */ if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)&htim->Instance->CNT, (uint32_t)pData, Length) != HAL_OK) { /* Return error status */ return HAL_ERROR; } /* Enable the TIM Capture/Compare 1 DMA request */ __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC1); break; } case TIM_CHANNEL_2: { /* Set the DMA capture callbacks */ htim->hdma[TIM_DMA_ID_CC2]->XferCpltCallback = EMBTIM_DMACaptureCplt; htim->hdma[TIM_DMA_ID_CC2]->XferHalfCpltCallback = EMBTIM_DMACaptureHalfCplt; /* Set the DMA error callback */ htim->hdma[TIM_DMA_ID_CC2]->XferErrorCallback = TIM_DMAError ; /* Enable the DMA channel */ if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC2], (uint32_t)&htim->Instance->CNT, (uint32_t)pData, Length) != HAL_OK) { /* Return error status */ return HAL_ERROR; } /* Enable the TIM Capture/Compare 2 DMA request */ __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC2); break; } default: status = HAL_ERROR; break; } /* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */ if (IS_TIM_SLAVE_INSTANCE(htim->Instance)) { tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS; if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr)) { __HAL_TIM_ENABLE(htim); } } else { __HAL_TIM_ENABLE(htim); } /* Return function status */ return status; } CubeIDEの方で対応してくれたらいいんですけど。。。 |