STM32L010 TIM Encoderの使い方(NVIC / DMA)


TIM2 のEncoder機能を使用してみます。

1. TIM2 Encoder 割込み

TIM2_CH1にA相、TIM2_CH2にB相を入力します。


1.1. TIM2の設定

Combined ChannelEncoder Modeに設定します。。
今回は4逓倍でカウントしますので、Encoder ModeEncoder 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 */
				
mainループ内でg_ulDataが更新された場合、ログを出力します。
  /* 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 */
  				



2. TIM2 TIM2 Encoder DMA

次にDMAですが、、、結論を先に言うと、現在のHAL DriverではEncodeのDMAは対応できません。orz
まずは、HAL Driverを使用した場合、どのような理由で動作しないかを説明します。


2.1. TIM2の設定

DMAを設定します。DMA RequestTIM2_UPしか選択できません。
TIM2 global interruptdisableに設定します。




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_Inithdma_tim2_upの設定を行い、最後にhdma->StateHAL_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_CC1TIM_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_CC1TIM_DMA_ID_CC2は未設定なので、エラーで戻ります。

結論としては、DMA RequestにTIM2_UPしか選択できないのが原因です。

3. TIM2 TIM2 Encoder 自力でDMA

原因が判明したので、自力で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_TIM2_Init実行前にEMBMX_DMA_Initを実行しなければなりませんが、MX_TIM2_Initを呼び出している箇所はユーザ変更不可です。
そこで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 */
}
  				
次に、htim2.hdmaを設定します。
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 */

  }
}
  				
hdma_tim2_cc1hdma_tim2_cc1は追加しておきます。
/* USER CODE BEGIN PV */
volatile uint16_t	g_ulData;

DMA_HandleTypeDef hdma_tim2_cc1;
DMA_HandleTypeDef hdma_tim2_cc2;
  				
次に、DMA割込みハンドラを追加します。stm32l0xx_it.cのユーザコード領域に追加します。
/* 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 */
  				
最後に、DMAスタート関数EMBHAL_TIM_Encoder_Start_DMAを作成します。
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;
}
  				
これでEncoderのDMA化が実現できます。
CubeIDEの方で対応してくれたらいいんですけど。。。