|
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の方で対応してくれたらいいんですけど。。。 |