皆さんこんにちは。KINKI KNIGHTSのラーメンおじさんです。 本記事はKINKI KNIGHTSのGWアドベントカレンダー3日目の記事です。
今日はNHK学生ロボコンの二次ビデオの結果発表がありました。 近畿の大学が多く二次ビデオを通過していたようで、喜ばしいですね。 我々も6月に控えたレスコンの予選に向行けて本腰を入れてかからねばと身が引き締まります。
閑話休題
ロボコンといえば、自動化ですね。そして自動化といえばエンコーダです。 回転軸の現在角度をエンコーダで読み取ることでモーターのフィードバック制御ができるようになります。 この記事ではエンコーダをどのようにマイコンで読み取るのが良いのかについて解説していきます。
エンコーダの種類
エンコーダは、アブソリュートエンコーダとインクリメンタルエンコーダの二種類に大別されます。
インクリメンタルエンコーダ
エンコーダが任意の位置からどのくらい回転したかを相対角度として検出するエンコーダです。 A相、B相と呼ばれる2本の信号線で状態を出力します。
シリアル通信が挟まるアブソリュートエンコーダと比べ、高速にリアルタイムな回転量を取得できるメリットがあります。
マイコンで移動量を管理するため、電源を切ったりマイコンをリセットすると現在の位置がわからなくなる欠点があります。
機構の端を検出できるリミットスイッチなどのセンサとセットで使用されることが多いです。
アブソリュートエンコーダ
エンコーダの軸自身が明確な原点を持ち、原点からの絶対角度を出力するエンコーダーです。
シリアル通信を用いてマイコンに回転量を送信するタイプが一般的です。
電源を切ったりマイコンをリセットしても、常に同じ回転量を返すため、リミットスイッチなどで原点セットを行う必要がありません。 1回転で初期値に戻る製品と複数回転に対応した製品があるので用途に応じて選定する必要があります。
インクリメンタルエンコーダの位置計算方法
インクリメンタルエンコーダはA相、B相の2つの信号線が少しずれてON・OFFを繰り返します。

ここで、A相の信号がONからOFFに変化する瞬間に着目してみましょう。

エンコーダが正転し水色の矢印方向に信号が変化しているとき、A相がOFFする瞬間にB相はONしています。
エンコーダが逆転し、緑色の矢印方向に信号が変化しているとき、A相がOFFする瞬間にB走破OFFになっています。
このように、インクリメンタルエンコーダではA・B相の信号が変化したタイミングでもう一方の相の状態を見ることで回転方向がわかります。
マイコンで定期的にA相の信号の状態を取得し、状態が変化した際に正転方向の動きであれば現在角度を+1、逆転方向であれば現在角度を-1するだけでエンコーダの回転量がわかります。
エンコーダの精度と選定
エンコーダの軸が1回転する間にA相もしくはB相が何度ONするかを示した値を分解能といいます。
分解能が大きいほど、より細やかに機構の制御を行うことが可能です。 例えば分解能が100のエンコーダの場合、1回転で100回A相がONするため、360度を100分割して検出することができます。
一般的なインクリメンタルエンコーダの場合、分解能の4倍の性能までを引き出すことができます。 A相がONする瞬間だけに注目すると、1周100回しか信号を検出できませんが、全ての信号の変化を利用することで1周400回信号を検出できます。

エンコーダの分解能は高いほどいいの?
ロボコンで良く用いられるAMT102というエンコーダは、本体についたスイッチで分解能を48~2048の間で自由に設定できます。
分解能が高いほど制御の精度が上がるのなら、常に最大の分解能を選択するのがベストなのでしょうか?

実はそうとも限りません。 例えば、1秒間に10回転するタイヤにAMT102を取り付けたケースを考えてみましょう。
分解能を最大値の2048に設定すると、タイヤを1回転した時のA相・B相の変化は4倍の8192回になります。 タイヤが10回転するとおよそ8万回信号が変化するわけです。
インクリメンタルエンコーダは、マイコンでA相・B相の変化を取得して処理する必要があります。 逆算すると1回のエンコーダの読み取り・計算処理に長くとも0.012ミリ秒しか時間をかけられないことになります。 (実際にはさらに2分の1の処理時間しか掛けられません。(標本化定理より))
一般的なマイコンではこれは現実的な処理速度ではありません。 信号の変化を数え漏らすと回転量がくるってしまうため、制御に必要な精度を満たしつつマイコンの処理周期で余裕をもって処理できる分解能をいい塩梅で選定することが大切です。
エンコーダの信号を読み漏らさないために
分解能を下げても他の機構や通信で時間がかかってしまうと、エンコーダの信号を読み漏らしてしまう事がよくあります。 最も確実なのは「メインの処理系とは別でエンコーダの信号をカウントする事」です。
この記事では次の2種類の手法について解説します。
- RaspberryPi Picoの2コア目を活用する
- ペリフェラルでエンコーダのカウントをできるマイコンを使用する
RaspberryPi Picoの2コア目を活用する
RaspberryPi PicoのCPUは2コア構成になっており、それぞれ独立して処理を行うことが可能です。 また、Arudino環境ではグローバル変数を通じて面倒な処理無しにCPU間で値をやり取りできます。
1コアをロボットの制御や通信に使い、もう1コアをエンコーダのカウントに専念させる事で安定した検出が可能になります。
volatile int encoder_count = 0;
int last_state = HIGH;
/* Core 0の処理 */
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(encoder_count);
delay(100);
}
/* Core 1の処理 */
void setup1() {
pinMode(2, INPUT_PULLUP); // A相
pinMode(3, INPUT_PULLUP); // B相
}
void loop1() {
static int last_A = HIGH;
int A = digitalRead(2);
int B = digitalRead(3);
// A相の立ち上がりで判定
if (last_A == LOW && A == HIGH) {
if (B == LOW) {
encoder_count++; // 正方向
} else {
encoder_count--; // 逆方向
}
}
last_A = A;
}
2コア処理についての詳細は割愛しますので、他のブログを参考にしてください。
ペリフェラルでカウントする
マイコンの機能(ペリフェラル)として、設定さえすれば勝手にエンコーダの信号をカウントしてくれる製品が存在します。
KINKI KNIGHTSではSTM32マイコンのタイマーペリフェラルに搭載されたエンコーダモードを活用して、エンコーダをカウントしています。
STM32F3マイコンでHALを用いてエンコーダモードの設定を行う方法を解説します。 HALの環境構築についてはHALでゴリゴリSTマイコンをご覧ください。
まず、タイマと使用するIOを有効化します。
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
次に、タイマーの設定を行います。
TIM_HandleTypeDef htim;
htim.Instance = TIM2;
htim.Init.Prescaler = 0;
htim.Init.CounterMode = TIM_COUNTERMODE_UP;
htim.Init.Period = 0xFFFF;
htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim) != HAL_OK)Error_Handler();
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim, &sConfigIC, _channel) != HAL_OK)Error_Handler();
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig) != HAL_OK)Error_Handler();
HAL_TIM_IC_Start(&htim, _channel);
エンコーダの入力を読み取るピンを初期化します。
GPIO_AF1_TIM2
を指定することで、タイマ2への入力に設定します。
void initGpio(GPIO_TypeDef *_gpio_group, uint32_t _gpio_pin){
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = _gpio_pin;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(_gpio_group, &GPIO_InitStruct);
}
initGpio(GPIOA, GPIO_PIN_15);
initGpio(GPIOB, GPIO_PIN_5);
これで設定は完了です。あとはマイコンが勝手にエンコーダの信号をカウントしてくれます。
エンコーダの回転量を取得したいときは次の関数をたたきます。
__HAL_TIM_GET_COUNTER(&htim)
エンコーダーのA相とB相は同じ高機能タイマのCH1とCH2に入力する必要があります。 回路設計時に信号を入力するピンを決めるときは気を付けましょう。
参考までに、STM32F303マイコンでは下記の組み合わせで2台のエンコーダをつなぐことができます。
- GPIO A15とGPIO B3 (TIM2)
- GPIO B4とGPIO B5 (TIM3)
上記のピンは全て5V耐圧のため、信号電圧が5VのAMTエンコーダもレベルシフターを通さず安心して使用できます。
最後に
今回はエンコーダ分解能の選定方法と、信号を取りこぼしにくい処理方法について解説しました。
たかがセンサ一つ、なかなか奥が深いものです。
KINKI KNIGHTSでは、来年のCoREに向けて一緒にロボコンするメンバーを大募集中です。
Xにてアドベントカレンダー投稿の情報や活動の様子なども発信していますので、是非フォローしてみてください!
明日のアドベントカレンダーは一体だれが書くのでしょうか? 進捗祭りのGWはまだまだこれからだ!!