ロータリエンコーダによる角度検出

20140530 coskx
T.SATOH


1. はじめに


ロータリエンコーダ(カウンタ利用)は軸の回転角度を測定するセンサである。ロータ リエンコーダは大別すると,角度をそのまま読むことのできるアブソリュート型と,発生パルスを数えるインクリメント型がある。ここではインクリメント型 ロータリエンコーダの使用について解説する。

図に示すような光学式ロータリエンコーダは回転角を測定するセンサーで,軸が回転するとパルスが発生す る。1回転につき,100パルス程度のものから20000パルス程度の分解能を持つものがある。ロータリエンコーダからのパルス信号はA相B相の2つがあ り,これにより,回転方向も判別することが出来るようになっている。STM32F051はA相B相の信号を受け取り,回転方向を判断し,UPカウント・ DOWNカウントを行なう32ビットカウンタ,16ビットカウンタを持っている。
プログラムでは,初期設定をすればあとは勝手にパルスをカウントしてカウンタに保存しているので,いつで もこのカウンタを読み出せば,角度に比例した値を読み出すことができる。1回転が何パルスに対応するかはエンコーダの仕様書や本体に書いてあり 1000P/R[pulses/revolution]と書いてあったら1回転あたり1000パルスである。



ロータリエンコーダ(インクリメント型光学式エンコーダ)からの信号は次のようになっている。正転時と逆転時ではA相B相の信号の位相が異なる。
STM32F051では位相カウンタとしての動作が出来る。正転時はカウントアップされ,逆転時にはカウントダウンする。
エンコーダ信号の1周期で4つカウントする設定にするので,ロータリエンコーダの1回転でのカウントは,仕様の4倍である。(4逓倍カウント)
すなわち,1000pulses/rev(1回転で1000パルス)仕様のロータリエンコーダでは1回転で4000カウントになる。



ロータリエンコーダからの出力方式はいくつかあるが,使いやすいのはプッシュプル出力(トーテムポール出力)とNPNオープンコレクタ出力である。
プッシュプル出力の場合,信号の0,1は0Vと設定電圧として出力される。(この場合設定電圧はマイコンと同じ電圧にしなければならない)
NPNオープンコレクタ出力の場合,信号の0,1は0Vとハイインピーダンス(電気的に絶縁状態)として出力される。
NPNオープンコレクタ出力のものば,マイコン側でマイコンの電源電圧にプルアップすることで,3V系マイコンでも5V系マイコンでも問題なく使える。
ロータリエンコーダの出力形式と,受け側の入力形式の対応は次のようになる。
ロータリエンコーダの出力形式 受け側の入力形式
プッシュプル出力 プルアップもプルダウンもしないレベル入力
NPNオープンコレクタ出力 プルアップ入力
プルアップするというのは,信号線から10kΩ程度の抵抗を介してマイコンの電源電圧に接続することであるが,マイコンがプルアップ機能を持っていることが多く,この機能を有効にするだけで良い。

2. プログラムの要点

STM32F303ではTIM1,TIM2,・・・と呼ばれる多用途タイマ・カウンタがあり,この内TIM1,TIM2,TIM3,TIM4,TIM8がエンコーダの2相入力をカウントできる。

最初に入力ピン2つをどこにするか決めなければならない。エンコーダ入力はDM00043574.pdf(RM0316) の19.3.15によればTI1,TI2である。DM00043574.pdf(RM0316)のFigure 173.によればTI1,TI2の外部からの入り口は TIMx_CH1,TIMx_CH2である。

しかし,DM00063382.pdf(UM1570 User Manual)Table 6. STM32F303VCT6 MCU pin description versus board functionによれば,対応するピンが評価キットで使われているところが多いため,TIM3(16bit counter)についてプログラムしてみる。
マニュアルDM00058181.pdfのTable13にてTIM3_CH1,TIM3_CH2はPB4,PB5がAF2の設定で使える。
また,DM00058181.pdfのTable6によればTIM3_CH1,TIM3_CH2はPC6,PC7もAF2の設定で使える。
ところでstm32f30x_gpio.hの/** @defgroup GPIO_Alternate_function_selection_define を見ると,TIM3はすべてAF2のようにも受け取れる様に書いてある。

ということで,以下のように決める。
TIM3への入力 : PB4とPB5 またはPC6とPC7

このピン割り当てに基づいてSTM32への初期化プログラムを作成する。
初期化においては次の手順になる。
(1)GPIOBまたはGPIOCにクロックを供給
(2)GPIOのピンをエンコーダ入力用として割り当てる設定
(3)エンコーダ入力用として割り当てられたピンの設定
(4)TIMxへのクロックを供給
(5)TIMxをエンコーダとして設定


3. プログラム

最初に示すのは,main関数である。semihostingを使って,カウンタの値を表示している。
エンコーダカウンタ機能を初期化し,カウンタをクリア(カウンタの値を0にする)した後,ループに入り,カウンタ値を読み取る。
入力ピンは
PB4とPB5
を使う。


main.c
#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>
#include "semihosting.h"

#include "rotary_encoder.h"

//  Two phase inputs from the encoder are connected to GPIO B Pin4, Pin5.
//  or
//  Two phase inputs from the encoder are connected to GPIO C Pin6, Pin7.

int main(void)
{
    //initialize encoder
    Enc_Init(GPIO_PuPd_UP); //for PB4,PB5
    //Enc_Init_alt(GPIO_PuPd_UP); //for PC6,PC7
    Enc_Clear();
    printf("Rotary Encoder Counter\n\n");
    while(1) {
        int cntr;
        cntr=Enc_Read();
        printf("%5d\n",cntr);
    }
}

実行結果例
Rotary Encoder Counter
    0
    0
   -2
   -2
   -2
 -322
  -49
 -156
-1963
-2597
-3363
-2706
-1401
   -2
  350
 1561
 3188
 4305
 4932



初期化関数,クリア関数,カウント値読み取り関数を以下に示す。
(これらの関数のオリジナルは機械工学科佐藤輝一君が作ってくれました。
初めてSTM32F3Discoveryで使う学習者向けに小坂がアレンジしています。)

rotary_encoder.c
初期化関数,クリア関数,カウント値読み取り関数
TIM3への入力 : PB4とPB5 または 
PC6とPC7
Enc1_Init()で初期化した時:入力はPB4とPB5
Enc_Init_alt()
で初期化した時:入力はPC6とPC7
/*
 *         File:    rotary_encoder.c
 *         Author:    teru
 *         fitting to STM32F3DISCOVERY by coskx
 *
 */

// Enc is comprised of TIM3.
//  Two phase inputs from the encoder are connected to GPIO B Pin4 ,Pin5.
//  setting AF2
//
//  another setting
//  Two phase inputs from the encoder are connected to GPIO C Pin6 ,Pin7.
//  setting AF2



#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"

#include "rotary_encoder.h"

void Enc_Init(GPIOPuPd_TypeDef GPIO_PuPd)
{
    /* GPIOB Periph clock enable */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

    /* alternate function enable*/
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_2);

    /* Configure PB4 PB5*/
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;       //*4
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* TIM3 clock enable */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    /* TIM3 encoder mode enable*/
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
    TIM_ICPolarity_Rising,
    TIM_ICPolarity_Rising);
    TIM_Cmd(TIM3, ENABLE);      //*3
}

void Enc_Init_alt(GPIOPuPd_TypeDef GPIO_PuPd)
{
    /* GPIOC Periph clock enable */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    /* alternate function enable*/
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_2);

    /* Configure PC6 PC7*/
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;       //*4
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    /* TIM3 clock enable */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    /* TIM3 encoder mode enable*/
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
    TIM_ICPolarity_Rising,
    TIM_ICPolarity_Rising);
    TIM_Cmd(TIM3, ENABLE);      //*3
}
int16_t Enc_Read(void)
{
    return TIM3->CNT;
}

void Enc_Clear(void)
{
    TIM3->CNT = 0;
}


.hファイルは次のようになる。
rotary_encoder.h
/*
 *         File:    rotary_encoder.h
 *         Author:    teru
 *         fitting to STM32F3DISCOVERY by coskx
 *
 *   Enc_Init(),Enc_Init_alt()の引数GPIO_PuPdについて
 *   エンコーダ出力がプッシュプル型(トーテムポール型)の場合は PuPd_NOPULL (なにもしない)
 *   エンコーダ出力がオープンコレクタ(オープンドレイン)型の場合は GPIO_PuPd_UP (プルアップする)
 *   を設定する。 Kosaka
 */

#ifndef __ROTARY_ENCODER_H__
#define __ROTARY_ENCODER_H__

#include <stm32f30x.h>

void Enc_Init(GPIOPuPd_TypeDef GPIO_PuPd);
void Enc_Init_alt(GPIOPuPd_TypeDef GPIO_PuPd);
int16_t Enc_Read(void);
void Enc_Clear(void);

#endif /* __ROTARY_ENCODER_H__*/