PIC

MM Systemの紹介

2020年12月3日

Microchip社のPIC32MMファミリーのマイクロコントローラーは、低価格で32ビットのCPUが使え、DIPパッケージもあるので、趣味の電子工作にはもってこいの石です。私自身も最近はこれを多用しています。そこで、色々な用途に簡単に使えるよう、最小限のインターフェースを簡単に実装するためのプラットフォームを作成しました。MM Systemと名付けています。

特徴:
 ・PIC32MM0064GPL028を使用
 ・出力用に、4桁の7セグメントLEDを標準装備
 ・入力用に、最大32個までのスイッチを接続化
 ・最大9個までのA/Dコンバーターの読み込みが容易
 ・PIC32MMに標準で組み込まれているSPIやUARTだけでなく、I2C通信も使用可

お気づきの方もいらっしゃると思いますが、これはZK-80 miniを改変したものです。ほとんどの用途では7セグメントLEDは4桁で良いでしょうし、8桁表示は暗くなりますので、MM Systemではとりあえず4桁を標準としました。高輝度の小型7セグLEDを使えば、ダイナミック点灯にトランジスターを使わなくても、実用に十分な明るさが出ます。

下の回路は、もっとも標準だと思われる、7セグメントLEDと8個までのタクトスイッチを接続する例です。
2020-11-29-schematic.png

この回路に、次のMM Systemのレポジトリーから得られるコードをコンパイルして得たファームウェアをダウンロードして実行すると、7セグメントLEDに「1234」と表示されます。
https://github.com/kmorimatsu/mmsystem

2020-11-29-123.4.png

あとは、main.cを目的のためにコード修正するだけです。例えば、上記回路図の8個のキー入力を使いたければ、「led7seg_init(0x08);」のコメントアウトを外して有効化し、「KEY24ON」「KEY25ON」などの値を参照して、キー入力があった際の反応部分のコードを書いたりすればよいです。主に、wile(1){ }の、永久ループ部分にコードを追加することになります。

MM Systemの利用例1:温度計

MM Systemの使い方に入る前に、まず使用例について説明します。どれぐらい簡単に実装できるかの参考になるはずです。

MM Systemと、Microchip社の温度センサー(熱電対)用のチップであるMCP9600を用いて、温度計を作成しました。MCP9600とMM Systemは、I2Cで通信を行います。回路図は以下の通りです。
2020-11-29-mcp9600.png

見て分かる通り、RA0とRA1に、MCP9600 (Adafruit社のボード)を接続しているだけです。ボタン入力は必要ないので省略しています。完成写真は、以下の通りです。
2020-11-29-thermo1.jpg
2020-11-29-thermo2.jpg
2020-11-29-thermo3.jpg

ソースコードは、main.cのmain()関数を以下の様に修正するだけです。
void main(){
    int tempX10;
    unsigned char data[2];
    // Enable interrupt
    __builtin_enable_interrupts();

    // Initializing 7 seg LED system. 
    led7seg_init(0x00); // No key input
    //led7seg_init(0x01); // Use only RB0 for key input
    //led7seg_init(0x08); // Use only RB3 for key input
    //led7seg_init(0x0f); // Use all RB0-3 for key input

    // Example of initializing ADC. RB0 (AN2) and RB1 (AN3) are used.
    //adc_init((1<<2)|(1<<3));
    
    // Initialize I2C if required
    i2c_init();

    // Infinite loop until reset switch will be pressed
    while(1){
    	// Read temperature (doesn't support below 0 degree)
        // Temperature = (UpperByte x 16 + LowerByte / 16)
    	i2c_write8(0x67,0x00);
    	i2c_read(0x67,(char*)&data[0],2);
    	tempX10=(((int)data[1])*10+8)/16;
    	tempX10+=((int)data[0])*160;
    	// Show temperature
        led7seg_dec4(tempX10);
        led7seg_point(2);
        asm volatile("wait");
    }
}

ほとんどの部分はMM Systemそのまま、あるいはコメントアウト部分を若干修正するだけでした。一から書いたコードは、以下の部分だけです。
    int tempX10;
    unsigned char data[2];

    	// Read temperature (doesn't support below 0 degree)
        // Temperature = (UpperByte x 16 + LowerByte / 16)
    	i2c_write8(0x67,0x00);
    	i2c_read(0x67,(char*)&data[0],2);
    	tempX10=(((int)data[1])*10+8)/16;
    	tempX10+=((int)data[0])*160;
    	// Show temperature
        led7seg_dec4(tempX10);
        led7seg_point(2);
やっているのは、I2C通信でMCP9600から2バイトのデーターを取ってきて、「摂氏x10」の値を計算してLEDに表示することです。このように、I2C通信を行ってダイナミック点灯で7セグメントLEDに表示するという、一から書けば結構複雑な処理が、このような数行のコードで簡単に実装できることが、分かると思います。

MM Systemの利用例2:LED 照射器
4種類の異なる色の高輝度LEDを、ミリカンデラで表わされる光度で光らせるためのものです。インターフェースとしては、出力に7セグLEDを、入力に4つのタクトスイッチと、2つの可変抵抗器をA/Dコンバーターに繋げたものを使っています。

2020-12-01-led1.jpg

2020-12-01-led2.jpg

MM Systemのごく初期の作品で、回路がほんの少し違っていますので、回路図の掲載は割愛します。下に、スイッチ入力とA/Dコンバーター部分のコードを紹介します。

// ADC settings
#define VOLUME_COARSE 2
#define VOLUME_FINE 3


void main(){
    int power,select,max;
	// Enable interrupt
	__builtin_enable_interrupts();
    // Initialize LEDs outputs, connected to RA0-RA3. First, all off (output L)
    TRISACLR=LATACLR=0x000F;

    // Initialize 7 seg LED system. Use only RB3 for key input
    led7seg_init(0x08);

    // Initialize ADC. RB0 (AN2; coarse) and RB1 (AN3; fine) are used.
    adc_init((1<<VOLUME_COARSE)|(1<<VOLUME_FINE));

    // Detect key
    while(1){
        // Power reading.
        max=5000;
        power=5*adc_read(VOLUME_COARSE)+(adc_read(VOLUME_FINE)>>5);
        if (max<power) power=max;
        // Display power
        led7seg_dec4(power);
        // Key scan
        select=0; // 468 nm
        max=5000;
        if (KEY24ON) break;
        select=1; // 522 nm
        max=3925;
        if (KEY25ON) break; 
        select=2; // 574 nm
        max=3500;
        if (KEY26ON) break; 
        select=3; // 639 nm
        max=4200;
        if (KEY27ON) break;
        wait_display();
    }
    
    // Output selection
    select_led(select,max);

    // Infinite loop until reset switch will be pressed
    while(1){
        // Power reading.
        power=5*adc_read(VOLUME_COARSE)+(adc_read(VOLUME_FINE)>>5);
        if (max<power) power=max;
        // Display power
        led7seg_dec4(power);
        // PWM setting
        set_power(power);
        // Wait for a display round
        wait_display();
    }
}

タクトスイッチからの入力は、「led7seg_init(0x08);」でRB3からの入力を有効にしておいて、「KEY24ON」などの表記でスイッチが押されているかどうか判定するだけです。A/Dコンバーターについては、「adc_init((1<<VOLUME_COARSE)|(1<<VOLUME_FINE));」で、RB0/RB1からの入力を有効にしておいて、「adc_read(VOLUME_COARSE)」と「adc_read(VOLUME_FINE)」で読んでいます。小難しい設定は、行う必要はありません。

MM Systemの使い方

まずは、GitHubからソースコードを取得してください。最新のコードは、リンク先の「Code」ボタンをクリックしたときに現れる「Download ZIP」をクリックすれば、Zipアーカイブとして得られます(バージョンごとのアーカイブは現在整備中)。

MPLAB X IDEで、「File」→「New Project」を選択し、「Standard Project」が選択されていることを確認して、「Next」を押します。「Device」に「PIC32MM0064GPL028」を選択し、「Next」を押します。「Compiler」にXC32の適当なもの(多くの場合、最新版が良い)選択し、「Next」を押します。「Project Name」にプロジェクト名を入力し、「Project Location」にプロジェクトを保存するフォルダーを指定します。「Encoding」は、好みに応じて「Shift_JIS」か「UTF-8」を選べばよいでしょう。

「プロジェクト名.X」というフォルダーが出来ますので、そこに、ダウンロードしたZipアーカイブの中から、拡張子が「c」の物と「h」の物をコピーします。2020年12月段階のバージョンでは、「adc.c」「adc.h」「i2c.c」「i2c.h」「led7seg.c」「led7seg.h」「main.c」の7つがあります。「Projects」ウィンドウの「Header Files」を右クリックし、「Add Existing Items」を選択します。「ctrl」キーを押しながらすべての「*.h」を選択し「Select」を押してください。同様に、「Source Files」から、すべての「*.c」ファイルを「Select」してください。最後に、「File」メニューから「Project Property」を選択します。「xc32-gcc」という項目がありますのでクリックし、「Option categories」に「optimization」を選んでください。「optimization-level」に「1」を選択して、「OK」ボタンを押してください。これで、ビルドする準備が出来ました。

「Production」メニューの「Build Main Project」を選択、もしくは金づちアイコンをクリックすれば、「BUILD SUCCESSFUL」の表示が出て、ビルドが無事成功したことが分かると思います。回路を組んであれば、PicKitにつないでPCに接続し、「Program Device for Production Main Project」を選択すれば、ファームウェアがダウンロードされて、「1234」がLEDディスプレイに表示されるはずです。

main.c の変更の仕方

基本的には「main.c」を修正するだけで、一部の例外を除いて他のファイルは変更する必要は無いはずです。ただし、MM Systemは現行 CPU のクロック速度が 8 MHz の固定ですから、これを変更したい場合は、他のCファイルも修正が必要です。簡単なプロジェクトなら大概、8 MHz の速度でよいのではないかと思います。

MM Systemは現行、「led7seg」「adc」「i2c」の3つの機能からなります。それぞれにCファイルとHファイルが割り当てられています。順に説明します。

led7seg

MM Systemのコアとなる機能で、4桁7セグメントLEDの表示と、最大32個のスイッチ入力を制御します。必須の機能で、これがないと他の機能が動作しないケース(A/Dコンバーターなど)があります。この機能は、Timer1 を利用していることに注意してください。

main() 関数の冒頭で、led7seg_init() 関数を呼び出してください。引数は 0x00 から 0x0f までの値で、RB0 から RB3 をボタン入力に割り当てるかどうかを指定します。RB3 だけなら 0x08 を、RB0-RB3 を4つとも割り当てる場合は 0x0f を指定します。8個以下のスイッチ数の場合は RB3 を割り当てるのが良いと思われます(RB0-RB2は多機能だが、RB3は純粋なデジタル入出力のみの為)。

led7seg.h を見ていただければ分かりますが、以下の4つの関数が定義されています。
 ・void led7seg_init(int useRB03);
 ・void led7seg_hex(int pos, char num);
 ・void led7seg_point(int pos);
 ・void led7seg_dec4(int num);

led7seg_init() は、先に説明しました。led7seg_hex() は、pos で示される場所(一番左が0,一番右が3)に、num で示される16進数の数字を表示します(16以上の値を指定すると、小数点ドットが点灯します)。led7seg_point() は、指定場所に小数点ドットを表示します。led7seg_dec4() は、4桁10進数の値を表示します。

KEY0ON から KEY31ON までの、32個のマクロが定義されています。これらは、特定のスイッチが ON になっているかどうかを示します。 ON の場合は0以外の値、OFF の場合は0です。KEY0ON - KEY7ON は RB0 に接続されたスイッチ、KEY8ON - KEY15ON は RB1 に接続されたスイッチ、KEY16ON - KEY23ON は RB2 に接続されたスイッチ、KEY24ON - KEY31ON は RB3 に接続されたスイッチです。先に書いた通り、led7seg_init() 関数の引数を正しく設定しないとスイッチ入力が正しく行われない事に注意してください。

adc

A/Dコンバーターを簡単に使用するための物です。adc.h には、次の2つの関数が定義されています。
 ・void adc_init(int used_adc);
 ・unsigned int adc_read(int adc_num);

まず、main() 関数の冒頭で adc_init() を呼び出してください。引数には、どのA/Dコンバーターを利用するかを指定します。値は、PIC32MMのデーターシートを参考にして(あるいは、adc.c を参照して)、AN0 から AN 11 までの数字を参考にして決めます。例えば、RA0 は AN0 、RB0 は AN2 です。AN0 を有効にする場合は引数の最下位ビットを1に、AN2 を有効にする場合は第2ビットを1にします。AN0 と AN2 を使う例では、0x05 ( 0b0101 )を指定してください。

adc_read() を呼び出せば、A/Dコンバーターの値を12ビットの整数値として得ることが出来ます。引数は、上のパラグラフで説明した ANx の x の値を指定してください。

i2c

マスターモードで i2c 機能を使うための機能です。クロックは 100 kHz 固定で、それ以外の速度では使えません。この機能を利用する場合、「i2c.h」ファイルを編集して、どのポートに I2C のラインを接続しているか指定してください。デフォルトでは「#define I2C_SCL_RA0」と「#define I2C_SDA_RA1」が有効で、RA0 に SCL を、RA1 に SDA を接続する設定です。他のポートを使用する場合は、それに応じて、必要な行のみを有効にし、不必要な行はコメントアウトします。

「i2c.h」では、次の関数が定義されています。
 ・void i2c_init();
 ・int i2c_write(char addr, char* data, int len);
 ・char i2c_read(char addr, char* data, int len);
 ・int i2c_write8(char addr, char data);
 ・char i2c_read8(char addr);
 ・int i2c_error();
 ・void _i2c_start();
 ・void _i2c_stop();
 ・char _i2c_write(char data);
 ・char _i2c_read(char ack);

これらのうち、関数名がアンダースコア( _ )で始まるものは、ローレベルの制御用で、特殊な用途の場合にしか必要ないと思いますので、説明は割愛します。その他の関数について、説明します。

i2c_init() を、main() 関数の冒頭で呼んでください。これを行わないと、I2C 機能が使えません。

i2c_write() は、データーを送信する場合に使います。引数は、7ビットの I2C アドレス、送信するデーターを格納した配列へのポインター、送信するデーターのバイト数です。戻り値が0の場合は送信の際にスレーブから ACK が返されており、エラーなく送信したことを示します。0以外の値の場合 NAK が返されており、エラーが起きたことを示します。

i2c_read() は、データーを受信するときに使います。。引数は、7ビットの I2C アドレス、受信するデーターを格納する配列へのポインター、受信するデーターのバイト数です。戻り値が0の場合は I2C アドレスの送信の際にスレーブから ACK が返されており、スレーブ側がデーターを送信を認識したことを示します。

i2c_write8() は、1バイトのデーターを送信する場合に使います。2番目の引数は、送信するデーターです。

i2c_read8() は、1バイトのデーターを受信する場合に使います。戻り値は、受信データーです。エラーが起きたかどうか(ACKがちゃんと返されているかどうか)は、次に述べる i2c_error() で調べてください。

i2c_error() は、上で解説した4つの関数の呼び出しの際に、エラーがあったかどうかを調べるための物です。エラーが無ければ0を、エラーがあれば0以外の値を返します。

MM Systemの今後の発展

今の所、私が使うマイクロコントローラーのメインが PIC32MM0064GPL028 ですので、これを使い続ける限り MM System は私自身が使い続けるだろうし、バージョンアップがされていくと思います。MachiKania type M では、I/O 機器の制御を簡単な BASIC のコードで実現できるように書いてありますが、MM System でもそれと同じ感覚で I/O 機器が使えるようになるのを目標として開発しています。私、個人的には、「MachiKania で接続と簡単なテスト」→「MM System に実装」が定番になりつつあります。

コメント

saito (2021年3月1日 15:22:03)

別件でしかも古い話ですいません。
2015年1月の記事console810に質問させていただきました。
よろしくお願いします。

コメント送信