Introduction: MacでのPIC開発
Edisonチップが想定とは異なったので、他のマイコンを探した。といっても、それほど選択肢はない。
・Arduino
・AVR
・Intel Quark D2000
・PIC
Windowsマシンはないので、Macで開発できることが大前提であるが、Macの開発環境は増えてきているとはいえ、まだハードルが高い。
Arduinoは環境も整っているがマイコンの能力を考えるとやや高め。周辺機器をつけなければいけないのでそれを考えると最初からAVRでも良いように思う。が、AVRの開発環境であるAVR StudioはWindowsのみ。D2000は秋月通商で2016年4月から使いが開始されていて良さそうではあるが、まだMacの開発環境はない上、Edisonの期待ハズレを思うと選択しにくい。
PICの開発環境は古いJava6でしか動かない問題は残るが、消去法でPICしかない。
ちなみに、Raspberry piはリアルタイム処理向けではない。Beagle Boneは意外といいかもしれないが、今ひとつマイナーなままなので今回は選択しなかった。
Step 1: PICKit3
PIC24FV32KA302(28ピンSPDIP)とPickit3をブレッドボードで接続してプログラムを書き込み。ところがDeviceIDが不一致の警告が出た後に書き込みエラー。試行錯誤した結果、PICがブレッドボードにしっかり刺さっていないことにより接触不良。15番ピンのPGE3がPickit3と未接続の状態でした。
これを修正したところ正常にデバッガも利用できました。Pickit3経由でのブレークポイントはPIC24FV32KA302では5つまで。ステップ実行する場合にもブレークポイントをかけながら進めるようですので、空きがないとステップ実行できないと警告が出ます。
ちなみに、環境は次の通り。
MacOS 10.11.5, MPLAB X IDE v2.20(3.3に更新したいがインストールしてみたものの、起動するとブランクページのまま)
MPLAB CodeConfiguratorを使って、PGED3(14番ピン)とPGE3(15番ピン)をデバッグ用に指定
電源はMPLABから5V供給。PICにはVCAPに10μFタンタルコンデンサを、MCLRとGND間にはマニュアル通り50kΩの抵抗を接続。
Step 2: UART送信とシミュレート
MPLAB X IDEのシミュレータでUART2の出力を試みた。
- シミュレータの設定
- PropertyのSimulatorを選択
- Option categoriesでUart2 IO Optionsを選択
- EnableにチェックしてOutputをWindowに設定
- Code Generatorの設定
- Code Configurator ResourcesでUART2::UARTを選択
- Code ConfiguratorでEnable UARTをチェック
- Generate Codeでコード作成
- 出力プログラムを作成(サンプルはuart2.hなどのヘッダファイルに記載あり)
- シミュレータで実行すると、UART2 Output UARTウィンドウに出力された。
コードの概要は次の通り。UART2_TasksTransmit();をループで回す必要あり。
UART2_Initialize(); // MCC Generaterで生成したコードではSYSTEM_Initialize()の中で呼び出されている
char buf[10] ;
sprintf(buf,"test\n") ;
int len = strlen((char *)buf) ;
UART2_WriteBuffer ( buf, len) ;
while (1) {
UART2_TasksTransmit();
}
Step 3: UART受信とシミュレート
MCC Generateで生成したコードでUART2受信をするには、UART2_TasksReceive() ;をループで回す必要がある。これらを使うには、初期化のための
UART2_Initialize();
を呼ぶ必要があるが、MCC Generateではそのコードも自動で生成されている。
またStimulusでU2RXREGに値を設定しても下記のコードでは受信しない。おそらく、受信データはRSRを経由してU2RXREGに書き込まれる際にステータスレジスタも更新される仕組みだからと思われる。しかし、StimulusにはRSRは指定できない。
仕方ないので、送信データをループバックしてみたところデータを受信した。
U2MODE = U2MODE | 0x0040 ; // loopback on
while (1)
{
UART2_TasksReceive() ;
if( !UART2_ReceiveBufferIsEmpty() ) {
char cmd = UART2_Read();
};
} ;
Step 4: UART
秋月でFT234Xを搭載したシリアル-USB変換器(AE-FT234X)を使ってMacとシリアル接続することにしました。
AE-FT234XはTX/RXの2線の端子が出ていて、RTS/CTSは未使用となっているので、ハードウェアフロー制御は使えません。そのため、PICのMPLAB Code ConfiguratorのUARTの設定のFlow ControlをNoneとします。MPLAB Code Configurator(MPLAB X IDE v2.20)のピンアサインでRTS/CTSを使わない設定にしていも、UARTの設定ではハードウェアフロー制御を選択できてしまうので注意が必要です。
Macのターミナルソフトでデバイス名を確認すると、
/dev/tty.usbserial-DJ00LO4C
となっていましたので、
screen /dev/tty.usbserial-DJ00LO4C 9600 cs8 -parenb -cstopb ixon
として接続します。screenの終了は、CTR-a の後、k です。
なお、PICとAE-FT234Xの接続は次のとおりです。
- +5V端子:未接続(PICは別電源で3.3Vを供給)
- GND端子:端子PICのGND(Vss)ラインへ
- TXD端子:PICのU2RX(PIC24FV32KA302の5pin)へ
- RXD端子:PICのU2TX(PIC24FV32KA302の4pin)へ
Step 5: UART CTS/RTSピン
PIC24FV32KA302でUART2を使用し、CTS/RTSは無効化しこのピンを出力に指定したものの、制御できない。どうやら、UARTを使用することにすると、CTS/RTSピンはその他の用途には使えない模様。
MCCではこの競合をチェックはしてくれないので要注意。
マニュアルには、CTS/RTSを無効にすると、ラッチ制御できるとある。ラッチ制御はLATレジスタによる制御のことのようなので、マニュアル通りであるなら、入出力に利用できるはず。何かの設定誤りがあったのだろうか。
Step 6: CLKOピン
PIC24FV32KA302(28pin)のCLKOピン(10番ピン)はクロック出力用のピンであるが、RA3の入出力ポートとしても利用できる仕様である。しかし、MCCから出力されるコードでは、なぜかCLKOの出力ピンがONに設定され、どのようにすればOFFになるかわからず。仕方ないので手でmcc.cを修正した。
========================================================================
[mcc.c]
// FOSCSEL
#pragma config FNOSC = FRCPLL // Oscillator Select->Fast RC Oscillator with Postscaler and PLL Module (FRCDIV+PLL) // FOSC #pragma config POSCFREQ = HS #pragma config OSCIOFNC = OFF
========================================================================
念のため、pin_manager.ccでTRISAが適切に設定されていることも確認しておく。
RA3は3ビット目、0なら出力設定である。
========================================================================
void PIN_MANAGER_Initialize(void)
{
// 前後省略
TRISA = 0x00;
// 前後省略
}
========================================================================
Step 7: UARTの通信速度
コンフィグを修正したら今まで正常に表示されていたシリアル出力ができず、文字化けする。
シミュレータでは正常に表示されるので通信速度等の影響のようだ。MCCでコードジェネレートしたのになぜか反映されていない。が、試行錯誤しているうちに、下記のようなコードになっていて、正常に戻った。
================================================================================
[mcc.c]
// FOSCSEL
#pragma config FNOSC = FRCPLL // Oscillator Select->Fast RC Oscillator with Postscaler and PLL Module (FRCDIV+PLL)
// FOSC
#pragma config POSCFREQ = HS
#pragma config OSCIOFNC = OFF
CLKDIV = 0x3000;
[uart2.c]
// RTSMD enabled; BRGH enabled; STSEL 1; UARTEN enabled; PDSEL 8N; LPBACK disabled; WAKE disabled; USIDL disabled; RXINV disabled; ABAUD disabled; IREN disabled; UEN TX_RX;
U2MODE = 0x8808;
// Baud Rate = 9600; BRG 416;
U2BRG = 0x01A0;
================================================================================
上記の設定でbaud レートを検算してみる
・BRGH:UxMODEのbit 3。U2MODEが0x8808なので、BRGH=1(高速モード)
・UxBRG:416(=0x01A0) -> 103 (0x0067)
・FCY:クロックの1/2。FNOSC = FRCPLLなので、クロックは8MHzで、PLLで4倍されて32MHz。FCYはその半分の16MHz。
以上から、
16MHz/(4*416+1) = 16,000,000/1665 = 9609.1 (0.1%)
となって、約9600 bps。
ユーザズマニュアルにはFCYはPLL無効などという記載もあるが、FCYは16MHzで正解のようだ。
Step 8: MCC I2C Master + ADXL345
MCCで出力したI2C割り込み利用のI2C1(Master)のコードでは加速度センサADXL345からデータを読めない。MCCのバージョンは不明だが、MPLAB X IDEは v2.20。ネットを見てもぴったりの回答を見つけられないが、データの読み出しで使われる複合パターンにおいてはWriteの後にはSTOPコンディションを送らないとの説明を見つけた。ADXL345のシーケンス(図)もこのパターンであるが、MCCで出力されたコードのI2C1_MasterWriteと割り込みプログラムを見ると書き込みを終了するとSTOPコンディションが必ず送信される。MCCのコードを読んでみた結果、複合パターンにおいては、次のようなプログラムを用意しなければならないことがわかった。
void I2C1_MasterReadWithRegisterAddress(
uint8_t *pRvData, // 受信バッファへのポインタ
uint8_t rvLength, // 受信バッファのサイズ(byte)
uint16_t devAddress, // デバイスのアドレス。ADXL345の場合は、0x1D(ALT ADDRESSピンがVDD)または0x53(ALT ADDRESSピンがGND)
uint8_t regAddress, // 読み出すレジスタのアドレス
I2C1_MESSAGE_STATUS *pstatus // ステータス
) {
static I2C1_TRANSACTION_REQUEST_BLOCK trBlock[2];
static uint8_t wbuf[1] ;
wbuf[0] = regAddress ;
if (I2C1_MasterQueueIsFull() != true) {
I2C1_MasterWriteTRBBuild(&trBlock[0], wbuf, 1, devAddress);
I2C1_MasterReadTRBBuild(&trBlock[1], pRvData, rvLength, devAddress);
I2C1_MasterTRBInsert(2, trBlock, pstatus);
}
else {
*pstatus = I2C1_MESSAGE_FAIL;
};
}
MCCから出力されたi2c1.cのI2C1_MasterReadとI2C1_MasterWriteを拡張したような関数である。
これらの関数では実際のI2Cの送受が割り込みによって処理され、ポインタ渡しした*pstatusの値がI2C1_MESSAGE_COMPLETEになると受信バッファに取得した値が格納されている。
また、上記関数で利用しているI2C1_MasterWriteの第2パラメータ(送信データのバッファへのポインタ)のポイントするバッファの内容へのアクセスは割り込み処理の中で適宜読み出されるため、通信が完了するまでその内容が変化しないように注意が必要である。上記では簡易的にstatic宣言することで対応した。
上記関数のポイントは、TRB(trBlock)を2つの配列で用意し、送信用と受信用としている点である。このような使い方をすると割り込みプログラム中でSTOPコンディションを送信せず、送信(レジスタアドレスの指定)と受信を連続して処理するようになっている。
ちなみに、ADXL345とは高速モード400kHzで通信できた。i2c1.cの初期設定ルーチンでは
I2C1BRG = 0x0025; // PIC24FV32KA304 FCY16MHzで404kHz
I2C1CON = 0x8000; // DISSLWスルーレート有効(bit9=0)
としている。
ADXL345からデータが読めず解決するまで3日かかった。MCCはまだ開発途上のようだ。最新版では解消されているのだろうか。
Step 9: PIC24FV32KA302+ADXL345
ADXL345+PIC24FV32KA302+ブラシレスモータ(12極18スロット)でジンバルに使うような水平を維持するための装置を作った。
ブラシレスモータの制御は正弦波PWMをフルスクラッチで作成したが詳細は省略。
センサーはかなりのノイズを拾うため、各種のフィルターやPID制御での調整が必要だった。概要は次の通り。
- センサーのサンプリング周期は100Hz。読み取りはベストエフォート(割り込みではない)
- xyz軸のデータを簡単なローパスフィルターに通す(V1 = (Z + 7*V0 ) / 8 : Zは測定値、V0は前回の値)
- 角度θを計算
- θをカルマンフィルタにかける(環境ノイズやプロセスノイズの分散は4度とした)
- θから制御量uをPIDで算出。P:I:D = 1:8:1。
- uから正弦波PWMの目標角度φと角速度Δφを算出しモータを駆動する。
以上の制御では安定時にモータが振動するので、もう少し調整が必要であるが、割り込みによる一定時間ごとのセンサー読み取りでもなく、ローパスフィルターを通しているにも関わらず、十分な応答速度が出ているところを見ると、ジンバルくらいの反応であれば上記の程度のタイミングで実用になりそうだ。
Step 10: SLAVE I2C (MCC)
[コンパイルエラーの対処]
MCCの出力(i2c2.c)90行目
#define I2C2_READ_NOT_WRITE_STATUS_BIT I2C2STATbits.R_W // I2C current transaction read/write status bit.
#define I2C2_DATA_NOT_ADDRESS_STATUS_BIT I2C2STATbits.D_A // I2C last byte receive was data/address status bit.
エラー
mcc_generated_files/i2c2.c:181:26: error: 'I2C2STATBITS' has no member named 'D_A'
mcc_generated_files/i2c2.c:191:21: error: 'I2C2STATBITS' has no member named 'R_W' mcc_generated_files/i2c2.c:229:26: error: 'I2C2STATBITS' has no member named 'D_A' mcc_generated_files/i2c2.c:233:21: error: 'I2C2STATBITS' has no member named 'R_W' mcc_generated_files/i2c2.c:284:21: error: 'I2C2STATBITS' has no member named 'D_A'
修正
#define I2C2_READ_NOT_WRITE_STATUS_BIT I2C2STATbits.R_NOT_W
#define I2C2_DATA_NOT_ADDRESS_STATUS_BIT I2C2STATbits.D_NOT_A
[アドレスの読み捨て]
I2C通信の最初に付加されるデバイスアドレスの情報が受信バッファ(I2C2RCV)に設定されるケースとされないケースがあるようであるが、どのマニュアルにも明確な記述がない。MCCで出力されたi2c2.cではスレーブ(PIC)からデータを送信するパターンにおいて受信バッファからデバイスアドレスを読んでおらず、そのためにバッファオーバフローが生じているようである。送信のケースにおいても受信バッファを読み捨てるようにしたところ正常に動作した。
これ以外にもSTOPビット受信時、複数データ受信の終了時に、状態の初期化やオーバフロービットのリセットを行うように修正した。
なお、MCCのバージョンはv2.25.2である。