(2018.3.3 作成)
このページではSTM32マイコンを使用してロータリーエンコーダーを使用する方法について紹介したいと思います。
ロータリーエンコーダーについてはここで述べるまでもなく多くのサイトで紹介されていますが、軸の回転を検出してダイヤルによる入力や回転速度などを検出するデバイスです。
一口にロータリーエンコーダーといっても数多くの種類があります。例えばRSコンポーネンツさんのサイトを見ると写真付きで何百種類も掲載されていたりします。
その中でも安価でよく使われるものは上の写真に載せたような機械式のインクリメンタルタイプだと思います。このタイプでは下の図のように1クリック毎にA相、B相とが時間をずらして立ち上がることで回転方向と回転数を検出することができるようになっています。
エンコーダーを使用する場合には頑張ってA,B相をそれぞれ読んで解釈するプログラムを作らないといけないのですが、このサイトで多く紹介しているSTM32ではTIMペリフェラルを使用することでハードウェアで解釈してくれるというありがたい機能があります。
そこでこのページではSTM32のエンコーダー入力機能を紹介しようと思ったのですが、すでに簡単な使い方はこのサイトで紹介されています。そこでこのページでは使用するにあたって少し気になった点、分かりにくかった点を中心に紹介したいと思います。
エンコーダーに接続する際にはA,B端子をTIMペリフェラルのCh1, 2にそれぞれつなぎ、コモン端子を3.3Vに接続します。その際Ch1, Ch2の端子をそれぞれをプルダウンしていないと右のように端子が不定になります。必ずプルダウンするようにしましょう。
またここではコモン端子を3.3Vに接続して正論理として記載していますが、コモン端子をGNDに接続し、Ch1,2をプルアップとすることで負論理として使用することもできます。
STM32のリファレンスマニュアルには一見エンコーダーを直接接続できるように書かれているのですが、実際のエンコーダーでは図のようにチャタリングが発生します。
このため軸を回しても期待通りに動かないことが多いです。
同マニュアルにはコンパレータを使うことで安定すると書かれていますが、ローパスフィルタと組み合わせて使うなどチャタリング対策は必要になります。
信号の入力部にディジタルフィルタがあるため、多少のノイズ対策はできるのですがリファレンスマニュアルを読んでもほぼ情報がありません。フィルタについてはtimer cookbook内に多少ヒントが入っているのでここを参考にする必要があります。
間違えているかもしれませんが、例えばstm32f103c8t6のTIM3で最も長いフィルタ時間に設定しようとすると
とすることで28.4usのフィルタを作ることができます。
しかしこれでも上の画像のように100usを超えるチャタリングが発生したら効果なしですけどね。
少し長いですが、stm32f103c8t6基板とLEDディスプレイを組み合わせてエンコーダーを使用するサンプルコードを下に載せます。LEDディスプレイを使用する部分についてはこちらの共通ライブラリを使用していますが、エンコーダー部分は標準的なHALドライバを使用しています。
ご参考になればと思います。
/* Copyright (c) 2018 DenshiKousakuSenka This software is released under the MIT License. http://opensource.org/licenses/mit-license.php */ #include "DKS_Common_F103xB.h" #include "DKS_F103C8T6.h" #include "DKS_Wait_F103xB.h" #include "DKS_TM1637.h" TIM_HandleTypeDef htim3; DKS::DigitalOut scl, sda; DKS::Wait w; DKS::LED_Display::LEDdisp_Clock disp; int main(void) { DKS::InitSystem(); DKS::STM32F103C8T6 board(DKS::BlackPill); scl.Init(GPIOA, GPIO_PIN_7, DKS::OpenDrain, DKS::No_Pull); sda.Init(GPIOB, GPIO_PIN_0, DKS::OpenDrain, DKS::No_Pull); w.Init(TIM4); disp.Init(&scl, &sda, &w, 0); disp.Show(0, 1); // PB4 ------> TIM3_CH1 // PB5 ------> TIM3_CH2 __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_TIM3_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); __HAL_AFIO_REMAP_TIM3_PARTIAL(); TIM_Encoder_InitTypeDef sConfig; TIM_MasterConfigTypeDef sMasterConfig; htim3.Instance = TIM3; htim3.Init.Prescaler = 1; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 2000; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; sConfig.EncoderMode = TIM_ENCODERMODE_TI1; sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC1Prescaler = TIM_ICPSC_DIV1; sConfig.IC1Filter = 0xF; sConfig.IC2Polarity = TIM_ICPOLARITY_RISING; sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC2Prescaler = TIM_ICPSC_DIV1; sConfig.IC2Filter = 0xF; if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK) while (true); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) while (true) ; if (HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_1) != HAL_OK) while (true); __HAL_TIM_SET_COUNTER(&htim3, 1000); HAL_NVIC_SetPriority(TIM3_IRQn, 0, 1); HAL_NVIC_EnableIRQ(TIM3_IRQn); for (;;); } extern "C" void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&htim3); disp.Show(htim3.Instance->CNT - 1000, 8); }