概要
今回は、QEI(Quadrature Encoder Interface)モジュールの使い方をご紹介します。
QEIモジュールは主にモータ制御など、回転を検出するアプリケーションで使用されます。モータに直結したエンコーダによって、機械的な位置、回転方向、回転速度などを検出します。
・4つの入力端子: 2つの位相信号、インデックス・パルス、ホーム・パルス
・入力のプログラマブル・デジタル・ノイズ・フィルター
・カウンタ・パルスとカウント方向を提供する直交デコーダ
・x4カウント分解能
・位置カウンターをリセットするインデックス・パルス
・汎用32ビット・タイマ/カウンタ・モード
・QEIまたはカウンタ・イベントによって生成される割り込み
・32ビット速度カウンタ
・32ビット位置カウンタ
・32ビットインデックスパルスカウンタ
・32ビット間隔タイマ
・32ビット位置初期化/捕捉/比較ハイワードレジスタ
・32ビット位置初期化/捕捉/比較下位ワード・レジスタ
・4X直交カウント・モード
・外部アップ/ダウン・カウント・モード
・外部ゲート・カウント・モード
・外部ゲート・タイマ・モード
・インターバル・タイマ・モード
エンコーダが出力するA相/B相の信号をQEIモジュールに入力します。このとき、入力エッジと逆相の信号レベルに応じてカウンタがアップまたはダウンします。さらに、Z相が入力されると、このカウンタがゼロにリセットされ、回転角度などを検出することが可能になります。

今回は基本的な使い方を紹介します。
関連記事
開発環境
開発環境を以下に示します。
今回はどちらのCPUボードでも動作いたします。
| 項目 | 値 | リンク |
| ベースボード | dsPIC33A CURIOSITY PLATFORM DEVELOPMENT BOARD | dsPIC33A Curiosity Platform Development Board User’s Guide (microchip.com) |
| CPUボード(EV68M17A) | EV68M17A – dsPIC33AK128MC106 Motor Control DIM | dsPIC33AK128MC106 Motor Control Dual In-Line Module (DIM) Information Sheet (microchip.com) |
| CPUボード(EV02G02A) | dsPIC33AK128MC106 General Purpose Dual In-Line Module (DIM) | dsPIC33AK128MC106 General Purpose Dual In-Line Module (DIM) | Microchip Technology |
| 統合開発環境 | MPLAB X IDE v6.20 | MPLAB® X IDE | Microchip Technology |
| コンパイラ | MPLAB XC DSC v3.10 | MPLAB® XC DSC Compiler | Microchip Technology |

QEIモジュールブロック図
以下にQEIモジュールのブロック図を示します。
外部エンコーダの場合、通常A相はQEIAxに、B相はQEIBxに、Z相はQEINDXxに接続します。
ペリフェラルのバススピードは’Standard’のため、最大100MHzの信号をキャプチャ可能です。

QEIレジスタ
以下にQEIレジスタとその説明を示します。
| レジスタ名(xは1) | ビット名 | 説明 |
| QEIxCON | QEIEN | モジュールの有効/無効化 |
| QEISIDL | Idleモードでの動作設定 | |
| PIMOD | ポジションカウンタ初期化モード | |
| IMV | インデックスマッチ判定 | |
| INTDIV | タイマープリスケーラ | |
| CNTPOL | カウンタアップダウン設定 | |
| GATEN | 外部ゲート有効化 | |
| CCM | カウンタ制御モード | |
| QEIxIOC | HCAPEN | ホームイベントによるキャプチャ有効化 |
| QCAPEN | インデックスマッチイベントによるキャプチャ有効化 | |
| FLTREN | デジタルフィルタ有効化 | |
| QFDIV | ブデジタルフィルタ分周設定 | |
| OUTFNC | 出力機能設定 | |
| SWPAB | A/B入力入れ替え設定 | |
| HOMPOL | Home入力極性ビット | |
| IDXPOL | Index入力極性ビット | |
| QEBPOL | B入力極性ビット | |
| QEAPOL | A入力極性ビット | |
| HOME | HOME入力ステータス | |
| INDEX | Index入力ステータス | |
| QEB | B入力ステータス | |
| QEA | A入力ステータス | |
| QEIxSTAT | PCHEQIRQ | POSCNTとQEIGECの比較結果 |
| PCHEQIEN | POSCNTとQEIGECの比較結果による割り込み有効化 | |
| PCLEQIRQ | POSCNTとQEILECの比較結果 | |
| PCLEQIEN | POSCNTとQEILECの比較結果による割り込み有効化 | |
| POSOVIRQ | POSCNTオーバーフローステータス | |
| POSOVIEN | POSCNTオーバーフロー割り込み有効化 | |
| PCIIRQ | POSCNT初期化プロセス終了ステータス | |
| PCIIEN | POSCNT初期化プロセス終了ステータス割り込み | |
| VELOVIRQ | VELCNTオーバーフローステータス | |
| VELOVIEN | VELCNTオーバーフロー割り込み有効化 | |
| HOMIRQ | HOMEイベントステータスビット | |
| HOMIEN | HOMEイベント割り込み有効化 | |
| IDXIRQ | Indexイベントステータスビット | |
| IDXIEN | Indexイベント割り込み有効化 | |
| POSxCNT | POSCNT | ポジションカウンタ |
| POSxHLD | POSHLD | ポジションカウンタホールドレジスタ |
| VELxCNT | VELCNT | ベロシティカウンタ |
| VELxHLD | VELHLD | ベロシティカウンタホールドレジスタ |
| INTxTMR | INTTMR | インターバルタイマレジスタ |
| INTxHLD | INTHLD | インターバルタイマホールドレジスタ |
| INDXxCNT | INDXCNT | Indexカウンタレジスタ |
| INDXxHLD | NDXHLD | Indexカウンタホールドレジスタ |
| QEIxIC | QEIIC | 初期化/キャプチャレジスタ |
| QEIxGEC | QEIGEC | 比較レジスタ(以上) |
| QEIxLEC | QEILEC | 比較レジスタ(以下) |
動作確認
今回は、A/B/Z相を出力するエンコーダが手元に無かったため、内部でダミー信号を生成し、それを観測することにしました。以下のブロック図のように構成しています。
SCCP1の割り込み間隔は、ポテンショメータで調整します。その割り込み内で、A/B/Zパルスを生成します。なお、A/B相は1回転あたり400パルスと仮定しています。

ソースコード
インクルードファイル
コンフィグレーションファイル、クロック設定ファイルは以下のファイルをインクルードしてください。
■コンフィグレーションファイル (config.h)
■クロック設定ソースファイル(Clock_Driver.c)
■クロックヘッダーファイル(clock_driver.h)
ソースコード全体
QEI1GEC レジスタが書き込んだ後にゼロクリアされてしまう現象が発生しましたので、メインルーチン内で常時書き込みを行うようにしています。
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "peripheral_init.h"
#include "BoardSupportPackage.h"
#include <dsp.h>
#define SPEED_MAX 100000
#define ENC_PPR 1600
int32_t s4g_PotData;
int32_t s4g_MV_Speed;
int32_t s4g_Direction;
int32_t s4g_Pos_AB;
volatile int32_t s4g_Pos_Z;
const uint32_t s4g_Lat[4] = {0x00,0x0400,0x440,0x040};
int main()
{
/*-----------------------------------------------------------------------*/
/*初期化*/
/*-----------------------------------------------------------------------*/
vdg_Clock_Set_Register();
s4g_MV_Speed = 10000;
s4g_Direction = 1;
/*-----------------------------------------------------------------------*/
/* ピン設定 */
/*-----------------------------------------------------------------------*/
ANSELAbits.ANSELA7 = 1u;
ANSELB = 0x0000u;
TRISBbits.TRISB10 = 0u; //A相出力
TRISBbits.TRISB6 = 0u; //B相出力
TRISCbits.TRISC8 = 0u; //Z相出力
RPOR9bits.RP40R = 45u; //QEI Cpmp
RPINR7bits.QEIA1R = 27u; //A相入力
RPINR7bits.QEIB1R = 23u; //B相入力
RPINR7bits.QEIINDX1R = 41u; //Z相入力
/*-----------------------------------------------------------------------*/
/* ADC設定 */
/*-----------------------------------------------------------------------*/
AD1CH0CONbits.TRG1SRC = 1u;
AD1CH0CONbits.TRG2SRC = 2u; //次回以降のAD変換は即時再トリガー
AD1CH0CONbits.PINSEL = 6u; //AD1AN6入力
AD1CH0CONbits.MODE = ADC_MODE_INTEGRATION; //積算モード
AD1CH0CNT = 2048u;
AD1CON = 0x00000000U;
AD1CONbits.ON = 1u;
while(AD1CONbits.ADRDY == 0u)
{
;
}
AD1SWTRGbits.CH0TRG = 1u;
/*-----------------------------------------------------------------------*/
/* SCCP1設定 */
/*-----------------------------------------------------------------------*/
CCP1CON1 = 0x00000000u;
CCP1CON1bits.T32 = 1u; //32bitモード
CCP1CON2 = 0x00000000u;
CCP1CON3 = 0x00000000u;
CCP1PR = s4g_MV_Speed;
IPC6bits.CCT1IP = 4u;
IFS1bits.CCT1IF = 0u;
IEC1bits.CCT1IE = 1u;
CCP1CON1bits.ON = 1u;
/*-----------------------------------------------------------------------*/
/* QEI設定 */
/*-----------------------------------------------------------------------*/
QEI1CON = 0x00000000u;
QEI1CONbits.PIMOD = 1u; //Index入力でクリア
QEI1IOC = 0x00000000u;
QEI1IOCbits.OUTFNC = 3u; //POSCNT > QEI1GECとPOSCNT < QEI1LECでHigh
QEI1IOCbits.QCAPEN = 1u;
QEI1LEC = 50u;
QEI1GEC = 1500u;
QEI1CONbits.ON = 1u;
INTCON1bits.GIE = 1u;
/*-----------------------------------------------------------------------*/
/* メインルーチン */
/*-----------------------------------------------------------------------*/
while(1)
{
QEI1GEC = 1500u; //QEI1GECがなぜかゼロクリアされるので、常に書込み
/***** ポテンションの値取得 ******/
if (AD1STATbits.CH0RDY == 1u)
{
s4g_PotData = AD1CH0DATA;
s4g_MV_Speed = 100000000/(0.04857 * (float)s4g_PotData -482) ;
AD1SWTRGbits.CH0TRG = 1u;
}
}
}
void __attribute__((interrupt, no_auto_psv)) _CCT1Interrupt(void)
{
/*-----------------------------------------------------------------------*/
/* ABパルス出力 */
/*-----------------------------------------------------------------------*/
s4g_Pos_AB += s4g_Direction;
s4g_Pos_AB &= 3u;
LATB = s4g_Lat[s4g_Pos_AB];
/*-----------------------------------------------------------------------*/
/* Zパルス出力 */
/*-----------------------------------------------------------------------*/
s4g_Pos_Z += s4g_Direction;
if ((s4g_Pos_Z >= ENC_PPR ))
{
s4g_Pos_Z = 0u;
}
else if ((s4g_Pos_Z < 0 ))
{
s4g_Pos_Z = ENC_PPR - 1u;
}
if ((s4g_Pos_Z <= 4u ) && (s4g_Pos_Z >= 0))
{
LATCbits.LATC8 = 1u;
}
else
{
LATCbits.LATC8 = 0u;
}
/*-----------------------------------------------------------------------*/
/* 割り込み間隔指定 */
/*-----------------------------------------------------------------------*/
CCP1PR = s4g_MV_Speed;
IFS1bits.CCT1IF = 0u;
}
結果
下記のように、Z相出力の前100パルスと後50パルスで、QEIのコンペア出力がHighになっていることが確認できました。

記事についての注意点
本記事は慎重に内容を検討し正確さに努めておりますが、内容に誤りがあったとしても、この記事を参考にして生じた損害等については一切の責任を負いません。

コメント