こんにちは、ENGかぴです。
PIC18F14K50 には 10bit ADC が搭載されており、土壌センサーなどのアナログ電圧を取得できます。任意のタイミングで AD 変換を行うには、MCC の ADC と Timer を組み合わせてソフトウェアタイマを構成する必要があります。
本記事では、MCC を使って ADC と Timer を設定し、一定周期で 土壌センサーからアナログ電圧を取得し土壌の状態を表示して動作確認を行います。
PIC18F14K50 を実装した基板は AE-USB-I02.0-TYPEC(秋月電子)を使用しています。以下では PIC モジュールとします。
MCC を使って動作確認したことを下記にまとめています。
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
MCC で各種機能を実装する
PIC モジュールは 12MHz のクリスタル発振子や USB に関する部品が実装されています。PIC モジュールは PIC18F14K50 が使用されているため MCC を使ってボードの設定を行うことができます。MCC の使い方については下記記事にまとめています。
MPLAB Code Configurator(MCC)の追加と使い方
参考記事は MCC Classic(現在サポート外で Melody が推奨される)を使用していますが、使い方はほぼ同じです。
本記事では土壌センサーの値の取得に使用する ADC、測定と動作中を示す LED の点灯/消灯のタイミングを管理するタイマライブラリ(Timer:TMR2)の設定を行います。
Configuration Bits の設定

MPLAB X IDE を起動し MCC のアイコンをクリックして MCC を有効にします。初期の画面のインターフェースでは設定画面が右隅に表示されて見にくいのでスライドさせて大きく表示するようにしています。
外部クリスタル発振子を使用する場合は Configuration Bits の CONFIG1H レジスタを設定します。Osillator Selection Bits から「HS oscillator」を選択すると Clock Control でシステムクロックが入力できるようになります。
別記事で本記事の内容を追加して使用するため USB に必要なシステムクロックに合わせて 4X PLL Enable bit で「Oscillator multiplied by 4」を選択してシステムクロックを 4 逓倍します。 USB を実装しない場合は PLL を使用しなくても問題ありません。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
Clock Control の設定

Clock Control を選択し、Clock Source から FOSC を選択します。Configuration Bits で HS を選択している場合は External Clock に外部クリスタルの発信周波数が入力できるので 「12,000,000 (12MHz)」を入力します。
左上隅の「Generate」をクリックすると MCC が設定した項目の初期化を含んだソースコードを自動で生成します。
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
ADC を設定する
Device ResourcesのDrivers から ADC を選択して追加します。 Drivers に追加された ADC を選択して設定します。

Hardware SettingsのResult Alignment を right(右揃え)にします。右揃えにすると AD 変換値が LSB からセットされます。
Positive Reference は外部の電源を使用する場合は external を選択しますが、今回は電源電圧にするため VDD を選択し、Negative Reference に VSS を選択しています。
Positive Input Channel は使用するアナログチャンネルを選択します。PinGrid View から使用する ANx を選択しても設定できます。
Conversion Clock Select は FOSC に対する分周比を選択します。システムクロックに対して分周比が低いと AD 変換の精度に影響が出ることがあるため適切な分周比になるように設定します。
MCC が生成した関数の使用例です。
ADC_SelectChannel(posChannel_AN9);
ADC_StartConversion();
if( ADC_IsConversionDone() ){
adc_an9 = ADC_GetConversionResult();
}
AD 変換は登録したチャンネル番号に対して処理されるため、ADC_SelectChannel() で AD変換開始の前に対象のチャンネル番号を指定する必要があります。例は AN9 をAD 変換する例を示しています。チャンネル番号は adc.h 内に adc_channel_t の型として enum で登録されています。
ADC_StartConversion() を使用すると AD 変換を開始します。AD 変換が完了すると割り込みを有効にしている場合は割り込みフラグがセットされます。割り込みを使用しない場合は、AD 変換が完了しているかを確認して変換値を取得します。
ADC_IsConversionDone() で AD 変換が完了していることを確認して AD 変換値を取得します。
PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
Timer ドライバーで TMR2 を設定する
Device ResourcesのDrivers から Timer を選択して追加します。

Dependency Selector 欄が表示されるので Timer PLIB Selector で使用するタイマを選択します。今回は TMR2 を使用しました。Requested Timer Period はタイマをオーバーフローさせるまでの時間を入力します。例では 1ms 毎にオーバーフローするように指定しています。
TMR2 は FOSC を使用して生成されるため PLL を使用するとFOSC が 4 逓倍されて動作します。MCCの表示は PLL が適用される前の FOSC の値を参照しているため 1ms と表示されていても 250us でオーバーフローするため注意が必要です。

Configure Prescaler を ON にしてプリスケーラ(Prescaler)を選択します。TMR2 が 1ms でオーバーフローできるように Requested Timer Period の範囲を確認してプリスケーラを選択します。
TMR2_PeriodMatchCallbackRegister(int_tmr2);
void int_tmr2(void){
++cnt10ms;
}
TMR2_Tasks();
MCC が生成した関数の使用例です。
TMR2_PeriodMatchCallbackRegister() は TMR2 がオーバーフローしたときにコールする関数を指定します。引数にコールする関数を指定します。例は int_tmr2() をコールして cnt10ms を更新しています。
TMR2_Tasks() は割り込みを使用しない場合に TMR2 がオーバーフローしたかをメインループで確認するために使用します。
void mainTimer(void){
if( cnt10ms >= 40){ //規定回数オーバーフローしたか
cnt10ms -= 40;
if( TimerLed > TIME_UP ){
--TimerLed; //タイマを更新
}
}
}
TMR2 がオーバーフローしたときに更新するカウンタを使用して、ソフトウェアタイマを生成する方法を説明します。
PLL を使用すると FOSC が 4 逓倍されるため TMR2 がオーバーフローすると 250us になるため 4 倍の値と比較してタイミングを生成します。
例では 40 回なので 250us×40 = 10ms になります。10ms 毎に TimerLed などのソフトウェアタイマを更新します。
PR:キャリア形成でお悩みのあなたへ!初回無料相談で相談できる[coachee]
Pin の設定

Pin Grid View の設定を行います。システムクロックに HS を選択すると OSC が表示されます。
ADC を追加すると Pin Grid View の Module に ADC が追加されます。AN9 ( RC7 ) をアナログ入力に設定しますが、残りのピンは GPIO の DOとして使用するため output を選択します。
GPIO が DI の状態でプルアップやプルダウンがない場合、信号が浮遊して ON、OFF を繰り返す可能性があるため未使用ピンは DO ピンに設定することをお勧めします。
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
土壌センサーの状態判定
if( adc_an9 <= 560){ //WET
IO_RC1_SetHigh();
IO_RC2_SetLow();
}
else if( 560< adc_an9 && adc_an9 <= 660){ //MOIST
IO_RC1_SetLow();
IO_RC2_SetHigh();
}
else{
IO_RC1_SetLow();
IO_RC2_SetLow();
}
土壌センサーは LEDイ ンジケーターによって土壌状態が分かるようになっていますが、取得した AD変換値を使って PIC モジュール で 土壌状態を LED で表示します。
Arduino UNO R4 WiFiで土壌センサーのデータを取得する
土壌センサーは水分が多ければ値が低くなりますが、参考記事の結果を目安にすると MOIST は約 560~660、WET は 560 未満になるので、それらの判定値として使用します。
AD 変換値が MOIST の範囲であれば RC1 を HIGH にして LED3(青)を点灯させます。WET の範囲であれば RC2 を HIGHにして LED2(赤)を点灯させます。
動作確認

土壌センサーの出力(OUT)を J1-7(RC7:AN9)に接続します。LED1 は動作していることを確認するためのもので 500ms 毎に点灯/消灯を繰り返します。
土壌センサーから取得した電圧を AD 変換し、MOIST の範囲に入ると LED3 が点灯、WET の範囲に入ると LED2 が点灯します。DRY の状態は LED2、LED3 は消灯します。
USB を接続し PIC モジュールに給電すると LED1 が動作開始します。PLL を使用すると TMR2 など FOSC からタイミングを生成するものに関しては MCC の表示と不一致になるためソフトウェアタイマが想定通りになっていることを確認しました。

この波形は LED1 のGNDから J1-0 間のものです。カーソル間の時間を測定すると 0.5s になっているため、PLL で 4 逓倍になった FOSC を考慮に入れたソフトウェアタイマが生成できていることが確認できました。
次に土壌センサーのプローブにスポンジを当てた状態で徐々に水分を与えたときの動作を確認しました。

スポンジを徐々に濡らしていくと土壌センサーのインジケーターが変化したタイミングくらいで LED3 が点灯しました。さらに濡らして水に浸すと LED2 が点灯しました。
以上から TMR2 の設定によるソフトウェアタイマの生成と AD 変換がうまくできていくことが確認できました。
PR:外資系・IT業界などハイクラスの転職に強い【AXIS Agent】
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
main.c のソースコード:
#include "mcc_generated_files/system/system.h"
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define TIME_BASE_MAX 40 //ベースタイマカウント値(PLLで4倍になっているのでカウント数を4倍)
#define TIME_LED_MAX 50
#define TIME_ADC_WAIT 500
#define SOIL_WET_MAX 560
#define SOIL_MOIST_MAX 660
int16_t cnt10ms;
int16_t TimerLed;
int16_t TimerAdc;
uint16_t adc_an9;
void mainApp(void);
void int_tmr2(void);
void mainTimer(void);
int main(void)
{
SYSTEM_Initialize();
TMR2_PeriodMatchCallbackRegister(int_tmr2);
//INTERRUPT_GlobalInterruptEnable();
//INTERRUPT_PeripheralInterruptEnable();
__delay_ms(2000);//PLLでメインクロックが4倍になっているので×4の値(500ms)
adc_an9 = ADC_GetConversion(posChannel_AN9);
while(1)
{
mainApp();
mainTimer();
TMR2_Tasks();
}
}
void int_tmr2(void){
++cnt10ms;
}
void mainTimer(void){
if( cnt10ms >= TIME_BASE_MAX){ //規定回数オーバーフローしたか
cnt10ms -= TIME_BASE_MAX;
if( TimerLed > TIME_UP ){
--TimerLed; //タイマを更新
}
if( TimerAdc > TIME_UP ){
--TimerAdc;
}
}
}
void mainApp(void){
if( TimerLed == TIME_UP ){
TimerLed = TIME_LED_MAX;
IO_RC0_Toggle();
}
if( TimerAdc == TIME_UP ){
TimerAdc = TIME_ADC_WAIT;
ADC_SelectChannel(posChannel_AN9);
ADC_StartConversion();
}
if( ADC_IsConversionDone() ){
adc_an9 = ADC_GetConversionResult();
if( adc_an9 <= SOIL_WET_MAX ){ //WET
IO_RC1_SetHigh();
IO_RC2_SetLow();
}
else if( SOIL_WET_MAX < adc_an9 && adc_an9 <= SOIL_MOIST_MAX ){ //MOIST
IO_RC1_SetLow();
IO_RC2_SetHigh();
}
else{
IO_RC1_SetLow();
IO_RC2_SetLow();
}
}
}
本ソースコードは MPLAB X IDE に MCC のプラグインをインストールしていることが前提となります。MCC をインストールする方法は下記記事を参考にしてください。
MPLAB Code Configurator(MCC)の追加と使い方
関連リンク
PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
最後まで、読んでいただきありがとうございました。
