ロータリエンコーダ(カウンタ利用)は軸の回転角度を測定するセンサである。ロータ リエンコーダは大別すると,角度をそのまま読むことのできるアブソリュート型と,発生パルスを数えるインクリメント型がある。ここではインクリメント型 ロータリエンコーダの使用について解説する。
図に示すような光学式ロータリエンコーダは回転角を測定するセンサーで,軸が回転するとパルスが発生す る。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Ω程度の抵抗を介してマイコンの電源電圧に接続することであるが,マイコンがプルアップ機能を持っていることが多く,この機能を有効にするだけで良い。
STM32F051ではTIM1,TIM2,・・・と呼ばれる多用途タイマ・カウンタがあり,この内TIM1,TIM2,TIM3がエンコーダの2相入力をカウントできる。
TIM1は多用途なので,TIM2(32bit counter),TIM3(16bit counter)についてプログラムしてみる。
最初に入力ピン2つをどこにするか決めなければならない。エンコーダ入力はdm00031936.pdf(RM0091) の17.3.12によればTI1,TI2である。dm00031936.pdf(RM0091)のFigure102.によればTI1,TI2の外部からの入り口は TIMx_CH1,TIMx_CH2である。
TIM2に関しては,TIM2_CH1とTIM2_CH2を入力ピンにするのだが, それはどこにあるかというとマニュアルdm00039193.pdfのTable14を探すことになる。TIM2_CH1はPA0,PA5,PA15がAF2の設定で使える。 (TIM2_CH1_ETRと書いてあるが_ETR入力にもなっているという意味)どれにするかは自分で決めるのだが, PA0はユーザボタンとして使用済みなので避けてPA5を使うことにする。 (PA0もプッシュスイッチへの結線を切断すれば使える。DM00050135.pdf(UM1525)の Table.5,SB3参照)またTIM2_CH2はPA1のみがAF2の設定で使える。
同様にマニュアルdm00039193.pdfのTable15にてTIM3_CH1,TIM3_CH2はPB4,PB5がAF1の設定で使える。
DM00050135.pdf(UM1525)のTable6のPA0.PA5,PA15に2_CH1_ETRと書いてある。 先に調べたdm00039193.pdfのTable14ではTIM2_CH1_ETRのことのようである。そうするとTIM3に関する記述は 3_CH1,3_CH2となって,DM00050135.pdf(UM1525)のTable6にあるはずである。PC6,PC7がそれとわかる。 (これもAF1の設定で使える。使えているサンプルがあるが,PortCに関するAFxの一覧表がマニュアルに見つからない。)
ところでstm32f0xx_gpio.hの/** @defgroup GPIO_Alternate_function_selection_define を見ると,TIM2はすべてAF2(GPIO_AF_2)でTIM3はすべてAF1のようにも受け取れる様に書いてある。
ということで,以下のように決める。
TIM2への入力 : PA5とPA1 またはPA0とPA1
TIM3への入力 : PB4とPB5 またはPC6とPC7
このピン割り当てに基づいてSTM32への初期化プログラムを作成する。
初期化においては次の手順になる。
(1)GPIOAにクロックを供給
(2)GPIOのピンをエンコーダ入力用として割り当てる設定
(3)エンコーダ入力用として割り当てられたピンの設定
(4)TIMxへのクロックを供給
(5)TIMxをエンコーダとして設定
最初に示すのは,main関数である。semihostingを使って,カウンタの値を表示している。
2つmain関数を示すが,最初のものはTIM2を用いたもので,2つめはTIM3を用いたものである。
エンコーダカウンタ機能を初期化し,カウンタをクリア(カウンタの値を0にする)した後,ループに入り,カウンタ値を読み取る。
main.c
Enc1はTIM2でのカウント,Enc2はTIM3でのカウント
#include <stm32f0xx.h>
#include <stdint.h>
#include "stm32f0xx_conf.h"
#include "stm32f0_discovery.h"
#include <stdio.h>
#include "semihosting.h"
#include "rotary_encoder.h"
int main(void)
{
//initialize encoder2
Enc1_Init(GPIO_PuPd_UP);
Enc1_Clear();
printf("Rotary Encoder Counter\n\n");
while(1) {
int32_t cntr;
cntr=Enc1_Read();
printf("%5d\n",cntr);
}
}
#include <stm32f0xx.h>
#include <stdint.h>
#include "stm32f0xx_conf.h"
#include "stm32f0_discovery.h"
#include <stdio.h>
#include "semihosting.h"
#include "rotary_encoder.h"
int main(void)
{
//initialize encoder2
Enc2_Init(GPIO_PuPd_UP);
Enc2_Clear();
printf("Rotary Encoder Counter\n\n");
while(1) {
int_fast16_t cntr;
cntr=Enc2_Read();
printf("%5d\n",cntr);
}
}
実行結果例
Rotary Encoder Counter
0
0
0
0
0
160
824
1518
1668
1758
1824
1829
初期化関数,クリア関数,カウント値読み取り関数を以下に示す。
(これらの関数のオリジナルは機械工学科佐藤輝一君が作ってくれました。
初めてSTM32F0Discoveryで使う学習者向けに小坂がアレンジしています。)
rotary_encoder.c
初期化関数,クリア関数,カウント値読み取り関数
TIM2への入力 : PA5とPA1
TIM3への入力 : PB4とPB5
#include <stm32f0xx.h>
#include <stdint.h>
#include "stm32f0xx_conf.h"
#include "stm32f0_discovery.h"
#include "rotary_encoder.h"
void Enc1_Init(GPIOPuPd_TypeDef GPIO_PuPd)
{
/* GPIOA Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
/* alternate function enable*/
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
/* Configure PA0 PA1*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* TIM2 encoder mode enable*/
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
TIM_Cmd(TIM2, ENABLE);
}
int32_t Enc1_Read(void)
{
return TIM2->CNT;
}
void Enc1_Clear(void)
{
TIM2->CNT = 0;
}
void Enc2_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_1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_1);
/* Configure PC6 PC7*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
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);
}
int16_t Enc2_Read(void)
{
return TIM3->CNT;
}
void Enc2_Clear(void)
{
TIM3->CNT = 0;
}
エンコーダ信号の入り口を変更する場合は次の初期化関数と差し替えて使うことになる。
初期化関数
TIM2への入力 : PA0とPA1
TIM3への入力 : PC6とPC7void Enc1_Init(GPIOPuPd_TypeDef GPIO_PuPd)
{
/* GPIOA Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
/* alternate function enable*/
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
/* Configure PAx PA1*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* TIM2 encoder mode enable*/
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
TIM_Cmd(TIM2, ENABLE);
}
void Enc2_Init(GPIOPuPd_TypeDef GPIO_PuPd)
{
/* GPIOC Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
/* alternate function enable*/
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_1);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_1);
/* Configure PC6 PC7*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
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);
}
.hファイルは次のようになる。
rotary_encoder.h
/*
* Enc1_Init(),Enc2_Init()の引数GPIO_PuPdについて
* エンコーダ出力がプッシュプル型(トーテムポール型)の場合は PuPd_NOPULL (なにもしない)
* エンコーダ出力がオープンコレクタ(オープンドレイン)型の場合は GPIO_PuPd_UP (プルアップする)
* を設定する。
*/
#ifndef __ROTARY_ENCODER_H__
#define __ROTARY_ENCODER_H__
#include <stm32f0xx.h>
void Enc1_Init(GPIOPuPd_TypeDef GPIO_PuPd);
int32_t Enc1_Read(void);
void Enc1_Clear(void);
void Enc2_Init(GPIOPuPd_TypeDef GPIO_PuPd);
int16_t Enc2_Read(void);
void Enc2_Clear(void);
#endif /* __ROTARY_ENCODER_H__*/