PIC16F1827はADC機能を持っておりジョイスティックの出力電圧を取得することができます。MPLAB X IDEのプラグインであるMCCでADCとDACを実装してジョイスティックの動作確認を行いました。
ジョイスティックはGrove-Thumb Joystick(Seeed Studio)を使用しています。PIC16F1827で動作確認したことについてリンクをまとめています。
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
Grove-Thumb Joystickを使用する
Grove-Thumb JoystickはSeeed Studioが製作しているGroveモジュールです。X方向とY方向にスティックを回すと操作位置に応じた電圧を出力します。詳細は下記のリンクを参考にしてください。
Grove – Thumb Joystick | Seeed Studio Wiki
PIC16F1827のアナログ入力ピンにGrove-Thumb Joystickの出力電圧を入力すると操作に応じた電圧を取得することができます。本記事では取得した電圧値をLEDの点灯パターンの切り替えとDAC出力の切り替えに使用します。
以下ではGrove-Thumb Joystickをジョイスティックとします。
ジョイスティックのWikiページに標準値が記載されていますが、電源電圧の条件とモジュール動作特性によって中央値と最大/最小値は変化するためモジュールの特性を確認します。

PICKITのデバッグモードでジョイスティックを±X方向と±Y方向に操作したときのアナログ値を確認します。

X軸の結果と同様にしてY軸の値も確認しました。X軸とY軸の結果をまとめると以下の通りになりました。
軸 | 最小値(-側) | 中央値(操作なし) | 最大値(+側) |
---|---|---|---|
X軸 | 293 | 604 | 916 |
Y軸 | 303 | 602 | 892 |
X軸の方がY軸よりもレンジが少し広いことが分かりました。本記事で使用するジョイスティックの基準値はX軸:604、Y軸:602とします。これらの値は基準値及び最小値/最大値を使ってDAC及びLEDの点灯パターンの切り替えに使用します。
MCCで各種機能を実装する
PIC16F1827の各種設定はMCCを使っています。MCCの使い方については下記記事にまとめています。
MPLAB Code Configurator(MCC)の追加と使い方
今回はADC、CMP1、DAC、FVR、TMR0の設定を行っています。
System Moduleの設定
最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

内部クロックを使用するためINTOSCを選択しています。クロック周波数は任意でもよいですが4MHz_HFを選択しています。他にも設定項目はありますがクロックの設定のみとしています。
ADCを設定する
ADCをDevice Resources内のPeripherals欄のADCを選択して追加します。

ADCを選択しEnable ADCにチェックを入れるとADCが使用できます。ADC ClockのClock SourceはFOSC/16を選択しています。分周が少なく高速なClock Sourceを選択すると推奨値を外れることがあるのでNotifications[MCC]で確認して最適な値を選択します。推奨の値を外れるとHINTに内容が通知されます。
Result Alignmentをright(右揃え)にします。左揃えでも問題ありませんが右揃えの方が扱いやすいため右揃えにしています。
Positive Referenceは外部の電源を使用する場合はexternalを選択しますが、今回は内部で生成したFVRの電源を使用するためFVRを選択し、Negative ReferenceにVSSを選択しています。
ADCが変換完了したタイミングでデータを取得するためにEnable ADC Interruptを有効にしています。
CMP1を設定する
CMP1をDevice Resources内のPeripherals欄のCMP1を選択して追加します。

CMP1を選択しEnable Comparatorにチェックを入れるとCMP1が使用できます。Positive Inputは基準電圧ですが、DACで基準電圧が可変できるようにするためDACを選択します。Negative Inputに入力ピンを選択します。
Enable Synchronous ModeはTimer1と同期をとるかの選択ですが、Timer1を使用しないのでチェックする必要はありません。
Enable Comparator Hysteresisは閾値付近で頻繁に動作しないようにヒステリシス特性を持たせるかの選択です。ヒステリシス特性はデータシートによると65mVほどです。
Enable output Pinはコンパレータの結果を出力する選択です。コンパレータの結果を出力する場合はチェックを入れます。
DACの設定をする
DACはDevice Resources内のPeripherals欄のDACを選択しDACをクリックして追加します。

DACを選択しEnable DACにチェックを入れるとDACが使用できます。DACはPositive Referenceで選択した電圧を32段階の5ビットで制御して出力する機能です。
Positive ReferenceはFVRで生成した電源を使用するためFVR_buf2を選択しています。Negative ReferenceはVSSを選択します。
Software SettingsはVddに対して出力したい電圧(Required ref)に値を入力することでDAC出力値をセットするサポート機能です。DACの電圧の初期値になります。例ではVddにFVRの電源電圧の4.096Vを指定しています。
DACの値はDACCON1レジスタを変更するかMCC関数のDAC_SetOutput()関数で指定した値で変更できます。
FVRを設定する
FVRはDevice Resources内のPeripherals欄のFVRを選択しFVRをクリックして追加します。

FVRを選択しEnable FVRにチェックを入れるとFVRが使用できます。
ADCの基準電圧はFVR_buffer1に割り当てられています。ADCの基準電圧をゲインの倍率で設定します。1xを選択すると1.024V、2xを選択すると2.048V、4xを選択すると4.096Vになります。
DACの基準電圧はFVR_buffer2に割り当てられています。FVRの基準電圧をゲインの倍率で設定します。1xを選択すると1.024V、2xを選択すると2.048V、4xを選択すると4.096Vになります。
TMR0を設定する
TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。
Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0A(10)を指定しているため10ms毎にコールバック関数が呼び出されます。
void mainTimer(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定
上記例ではmainTimer()が10ms毎にコールされます。
PR:受講者20万人超!スマホで学べる人気のオンライン資格講座【STUDYing(スタディング)】
MCCが生成した関数の使用例
ADC_SetInterruptHandler(AdcItr); //AD変換完了割り込み時にコールする関数を指定
ADC_SelectChannel(channel_AN10); //AN10を指定
ADC_StartConversion(); //変換開始
/* AD変換割り込み処理 */
void AdcItr(void){
an10Value = ADC_GetConversionResult(); //変換結果を変数に格納
}
ADC_SetInterruptHandler()関数はAD変換割り込みでコールバックする関数を指定します。例では自作の関数のAdcIrt()を指定しています。
AD変換は登録したチャンネル番号に対して処理されるためAD変換開始の前に対象のチャンネル番号を指定する必要があります。例ではAN10をAD変換する例を示しています。チャンネル番号はadc.h内にadc_channel_tの型としてenumで登録されています。
ADC_StartConversion()関数を使用するとAD変換が開始します。AD変換が完了するとADC割り込みが発生しAdcItr()関数がコールバックされます。AdcItr()ではADC_GetConversionResult()関数でAD変換の結果を格納しています。
DAC_SetOutput(dac_hosei); //0~0x1FでDAC出力を指定
DACの出力電圧の変更はDAC_SetOutput()関数を使用します。DACは5ビット対応なので0~31(0x1F)の範囲で値を指定します。
ピンの設定
MCCのピン設定は以下の通りです。

ADCピン、CMP1ピン、DACピンはAnalogにチェックを入れます。そのほかのピンはOutputにします。DACは内部でコンパレータの基準電圧と使用し、DACOUTピンからも電圧を出力します。
動作確認

電源を入れるとジョイスティックから出力電圧の取得を開始します。X軸方向はLED1の点灯/消灯のパターンを切り替えます。Y軸方向はDACの値を変更してコンパレータの基準を切り替えます。
VR1で分圧した電圧をコンパレータの-側に入力した時、基準電圧(DACが生成)以下であればLED2が点灯します。逆にVR1の電圧が基準電圧を超えるとLED2が消灯します。
ジョイスティックを+X方向に操作するとLED1の点灯/消灯の間隔が長くなり、-X方向に操作すると点灯/消灯の間隔が短くなります。
ジョイスティックを+Y方向に操作するとDACの電圧が高くなるためコンパレータの基準電圧が上昇します。-Y方向に操作するとコンパレータの基準電圧が低下します。
DACの基準電圧が更新されていることを確認するため、ジョイスティックを+Y側に最大で操作した場合、中央の場合、-Y側に最大で操作した場合においてLED2が消灯するVR1の電圧を測定します。
+Y軸に最大操作した場合は4.02V、中央の場合は2.04V、-Y軸に最大操作した場合は0.2Vになりました。
DACの分解能は1デジット当たり0.128Vなのでレジスタに換算すると+Y軸に最大操作した場合は0x1E~0x1F、中央の場合は0x10、-Y軸に最大操作した場合は0x00~0x02になるためDACによる基準電圧の更新がうまく動作していることが確認できました。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include "mcc_generated_files/mcc.h"
#define TIME_OFF -1
#define TIME_UP 0
#define TIME_ADC_WAIT 10
#define TIME_ADC_ERR 50
#define JOY_BASE 600
#define DAC_BASE 0x10
#define DAC_HOSEI 20
typedef enum{
AD_AN10 = 0,
AD_AN10_WAIT,
AD_AN11,
AD_AN11_WAIT
}ADC_MODE_NO;
uint16_t an10Value;
uint16_t an11Value;
int16_t timAdc;
int16_t timAdcErr;
int16_t timled;
int16_t dac_val;
int16_t dac_hosei;
ADC_MODE_NO adcmode;
void mainTimer(void);
void AdcItr(void);
void mainApp(void);
/* Main application */
void main(void)
{
SYSTEM_Initialize();
TMR0_SetInterruptHandler(mainTimer);
ADC_SetInterruptHandler(AdcItr);
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
timAdcErr = TIME_OFF;
while (1)
{
mainApp();
}
}
/* タイマ管理 */
void mainTimer(void){
if( timAdc > TIME_UP ){
--timAdc;
}
if( timAdcErr > TIME_UP ){
--timAdcErr;
}
if( timled > TIME_UP ){
--timled;
}
}
/* メイン処理 */
void mainApp(void){
if( timAdc == TIME_UP){
timAdc = TIME_ADC_WAIT;
switch(adcmode){
case AD_AN10:
ADC_SelectChannel(channel_AN10);
ADC_StartConversion();
adcmode = AD_AN10_WAIT;
timAdcErr = TIME_ADC_ERR;
break;
case AD_AN10_WAIT:
//AD変換待ち
break;
case AD_AN11:
ADC_SelectChannel(channel_AN11);
ADC_StartConversion();
adcmode = AD_AN11_WAIT;
timAdcErr = TIME_ADC_ERR;
break;
case AD_AN11_WAIT:
//AD変換待ち
break;
}
dac_val= an10Value - JOY_BASE;
dac_hosei = dac_val / DAC_HOSEI;
if( dac_hosei >= 0x0F){
dac_hosei = 0x0F;
}
dac_hosei = dac_hosei + DAC_BASE;
DAC_SetOutput(dac_hosei);
}
if( timled == TIME_UP){
timled = an11Value/10;
IO_RB3_Toggle();
}
if( timAdcErr == TIME_UP){
timAdcErr = TIME_OFF;
adcmode = AD_AN10;
}
}
/* ADCの割り込み */
void AdcItr(void){
switch(adcmode){
case AD_AN10_WAIT:
adcmode = AD_AN11;
an10Value = ADC_GetConversionResult();
break;
case AD_AN11_WAIT:
adcmode = AD_AN10;
an11Value = ADC_GetConversionResult();
break;
}
timAdcErr = TIME_OFF;
}
/* End of File */
本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていることが前提となります。MCCをインストールする方法は下記記事を参考にしてください。
MPLAB Code Configurator(MCC)の追加と使い方
関連リンク
PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
最後まで、読んでいただきありがとうございました。