概要
8ビットPICや16ビットPICには、実行中の変数などをリアルタイムで確認するための専用ハードウェア機能はありません。その代わりに、MPLAB X IDEには「DataVisualiser」と「X2CScope」というアドオン機能が用意されています。両機能共に、基本的にはUARTを使用してPCと接続し、MPLAB X IDE上で値のモニタリングや変更を行います。

「X2CScope」については、「MICROCHIP UNIVERSITY」の中の「モーター制御」セクションに、「モータ制御の開発やデバッグ作業を簡略化するソフトウェアツール」として、概要からインストール方法、使用方法まで詳細に記載されていますので、参考にしてください。
Microchip University (Japanese)

導入
「今回は、MCCを使わずにベアメタルでの構築手順を紹介します。また、ベースボードとしてdsPIC33CH Curiosityボードを使用し、動作を確認します。」
①MPLAB X IDE、XCコンパイラを最新にする。
②Githubの以下のサイトからライブラリ(X2Cscope_libraries_v0.6.zip ※執筆時点)をダウンロードし適当なフォルダに展開する。
Releases · X2Cscope/X2Cscope_library_make (github.com)

③ベースのプロジェクトに「X2CScope.c」「X2CScope.h」「X2CScopeComm.c」「X2CScopeComm.h」と対象のCPUのライブラリファイルをコピーする。

④プロジェクトのヘッダーファイル、ソースファイル、ライブラリにそれぞれインポートする。

⑤「XCScopeComm.cを開き、以下の4つの関数の中身をCPUとボードに合わせて書き換えます。今回は「Uart1」を使用し、「dsPIC33C系」のため、関数の中身は以下の通りとなります。」
| 関数 | 役割 | dsPIC33C系の場合 | dsPIC33E系の場合 |
| sendSerial() | シリアル送信レジスタに1文字書込む | U1TXREG = data; | U1TXREG = data; |
| receiveSerial() | シリアル受信レジスタから1文字読込む | return U1RXREG; | return U1RXREG; |
| isReceiveDataAvailable() | シリアル受信データが有るかを取得する | return (U1STAHbits.URXBE == 0); | return (U1STAbits.URXDA == 1); |
| isSendReady() | シリアル送信バッファがフルかを取得する | return (U1STAHbits.UTXBF == 0); | return (U1STAbits.UTXBF == 0); |
void sendSerial(uint8_t data)
{
U1TXREG = data;
}
uint8_t receiveSerial()
{
return U1RXREG;
}
uint8_t isReceiveDataAvailable()
{
return (U1STAHbits.URXBE == 0);
}
uint8_t isSendReady()
{
return (U1STAHbits.UTXBF == 0);
}
⑥MPLAB XでX2CScopeプラグインをインストールします。
1)Tools > Plugins

2)Available Pluginsのタブの中から X2C-Scopeにチェックを入れInstalボタンを押下します。

⑦「プロジェクトに以下の3つの関数を追加します。X2CScope_Update()はサンプリング周期を指定するため、基本的にはタイマー割り込みを使用します。今回は100μ秒を設定していますが、CPU負荷やUARTのボーレートなどの条件によって、より高速なサンプリングも可能です。」
| 関数名 | 役割 | 挿入場所 |
| X2CScope_Init() | 初期化関数 | メインループ前 |
| X2CScope_Communicate() | データの送信 | メインループ |
| X2CScope_Update() | データのサンプリング | 一定周期割り込みルーチン内 |
⑧以下の例の様にコードを記載しコンパイル後実行します。
/*----------------------------------------------------------------------------*/
/*【INC】インクルードファイル*/
/*----------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include <libpic30.h>
#include <dsp.h>
/*----------------------------------------------------------------------------*/
/*変数定義*/
/*----------------------------------------------------------------------------*/
struct
{
unsigned int u2_Sum;
unsigned int u2_Head;
unsigned int u2_Ave;
unsigned int u2_Buffer[8];
}Pot;
#define TIMER1_USER_INTERVAL 10000 /* 1kHz */
#define TIMER1_USER_PR1 (_u2)( FP / (8 * TIMER1_USER_INTERVAL))
#define UART1_BAUDRATE 460800
/*----------------------------------------------------------------------------*/
/* Main関数 */
/*----------------------------------------------------------------------------*/
int main(int argc, char** argv)
{
unsigned long u4l_Temp;
unsigned int u2_SamplingTime;
/*----------------------------------------------------------------------------*/
/* クロック初期化*/
/*----------------------------------------------------------------------------*/
vds_Main_Init_Clock_Register(); /* クロック初期化 */
/*----------------------------------------------------------------------------*/
/* GPIO初期化*/
/*----------------------------------------------------------------------------*/
TRISEbits.TRISE0 = 0u; /* LED1 */
ANSELAbits.ANSELA0 = 1u; /* RA0ピンはアナログピン(ポテンション入力)*/
RPINR18bits.U1RXR = 58u; /* U1RX入力ピン選択ビット */
RPOR13bits.RP59R = 1u; /* RP59ピン出力機能選択ビット */
/*----------------------------------------------------------------------------*/
/* UART初期化*/
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/* UART1レジスタ */
/*----------------------------------------------------------------------------*/
U1MODE = 0x0000u;
U1MODEbits.BRGH = 1u; /* 高 baudレート選択ビット */
U1MODEbits.UTXEN = 1u; /* 送信イネーブルビット */
U1MODEbits.URXEN = 1u; /* 受信イネーブルビット */
U1MODEbits.MOD = 0u; /* UARTモードビット */
U1MODEH = 0x0000u;
U1STA = 0x0000u;
U1STAH = 0x0000u;
/*----------------------------------------------------------------------------*/
/* UARTxボーレートレジスタ */
/*----------------------------------------------------------------------------*/
u4l_Temp = (_u4)(FP / (UART1_BAUDRATE * 4u)) - 1;
U1BRGH = (u4l_Temp >> 16u) & 0xFFFFu;
U1BRG = u4l_Temp - (_u4)U1BRGH * 65536L;
U1RXREG = 0x0000u;
U1TXREG = 0x0000u;
U1MODEbits.UARTEN = 1u; /*Uart1を有効にする */
/*----------------------------------------------------------------------------*/
/* AD初期化*/
/*----------------------------------------------------------------------------*/
ADCON1L = 0x0000u;
ADCON1H = 0x0000u;
ADCON2L = 0x0000u;
ADCON2H = 0x0000u;
ADCON3L = 0x0000u;
ADCON3H = 0x0000u;
ADCON5H = 0x0000u;
/***** AD制御レジスタの設定 ******/
ADCON1Hbits.SHRRES = 3u; /* シェアADC分解能 (3 = 12bit) */
ADCON2Lbits.SHRADCS = 2u; /* シェアADCクロックディバイダ (2 = 4Clock) */
ADCON2Hbits.SHRSAMC = 4u; /* シェアADCサンプル時間 (3 = 4TAD) */
ADCON3Lbits.CNVCHSEL = 0u; /* ソフトウェアAD入力(0 =AN0入力)(Don't Care) */
ADCON3Hbits.CLKSEL = 2u; /* ADCクロックソース (2 = AFVCODIV) */
ADCON3Hbits.CLKDIV = 2u; /* ADCクロックディバイダ(2 = 3Clock)*/
ADCON5Hbits.WARMTIME = 15u; /* ウォームアップ時間(15 = 32768Clock) */
/***** ADの有効化 ******/
ADCON1Lbits.ADON = 1u; /* ADC全体のパワーON */
ADCON5Lbits.SHRPWR = 1u; /* シェアADCのパワーON */
while(ADCON5Lbits.SHRRDY == 0u){;}
ADCON3Hbits.SHREN = 1u; /* シェアADCの有効化 */
ADCON3Lbits.CNVCHSEL = 0u;
X2CScope_Init();
/*------------------------------------------------------------------------*/
/* Timer1初期化*/
/*------------------------------------------------------------------------*/
PR1 = TIMER1_USER_PR1; /* 周期設定 */
T1CON = 0x0000u;
T1CONbits.TCKPS = 1u; /* クロックプリスケーラ選択(1 = 1:8) */
T1CONbits.TCS = 0u; /* クロック源選択(0 = FP) */
T1CONbits.TON = 1u; /* 起動 */
/*------------------------------------------------------------------------*/
/* Timer1割り込み開始*/
/*------------------------------------------------------------------------*/
IPC0bits.T1IP = 4u; /* 割り込みレベル */
IFS0bits.T1IF = 0u; /* 割り込みフラグクリア */
IEC0bits.T1IE = 1u; /* 割り込み有効化 */
/*----------------------------------------------------------------------------*/
/* メインルーチン*/
/*----------------------------------------------------------------------------*/
while(1)
{
/*----------------------------------------------------------------------------*/
/* サンプリング*/
/*----------------------------------------------------------------------------*/
u2_SamplingTime = 30u;
while (ADCON3Lbits.CNVRTCH == 1u){;}
ADCON3Lbits.SHRSAMP = 1u;
while(u2_SamplingTime > 0){u2_SamplingTime --;}
ADCON3Lbits.SHRSAMP = 0u;
/*----------------------------------------------------------------------------*/
/* AD変換*/
/*----------------------------------------------------------------------------*/
ADCON3Lbits.CNVRTCH = 1u;
u2_SamplingTime = 10;
while(u2_SamplingTime > 0){u2_SamplingTime --;}
/*----------------------------------------------------------------------------*/
/* 移動平均*/
/*----------------------------------------------------------------------------*/
if ((ADSTATL & 0x0001u) == 0x0001u)
{
Pot.u2_Sum -= Pot.u2_Buffer[Pot.u2_Head];
Pot.u2_Buffer[Pot.u2_Head] = ADCBUF0;
Pot.u2_Sum += Pot.u2_Buffer[Pot.u2_Head];
Pot.u2_Head ++;
Pot.u2_Head &= 0x07;
Pot.u2_Ave = Pot.u2_Sum >> 3u;
}
/*----------------------------------------------------------------------------*/
/* X2C*/
/*----------------------------------------------------------------------------*/
X2CScope_Communicate();
}
}
/*----------------------------------------------------------------------------*/
/* <FuncName> _T1Interrupt(void) */
/* <Function> T1割り込み */
/* <Input> 無し*/
/* <Return> 無し*/
/* <Side Effects> 無し*/
/* <PreCondition> 無し */
/* <Note> 無し */
/*----------------------------------------------------------------------------*/
void __attribute__((interrupt, no_auto_psv)) _T1Interrupt(void)
{
__builtin_btg(&LATE,0u);
X2CScope_Update();
/*------------------------------------------------------------------------*/
/* 割り込みフラグクリア*/
/*------------------------------------------------------------------------*/
IFS0bits.T1IF = 0u;
}
⑨Tools > Embedded > X2CScopeを選択します。

⑩Select Projectボタンを押下し対象プロジェクトを選択します。

⑪対象のシリアルポートやボーレートを選択し「Disconected」ボタンを押下します。
「Conected」に変われば通信が確立しています。

⑫Data Viewタブを開き「Open Scope View」を押下すると、スコープパネルが表示されます。
また「Open Watch View」を押下すると、変数値が表示されます。



オシロスコープのようなスクロール表示ではなく、サンプリング間隔×2500バッファのデータが一括で画面が更新されます。言い換えると、サンプリング間隔が1kHzであれば、2.5秒に1回の更新となります。現時点ではバッファ数は調整できないようです。
今回は紹介しませんでしたが、Pythonなどのスクリプトも実行可能です。例えば、Pythonでリアルタイムに計算した結果を特定の変数に書き込むといった使い方も可能です。


コメント